/// // Copyright (C) 2002 - 2004, Fredrik Arnerup & Rasmus Kaj, See COPYING /// #include "rasterframe.h" #include #include "util/stringutil.h" #include "util/filesys.h" #include "util/warning.h" #include "util/rectboundary.h" #include "util/xmlwrap.h" #include "ps/encode.h" #include "ps/misc.h" #include "pptout/config.h" // Todo RasterFrame::RasterFrame(const ElementWrap& xml, Group *parent) : BasicFrame(xml, parent), association(xml.get_filename("file")), filewatcher(association) { if(association.empty()) throw Error::Read("\"file\" attribute missing or empty in RasterFrame"); filewatcher.modified_signal.connect (sigc::mem_fun(*this, &RasterFrame::on_file_modified)); on_file_modified(); // Handle widht and height for backwards compatibility float width = xml.get_attribute("width"), height = xml.get_attribute("height"); if(width != 0 || height != 0) { warning << "Converting from old save format, with width/height for raster." << std::endl; set_ppi(Vector(72 * unit_size.get_x() / width, 72 * unit_size.get_y() / height)); } } RasterFrame::RasterFrame(Group* parent, const std::string& filename, float res) : BasicFrame(parent, "Raster " + basename(filename)), association(filename), filewatcher(association) { filewatcher.modified_signal.connect (sigc::mem_fun(*this, &RasterFrame::on_file_modified)); try { filepixbuf = Gdk::Pixbuf::create_from_file(association); /// \todo care for what the image iself says about its resolution? unit_size = Gdk::Point(filepixbuf->get_width(), filepixbuf->get_height()); } catch(Glib::Error e) { throw Error::Read("Cannot load raster image file " + association + "\n" + e.what()); } set_ppi(res > 0 ? Vector(res, res) : get_default_ppi()); } RasterFrame::~RasterFrame() {} void RasterFrame::on_file_modified() { try { filepixbuf = Gdk::Pixbuf::create_from_file(association); /// \todo care for what the image iself says about its resolution? unit_size = Gdk::Point(filepixbuf->get_width(), filepixbuf->get_height()); } catch(const Glib::Error &e){ filepixbuf.clear(); unit_size = Gdk::Point(1,1); warning << get_name() << ": "; warning << e.what() << std::endl; } object_changed_signal(); } std::string RasterFrame::getTypeName() const { return "raster"; } xmlpp::Element *RasterFrame::save(xmlpp::Element& parent_node, const FileContext &context) const { xmlpp::Element *node = BasicFrame::save(parent_node, context); node->set_attribute("type", "raster"); node->set_attribute("file", context.to(association)); return node; } namespace { struct Statistics{ bool grayscale; bool suggest_rle; }; Statistics calc_stats(Glib::RefPtr pixbuf, int samples = 100) { /// \todo check that samples is well below number of pixels Statistics result = {true, false}; const guint8 *data = pixbuf->get_pixels(); const int pixnum = pixbuf->get_width() * pixbuf->get_height(); const int bytes_per_pixel = pixbuf->get_n_channels(); const int rowstride = pixbuf->get_rowstride(); debug << "Rowstride: " << rowstride << std::endl; int rle_count = 0; for(int i = 0; i < 100; i++) debug << int(*(data + i)) << ", "; for(int i = 0; i < samples; i++) { guint32 index = bytes_per_pixel * i * (pixnum / samples); // rowstride does not seem to have to be divisible by bytes_per_pixel: index = (index / rowstride) * rowstride + ((index % rowstride) / bytes_per_pixel) * bytes_per_pixel; const guint8 *pixel = data + index; // is it grayscale? bool grayscale = ((*pixel == *(pixel + 1)) && (*pixel == *(pixel + 2))); result.grayscale = result.grayscale && grayscale; debug << index << ": " << int(*pixel) << " " << int(*(pixel + 1)) << " " << int(*(pixel + 2)) << " " << result.grayscale << std::endl; /// \todo check if b/w // count similar consecutive pixels: rle_count += (grayscale && *pixel == *(pixel + bytes_per_pixel) && *(pixel + 1) == *(pixel + bytes_per_pixel + 1) && *(pixel + 2) == *(pixel + bytes_per_pixel + 2) ) ? 1 : 0; } // minimum amount of similar pixels when suggesting rle: const int suggest_rle_threshold = 3; result.suggest_rle = (rle_count >= suggest_rle_threshold); return result; } } void RasterFrame::print(std::ostream &out, bool grayscale) const { if(!filepixbuf) { out << "% - - - no raster data to print for " << get_name() << std::endl; warning << "No raster data to print for " << get_name() << std::endl; } else { assert(filepixbuf->get_colorspace() == Gdk::COLORSPACE_RGB); Statistics statistics = calc_stats(filepixbuf); const bool print_rle = statistics.suggest_rle; const bool print_gray = grayscale || statistics.grayscale; /// \todo config option for filter, and clever filter selection. PS::EncodeFilter *filter; PS::ASCIIHexEncodeFilter filterHex; PS::ASCII85EncodeFilter filter85; PS::RunLengthEncodeFilter filterRLE; PS::FilterSequence sequence; sequence.push_back(&filter85); if(print_rle) sequence.push_back(&filterRLE); PS::EncodeFilterCascade filterCascade(sequence); filter = &filterCascade; out << "% Raster image: " << basename(association) << "\nsave\n" << PS::Concat(Matrix::scaling(unit_size.get_x(), unit_size.get_y()) * get_matrix()) << (print_gray ? "/DeviceGray" : "/DeviceRGB") << " setcolorspace\n" << "<<\n" << " /ImageType 1" << " /Width " << unit_size.get_x() << " /Height " << unit_size.get_y() << '\n' << " /BitsPerComponent 8\n" << (print_gray ? " /Decode [0 1]\n" : " /Decode [0 1 0 1 0 1]\n") << " /ImageMatrix [" << unit_size.get_x() << " 0 0 " << -unit_size.get_y() << " 0 " << unit_size.get_y() << "]\n" << " /DataSource currentfile\n" << " " << filter->decode_command() << "\n>> image" << std::endl; std::clock_t start = std::clock(); // Dump the actual image data to postscript. guchar* data = filepixbuf->get_pixels(); const int bytes_per_row = filepixbuf->get_rowstride(); const int bytes_per_pixel = filepixbuf->get_n_channels(); filter->begin(&out); // intialize filter // Use a buffer so we don't have to call filter->write() so often. const long buffer_size = 100000; // size? the bigger the better // + 2 for margin when writing three-by-three: guchar *buffer = new guchar[buffer_size + 2]; long i = 0; // buffer index for(int y = 0; y < unit_size.get_y(); ++y) for(int x = 0; x < unit_size.get_x(); ++x) { // Yucky pointer arithmetic ... guchar* pixel = data + bytes_per_row * y + bytes_per_pixel * x; // Note: This assumes that the 3 first channels are RGB, which seem to // be ok currently, even grayscale files are loaded to RGB pixbufs. if(grayscale) { // convert to grayscale according to the red book: guchar g = guchar(0.3 * pixel[0] // r + 0.59 * pixel[1] // g + 0.11 * pixel[2] // b + 0.5); buffer[i++] = g; } else { // memcpy? buffer[i++] = pixel[0]; if(!print_gray) { buffer[i++] = pixel[1]; buffer[i++] = pixel[2]; } } if(i >= buffer_size) { filter->write(buffer, i); i = 0; } } filter->write(buffer, i); filter->end(); // finalize filter delete[] buffer; verbose << "Time to print " << basename(association) << ": " << (std::clock() - start) / 1000 << " ms (" << (print_gray ? "grayscale" : "color") << (print_rle ? ", RLE" : "") << ")" << std::endl; out << "\nrestore" << std::endl; } } void RasterFrame::print_pdf(PDF::Content::Ptr pdf) const { PDF::Stream::Ptr obj = PDF::Stream::create(); obj->set_entry_name("Type", "XObject"); obj->set_entry_name("Subtype", "Image"); obj->set_entry_int("Width", unit_size.get_x()); obj->set_entry_int("Height", unit_size.get_y()); /// \Todo: Use the colorspace and bits per component of the source image obj->set_entry_name("ColorSpace", "DeviceRGB"); obj->set_entry_int("BitsPerComponent", 8); /// \Todo: Lots of optional fields that could probably be nice ... /// \Todo: Tell the stream how to encode/decode the data. Currently, we use /// no encoding at all ... const guchar* data = filepixbuf->get_pixels(); const int bytes_per_row = filepixbuf->get_rowstride(); const int bytes_per_pixel = filepixbuf->get_n_channels(); for(int y = 0; y < unit_size.get_y(); ++y) for(int x = 0; x < unit_size.get_x(); ++x) { // Yucky pointer arithmetic ... const guchar* pixel = data + bytes_per_row * y + bytes_per_pixel * x; // RGB is 3 bytes to be written. // Note: This assumes that the 3 first channels are RGB, which seem to // be ok currently, even grayscale files are loaded to RGB pixbufs. obj->data().write(reinterpret_cast(pixel), 3); } const std::string objname = pdf->registerXObj(obj); Vector sz = get_inherent_size(); pdf->data() << "q\n" << Matrix::scaling(unit_size.get_x(), unit_size.get_y()) * get_matrix()<< " cm\n" << '/' << objname << " Do\n" << "Q\n"; } void RasterFrame::set_association(const std::string &s) { if(s == association) return; association = s; filewatcher.set_file(association); on_file_modified(); props_changed_signal(); } Boundary RasterFrame::get_box() const { return RectBoundary::create(get_matrix(), unit_size.get_x(), unit_size.get_y()); } Vector RasterFrame::get_ppi() const { return Vector(72 / get_matrix().sc_x(), 72 / get_matrix().sc_y()); } Vector RasterFrame::get_default_ppi() { // could conceivably get resolution from image file Config::Floats &values = config.DefaultResolution.values; switch(values.size()) { case 1: return Vector(values[0], values[0]); break; case 2: return Vector(values[0], values[1]); break; default: return Vector(72, 72); break; } } Vector RasterFrame::get_inherent_size() const { return Vector(unit_size.get_x(), unit_size.get_y()); } void RasterFrame::set_ppi(const Vector& ppi) { if(ppi.x == 0 || ppi.y == 0) throw std::runtime_error("Illegal raster ppi: " + tostr(ppi)); Matrix m = get_matrix(); set_matrix(m.set_scale(72 / ppi.x, 72 / ppi.y)); }