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 "support/filetools.h" // IsFileReadable
21 #include "support/lstrings.h"
23 #include <iomanip> // std::setfill, etc
24 #include <cmath> // cos, sin
25 #include <cstdlib> // malloc, free
27 #include FORMS_H_LOCATION
29 #ifndef CXX_GLOBAL_CSTD
39 /// Access to this class is through this static method.
40 ImagePtr GImageXPM::newImage()
43 ptr.reset(new GImageXPM);
48 /// Return the list of loadable formats.
49 GImage::FormatList GImageXPM::loadableFormats()
51 FormatList formats(1);
57 GImageXPM::GImageXPM()
59 pixmap_status_(PIXMAP_UNINITIALISED)
63 GImageXPM::GImageXPM(GImageXPM const & other)
67 pixmap_status_(PIXMAP_UNINITIALISED)
71 GImageXPM::~GImageXPM()
74 XFreePixmap(fl_get_display(), pixmap_);
78 GImage * GImageXPM::clone() const
80 return new GImageXPM(*this);
84 unsigned int GImageXPM::getWidth() const
86 return image_.width();
90 unsigned int GImageXPM::getHeight() const
92 return image_.height();
96 Pixmap GImageXPM::getPixmap() const
98 if (!pixmap_status_ == PIXMAP_SUCCESS)
104 void GImageXPM::load(string const & filename, GImage::SignalTypePtr on_finish)
106 if (filename.empty()) {
107 on_finish->operator()(false);
111 if (!image_.empty()) {
112 lyxerr[Debug::GRAPHICS]
113 << "Image is loaded already!" << std::endl;
114 on_finish->operator()(false);
118 XpmImage * xpm_image = new XpmImage;
121 XpmReadFileToXpmImage(const_cast<char *>(filename.c_str()),
126 lyxerr[Debug::GRAPHICS]
127 << "No XPM image file found." << std::endl;
131 lyxerr[Debug::GRAPHICS]
132 << "File format is invalid" << std::endl;
136 lyxerr[Debug::GRAPHICS]
137 << "Insufficient memory to read in XPM file"
142 if (success != XpmSuccess) {
143 XpmFreeXpmImage(xpm_image);
146 lyxerr[Debug::GRAPHICS]
147 << "Error reading XPM file '"
148 << XpmGetErrorString(success) << "'"
151 image_.reset(*xpm_image);
154 on_finish->operator()(success == XpmSuccess);
158 bool GImageXPM::setPixmap(GParams const & params)
160 if (image_.empty() || params.display == GParams::NONE) {
164 Display * display = fl_get_display();
166 if (pixmap_ && pixmap_status_ == PIXMAP_SUCCESS)
167 XFreePixmap(display, pixmap_);
170 // This might be a dirty thing, but I dont know any other solution.
171 Screen * screen = ScreenOfDisplay(display, fl_screen);
176 XpmAttributes attrib;
178 // Allow libXPM lots of leeway when trying to allocate colors.
179 attrib.closeness = 10000;
180 attrib.valuemask = XpmCloseness;
182 // The XPM file format allows multiple pixel colours to be defined
183 // as c_color, g_color or m_color.
184 switch (params.display) {
185 case GParams::MONOCHROME:
186 attrib.color_key = XPM_MONO;
188 case GParams::GRAYSCALE:
189 attrib.color_key = XPM_GRAY;
192 default: // NONE cannot happen!
193 attrib.color_key = XPM_COLOR;
197 attrib.valuemask |= XpmColorKey;
199 // Set the color "none" entry to the color of the background.
200 XpmColorSymbol xpm_col[2];
202 xpm_col[0].value = "none";
203 xpm_col[0].pixel = lyxColorHandler->colorPixel(LColor::graphicsbg);
205 // some image magick versions use this
207 xpm_col[1].value = "opaque";
208 xpm_col[1].pixel = lyxColorHandler->colorPixel(LColor::white);
210 attrib.numsymbols = 2;
211 attrib.colorsymbols = xpm_col;
212 attrib.valuemask |= XpmColorSymbols;
214 // Load up the pixmap
215 XpmImage xpm_image = image_.get();
217 XpmCreatePixmapFromXpmImage(display,
218 XRootWindowOfScreen(screen),
220 &pixmap, &mask, &attrib);
222 XpmFreeAttributes(&attrib);
224 if (status != XpmSuccess) {
225 lyxerr << "Error creating pixmap from xpm_image '"
226 << XpmGetErrorString(status) << "'"
228 pixmap_status_ = PIXMAP_FAILED;
233 pixmap_status_ = PIXMAP_SUCCESS;
238 void GImageXPM::clip(GParams const & params)
243 if (params.bb.empty())
244 // No clipping is necessary.
247 typedef unsigned int dimension;
249 dimension const new_width = params.bb.xr - params.bb.xl;
250 dimension const new_height = params.bb.yt - params.bb.yb;
252 if (new_width > image_.width() || new_height > image_.height())
253 // Bounds are invalid.
256 if (new_width == image_.width() && new_height == image_.height())
257 // Bounds are unchanged.
260 dimension * new_data = image_.initialisedData(new_width, new_height);
261 dimension * it = new_data;
263 // The image is stored in memory from upper-left to lower-right,
264 // so we loop from yt to yb.
265 dimension const * old_data = image_.data();
266 dimension const * start_row = old_data +
267 image_.width() * (image_.height() - params.bb.yt);
269 // the Bounding Box dimensions are never less than zero, so we can use
270 // "unsigned int row" here
271 for (dimension row = params.bb.yb; row < params.bb.yt; ++row) {
272 dimension const * begin = start_row + params.bb.xl;
273 dimension const * end = start_row + params.bb.xr;
274 it = std::copy(begin, end, it);
275 start_row += image_.width();
278 image_.resetData(new_width, new_height, new_data);
282 void GImageXPM::rotate(GParams const & params)
288 // No rotation is necessary.
291 // Ascertain the bounding box of the rotated image
292 // Rotate about the bottom-left corner
293 static double const pi = 3.14159265358979323846;
294 // The minus sign is needed to rotate in the same sense as xdvi et al.
295 double const angle = -double(params.angle) * pi / 180.0;
296 double const cos_a = cos(angle);
297 double const sin_a = sin(angle);
300 double max_x = 0; double min_x = 0;
301 double max_y = 0; double min_y = 0;
303 // (old_xpm->width, 0)
304 double x_rot = cos_a * image_.width();
305 double y_rot = sin_a * image_.width();
306 max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
307 max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
309 // (image_.width, image_.height)
310 x_rot = cos_a * image_.width() - sin_a * image_.height();
311 y_rot = sin_a * image_.width() + cos_a * image_.height();
312 max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
313 max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
315 // (0, image_.height)
316 x_rot = - sin_a * image_.height();
317 y_rot = cos_a * image_.height();
318 max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
319 max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
321 typedef unsigned int dimension;
323 dimension const new_width = 1 + int(max_x - min_x); // round up!
324 dimension const new_height = 1 + int(max_y - min_y);
326 dimension * new_data = image_.initialisedData(new_width, new_height);
327 dimension const * old_data = image_.data();
330 for (dimension y_old = 0; y_old < image_.height(); ++y_old) {
331 for (dimension x_old = 0; x_old < image_.width(); ++x_old) {
332 double const x_pos = cos_a*x_old - sin_a*y_old - min_x;
333 double const y_pos = sin_a*x_old + cos_a*y_old - min_y;
335 // ensure that there are no rounding errors
336 dimension x_new = (x_pos > 0) ? dimension(x_pos) : 0;
337 dimension y_new = (y_pos > 0) ? dimension(y_pos) : 0;
338 x_new = std::min(new_width - 1, x_new);
339 y_new = std::min(new_height - 1, y_new);
341 size_t const id_old = x_old + image_.width() * y_old;
342 size_t const id_new = x_new + new_width * y_new;
344 new_data[id_new] = old_data[id_old];
348 image_.resetData(new_width, new_height, new_data);
352 void GImageXPM::scale(GParams const & params)
357 typedef unsigned int dimension;
359 // boost::tie produces horrible compilation errors on my machine
361 std::pair<dimension, dimension> d = getScaledDimensions(params);
362 dimension const new_width = d.first;
363 dimension const new_height = d.second;
364 if (new_width == getWidth() && new_height == getHeight())
368 dimension * new_data = image_.initialisedData(new_width, new_height);
369 dimension const * old_data = image_.data();
371 double const x_scale = double(image_.width()) / double(new_width);
372 double const y_scale = double(image_.height()) / double(new_height);
374 // A very simple scaling routine.
375 // Ascertain the old pixel corresponding to the new one.
376 // There is no dithering at all here.
377 for (dimension x_new = 0; x_new < new_width; ++x_new) {
378 dimension x_old = dimension(x_new * x_scale);
380 for (dimension y_new = 0; y_new < new_height; ++y_new) {
381 dimension y_old = dimension(y_new * y_scale);
383 size_t const id_old = x_old + image_.width() * y_old;
384 size_t const id_new = x_new + new_width * y_new;
386 new_data[id_new] = old_data[id_old];
390 image_.resetData(new_width, new_height, new_data);
398 void free_color_table(XpmColor * colorTable, size_t size);
400 void copy_color_table(XpmColor const * in, size_t size, XpmColor * out);
402 bool contains_color_none(XpmImage const & image);
404 string const unique_color_string(XpmImage const & image);
406 // libXpm cannot cope with strings of the form #rrrrggggbbbb,
407 // #rrrgggbbb or #rgb, so convert them to #rrggbb.
408 string const convertTo7chars(string const &);
410 // create a copy (using malloc and strcpy). If (!in) return 0;
411 char * clone_c_string(char const * in);
413 // Given a string of the form #ff0571 create appropriate grayscale and
414 // monochrome colors.
415 void mapcolor(char const * c_color, char ** g_color_ptr, char ** m_color_ptr);
422 GImageXPM::Data::Data()
423 : width_(0), height_(0), cpp_(0), ncolors_(0)
427 GImageXPM::Data::~Data()
429 if (colorTable_.unique())
430 free_color_table(colorTable_.get(), ncolors_);
434 void GImageXPM::Data::reset(XpmImage & image)
436 width_ = image.width;
437 height_ = image.height;
440 // Move the data ptr into this store and free up image.data
441 data_.reset(image.data);
444 // Don't just store the color table, but check first that it contains
445 // all that we require of it.
446 // The idea is to store the color table in a shared_ptr and for all
447 // modified images to use the same table.
448 // It must, therefore, have a c_color "none" entry and g_color and
449 // m_color entries corresponding to each and every c_color entry
452 // 1. Create a copy of the color table.
453 // Add a c_color "none" entry to the table if it isn't already there.
454 bool const add_color = !contains_color_none(image);
458 ncolors_ = 1 + image.ncolors;
459 size_t const mem_size = sizeof(XpmColor) * ncolors_;
460 XpmColor * table = static_cast<XpmColor *>(malloc(mem_size));
462 copy_color_table(image.colorTable, image.ncolors, table);
464 XpmColor & color = table[ncolors_ - 1];
470 clone_c_string(unique_color_string(image).c_str());
471 color.c_color = clone_c_string("none");
473 free_color_table(image.colorTable, image.ncolors);
474 colorTable_.reset(table);
478 // Just move the pointer across
479 ncolors_ = image.ncolors;
480 colorTable_.reset(image.colorTable);
481 image.colorTable = 0;
484 // Clean-up the remaining entries of image.
490 // 2. Ensure that the color table has g_color and m_color entries
491 XpmColor * table = colorTable_.get();
493 for (size_t i = 0; i < ncolors_; ++i) {
494 XpmColor & entry = table[i];
498 // libXpm cannot cope with strings of the form #rrrrggggbbbb,
499 // #rrrgggbbb or #rgb, so convert them to #rrggbb.
500 string c_color = entry.c_color;
501 if (c_color[0] == '#' && c_color.size() != 7) {
502 c_color = convertTo7chars(c_color);
504 entry.c_color = clone_c_string(c_color.c_str());
507 // If the c_color is defined and the equivalent
508 // grayscale or monochrome ones are not, then define them.
509 mapcolor(entry.c_color, &entry.g_color, &entry.m_color);
514 XpmImage GImageXPM::Data::get() const
517 image.width = width_;
518 image.height = height_;
520 image.ncolors = ncolors_;
521 image.data = data_.get();
522 image.colorTable = colorTable_.get();
527 void GImageXPM::Data::resetData(int w, int h, unsigned int * d)
535 unsigned int * GImageXPM::Data::initialisedData(int w, int h) const
537 size_t const data_size = w * h;
539 size_t const mem_size = sizeof(unsigned int) * data_size;
540 unsigned int * ptr = static_cast<unsigned int *>(malloc(mem_size));
542 unsigned int none_id = color_none_id();
543 std::fill(ptr, ptr + data_size, none_id);
549 unsigned int GImageXPM::Data::color_none_id() const
551 XpmColor * table = colorTable_.get();
552 for (size_t i = 0; i < ncolors_; ++i) {
553 char const * const color = table[i].c_color;
554 if (color && lowercase(color) == "none")
564 // libXpm cannot cope with strings of the form #rrrrggggbbbb,
565 // #rrrgggbbb or #rgb, so convert them to #rrggbb.
566 string const convertTo7chars(string const & input)
568 string::size_type size = input.size();
569 if (size != 13 && size != 10 && size != 4)
570 // Can't deal with it.
574 // Can't deal with it.
577 string format(input);
580 case 13: // #rrrrggggbbbb
585 case 10: // #rrrgggbbb
591 format.insert(2, 1, '0');
592 format.insert(4, 1, '0');
593 format.append(1, '0');
601 // Given a string of the form #ff0571 create appropriate grayscale and
602 // monochrome colors.
603 void mapcolor(char const * c_color, char ** g_color_ptr, char ** m_color_ptr)
608 char * g_color = *g_color_ptr;
609 char * m_color = *m_color_ptr;
611 if (g_color && m_color)
615 Display * display = fl_get_display();
616 Colormap cmap = fl_colormap;
619 if (XLookupColor(display, cmap, c_color, &xcol, &ccol) == 0)
620 // Unable to parse c_color.
623 // Note that X stores the RGB values in the range 0 - 65535
624 // whilst we require them in the range 0 - 255.
625 int const r = xcol.red / 256;
626 int const g = xcol.green / 256;
627 int const b = xcol.blue / 256;
629 // This gives a good match to a human's RGB to luminance conversion.
630 // (From xv's Postscript code --- Mike Ressler.)
631 int const gray = int((0.32 * r) + (0.5 * g) + (0.18 * b));
633 ostringstream gray_stream;
634 gray_stream << "#" << std::setbase(16) << std::setfill('0')
635 << std::setw(2) << gray
636 << std::setw(2) << gray
637 << std::setw(2) << gray;
639 int const mono = (gray < 128) ? 0 : 255;
640 ostringstream mono_stream;
641 mono_stream << "#" << std::setbase(16) << std::setfill('0')
642 << std::setw(2) << mono
643 << std::setw(2) << mono
644 << std::setw(2) << mono;
646 // This string is going into an XpmImage struct, so create copies that
647 // libXPM can free successfully.
649 *g_color_ptr = clone_c_string(gray_stream.str().c_str());
651 *m_color_ptr = clone_c_string(mono_stream.str().c_str());
655 void copy_color_table(XpmColor const * in, size_t size, XpmColor * out)
657 for (size_t i = 0; i < size; ++i) {
658 out[i].string = clone_c_string(in[i].string);
659 out[i].symbolic = clone_c_string(in[i].symbolic);
660 out[i].m_color = clone_c_string(in[i].m_color);
661 out[i].g_color = clone_c_string(in[i].g_color);
662 out[i].g4_color = clone_c_string(in[i].g4_color);
663 out[i].c_color = clone_c_string(in[i].c_color);
668 void free_color_table(XpmColor * table, size_t size)
670 for (size_t i = 0; i < size; ++i) {
671 free(table[i].string);
672 free(table[i].symbolic);
673 free(table[i].m_color);
674 free(table[i].g_color);
675 free(table[i].g4_color);
676 free(table[i].c_color);
678 // Don't free the table itself. Let the shared_c_ptr do that.
683 char * clone_c_string(char const * in)
688 // Don't forget the '\0'
689 char * out = static_cast<char *>(malloc(strlen(in) + 1));
690 return strcpy(out, in);
694 bool contains_color_none(XpmImage const & image)
696 for (size_t i = 0; i < image.ncolors; ++i) {
697 char const * const color = image.colorTable[i].c_color;
698 if (color && lowercase(color) == "none")
705 string const unique_color_string(XpmImage const & image)
707 string id(image.cpp, ' ');
710 bool found_it = false;
711 for (size_t i = 0; i < image.ncolors; ++i) {
712 string const c_id = image.colorTable[i].string;
722 // Loop over the printable characters in the ASCII table.
723 // Ie, count from char 32 (' ') to char 126 ('~')
724 // A base 94 counter!
725 string::size_type current_index = id.size() - 1;
726 bool continue_loop = true;
727 while(continue_loop) {
728 continue_loop = false;
730 if (id[current_index] == 126) {
731 continue_loop = true;
732 if (current_index == 0)
733 // Unable to find a unique string
734 return image.colorTable[0].string;
736 id[current_index] = 32;
739 id[current_index] += 1;
740 // Note that '"' is an illegal char in this
742 if (id[current_index] == '"')
743 id[current_index] += 1;
747 // Unable to find a unique string