Logo Search packages:      
Sourcecode: passepartout version File versions  Download package

rasterframe.cc

///
// Copyright (C) 2002 - 2004, Fredrik Arnerup & Rasmus Kaj, See COPYING
///
#include "rasterframe.h"
#include <cassert>
#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(slot(*this, 
                                 &RasterFrame::on_file_modified));
  on_file_modified();
  
  // Handle widht and height for backwards compatibility
  float width = xml.get_attribute<float>("width"),
    height = xml.get_attribute<float>("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(slot(*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(this);
}

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<Gdk::Pixbuf> 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); /// \todo "+ 0.5" ??? This was added
                            /// in revision 1.60, before the
                            /// move to document/, but I dont
                            /// know why.
        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<const char*>(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(this);
}

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));
}


Generated by  Doxygen 1.6.0   Back to index