Commit 09f2b4a7 authored by Tavmjong Bah's avatar Tavmjong Bah
Browse files

Avoid writing out and reading in PNG file when make a bitmap copy.

Simplify calls to sp_generate_internal_bitmap() (removed unused/unnecessary variables).
parent 38092e10
Pipeline #296404416 failed with stages
in 45 minutes and 49 seconds
......@@ -335,7 +335,7 @@ Pixbuf *Pixbuf::create_from_data_uri(gchar const *uri_data, double svgdpi)
if (svgdpi && svgdpi > 0) {
dpi = svgdpi;
}
std::cout << dpi << "dpi" << std::endl;
// Get the size of the document
Inkscape::Util::Quantity svgWidth = svgDoc->getWidth();
Inkscape::Util::Quantity svgHeight = svgDoc->getHeight();
......@@ -347,13 +347,9 @@ Pixbuf *Pixbuf::create_from_data_uri(gchar const *uri_data, double svgdpi)
return nullptr;
}
// Now get the resized values
const int scaledSvgWidth = round(svgWidth_px/(96.0/dpi));
const int scaledSvgHeight = round(svgHeight_px/(96.0/dpi));
assert(!pixbuf);
pixbuf = sp_generate_internal_bitmap(svgDoc.get(), nullptr, 0, 0, svgWidth_px, svgHeight_px, scaledSvgWidth,
scaledSvgHeight, dpi, dpi, 0xffffff00, nullptr);
Geom::Rect area(0, 0, svgWidth_px, svgHeight_px);
pixbuf = sp_generate_internal_bitmap(svgDoc.get(), area, dpi);
GdkPixbuf const *buf = pixbuf->getPixbufRaw();
// Tidy up
......@@ -457,12 +453,8 @@ Pixbuf *Pixbuf::create_from_buffer(gchar *&&data, gsize len, double svgdpi, std:
return nullptr;
}
// Now get the resized values
const int scaledSvgWidth = round(svgWidth_px/(96.0/dpi));
const int scaledSvgHeight = round(svgHeight_px/(96.0/dpi));
pb = sp_generate_internal_bitmap(svgDoc.get(), nullptr, 0, 0, svgWidth_px, svgHeight_px, scaledSvgWidth,
scaledSvgHeight, dpi, dpi, 0xffffff00);
Geom::Rect area(0, 0, svgWidth_px, svgHeight_px);
pb = sp_generate_internal_bitmap(svgDoc.get(), area, dpi);
buf = pb->getPixbufRaw();
// Tidy up
......
......@@ -568,10 +568,10 @@ static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx)
// Do the export
SPDocument *document = item->document;
std::unique_ptr<Inkscape::Pixbuf> pb(
sp_generate_internal_bitmap(document, nullptr,
bbox->min()[Geom::X], bbox->min()[Geom::Y], bbox->max()[Geom::X], bbox->max()[Geom::Y],
width, height, res, res, (guint32) 0xffffff00, item ));
std::vector<SPItem*> items;
items.push_back(item);
std::unique_ptr<Inkscape::Pixbuf> pb(sp_generate_internal_bitmap(document, *bbox, res, items, true));
if (pb) {
//TEST(gdk_pixbuf_save( pb, "bitmap.png", "png", NULL, NULL ));
......
......@@ -31,26 +31,30 @@
// TODO look for copy-n-paste duplication of this function:
/**
* Hide all items except @item, recursively, skipping groups and defs.
* Hide all items that are not in list, recursively, skipping groups and defs.
*/
static void hide_other_items_recursively(SPObject *o, SPItem *i, unsigned dkey)
static void hide_other_items_recursively(SPObject *object, const std::vector<SPItem*> &items, unsigned dkey)
{
SPItem *item = dynamic_cast<SPItem *>(o);
if ( item
&& !dynamic_cast<SPDefs *>(item)
&& !dynamic_cast<SPRoot *>(item)
&& !dynamic_cast<SPGroup *>(item)
&& !dynamic_cast<SPUse *>(item)
&& (i != o) )
{
SPItem *item = dynamic_cast<SPItem *>(object);
if (!item) {
// <defs>, <metadata>, etc.
return;
}
if (std::find (items.begin(), items.end(), item) != items.end()) {
// It's in our list, don't hide!
return;
}
if ( !dynamic_cast<SPRoot *>(item) &&
!dynamic_cast<SPGroup *>(item) &&
!dynamic_cast<SPUse *>(item) ) {
// Hide if not container or def.
item->invoke_hide(dkey);
}
// recurse
if (i != o) {
for (auto& child: o->children) {
hide_other_items_recursively(&child, i, dkey);
}
for (auto& child: object->children) {
hide_other_items_recursively(&child, items, dkey);
}
}
......@@ -58,80 +62,87 @@ static void hide_other_items_recursively(SPObject *o, SPItem *i, unsigned dkey)
/**
generates a bitmap from given items
the bitmap is stored in RAM and not written to file
@param x0 area left in document coordinates
@param y0 area top in document coordinates
@param x1 area right in document coordinates
@param y1 area bottom in document coordinates
@param width bitmap width in pixels
@param height bitmap height in pixels
@param xdpi
@param ydpi
@return the created GdkPixbuf structure or NULL if no memory is allocable
@param document Inkscape document.
@param area Export area in document units.
@param dpi Resolution.
@param items Vector of pointers to SPItems to export. Export all items if empty.
@param opaque Set items opacity to 1 (used by Cairo renderer for filtered objects rendered as bitmaps).
@return The created GdkPixbuf structure or nullptr if rendering failed.
*/
Inkscape::Pixbuf *sp_generate_internal_bitmap(SPDocument *doc, gchar const */*filename*/,
double x0, double y0, double x1, double y1,
unsigned width, unsigned height, double xdpi, double ydpi,
unsigned long /*bgcolor*/,
SPItem *item_only)
Inkscape::Pixbuf *sp_generate_internal_bitmap(SPDocument *document,
Geom::Rect const &area,
double dpi,
std::vector<SPItem *> items,
bool opaque)
{
if (width == 0 || height == 0) return nullptr;
Inkscape::Pixbuf *inkpb = nullptr;
/* Create new drawing for offscreen rendering*/
Inkscape::Drawing drawing;
drawing.setExact(true);
unsigned dkey = SPItem::display_key_new(1);
// Geometry
if (area.hasZeroArea()) {
return nullptr;
}
doc->ensureUpToDate();
Geom::Point origin = area.min();
double scale_factor = Inkscape::Util::Quantity::convert(dpi, "px", "in");
Geom::Affine affine = Geom::Translate(-origin) * Geom::Scale (scale_factor, scale_factor);
Geom::Rect screen=Geom::Rect(Geom::Point(x0,y0), Geom::Point(x1, y1));
int width = std::ceil(scale_factor * area.width());
int height = std::ceil(scale_factor * area.height());
Geom::Point origin = screen.min();
// Document
document->ensureUpToDate();
unsigned dkey = SPItem::display_key_new(1);
Geom::Scale scale(Inkscape::Util::Quantity::convert(xdpi, "px", "in"), Inkscape::Util::Quantity::convert(ydpi, "px", "in"));
Geom::Affine affine = scale * Geom::Translate(-origin * scale);
// Drawing
Inkscape::Drawing drawing; // New drawing for offscreen rendering.
drawing.setExact(true); // Maximum quality for blurs.
/* Create ArenaItems and set transform */
Inkscape::DrawingItem *root = doc->getRoot()->invoke_show( drawing, dkey, SP_ITEM_SHOW_DISPLAY);
Inkscape::DrawingItem *root = document->getRoot()->invoke_show( drawing, dkey, SP_ITEM_SHOW_DISPLAY);
root->setTransform(affine);
drawing.setRoot(root);
// We show all and then hide all items we don't want, instead of showing only requested items,
// because that would not work if the shown item references something in defs
if (item_only) {
hide_other_items_recursively(doc->getRoot(), item_only, dkey);
// TODO: The following line forces 100% opacity as required by sp_asbitmap_render() in cairo-renderer.cpp
// Make it conditional if 'item_only' is ever used by other callers which need to retain opacity
if (item_only->get_arenaitem(dkey)) {
item_only->get_arenaitem(dkey)->setOpacity(1.0);
} else {
g_warning("sp_generate_internal_bitmap: trying to set opacity of non-existing arenaitem");
}
// Hide all items we don't want, instead of showing only requested items,
// because that would not work if the shown item references something in defs.
if (!items.empty()) {
hide_other_items_recursively(document->getRoot(), items, dkey);
}
Geom::IntRect final_bbox = Geom::IntRect::from_xywh(0, 0, width, height);
drawing.update(final_bbox);
Geom::IntRect final_area = Geom::IntRect::from_xywh(0, 0, width, height);
drawing.update(final_area);
if (opaque) {
// Required by sp_asbitmap_render().
for (auto item : items) {
if (item->get_arenaitem(dkey)) {
item->get_arenaitem(dkey)->setOpacity(1.0);
}
}
}
// Rendering
cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
Inkscape::Pixbuf* pixbuf = nullptr;
if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) {
Inkscape::DrawingContext dc(surface, Geom::Point(0,0));
// render items
drawing.render(dc, final_bbox, Inkscape::DrawingItem::RENDER_BYPASS_CACHE);
drawing.render(dc, final_area, Inkscape::DrawingItem::RENDER_BYPASS_CACHE);
inkpb = new Inkscape::Pixbuf(surface);
}
else
{
long long size = (long long) height * (long long) cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
pixbuf = new Inkscape::Pixbuf(surface);
} else {
long long size =
(long long) height *
(long long) cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
g_warning("sp_generate_internal_bitmap: not enough memory to create pixel buffer. Need %lld.", size);
cairo_surface_destroy(surface);
}
doc->getRoot()->invoke_hide(dkey);
return inkpb;
// Return to previous state.
document->getRoot()->invoke_hide(dkey);
return pixbuf;
}
/*
......
......@@ -19,9 +19,9 @@ class SPDocument;
class SPItem;
namespace Inkscape { class Pixbuf; }
Inkscape::Pixbuf *sp_generate_internal_bitmap(SPDocument *doc, gchar const *filename,
double x0, double y0, double x1, double y1,
unsigned width, unsigned height, double xdpi, double ydpi,
unsigned long bgcolor, SPItem *item_only = nullptr);
Inkscape::Pixbuf *sp_generate_internal_bitmap(SPDocument *document,
Geom::Rect const &area,
double dpi,
std::vector<SPItem *> items = std::vector<SPItem*>(),
bool set_opaque = false);
#endif
......@@ -53,7 +53,7 @@
#include "display/curve.h"
#include "display/control/canvas-item-bpath.h"
#include "helper/png-write.h"
#include "helper/pixbuf-ops.h"
#include "io/resource.h"
......@@ -3684,7 +3684,8 @@ void ObjectSet::createBitmapCopy()
desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to make a bitmap copy."));
return;
}
if(desktop()){
if (desktop()) {
desktop()->messageStack()->flash(Inkscape::IMMEDIATE_MESSAGE, _("Rendering bitmap..."));
// set "busy" cursor
desktop()->setWaitingCursor();
......@@ -3703,32 +3704,7 @@ void ObjectSet::createBitmapCopy()
std::vector<SPItem*> items_(items().begin(), items().end());
// Sort items so that the topmost comes last
sort(items_.begin(),items_.end(),sp_item_repr_compare_position_bool);
// Generate a random value from the current time (you may create bitmap from the same object(s)
// multiple times, and this is done so that they don't clash)
guint current = guint(g_get_monotonic_time() % 1024);
// Create the filename.
gchar *const basename = g_strdup_printf("%s-%s-%u.png",
doc->getDocumentName(),
items_[0]->getRepr()->attribute("id"),
current);
// Imagemagick is known not to handle spaces in filenames, so we replace anything but letters,
// digits, and a few other chars, with "_"
g_strcanon(basename, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.=+~$#@^&!?", '_');
// Build the complete path by adding document base dir, if set, otherwise home dir
gchar *directory = nullptr;
if ( doc->getDocumentFilename() ) {
directory = g_path_get_dirname( doc->getDocumentFilename() );
}
if (directory == nullptr) {
directory = Inkscape::IO::Resource::homedir_path(nullptr);
}
gchar *filepath = g_build_filename(directory, basename, NULL);
g_free(directory);
//g_print("%s\n", filepath);
sort(items_.begin(), items_.end(), sp_item_repr_compare_position_bool);
// Remember parent and z-order of the topmost one
gint pos = items_.back()->getRepr()->position();
......@@ -3765,91 +3741,25 @@ void ObjectSet::createBitmapCopy()
}
}
// The width and height of the bitmap in pixels
unsigned width = (unsigned) floor(bbox->width() * Inkscape::Util::Quantity::convert(res, "px", "in"));
unsigned height =(unsigned) floor(bbox->height() * Inkscape::Util::Quantity::convert(res, "px", "in"));
// Find out if we have to run an external filter
gchar const *run = nullptr;
Glib::ustring filter = prefs->getString("/options/createbitmap/filter");
if (!filter.empty()) {
// filter command is given;
// see if we have a parameter to pass to it
Glib::ustring param1 = prefs->getString("/options/createbitmap/filter_param1");
if (!param1.empty()) {
if (param1[param1.length() - 1] == '%') {
// if the param string ends with %, interpret it as a percentage of the image's max dimension
gchar p1[256];
g_ascii_dtostr(p1, 256, ceil(g_ascii_strtod(param1.data(), nullptr) * MAX(width, height) / 100));
// the first param is always the image filename, the second is param1
run = g_strdup_printf("%s \"%s\" %s", filter.data(), filepath, p1);
} else {
// otherwise pass the param1 unchanged
run = g_strdup_printf("%s \"%s\" %s", filter.data(), filepath, param1.data());
}
} else {
// run without extra parameter
run = g_strdup_printf("%s \"%s\"", filter.data(), filepath);
}
}
// Calculate the matrix that will be applied to the image so that it exactly overlaps the source objects
Geom::Affine eek;
{
SPItem *parentItem = dynamic_cast<SPItem *>(parent_object);
if (parentItem) {
eek = parentItem->i2doc_affine();
} else {
g_assert_not_reached();
}
}
Geom::Affine t;
double shift_x = bbox->left();
double shift_y = bbox->top();
if (res == Inkscape::Util::Quantity::convert(1, "in", "px")) { // for default 96 dpi, snap it to pixel grid
shift_x = round(shift_x);
shift_y = round(shift_y);
bbox = bbox->roundOutwards();
}
t = Geom::Translate(shift_x, shift_y) * eek.inverse();
// TODO: avoid roundtrip via file
// Do the export
sp_export_png_file(doc, filepath,
bbox->min()[Geom::X], bbox->min()[Geom::Y],
bbox->max()[Geom::X], bbox->max()[Geom::Y],
width, height, res, res,
(guint32) 0xffffff00,
nullptr, nullptr,
true, /*bool force_overwrite,*/
items_);
// Run filter, if any
if (run) {
g_print("Running external filter: %s\n", run);
int result = system(run);
if(result == -1)
g_warning("Could not run external filter: %s\n", run);
}
Inkscape::Pixbuf *pb = sp_generate_internal_bitmap(doc, *bbox, res, items_);
// Import the image back
Inkscape::Pixbuf *pb = Inkscape::Pixbuf::create_from_file(filepath);
if (pb) {
// Create the repr for the image
// TODO: avoid unnecessary roundtrip between data URI and decoded pixbuf
Inkscape::XML::Node * repr = xml_doc->createElement("svg:image");
sp_embed_image(repr, pb);
if (res == Inkscape::Util::Quantity::convert(1, "in", "px")) { // for default 96 dpi, snap it to pixel grid
sp_repr_set_svg_double(repr, "width", width);
sp_repr_set_svg_double(repr, "height", height);
} else {
sp_repr_set_svg_double(repr, "width", bbox->width());
sp_repr_set_svg_double(repr, "height", bbox->height());
}
sp_repr_set_svg_double(repr, "width", bbox->width());
sp_repr_set_svg_double(repr, "height", bbox->height());
// Calculate the matrix that will be applied to the image so that it exactly overlaps the source objects
SPItem *parentItem = dynamic_cast<SPItem *>(parent_object);
Geom::Affine affine = Geom::Translate(bbox->left(), bbox->top()) * parentItem->i2doc_affine().inverse();
// Write transform
repr->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(t));
repr->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(affine));
// add the new repr to the parent
parent->addChildAtPos(repr, pos + 1);
......@@ -3866,11 +3776,10 @@ void ObjectSet::createBitmapCopy()
DocumentUndo::done(doc, SP_VERB_SELECTION_CREATE_BITMAP,
_("Create bitmap"));
}
if(desktop())
desktop()->clearWaitingCursor();
g_free(basename);
g_free(filepath);
if(desktop()) {
desktop()->clearWaitingCursor();
}
}
/* Creates a mask or clipPath from selection.
......
......@@ -151,21 +151,20 @@ load_svg_cursor(Glib::RefPtr<Gdk::Display> display,
auto w = document->getWidth().value("px");
auto h = document->getHeight().value("px");
int sw = w * scale;
int sh = h * scale;
int dpix = 96 * scale; // DPI
int dpiy = 96 * scale;
// Calculate the hotspot.
int hotspot_x = root->getIntAttribute("inkscape:hotspot_x", 0); // Do not include window scale factor!
int hotspot_y = root->getIntAttribute("inkscape:hotspot_y", 0);
auto ink_pixbuf = sp_generate_internal_bitmap(document.get(), nullptr, 0, 0, w, h, sw, sh, dpix, dpiy, 0, nullptr);
Geom::Rect area(0, 0, w, h);
int dpi = 96 * scale;
auto ink_pixbuf = sp_generate_internal_bitmap(document.get(), area, dpi);
auto pixbuf = Glib::wrap(ink_pixbuf->getPixbufRaw());
if (pixbuf) {
// Calculate the hotspot.
int hotspot_x = root->getIntAttribute("inkscape:hotspot_x", 0); // Do not include window scale factor!
int hotspot_y = root->getIntAttribute("inkscape:hotspot_y", 0);
cursor = Gdk::Cursor::create(display, pixbuf, hotspot_x, hotspot_y);
window->set_cursor(cursor);
} else {
std::cerr << "load_svg_cursor: failed to create pixbuf for: " << full_file_path << std::endl;
}
......
......@@ -1028,14 +1028,10 @@ bool FileOpenDialogImplWin32::set_svg_preview()
const double scaleFactorY = PreviewSize / svgHeight_px;
const double scaleFactor = (scaleFactorX > scaleFactorY) ? scaleFactorY : scaleFactorX;
// Now get the resized values
const int scaledSvgWidth = round(scaleFactor * svgWidth_px);
const int scaledSvgHeight = round(scaleFactor * svgHeight_px);
const double dpi = 96*scaleFactor;
const double dpi = 96 * scaleFactor;
Geom::Rect area(0, 0, svgWidth_px, svgHeight_px);
Inkscape::Pixbuf *pixbuf =
sp_generate_internal_bitmap(svgDoc.get(), NULL, 0, 0, svgWidth_px, svgHeight_px, scaledSvgWidth,
scaledSvgHeight, dpi, dpi, (guint32)0xffffff00, NULL);
sp_generate_internal_bitmap(svgDoc.get(), area, dpi);
// Tidy up
if (pixbuf == NULL) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment