2 * \file GraphicsImageXPM.C
3 * Copyright 2002 the LyX Team
4 * Read the file COPYING
6 * \author Baruch Even <baruch.even@writeme.com>
7 * \author Angus Leeming <leeming@lyx.org>
13 #pragma implementation
16 #include "GraphicsImageXPM.h"
17 #include "GraphicsParams.h"
18 #include "frontends/xforms/ColorHandler.h"
20 #include "support/filetools.h" // IsFileReadable
21 #include "support/lstrings.h"
24 #include <boost/tuple/tuple.hpp>
25 #include <boost/bind.hpp>
27 #include FORMS_H_LOCATION
29 #include <iomanip> // std::setfill, etc
30 #include <cmath> // cos, sin
31 #include <cstdlib> // malloc, free
33 #ifndef CXX_GLOBAL_CSTD
43 /// Access to this class is through this static method.
44 Image::ImagePtr ImageXPM::newImage()
47 ptr.reset(new ImageXPM);
52 /// Return the list of loadable formats.
53 Image::FormatList ImageXPM::loadableFormats()
55 FormatList formats(1);
63 pixmap_status_(PIXMAP_UNINITIALISED)
67 ImageXPM::ImageXPM(ImageXPM const & other)
71 pixmap_status_(PIXMAP_UNINITIALISED)
78 XFreePixmap(fl_get_display(), pixmap_);
82 Image * ImageXPM::clone() const
84 return new ImageXPM(*this);
88 unsigned int ImageXPM::getWidth() const
90 return image_.width();
94 unsigned int ImageXPM::getHeight() const
96 return image_.height();
100 bool ImageXPM::isDrawable() const
106 Pixmap ImageXPM::getPixmap() const
108 if (!pixmap_status_ == PIXMAP_SUCCESS)
114 void ImageXPM::load(string const & filename)
116 if (filename.empty()) {
117 finishedLoading(false);
121 if (!image_.empty()) {
122 lyxerr[Debug::GRAPHICS]
123 << "Image is loaded already!" << std::endl;
124 finishedLoading(false);
128 XpmImage * xpm_image = new XpmImage;
131 XpmReadFileToXpmImage(const_cast<char *>(filename.c_str()),
136 lyxerr[Debug::GRAPHICS]
137 << "No XPM image file found." << std::endl;
141 lyxerr[Debug::GRAPHICS]
142 << "File format is invalid" << std::endl;
146 lyxerr[Debug::GRAPHICS]
147 << "Insufficient memory to read in XPM file"
152 if (success != XpmSuccess) {
153 XpmFreeXpmImage(xpm_image);
156 lyxerr[Debug::GRAPHICS]
157 << "Error reading XPM file '"
158 << XpmGetErrorString(success) << "'"
161 image_.reset(*xpm_image);
164 finishedLoading(success == XpmSuccess);
168 bool ImageXPM::setPixmap(Params const & params)
170 if (image_.empty() || params.display == NoDisplay) {
174 Display * display = fl_get_display();
176 if (pixmap_ && pixmap_status_ == PIXMAP_SUCCESS)
177 XFreePixmap(display, pixmap_);
180 // This might be a dirty thing, but I dont know any other solution.
181 Screen * screen = ScreenOfDisplay(display, fl_screen);
186 XpmAttributes attrib;
188 // Allow libXPM lots of leeway when trying to allocate colors.
189 attrib.closeness = 10000;
190 attrib.valuemask = XpmCloseness;
192 // The XPM file format allows multiple pixel colours to be defined
193 // as c_color, g_color or m_color.
194 switch (params.display) {
195 case MonochromeDisplay:
196 attrib.color_key = XPM_MONO;
198 case GrayscaleDisplay:
199 attrib.color_key = XPM_GRAY;
202 default: // NoDisplay cannot happen!
203 attrib.color_key = XPM_COLOR;
207 attrib.valuemask |= XpmColorKey;
209 // Set the color "none" entry to the color of the background.
210 XpmColorSymbol xpm_col[2];
212 xpm_col[0].value = "none";
213 xpm_col[0].pixel = lyxColorHandler->colorPixel(LColor::graphicsbg);
215 // some image magick versions use this
217 xpm_col[1].value = "opaque";
218 xpm_col[1].pixel = lyxColorHandler->colorPixel(LColor::black);
220 attrib.numsymbols = 2;
221 attrib.colorsymbols = xpm_col;
222 attrib.valuemask |= XpmColorSymbols;
224 // Load up the pixmap
225 XpmImage xpm_image = image_.get();
227 XpmCreatePixmapFromXpmImage(display,
228 XRootWindowOfScreen(screen),
230 &pixmap, &mask, &attrib);
232 XpmFreeAttributes(&attrib);
234 if (status != XpmSuccess) {
235 lyxerr << "Error creating pixmap from xpm_image '"
236 << XpmGetErrorString(status) << "'"
238 pixmap_status_ = PIXMAP_FAILED;
243 pixmap_status_ = PIXMAP_SUCCESS;
248 void ImageXPM::clip(Params const & params)
253 if (params.bb.empty())
254 // No clipping is necessary.
257 typedef unsigned int dimension;
259 dimension const new_width = params.bb.xr - params.bb.xl;
260 dimension const new_height = params.bb.yt - params.bb.yb;
262 if (new_width > image_.width() || new_height > image_.height())
263 // Bounds are invalid.
266 if (new_width == image_.width() && new_height == image_.height())
267 // Bounds are unchanged.
270 dimension * new_data = image_.initialisedData(new_width, new_height);
271 dimension * it = new_data;
273 // The image is stored in memory from upper-left to lower-right,
274 // so we loop from yt to yb.
275 dimension const * old_data = image_.data();
276 dimension const * start_row = old_data +
277 image_.width() * (image_.height() - params.bb.yt);
279 // the Bounding Box dimensions are never less than zero, so we can use
280 // "unsigned int row" here
281 for (dimension row = params.bb.yb; row < params.bb.yt; ++row) {
282 dimension const * begin = start_row + params.bb.xl;
283 dimension const * end = start_row + params.bb.xr;
284 it = std::copy(begin, end, it);
285 start_row += image_.width();
288 image_.resetData(new_width, new_height, new_data);
292 void ImageXPM::rotate(Params const & params)
298 // No rotation is necessary.
301 // Ascertain the bounding box of the rotated image
302 // Rotate about the bottom-left corner
303 static double const pi = 3.14159265358979323846;
304 // The minus sign is needed to rotate in the same sense as xdvi et al.
305 double const angle = -double(params.angle) * pi / 180.0;
306 double const cos_a = cos(angle);
307 double const sin_a = sin(angle);
310 double max_x = 0; double min_x = 0;
311 double max_y = 0; double min_y = 0;
313 // (old_xpm->width, 0)
314 double x_rot = cos_a * image_.width();
315 double y_rot = sin_a * image_.width();
316 max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
317 max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
319 // (image_.width, image_.height)
320 x_rot = cos_a * image_.width() - sin_a * image_.height();
321 y_rot = sin_a * image_.width() + cos_a * image_.height();
322 max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
323 max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
325 // (0, image_.height)
326 x_rot = - sin_a * image_.height();
327 y_rot = cos_a * image_.height();
328 max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
329 max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
331 typedef unsigned int dimension;
333 dimension const new_width = 1 + int(max_x - min_x); // round up!
334 dimension const new_height = 1 + int(max_y - min_y);
336 dimension * new_data = image_.initialisedData(new_width, new_height);
337 dimension const * old_data = image_.data();
340 for (dimension y_old = 0; y_old < image_.height(); ++y_old) {
341 for (dimension x_old = 0; x_old < image_.width(); ++x_old) {
342 double const x_pos = cos_a*x_old - sin_a*y_old - min_x;
343 double const y_pos = sin_a*x_old + cos_a*y_old - min_y;
345 // ensure that there are no rounding errors
346 dimension x_new = (x_pos > 0) ? dimension(x_pos) : 0;
347 dimension y_new = (y_pos > 0) ? dimension(y_pos) : 0;
348 x_new = std::min(new_width - 1, x_new);
349 y_new = std::min(new_height - 1, y_new);
351 size_t const id_old = x_old + image_.width() * y_old;
352 size_t const id_new = x_new + new_width * y_new;
354 new_data[id_new] = old_data[id_old];
358 image_.resetData(new_width, new_height, new_data);
362 void ImageXPM::scale(Params const & params)
367 typedef unsigned int dimension;
370 dimension new_height;
371 boost::tie(new_width, new_height) = getScaledDimensions(params);
373 if (new_width == getWidth() && new_height == getHeight())
377 dimension * new_data = image_.initialisedData(new_width, new_height);
378 dimension const * old_data = image_.data();
380 double const x_scale = double(image_.width()) / double(new_width);
381 double const y_scale = double(image_.height()) / double(new_height);
383 // A very simple scaling routine.
384 // Ascertain the old pixel corresponding to the new one.
385 // There is no dithering at all here.
386 for (dimension x_new = 0; x_new < new_width; ++x_new) {
387 dimension x_old = dimension(x_new * x_scale);
389 for (dimension y_new = 0; y_new < new_height; ++y_new) {
390 dimension y_old = dimension(y_new * y_scale);
392 size_t const id_old = x_old + image_.width() * y_old;
393 size_t const id_new = x_new + new_width * y_new;
395 new_data[id_new] = old_data[id_old];
399 image_.resetData(new_width, new_height, new_data);
407 void free_color_table(XpmColor * colorTable, size_t size);
409 void copy_color_table(XpmColor const * in, size_t size, XpmColor * out);
411 bool contains_color_none(XpmImage const & image);
413 string const unique_color_string(XpmImage const & image);
415 // libXpm cannot cope with strings of the form #rrrrggggbbbb,
416 // #rrrgggbbb or #rgb, so convert them to #rrggbb.
417 string const convertTo7chars(string const &);
419 // create a copy (using malloc and strcpy). If (!in) return 0;
420 char * clone_c_string(char const * in);
422 // Given a string of the form #ff0571 create appropriate grayscale and
423 // monochrome colors.
424 void mapcolor(char const * c_color, char ** g_color_ptr, char ** m_color_ptr);
431 ImageXPM::Data::Data()
432 : width_(0), height_(0), cpp_(0), ncolors_(0)
436 ImageXPM::Data::~Data()
441 void ImageXPM::Data::reset(XpmImage & image)
443 width_ = image.width;
444 height_ = image.height;
447 // Move the data ptr into this store and free up image.data
448 data_.reset(image.data, free);
451 // Don't just store the color table, but check first that it contains
452 // all that we require of it.
453 // The idea is to store the color table in a shared_ptr and for all
454 // modified images to use the same table.
455 // It must, therefore, have a c_color "none" entry and g_color and
456 // m_color entries corresponding to each and every c_color entry
459 // 1. Create a copy of the color table.
460 // Add a c_color "none" entry to the table if it isn't already there.
461 bool const add_color = !contains_color_none(image);
465 ncolors_ = 1 + image.ncolors;
466 size_t const mem_size = sizeof(XpmColor) * ncolors_;
467 XpmColor * table = static_cast<XpmColor *>(malloc(mem_size));
469 copy_color_table(image.colorTable, image.ncolors, table);
471 XpmColor & color = table[ncolors_ - 1];
477 clone_c_string(unique_color_string(image).c_str());
478 color.c_color = clone_c_string("none");
480 free_color_table(image.colorTable, image.ncolors);
481 colorTable_.reset(table, boost::bind(free_color_table, _1, ncolors_));
485 // Just move the pointer across
486 ncolors_ = image.ncolors;
487 colorTable_.reset(image.colorTable,
488 boost::bind(free_color_table, _1, ncolors_));
489 image.colorTable = 0;
492 // Clean-up the remaining entries of image.
498 // 2. Ensure that the color table has g_color and m_color entries
499 XpmColor * table = colorTable_.get();
501 for (size_t i = 0; i < ncolors_; ++i) {
502 XpmColor & entry = table[i];
506 // libXpm cannot cope with strings of the form #rrrrggggbbbb,
507 // #rrrgggbbb or #rgb, so convert them to #rrggbb.
508 string c_color = entry.c_color;
509 if (c_color[0] == '#' && c_color.size() != 7) {
510 c_color = convertTo7chars(c_color);
512 entry.c_color = clone_c_string(c_color.c_str());
515 // If the c_color is defined and the equivalent
516 // grayscale or monochrome ones are not, then define them.
517 mapcolor(entry.c_color, &entry.g_color, &entry.m_color);
522 XpmImage ImageXPM::Data::get() const
525 image.width = width_;
526 image.height = height_;
528 image.ncolors = ncolors_;
529 image.data = data_.get();
530 image.colorTable = colorTable_.get();
535 void ImageXPM::Data::resetData(int w, int h, unsigned int * d)
539 data_.reset(d, free);
543 unsigned int * ImageXPM::Data::initialisedData(int w, int h) const
545 size_t const data_size = w * h;
547 size_t const mem_size = sizeof(unsigned int) * data_size;
548 unsigned int * ptr = static_cast<unsigned int *>(malloc(mem_size));
550 unsigned int none_id = color_none_id();
551 std::fill(ptr, ptr + data_size, none_id);
557 unsigned int ImageXPM::Data::color_none_id() const
559 XpmColor * table = colorTable_.get();
560 for (size_t i = 0; i < ncolors_; ++i) {
561 char const * const color = table[i].c_color;
562 if (color && ascii_lowercase(color) == "none")
563 return static_cast<unsigned int>(i);
572 // libXpm cannot cope with strings of the form #rrrrggggbbbb,
573 // #rrrgggbbb or #rgb, so convert them to #rrggbb.
574 string const convertTo7chars(string const & input)
576 string::size_type size = input.size();
577 if (size != 13 && size != 10 && size != 9 && size != 4)
578 // Can't deal with it.
582 // Can't deal with it.
585 string format(input);
588 case 13: // #rrrrggggbbbb
593 case 10: // #rrrgggbbb
602 format.insert(2, 1, '0');
603 format.insert(4, 1, '0');
604 format.append(1, '0');
612 // Given a string of the form #ff0571 create appropriate grayscale and
613 // monochrome colors.
614 void mapcolor(char const * c_color, char ** g_color_ptr, char ** m_color_ptr)
619 char * g_color = *g_color_ptr;
620 char * m_color = *m_color_ptr;
622 if (g_color && m_color)
626 Display * display = fl_get_display();
627 Colormap cmap = fl_state[fl_get_vclass()].colormap;
630 if (XLookupColor(display, cmap, c_color, &xcol, &ccol) == 0)
631 // Unable to parse c_color.
634 // Note that X stores the RGB values in the range 0 - 65535
635 // whilst we require them in the range 0 - 255.
636 int const r = xcol.red / 256;
637 int const g = xcol.green / 256;
638 int const b = xcol.blue / 256;
640 // This gives a good match to a human's RGB to luminance conversion.
641 // (From xv's Postscript code --- Mike Ressler.)
642 int const gray = int((0.32 * r) + (0.5 * g) + (0.18 * b));
644 ostringstream gray_stream;
645 gray_stream << "#" << std::setbase(16) << std::setfill('0')
646 << std::setw(2) << gray
647 << std::setw(2) << gray
648 << std::setw(2) << gray;
650 int const mono = (gray < 128) ? 0 : 255;
651 ostringstream mono_stream;
652 mono_stream << "#" << std::setbase(16) << std::setfill('0')
653 << std::setw(2) << mono
654 << std::setw(2) << mono
655 << std::setw(2) << mono;
657 // This string is going into an XpmImage struct, so create copies that
658 // libXPM can free successfully.
660 *g_color_ptr = clone_c_string(gray_stream.str().c_str());
662 *m_color_ptr = clone_c_string(mono_stream.str().c_str());
666 void copy_color_table(XpmColor const * in, size_t size, XpmColor * out)
668 for (size_t i = 0; i < size; ++i) {
669 out[i].string = clone_c_string(in[i].string);
670 out[i].symbolic = clone_c_string(in[i].symbolic);
671 out[i].m_color = clone_c_string(in[i].m_color);
672 out[i].g_color = clone_c_string(in[i].g_color);
673 out[i].g4_color = clone_c_string(in[i].g4_color);
674 out[i].c_color = clone_c_string(in[i].c_color);
679 void free_color_table(XpmColor * table, size_t size)
681 for (size_t i = 0; i < size; ++i) {
682 free(table[i].string);
683 free(table[i].symbolic);
684 free(table[i].m_color);
685 free(table[i].g_color);
686 free(table[i].g4_color);
687 free(table[i].c_color);
693 char * clone_c_string(char const * in)
698 // Don't forget the '\0'
699 char * out = static_cast<char *>(malloc(strlen(in) + 1));
700 return strcpy(out, in);
704 bool contains_color_none(XpmImage const & image)
706 for (size_t i = 0; i < image.ncolors; ++i) {
707 char const * const color = image.colorTable[i].c_color;
708 if (color && ascii_lowercase(color) == "none")
715 string const unique_color_string(XpmImage const & image)
717 string id(image.cpp, ' ');
720 bool found_it = false;
721 for (size_t i = 0; i < image.ncolors; ++i) {
722 string const c_id = image.colorTable[i].string;
732 // Loop over the printable characters in the ASCII table.
733 // Ie, count from char 32 (' ') to char 126 ('~')
734 // A base 94 counter!
735 string::size_type current_index = id.size() - 1;
736 bool continue_loop = true;
737 while(continue_loop) {
738 continue_loop = false;
740 if (id[current_index] == 126) {
741 continue_loop = true;
742 if (current_index == 0)
743 // Unable to find a unique string
744 return image.colorTable[0].string;
746 id[current_index] = 32;
749 id[current_index] += 1;
750 // Note that '"' is an illegal char in this
752 if (id[current_index] == '"')
753 id[current_index] += 1;
757 // Unable to find a unique string