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 <a.leeming@ic.ac.uk>
13 #pragma implementation
16 #include "GraphicsImageXPM.h"
17 #include "GraphicsParams.h"
18 #include "frontends/xforms/ColorHandler.h"
20 #include "frontends/GUIRunTime.h" // x11Display, x11Screen
21 #include "support/filetools.h" // IsFileReadable
22 #include "support/lstrings.h"
24 #include <iomanip> // std::setfill, etc
25 #include <cmath> // cos, sin
26 #include <cstdlib> // malloc, free
28 #ifndef CXX_GLOBAL_CSTD
38 /// Access to this class is through this static method.
39 ImagePtr GImageXPM::newImage()
42 ptr.reset(new GImageXPM);
47 /// Return the list of loadable formats.
48 GImage::FormatList GImageXPM::loadableFormats()
50 FormatList formats(1);
56 GImageXPM::GImageXPM()
58 pixmap_status_(PIXMAP_UNINITIALISED)
62 GImageXPM::GImageXPM(GImageXPM const & other)
66 pixmap_status_(PIXMAP_UNINITIALISED)
70 GImageXPM::~GImageXPM()
73 XFreePixmap(GUIRunTime::x11Display(), pixmap_);
77 GImage * GImageXPM::clone() const
79 return new GImageXPM(*this);
83 unsigned int GImageXPM::getWidth() const
85 return image_.width();
89 unsigned int GImageXPM::getHeight() const
91 return image_.height();
95 Pixmap GImageXPM::getPixmap() const
97 if (!pixmap_status_ == PIXMAP_SUCCESS)
103 void GImageXPM::load(string const & filename, GImage::SignalTypePtr on_finish)
105 if (filename.empty()) {
106 on_finish->operator()(false);
110 if (!image_.empty()) {
111 lyxerr[Debug::GRAPHICS]
112 << "Image is loaded already!" << std::endl;
113 on_finish->operator()(false);
117 XpmImage * xpm_image = new XpmImage;
120 XpmReadFileToXpmImage(const_cast<char *>(filename.c_str()),
125 lyxerr[Debug::GRAPHICS]
126 << "No XPM image file found." << std::endl;
130 lyxerr[Debug::GRAPHICS]
131 << "File format is invalid" << std::endl;
135 lyxerr[Debug::GRAPHICS]
136 << "Insufficient memory to read in XPM file"
141 if (success != XpmSuccess) {
142 XpmFreeXpmImage(xpm_image);
145 lyxerr[Debug::GRAPHICS]
146 << "Error reading XPM file '"
147 << XpmGetErrorString(success) << "'"
150 image_.reset(*xpm_image);
153 on_finish->operator()(success == XpmSuccess);
157 bool GImageXPM::setPixmap(GParams const & params)
159 if (image_.empty() || params.display == GParams::NONE) {
163 Display * display = GUIRunTime::x11Display();
165 if (pixmap_ && pixmap_status_ == PIXMAP_SUCCESS)
166 XFreePixmap(display, pixmap_);
169 // This might be a dirty thing, but I dont know any other solution.
170 Screen * screen = ScreenOfDisplay(display, GUIRunTime::x11Screen());
175 XpmAttributes attrib;
177 // Allow libXPM lots of leeway when trying to allocate colors.
178 attrib.closeness = 10000;
179 attrib.valuemask = XpmCloseness;
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;
187 case GParams::GRAYSCALE:
188 attrib.color_key = XPM_GRAY;
191 default: // NONE cannot happen!
192 attrib.color_key = XPM_COLOR;
196 attrib.valuemask |= XpmColorKey;
198 // Set the color "none" entry to the color of the background.
199 XpmColorSymbol xpm_col[2];
201 xpm_col[0].value = "none";
202 xpm_col[0].pixel = lyxColorHandler->colorPixel(LColor::graphicsbg);
204 // some image magick versions use this
206 xpm_col[1].value = "opaque";
207 xpm_col[1].pixel = lyxColorHandler->colorPixel(LColor::white);
209 attrib.numsymbols = 2;
210 attrib.colorsymbols = xpm_col;
211 attrib.valuemask |= XpmColorSymbols;
213 // Load up the pixmap
214 XpmImage xpm_image = image_.get();
216 XpmCreatePixmapFromXpmImage(display,
217 XRootWindowOfScreen(screen),
219 &pixmap, &mask, &attrib);
221 XpmFreeAttributes(&attrib);
223 if (status != XpmSuccess) {
224 lyxerr << "Error creating pixmap from xpm_image '"
225 << XpmGetErrorString(status) << "'"
227 pixmap_status_ = PIXMAP_FAILED;
232 pixmap_status_ = PIXMAP_SUCCESS;
237 void GImageXPM::clip(GParams const & params)
242 if (params.bb.empty())
243 // No clipping is necessary.
246 typedef unsigned int dimension;
248 dimension const new_width = params.bb.xr - params.bb.xl;
249 dimension const new_height = params.bb.yt - params.bb.yb;
251 if (new_width > image_.width() || new_height > image_.height())
252 // Bounds are invalid.
255 if (new_width == image_.width() && new_height == image_.height())
256 // Bounds are unchanged.
259 dimension * new_data = image_.initialisedData(new_width, new_height);
260 dimension * it = new_data;
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);
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();
277 image_.resetData(new_width, new_height, new_data);
281 void GImageXPM::rotate(GParams const & params)
287 // No rotation is necessary.
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);
299 double max_x = 0; double min_x = 0;
300 double max_y = 0; double min_y = 0;
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);
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);
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);
320 typedef unsigned int dimension;
322 dimension const new_width = 1 + int(max_x - min_x); // round up!
323 dimension const new_height = 1 + int(max_y - min_y);
325 dimension * new_data = image_.initialisedData(new_width, new_height);
326 dimension const * old_data = image_.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;
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);
340 size_t const id_old = x_old + image_.width() * y_old;
341 size_t const id_new = x_new + new_width * y_new;
343 new_data[id_new] = old_data[id_old];
347 image_.resetData(new_width, new_height, new_data);
351 void GImageXPM::scale(GParams const & params)
356 typedef unsigned int dimension;
358 // boost::tie produces horrible compilation errors on my machine
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())
367 dimension * new_data = image_.initialisedData(new_width, new_height);
368 dimension const * old_data = image_.data();
370 double const x_scale = double(image_.width()) / double(new_width);
371 double const y_scale = double(image_.height()) / double(new_height);
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);
379 for (dimension y_new = 0; y_new < new_height; ++y_new) {
380 dimension y_old = dimension(y_new * y_scale);
382 size_t const id_old = x_old + image_.width() * y_old;
383 size_t const id_new = x_new + new_width * y_new;
385 new_data[id_new] = old_data[id_old];
389 image_.resetData(new_width, new_height, new_data);
397 void free_color_table(XpmColor * colorTable, size_t size);
399 void copy_color_table(XpmColor const * in, size_t size, XpmColor * out);
401 bool contains_color_none(XpmImage const & image);
403 string const unique_color_string(XpmImage const & image);
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 &);
409 // create a copy (using malloc and strcpy). If (!in) return 0;
410 char * clone_c_string(char const * in);
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);
421 GImageXPM::Data::Data()
422 : width_(0), height_(0), cpp_(0), ncolors_(0)
426 GImageXPM::Data::~Data()
428 if (colorTable_.unique())
429 free_color_table(colorTable_.get(), ncolors_);
433 void GImageXPM::Data::reset(XpmImage & image)
435 width_ = image.width;
436 height_ = image.height;
439 // Move the data ptr into this store and free up image.data
440 data_.reset(image.data);
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
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);
457 ncolors_ = 1 + image.ncolors;
458 size_t const mem_size = sizeof(XpmColor) * ncolors_;
459 XpmColor * table = static_cast<XpmColor *>(malloc(mem_size));
461 copy_color_table(image.colorTable, image.ncolors, table);
463 XpmColor & color = table[ncolors_ - 1];
469 clone_c_string(unique_color_string(image).c_str());
470 color.c_color = clone_c_string("none");
472 free_color_table(image.colorTable, image.ncolors);
473 colorTable_.reset(table);
477 // Just move the pointer across
478 ncolors_ = image.ncolors;
479 colorTable_.reset(image.colorTable);
480 image.colorTable = 0;
483 // Clean-up the remaining entries of image.
489 // 2. Ensure that the color table has g_color and m_color entries
490 XpmColor * table = colorTable_.get();
492 for (size_t i = 0; i < ncolors_; ++i) {
493 XpmColor & entry = table[i];
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);
503 entry.c_color = clone_c_string(c_color.c_str());
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);
513 XpmImage GImageXPM::Data::get() const
516 image.width = width_;
517 image.height = height_;
519 image.ncolors = ncolors_;
520 image.data = data_.get();
521 image.colorTable = colorTable_.get();
526 void GImageXPM::Data::resetData(int w, int h, unsigned int * d)
534 unsigned int * GImageXPM::Data::initialisedData(int w, int h) const
536 size_t const data_size = w * h;
538 size_t const mem_size = sizeof(unsigned int) * data_size;
539 unsigned int * ptr = static_cast<unsigned int *>(malloc(mem_size));
541 unsigned int none_id = color_none_id();
542 std::fill(ptr, ptr + data_size, none_id);
548 unsigned int GImageXPM::Data::color_none_id() const
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")
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)
567 string::size_type size = input.size();
568 if (size != 13 && size != 10 && size != 4)
569 // Can't deal with it.
573 // Can't deal with it.
576 string format(input);
579 case 13: // #rrrrggggbbbb
584 case 10: // #rrrgggbbb
590 format.insert(2, 1, '0');
591 format.insert(4, 1, '0');
592 format.append(1, '0');
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)
607 char * g_color = *g_color_ptr;
608 char * m_color = *m_color_ptr;
610 if (g_color && m_color)
614 Display * display = GUIRunTime::x11Display();
615 Colormap cmap = GUIRunTime::x11Colormap();
618 if (XLookupColor(display, cmap, c_color, &xcol, &ccol) == 0)
619 // Unable to parse c_color.
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;
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));
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;
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;
645 // This string is going into an XpmImage struct, so create copies that
646 // libXPM can free successfully.
648 *g_color_ptr = clone_c_string(gray_stream.str().c_str());
650 *m_color_ptr = clone_c_string(mono_stream.str().c_str());
654 void copy_color_table(XpmColor const * in, size_t size, XpmColor * out)
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);
667 void free_color_table(XpmColor * table, size_t size)
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);
677 // Don't free the table itself. Let the shared_c_ptr do that.
682 char * clone_c_string(char const * in)
687 // Don't forget the '\0'
688 char * out = static_cast<char *>(malloc(strlen(in) + 1));
689 return strcpy(out, in);
693 bool contains_color_none(XpmImage const & image)
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")
704 string const unique_color_string(XpmImage const & image)
706 string id(image.cpp, ' ');
709 bool found_it = false;
710 for (size_t i = 0; i < image.ncolors; ++i) {
711 string const c_id = image.colorTable[i].string;
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;
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;
735 id[current_index] = 32;
738 id[current_index] += 1;
739 // Note that '"' is an illegal char in this
741 if (id[current_index] == '"')
742 id[current_index] += 1;
746 // Unable to find a unique string