3 * sheet-object-image.c: a wrapper for gdkpixbuf to display images.
6 * Jody Goldberg (jody@gnome.org)
8 #include <gnumeric-config.h>
11 #include <sheet-object-image.h>
16 #include <sheet-object-impl.h>
17 #include <sheet-control-gui.h>
19 #include <application.h>
23 #include <goffice/goffice.h>
24 #include <gsf/gsf-impl-utils.h>
25 #include <gsf/gsf-output-stdio.h>
26 #include <gsf/gsf-utils.h>
32 #define d(code) do { code; } while (0)
37 #define CC2XML(s) ((xmlChar const *)(s))
38 #define CXML2C(s) ((char const *)(s))
40 static inline gboolean
41 attr_eq (const xmlChar
*a
, const char *s
)
43 return !strcmp (CXML2C (a
), s
);
47 so_image_view_set_bounds (SheetObjectView
*sov
, double const *coords
, gboolean visible
)
49 GocItem
*view
= GOC_ITEM (GOC_GROUP (sov
)->children
->data
);
50 double scale
= goc_canvas_get_pixels_per_unit (view
->canvas
);
53 double x
, y
, width
, height
;
54 double old_x1
, old_y1
, old_x2
, old_y2
, old_width
, old_height
;
55 GdkPixbuf
*placeholder
= g_object_get_data (G_OBJECT (view
), "tile");
57 x
= MIN (coords
[0], coords
[2]) / scale
;
58 y
= MIN (coords
[1], coords
[3]) / scale
;
59 width
= fabs (coords
[2] - coords
[0]) / scale
;
60 height
= fabs (coords
[3] - coords
[1]) / scale
;
62 goc_item_get_bounds (view
, &old_x1
, &old_y1
, &old_x2
, &old_y2
);
65 "width", width
, "height", height
,
68 /* regenerate the image if necessary */
69 old_width
= fabs (old_x1
- old_x2
);
70 old_height
= fabs (old_y1
- old_y2
);
71 if (placeholder
!= NULL
&&
72 (fabs (width
- old_width
) > 0.5 || fabs (height
- old_height
) > 0.5)) {
73 GdkPixbuf
*newimage
= go_gdk_pixbuf_tile (placeholder
,
74 (guint
)width
, (guint
)height
);
75 goc_item_set (view
, "pixbuf", newimage
, NULL
);
76 g_object_unref (newimage
);
85 so_image_goc_view_class_init (SheetObjectViewClass
*sov_klass
)
87 sov_klass
->set_bounds
= so_image_view_set_bounds
;
89 typedef SheetObjectView SOImageGocView
;
90 typedef SheetObjectViewClass SOImageGocViewClass
;
91 static GSF_CLASS (SOImageGocView
, so_image_goc_view
,
92 so_image_goc_view_class_init
, NULL
,
95 /****************************************************************************/
96 struct _SheetObjectImage
{
97 SheetObject sheet_object
;
110 SheetObjectClass parent_class
;
111 } SheetObjectImageClass
;
113 static SheetObjectClass
*gnm_soi_parent_class
;
123 * sheet_object_image_set_image:
124 * @soi: #SheetObjectImage
129 * Assign raw data and type to @soi.
133 sheet_object_image_set_image (SheetObjectImage
*soi
,
138 g_return_if_fail (GNM_IS_SO_IMAGE (soi
));
141 soi
->type
= (type
&& *type
) ? g_strdup (type
) : NULL
;
143 g_object_unref (soi
->image
);
144 soi
->image
= go_image_new_from_data (soi
->type
, data
, data_len
,
145 ((soi
->type
== NULL
)? &soi
->type
: NULL
), NULL
);
147 if (soi
->sheet_object
.sheet
!= NULL
) {
148 /* Share within document. */
149 GOImage
*image
= go_doc_add_image (GO_DOC (soi
->sheet_object
.sheet
->workbook
), NULL
, soi
->image
);
150 if (image
!= soi
->image
) {
151 g_object_unref (soi
->image
);
152 soi
->image
= g_object_ref (image
);
158 sheet_object_image_set_crop (SheetObjectImage
*soi
,
159 double crop_left
, double crop_top
,
160 double crop_right
, double crop_bottom
)
162 g_return_if_fail (GNM_IS_SO_IMAGE (soi
));
164 soi
->crop_left
= crop_left
;
165 soi
->crop_top
= crop_top
;
166 soi
->crop_right
= crop_right
;
167 soi
->crop_bottom
= crop_bottom
;
171 gnm_soi_finalize (GObject
*object
)
173 SheetObjectImage
*soi
;
175 soi
= GNM_SO_IMAGE (object
);
179 g_object_unref (soi
->image
);
181 G_OBJECT_CLASS (gnm_soi_parent_class
)->finalize (object
);
184 static SheetObjectView
*
185 gnm_soi_new_view (SheetObject
*so
, SheetObjectViewContainer
*container
)
187 SheetObjectImage
*soi
= GNM_SO_IMAGE (so
);
188 GocItem
*item
= NULL
;
190 item
= goc_item_new (
191 gnm_pane_object_group (GNM_PANE (container
)),
192 so_image_goc_view_get_type (),
195 goc_item_hide (goc_item_new (GOC_GROUP (item
),
198 "crop-bottom", soi
->crop_bottom
,
199 "crop-left", soi
->crop_left
,
200 "crop-right", soi
->crop_right
,
201 "crop-top", soi
->crop_top
,
205 GdkPixbuf
*placeholder
=
206 gdk_pixbuf_new_from_resource ("/org/gnumeric/gnumeric/images/unknown_image.png",
208 GdkPixbuf
*pixbuf
= gdk_pixbuf_copy (placeholder
);
210 goc_item_hide (goc_item_new (GOC_GROUP (item
),
214 g_object_unref (pixbuf
);
215 g_object_set_data (G_OBJECT (item
), "tile", placeholder
);
218 return gnm_pane_object_register (so
, item
, TRUE
);
221 static GtkTargetList
*
222 gnm_soi_get_target_list (SheetObject
const *so
)
224 SheetObjectImage
*soi
= GNM_SO_IMAGE (so
);
225 GtkTargetList
*tl
= gtk_target_list_new (NULL
, 0);
228 GdkPixbuf
*pixbuf
= NULL
;
230 if (soi
->type
== NULL
&& soi
->image
!= NULL
)
231 pixbuf
= go_image_get_pixbuf (soi
->image
);
232 mime_str
= go_image_format_to_mime (soi
->type
);
234 mimes
= go_strsplit_to_slist (mime_str
, ',');
235 for (ptr
= mimes
; ptr
!= NULL
; ptr
= ptr
->next
) {
236 const char *mime
= ptr
->data
;
238 if (mime
!= NULL
&& *mime
!= '\0')
239 gtk_target_list_add (tl
, gdk_atom_intern (mime
, FALSE
),
243 g_slist_free_full (mimes
, g_free
);
245 /* No need to eliminate duplicates. */
246 if (soi
->image
!= NULL
|| pixbuf
!= NULL
) {
247 gtk_target_list_add_image_targets (tl
, 0, TRUE
);
249 g_object_unref (pixbuf
);
256 gnm_soi_write_image (SheetObject
const *so
, char const *format
,
257 G_GNUC_UNUSED
double resolution
,
258 GsfOutput
*output
, GError
**err
)
260 SheetObjectImage
*soi
= GNM_SO_IMAGE (so
);
264 GOImage
*image
= NULL
;
265 GOImageFormatInfo
const *src_info
;
266 GOImageFormatInfo
const *dst_info
;
268 g_return_if_fail (soi
->image
!= NULL
);
270 src_info
= go_image_get_info (soi
->image
);
272 ? go_image_get_format_info (go_image_get_format_from_name (format
))
275 if (src_info
!= dst_info
) {
276 GdkPixbuf
*pixbuf
= go_image_get_pixbuf (soi
->image
);
277 image
= go_pixbuf_new_from_pixbuf (pixbuf
);
278 g_object_set (image
, "image-type", format
, NULL
);
279 g_object_unref (pixbuf
);
282 data
= go_image_get_data (image
? image
: soi
->image
, &length
);
283 res
= gsf_output_write (output
, length
, data
);
285 if (!res
&& err
&& *err
== NULL
)
286 *err
= g_error_new (gsf_output_error_id (), 0,
287 _("Unknown failure while saving image"));
289 if (image
) g_object_unref (image
);
293 soi_cb_save_as (SheetObject
*so
, SheetControl
*sc
)
299 GOImageFormat sel_fmt
;
300 GOImageFormatInfo
const *format_info
;
301 GdkPixbuf
*pixbuf
= NULL
;
303 SheetObjectImage
*soi
= GNM_SO_IMAGE (so
);
305 g_return_if_fail (soi
!= NULL
);
307 sel_fmt
= go_image_get_format_from_name (soi
->type
);
308 if ((pixbuf
= go_image_get_pixbuf (soi
->image
)) != NULL
)
309 l
= go_image_get_formats_with_pixbuf_saver ();
310 /* Move original format first in menu */
311 if (sel_fmt
!= GO_IMAGE_FORMAT_UNKNOWN
) {
312 l
= g_slist_remove (l
, GUINT_TO_POINTER (sel_fmt
));
313 l
= g_slist_prepend (l
, GUINT_TO_POINTER (sel_fmt
));
316 wbcg
= scg_wbcg (GNM_SCG (sc
));
318 uri
= go_gui_get_image_save_info (wbcg_toplevel (wbcg
), l
, &sel_fmt
, NULL
);
322 output
= go_file_create (uri
, &err
);
325 format_info
= go_image_get_format_info (sel_fmt
);
326 sheet_object_write_image (so
, (format_info
? format_info
->name
: NULL
), -1.0, output
, &err
);
327 gsf_output_close (output
);
328 g_object_unref (output
);
331 go_cmd_context_error (GO_CMD_CONTEXT (wbcg
), err
);
335 g_object_unref (pixbuf
);
341 gnm_soi_populate_menu (SheetObject
*so
, GPtrArray
*actions
)
343 static SheetObjectAction
const soi_action
=
344 { "document-save-as", N_("_Save As Image"), NULL
, 0, soi_cb_save_as
};
345 gnm_soi_parent_class
->populate_menu (so
, actions
);
346 g_ptr_array_insert (actions
, 1, (gpointer
) &soi_action
);
350 content_start (GsfXMLIn
*xin
, xmlChar
const **attrs
)
352 SheetObject
*so
= gnm_xml_in_cur_obj (xin
);
353 SheetObjectImage
*soi
= GNM_SO_IMAGE (so
);
354 char const *image_type
= NULL
, *image_name
= NULL
;
356 for (; attrs
!= NULL
&& attrs
[0] && attrs
[1] ; attrs
+= 2)
357 if (attr_eq (attrs
[0], "image-type"))
358 image_type
= CXML2C (attrs
[1]);
359 else if (attr_eq (attrs
[0], "name"))
360 image_name
= CXML2C (attrs
[1]);
363 soi
->type
= g_strdup (image_type
);
365 soi
->name
= g_strdup (image_name
);
368 content_end (GsfXMLIn
*xin
, G_GNUC_UNUSED GsfXMLBlob
*unknown
)
370 SheetObject
*so
= gnm_xml_in_cur_obj (xin
);
371 SheetObjectImage
*soi
= GNM_SO_IMAGE (so
);
372 GString
*data
= xin
->content
;
374 if (data
->len
>= 4) {
375 size_t len
= gsf_base64_decode_simple (data
->str
, data
->len
);
377 g_object_unref (soi
->image
);
378 soi
->image
= go_image_new_from_data (soi
->type
, data
->str
, len
,
384 gnm_soi_prep_sax_parser (SheetObject
*so
, GsfXMLIn
*xin
,
385 xmlChar
const **attrs
,
386 G_GNUC_UNUSED GnmConventions
const *convs
)
388 static GsfXMLInNode
const dtd
[] = {
389 GSF_XML_IN_NODE (CONTENT
, CONTENT
, -1, "Content", GSF_XML_CONTENT
, &content_start
, &content_end
),
392 static GsfXMLInDoc
*doc
= NULL
;
393 SheetObjectImage
*soi
= GNM_SO_IMAGE (so
);
396 doc
= gsf_xml_in_doc_new (dtd
, NULL
);
397 gnm_xml_in_doc_dispose_on_exit (&doc
);
399 gsf_xml_in_push_state (xin
, doc
, NULL
, NULL
, attrs
);
401 for (; attrs
!= NULL
&& attrs
[0] && attrs
[1] ; attrs
+= 2) {
402 if (gnm_xml_attr_double (attrs
, "crop-top", &soi
->crop_top
)) ;
403 else if (gnm_xml_attr_double (attrs
, "crop-bottom", &soi
->crop_bottom
))
405 else if (gnm_xml_attr_double (attrs
, "crop-left", &soi
->crop_left
))
407 else if (gnm_xml_attr_double (attrs
, "crop-right", &soi
->crop_right
))
413 gnm_soi_write_xml_sax (SheetObject
const *so
, GsfXMLOut
*output
,
414 G_GNUC_UNUSED GnmConventions
const *convs
)
416 SheetObjectImage
*soi
;
418 g_return_if_fail (GNM_IS_SO_IMAGE (so
));
419 soi
= GNM_SO_IMAGE (so
);
421 go_xml_out_add_double (output
, "crop-top", soi
->crop_top
);
422 go_xml_out_add_double (output
, "crop-bottom", soi
->crop_bottom
);
423 go_xml_out_add_double (output
, "crop-left", soi
->crop_left
);
424 go_xml_out_add_double (output
, "crop-right", soi
->crop_right
);
425 gsf_xml_out_start_element (output
, "Content");
426 if (soi
->type
!= NULL
)
427 gsf_xml_out_add_cstr (output
, "image-type", soi
->type
);
429 const char *name
= go_image_get_name (soi
->image
);
430 Sheet
*sheet
= sheet_object_get_sheet (so
);
432 gsf_xml_out_add_cstr (output
, "name", name
);
434 go_doc_save_image (GO_DOC (sheet
->workbook
), go_image_get_name (soi
->image
));
436 /* looks that this may happen when pasting from another process, see #687414 */
438 guint8
const *data
= go_image_get_data (soi
->image
, &length
);
439 gsf_xml_out_add_uint (output
, "size-bytes", length
);
440 gsf_xml_out_add_base64 (output
, NULL
, data
, length
);
443 gsf_xml_out_add_uint (output
, "size-bytes", 0);
445 gsf_xml_out_end_element (output
);
449 gnm_soi_copy (SheetObject
*dst
, SheetObject
const *src
)
451 SheetObjectImage
const *soi
= GNM_SO_IMAGE (src
);
452 SheetObjectImage
*new_soi
= GNM_SO_IMAGE (dst
);
454 new_soi
->type
= g_strdup (soi
->type
);
455 new_soi
->crop_top
= soi
->crop_top
;
456 new_soi
->crop_bottom
= soi
->crop_bottom
;
457 new_soi
->crop_left
= soi
->crop_left
;
458 new_soi
->crop_right
= soi
->crop_right
;
459 new_soi
->image
= soi
->image
? g_object_ref (soi
->image
) : NULL
;
463 gnm_soi_draw_cairo (SheetObject
const *so
, cairo_t
*cr
,
464 double width
, double height
)
466 SheetObjectImage
*soi
= GNM_SO_IMAGE (so
);
469 int w
= go_image_get_width (soi
->image
);
470 int h
= go_image_get_height (soi
->image
);
471 w
-= soi
->crop_left
- soi
->crop_right
;
472 h
-= soi
->crop_top
- soi
->crop_bottom
;
473 if (w
<= 0 || h
<= 0)
476 cairo_rectangle (cr
, 0, 0, width
, height
);
478 cairo_scale (cr
, width
/ w
, height
/ h
);
479 cairo_translate (cr
, -soi
->crop_left
, -soi
->crop_top
);
480 go_image_draw (soi
->image
, cr
);
486 gnm_soi_default_size (SheetObject
const *so
, double *w
, double *h
)
488 SheetObjectImage
*soi
= GNM_SO_IMAGE (so
);
490 *w
= go_image_get_width (soi
->image
);
491 *h
= go_image_get_height (soi
->image
);
498 gnm_soi_assign_to_sheet (SheetObject
*so
, Sheet
*sheet
)
500 SheetObjectImage
*soi
= GNM_SO_IMAGE (so
);
502 if (soi
->image
/* && !go_image_get_name (soi->image)*/) {
503 GODoc
*doc
= GO_DOC (sheet
->workbook
);
504 GOImage
*image
= go_doc_add_image
506 (soi
->name
!= NULL
) ? soi
->name
: go_image_get_name (soi
->image
),
508 if (soi
->image
!= image
) {
509 g_object_unref (soi
->image
);
510 soi
->image
= g_object_ref (image
);
512 } else if (soi
->name
) {
513 GODoc
*doc
= GO_DOC (sheet
->workbook
);
514 GType type
= go_image_type_for_format (soi
->type
);
516 soi
->image
= g_object_ref (go_doc_image_fetch (doc
, soi
->name
, type
));
517 if (GO_IS_PIXBUF (soi
->image
))
518 /* we need to ensure that the pixbuf type is set because it used to be missed, see #745297 */
519 g_object_set (soi
->image
, "image-type", soi
->type
, NULL
);
522 /* There is nothing we can do */
529 gnm_soi_get_property (GObject
*object
,
534 SheetObjectImage
*soi
= GNM_SO_IMAGE (object
);
537 switch (property_id
) {
538 case PROP_IMAGE_TYPE
:
539 g_value_set_string (value
, soi
->type
);
542 g_value_set_object (value
, soi
->image
);
545 pixbuf
= go_image_get_pixbuf (soi
->image
);
546 g_value_take_object (value
, pixbuf
);
549 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
555 gnm_soi_class_init (GObjectClass
*object_class
)
557 SheetObjectClass
*so_class
;
559 gnm_soi_parent_class
= g_type_class_peek_parent (object_class
);
561 /* Object class method overrides */
562 object_class
->finalize
= gnm_soi_finalize
;
563 object_class
->get_property
= gnm_soi_get_property
;
565 /* SheetObject class method overrides */
566 so_class
= GNM_SO_CLASS (object_class
);
567 so_class
->new_view
= gnm_soi_new_view
;
568 so_class
->populate_menu
= gnm_soi_populate_menu
;
569 so_class
->write_xml_sax
= gnm_soi_write_xml_sax
;
570 so_class
->prep_sax_parser
= gnm_soi_prep_sax_parser
;
571 so_class
->copy
= gnm_soi_copy
;
572 so_class
->draw_cairo
= gnm_soi_draw_cairo
;
573 so_class
->user_config
= NULL
;
574 so_class
->default_size
= gnm_soi_default_size
;
575 so_class
->assign_to_sheet
= gnm_soi_assign_to_sheet
;
576 so_class
->rubber_band_directly
= TRUE
;
578 /* The property strings don't need translation */
579 g_object_class_install_property (object_class
, PROP_IMAGE_TYPE
,
580 g_param_spec_string ("image-type",
584 GSF_PARAM_STATIC
| G_PARAM_READABLE
));
585 g_object_class_install_property (object_class
, PROP_IMAGE
,
586 g_param_spec_object ("image",
590 GSF_PARAM_STATIC
| G_PARAM_READABLE
));
591 g_object_class_install_property (object_class
, PROP_PIXBUF
,
592 g_param_spec_object ("pixbuf", "Pixbuf",
595 GSF_PARAM_STATIC
| G_PARAM_READABLE
));
599 gnm_soi_init (GObject
*obj
)
601 SheetObjectImage
*soi
;
604 soi
= GNM_SO_IMAGE (obj
);
605 soi
->crop_top
= soi
->crop_bottom
= soi
->crop_left
= soi
->crop_right
609 so
->anchor
.base
.direction
= GOD_ANCHOR_DIR_DOWN_RIGHT
;
610 so
->anchor
.mode
= GNM_SO_ANCHOR_ONE_CELL
;
614 soi_imageable_init (SheetObjectImageableIface
*soi_iface
)
616 soi_iface
->get_target_list
= gnm_soi_get_target_list
;
617 soi_iface
->write_image
= gnm_soi_write_image
;
620 GSF_CLASS_FULL (SheetObjectImage
, sheet_object_image
,
621 NULL
, NULL
, gnm_soi_class_init
, NULL
,
622 gnm_soi_init
, GNM_SO_TYPE
, 0,
623 GSF_INTERFACE (soi_imageable_init
, GNM_SO_IMAGEABLE_TYPE
))