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 "ColorHandler.h"
20 #include "frontends/GUIRunTime.h" // x11Display
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
30 /// Access to this class is through this static method.
31 ImagePtr GImageXPM::newImage()
34 ptr.reset(new GImageXPM());
39 /// Return the list of loadable formats.
40 GImage::FormatList GImageXPM::loadableFormats()
42 FormatList formats(1);
48 GImageXPM::GImageXPM()
50 pixmap_status_(PIXMAP_UNINITIALISED)
54 GImageXPM::GImageXPM(GImageXPM const & other)
57 pixmap_(other.pixmap_),
58 pixmap_status_(other.pixmap_status_)
62 GImageXPM::~GImageXPM()
64 if (pixmap_status_ == PIXMAP_SUCCESS)
65 XFreePixmap(GUIRunTime::x11Display(), pixmap_);
69 GImage * GImageXPM::clone() const
71 return new GImageXPM(*this);
75 unsigned int GImageXPM::getWidth() const
77 return image_.width();
81 unsigned int GImageXPM::getHeight() const
83 return image_.height();
87 Pixmap GImageXPM::getPixmap() const
89 if (!pixmap_status_ == PIXMAP_SUCCESS)
95 void GImageXPM::load(string const & filename, GImage::SignalTypePtr on_finish)
97 if (filename.empty()) {
98 on_finish->emit(false);
102 if (!image_.empty()) {
103 lyxerr[Debug::GRAPHICS]
104 << "Image is loaded already!" << std::endl;
105 on_finish->emit(false);
109 XpmImage * xpm_image = new XpmImage;
112 XpmReadFileToXpmImage(const_cast<char *>(filename.c_str()),
117 lyxerr[Debug::GRAPHICS]
118 << "No XPM image file found." << std::endl;
122 lyxerr[Debug::GRAPHICS]
123 << "File format is invalid" << std::endl;
127 lyxerr[Debug::GRAPHICS]
128 << "Insufficient memory to read in XPM file"
133 if (success != XpmSuccess) {
134 XpmFreeXpmImage(xpm_image);
137 lyxerr[Debug::GRAPHICS]
138 << "Error reading XPM file '"
139 << XpmGetErrorString(success) << "'"
142 image_.reset(*xpm_image);
145 on_finish->emit(success == XpmSuccess);
149 bool GImageXPM::setPixmap(GParams const & params)
151 if (image_.empty() || params.display == GParams::NONE) {
155 if (pixmap_status_ == PIXMAP_FAILED) {
159 if (pixmap_status_ == PIXMAP_SUCCESS) {
163 using namespace grfx;
164 Display * display = GUIRunTime::x11Display();
167 // This might be a dirty thing, but I dont know any other solution.
168 Screen * screen = ScreenOfDisplay(display, GUIRunTime::x11Screen());
173 XpmAttributes attrib;
175 // Allow libXPM lots of leeway when trying to allocate colors.
176 attrib.closeness = 10000;
177 attrib.valuemask = XpmCloseness;
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;
185 case GParams::GRAYSCALE:
186 attrib.color_key = XPM_GRAY;
189 default: // NONE cannot happen!
190 attrib.color_key = XPM_COLOR;
194 attrib.valuemask |= XpmColorKey;
196 // Set the color "none" entry to the color of the background.
197 XpmColorSymbol xpm_col;
199 xpm_col.value = "none";
200 xpm_col.pixel = lyxColorHandler->colorPixel(LColor::graphicsbg);
202 attrib.numsymbols = 1;
203 attrib.colorsymbols = &xpm_col;
204 attrib.valuemask |= XpmColorSymbols;
206 // Load up the pixmap
207 XpmImage xpm_image = image_.get();
209 XpmCreatePixmapFromXpmImage(display,
210 XRootWindowOfScreen(screen),
212 &pixmap, &mask, &attrib);
214 XpmFreeAttributes(&attrib);
216 if (status != XpmSuccess) {
217 lyxerr << "Error creating pixmap from xpm_image '"
218 << XpmGetErrorString(status) << "'"
220 pixmap_status_ = PIXMAP_FAILED;
225 pixmap_status_ = PIXMAP_SUCCESS;
230 void GImageXPM::clip(GParams const & params)
235 if (params.bb.empty())
236 // No clipping is necessary.
239 unsigned int const new_width = params.bb.xr - params.bb.xl;
240 unsigned int const new_height = params.bb.yt - params.bb.yb;
242 if (new_width > image_.width() || new_height > image_.height())
243 // Bounds are invalid.
246 if (new_width == image_.width() && new_height == image_.height())
247 // Bounds are unchanged.
250 unsigned int * new_data = image_.initialisedData(new_width, new_height);
251 unsigned int const * old_data = image_.data();
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();
262 image_.resetData(new_width, new_height, new_data);
266 void GImageXPM::rotate(GParams const & params)
272 // No rotation is necessary.
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);
283 double max_x = 0; double min_x = 0;
284 double max_y = 0; double min_y = 0;
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);
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);
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);
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);
307 unsigned int * new_data = image_.initialisedData(new_width, new_height);
308 unsigned int const * old_data = image_.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);
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);
322 int const old_id = x_old + image_.width() * y_old;
323 int const new_id = x_new + new_width * y_new;
325 new_data[new_id] = old_data[old_id];
329 image_.resetData(new_width, new_height, new_data);
333 void GImageXPM::scale(GParams const & params)
338 // boost::tie produces horrible compilation errors on my machine
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())
347 unsigned int * new_data = image_.initialisedData(new_width, new_height);
348 unsigned int const * old_data = image_.data();
350 double const x_scale = double(image_.width()) / double(new_width);
351 double const y_scale = double(image_.height()) / double(new_height);
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);
361 int const old_id = x_old + image_.width() * y_old;
362 int const new_id = x_new + new_width * y_new;
364 new_data[new_id] = old_data[old_id];
368 image_.resetData(new_width, new_height, new_data);
376 void free_color_table(XpmColor * colorTable, size_t size);
378 void copy_color_table(XpmColor const * in, size_t size, XpmColor * out);
380 bool contains_color_none(XpmImage const & image);
382 string const unique_color_string(XpmImage const & image);
384 // create a copy (using malloc and strcpy). If (!in) return 0;
385 char * clone_c_string(char const * in);
387 // Given a string of the form #ff0571 create appropriate grayscale and
388 // monochrome colors.
389 void mapcolor(char const * c_color, char ** g_color_ptr, char ** m_color_ptr);
397 GImageXPM::Data::Data()
398 : width_(0), height_(0), cpp_(0), ncolors_(0)
402 GImageXPM::Data::~Data()
404 if (colorTable_.unique())
405 free_color_table(colorTable_.get(), ncolors_);
409 void GImageXPM::Data::reset(XpmImage & image)
411 width_ = image.width;
412 height_ = image.height;
415 // Move the data ptr into this store and free up image.data
416 data_.reset(image.data);
419 // Don't just store the color table, but check first that it contains
420 // all that we require of it.
421 // The idea is to store the color table in a shared_ptr and for all
422 // modified images to use the same table.
423 // It must, therefore, have a c_color "none" entry and g_color and
424 // m_color entries corresponding to each and every c_color entry
427 // 1. Create a copy of the color table.
428 // Add a c_color "none" entry to the table if it isn't already there.
429 bool const add_color = !contains_color_none(image);
433 ncolors_ = 1 + image.ncolors;
434 size_t const mem_size = sizeof(XpmColor) * ncolors_;
435 XpmColor * table = static_cast<XpmColor *>(malloc(mem_size));
437 copy_color_table(image.colorTable, image.ncolors, table);
439 XpmColor & color = table[ncolors_ - 1];
445 clone_c_string(unique_color_string(image).c_str());
446 color.c_color = clone_c_string("none");
448 free_color_table(image.colorTable, image.ncolors);
449 colorTable_.reset(table);
453 // Just move the pointer across
454 ncolors_ = image.ncolors;
455 colorTable_.reset(image.colorTable);
456 image.colorTable = 0;
459 // Clean-up the remaining entries of image.
465 // 2. Ensure that the color table has g_color and m_color entries
466 XpmColor * table = colorTable_.get();
469 for (size_t i = 0; i < ncolors_; ++i) {
470 XpmColor & entry = table[i];
474 // A work-around for buggy XPM files that may be created by
475 // ImageMagick's convert.
476 string c_color = entry.c_color;
477 if (c_color[0] == '#' && c_color.size() > 7) {
478 if (buggy_color.empty())
479 buggy_color = c_color;
481 c_color = c_color.substr(0, 7);
483 entry.c_color = clone_c_string(c_color.c_str());
486 // If the c_color is defined and the equivalent
487 // grayscale or monochrome ones are not, then define them.
488 mapcolor(entry.c_color, &entry.g_color, &entry.m_color);
491 if (!buggy_color.empty()) {
492 lyxerr << "The XPM file contains silly colors, "
493 << "an example being \""
494 << buggy_color << "\".\n"
495 << "This was cropped to \""
496 << buggy_color.substr(0, 7)
497 << "\" so you can see something!\n"
498 << "If this file was created by ImageMagick's convert,\n"
499 << "then upgrading may cure the problem."
505 XpmImage GImageXPM::Data::get() const
508 image.width = width_;
509 image.height = height_;
511 image.ncolors = ncolors_;
512 image.data = data_.get();
513 image.colorTable = colorTable_.get();
518 void GImageXPM::Data::resetData(int w, int h, unsigned int * d)
525 unsigned int * GImageXPM::Data::initialisedData(int w, int h) const
527 size_t const data_size = w * h;
529 size_t const mem_size = sizeof(unsigned int) * data_size;
530 unsigned int * ptr = static_cast<unsigned int *>(malloc(mem_size));
532 unsigned int none_id = color_none_id();
533 std::fill(ptr, ptr + data_size, none_id);
539 unsigned int GImageXPM::Data::color_none_id() const
541 XpmColor * table = colorTable_.get();
542 for (size_t i = 0; i < ncolors_; ++i) {
543 char const * const color = table[i].c_color;
544 if (color && lowercase(color) == "none")
554 // Given a string of the form #ff0571 create appropriate grayscale and
555 // monochrome colors.
556 void mapcolor(char const * c_color, char ** g_color_ptr, char ** m_color_ptr)
561 char * g_color = *g_color_ptr;
562 char * m_color = *m_color_ptr;
564 if (g_color && m_color)
568 Display * display = GUIRunTime::x11Display();
569 Colormap cmap = GUIRunTime::x11Colormap();
572 if (XLookupColor(display, cmap, c_color, &xcol, &ccol) == 0)
573 // Unable to parse c_color.
576 // Note that X stores the RGB values in the range 0 - 65535
577 // whilst we require them in the range 0 - 255.
578 int const r = xcol.red / 256;
579 int const g = xcol.green / 256;
580 int const b = xcol.blue / 256;
582 // This gives a good match to a human's RGB to luminance conversion.
583 // (From xv's Postscript code --- Mike Ressler.)
584 int const gray = int((0.32 * r) + (0.5 * g) + (0.18 * b));
586 ostringstream gray_stream;
587 gray_stream << "#" << std::setbase(16) << std::setfill('0')
588 << std::setw(2) << gray
589 << std::setw(2) << gray
590 << std::setw(2) << gray;
592 int const mono = (gray < 128) ? 0 : 255;
593 ostringstream mono_stream;
594 mono_stream << "#" << std::setbase(16) << std::setfill('0')
595 << std::setw(2) << mono
596 << std::setw(2) << mono
597 << std::setw(2) << mono;
599 // This string is going into an XpmImage struct, so create copies that
600 // libXPM can free successfully.
602 *g_color_ptr = clone_c_string(gray_stream.str().c_str());
604 *m_color_ptr = clone_c_string(mono_stream.str().c_str());
608 void copy_color_table(XpmColor const * in, size_t size, XpmColor * out)
610 for (size_t i = 0; i < size; ++i) {
611 out[i].string = clone_c_string(in[i].string);
612 out[i].symbolic = clone_c_string(in[i].symbolic);
613 out[i].m_color = clone_c_string(in[i].m_color);
614 out[i].g_color = clone_c_string(in[i].g_color);
615 out[i].g4_color = clone_c_string(in[i].g4_color);
616 out[i].c_color = clone_c_string(in[i].c_color);
621 void free_color_table(XpmColor * table, size_t size)
623 for (size_t i = 0; i < size; ++i) {
624 free(table[i].string);
625 free(table[i].symbolic);
626 free(table[i].m_color);
627 free(table[i].g_color);
628 free(table[i].g4_color);
629 free(table[i].c_color);
631 // Don't free the table itself. Let the shared_c_ptr do that.
636 char * clone_c_string(char const * in)
641 // Don't forget the '\0'
642 char * out = static_cast<char *>(malloc(strlen(in) + 1));
643 return strcpy(out, in);
647 bool contains_color_none(XpmImage const & image)
649 for (size_t i = 0; i < image.ncolors; ++i) {
650 char const * const color = image.colorTable[i].c_color;
651 if (color && lowercase(color) == "none")
658 string const unique_color_string(XpmImage const & image)
660 string id(image.cpp, ' ');
663 bool found_it = false;
664 for (size_t i = 0; i < image.ncolors; ++i) {
665 string const c_id = image.colorTable[i].string;
673 std::cerr << "unique_color_string: \"" << id
674 << "\"" << std::endl;
678 // Loop over the printable characters in the ASCII table.
679 // Ie, count from char 32 (' ') to char 126 ('~')
680 // A base 94 counter!
681 string::size_type current_index = id.size() - 1;
682 bool continue_loop = true;
683 while(continue_loop) {
684 continue_loop = false;
687 if (id[current_index] == 126) {
688 continue_loop = true;
689 if (current_index == 0)
690 // Unable to find a unique string
691 return image.colorTable[0].string;
693 id[current_index] = 32;
696 id[current_index] += 1;
697 // Note that '"' is an illegal char in this
699 if (id[current_index] == '"')
700 id[current_index] += 1;
704 // Unable to find a unique string