1 /* gEDA - GPL Electronic Design Automation
2 * gschem - gEDA Schematic Capture
3 * Copyright (C) 1998-2010 Ales Hvezda
4 * Copyright (C) 1998-2010 gEDA Contributors (see ChangeLog for details)
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
35 #ifdef HAVE_LIBDMALLOC
39 #define X_IMAGE_DEFAULT_SIZE "800x600"
41 #define X_IMAGE_SIZE_MENU_NAME "image_size_menu"
42 #define X_IMAGE_TYPE_MENU_NAME "image_type_menu"
44 #define X_IMAGE_DEFAULT_TYPE "PNG"
46 static char *x_image_sizes
[] = {"320x240", "640x480", "800x600", "1200x768",
47 "1280x960", "1600x1200", "3200x2400", NULL
};
49 #if ((GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION < 6))
50 /* gtk_combo_box_get_active_text was included in GTK 2.6, so we need to store
51 the different image type descriptions in a list. */
52 GSList
*image_type_descriptions
= NULL
;
56 #if ((GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION < 6))
57 static void free_image_type_descriptions_list ()
61 /* Free the data stored in each node */
62 ptr
= image_type_descriptions
;
66 ptr
= g_slist_next(ptr
);
71 g_slist_free(image_type_descriptions
);
72 image_type_descriptions
= NULL
;
77 /*! \brief Create the options of the image size combobox
78 * \par This function adds the options of the image size to the given combobox.
79 * \param combo [in] the combobox to add the options to.
82 * This function is only used in this file, there are other create_menus...
84 static void create_size_menu (GtkComboBox
*combo
)
88 int i
, default_index
= 0;
90 default_size
= g_strdup (X_IMAGE_DEFAULT_SIZE
);
91 for (i
=0; x_image_sizes
[i
] != NULL
;i
++) {
92 /* Create a new string and add it as an option*/
93 buf
= g_strdup (x_image_sizes
[i
]);
94 gtk_combo_box_append_text (GTK_COMBO_BOX (combo
), buf
);
96 /* Compare with the default size, to get the default index */
97 if (strcasecmp(buf
, default_size
) == 0) {
102 g_free(default_size
);
104 /* Set the default menu */
105 gtk_combo_box_set_active(GTK_COMBO_BOX (combo
), default_index
);
110 /*! \brief Create the options of the image type combobox
111 * \par This function adds the options of the image type to the given combobox.
112 * \param combo [in] the combobox to add the options to.
115 * This function is only used in this file, there are other create_menus...
117 static void create_type_menu(GtkComboBox
*combo
)
119 GSList
*formats
= gdk_pixbuf_get_formats ();
122 int i
=0, default_index
=0;
124 #if ((GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION < 6))
125 /* If GTK < 2.6, free the descriptions list */
126 free_image_type_descriptions_list();
131 if (gdk_pixbuf_format_is_writable (ptr
->data
)) {
132 /* Get the format description and add it to the menu */
133 buf
= g_strdup (gdk_pixbuf_format_get_description(ptr
->data
));
134 gtk_combo_box_append_text (GTK_COMBO_BOX (combo
), buf
);
136 /* If GTK < 2.6, then add it also to the descriptions list. */
137 #if ((GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION < 6))
138 image_type_descriptions
= g_slist_append(image_type_descriptions
,
142 /* Compare the name with "png" and store the index */
143 buf
= g_strdup (gdk_pixbuf_format_get_name(ptr
->data
));
144 if (strcasecmp(buf
, X_IMAGE_DEFAULT_TYPE
) == 0) {
147 i
++; /* this is the count of items added to the combo box */
148 /* not the total number of pixbuf formats */
153 g_slist_free (formats
);
154 gtk_combo_box_append_text(GTK_COMBO_BOX(combo
), "Encapsulated Postscript");
156 /* Set the default menu */
157 gtk_combo_box_set_active(GTK_COMBO_BOX(combo
), default_index
);
161 /*! \brief Given a gdk-pixbuf image type description, it returns the type,
162 * or extension of the image.
163 * \par Return the gdk-pixbuf image type, or extension, which has the
164 * given gdk-pixbuf description.
165 * \param description The gdk-pixbuf image type description.
166 * \return The gdk-pixbuf type, or extension, of the image.
167 * \note This function is only used in this file.
169 static char *x_image_get_type_from_description(char *description
) {
170 gchar
*descr
= g_strdup (description
);
171 GSList
*formats
= gdk_pixbuf_get_formats ();
175 /*WK - catch EPS export case*/
176 if (strcmp(descr
, _("Encapsulated Postscript")) == 0) {
177 return(g_strdup("eps"));
182 ptr_descr
= gdk_pixbuf_format_get_description (ptr
->data
);
183 if (ptr_descr
&& (strcasecmp(ptr_descr
, descr
) == 0)) {
185 return(gdk_pixbuf_format_get_name(ptr
->data
));
194 /*! \brief Update the filename of a file dialog, when the image type has changed.
195 * \par Given a combobox inside a file chooser dialog, this function updates
196 * the filename displayed by the dialog, removing the current extension, and
197 * adding the extension of the image type selected.
198 * \param combo [in] A combobox inside a file chooser dialog, with gdk-pixbuf image type descriptions.
199 * \param w_current [in] the GSCHEM_TOPLEVEL structure.
203 static void x_image_update_dialog_filename(GtkComboBox
*combo
,
204 GSCHEM_TOPLEVEL
*w_current
) {
205 TOPLEVEL
*toplevel
= w_current
->toplevel
;
206 char* image_type_descr
= NULL
;
207 char *image_type
= NULL
;
208 char *old_image_filename
= NULL
;
209 char *file_basename
= NULL
;
210 char *file_name
= NULL
;
211 char *new_image_filename
= NULL
;
212 GtkWidget
*file_chooser
;
214 /* Get the current image type */
215 #if ((GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION < 6))
217 /* If GTK < 2.6, get the description from the descriptions list */
218 ptr
= g_slist_nth(image_type_descriptions
,
219 gtk_combo_box_get_active(GTK_COMBO_BOX(combo
)));
221 image_type_descr
= (char *) (ptr
->data
);
223 image_type_descr
= NULL
;
226 image_type_descr
= gtk_combo_box_get_active_text(GTK_COMBO_BOX(combo
));
228 image_type
= x_image_get_type_from_description(image_type_descr
);
230 /* Get the parent dialog */
231 file_chooser
= gtk_widget_get_ancestor(GTK_WIDGET(combo
),
232 GTK_TYPE_FILE_CHOOSER
);
234 /* Get the previous file name. If none, revert to the page filename */
235 old_image_filename
= gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(file_chooser
));
236 if (!old_image_filename
) {
237 old_image_filename
= toplevel
->page_current
->page_filename
;
240 /* Get the file name, without extension */
241 if (old_image_filename
) {
242 file_basename
= g_path_get_basename(old_image_filename
);
244 if (g_strrstr(file_basename
, ".") != NULL
) {
245 file_name
= g_strndup(file_basename
,
246 g_strrstr(file_basename
, ".") - file_basename
);
250 /* Add the extension */
252 new_image_filename
= g_strdup_printf("%s.%s", file_name
,
255 new_image_filename
= g_strdup_printf("%s.%s", file_basename
,
259 /* Set the new filename */
261 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(file_chooser
),
264 s_log_message("x_image_update_dialog_filename: No parent file chooser found!.\n");
268 g_free(file_basename
);
269 g_free(new_image_filename
);
272 /*! \brief Write eps image file.
273 * \par This function writes the eps file, using the postscript print code
274 * from libgeda. Orientation is portrait and type is extents without margins.
275 * \param w_current [in] the GSCHEM_TOPLEVEL structure.
276 * \param filename [in] the image filename.
280 void x_image_write_eps(GSCHEM_TOPLEVEL
*w_current
, const char* filename
)
282 TOPLEVEL
*toplevel
= w_current
->toplevel
;
284 int w
, h
, orientation
, type
;
285 w
= toplevel
->paper_width
;
286 h
= toplevel
->paper_height
;
287 orientation
= toplevel
->print_orientation
;
288 type
= toplevel
->print_output_type
;
290 toplevel
->paper_width
= 0;
291 toplevel
->paper_height
= 0;
292 toplevel
->print_orientation
= PORTRAIT
;
293 toplevel
->print_output_type
= EXTENTS_NOMARGINS
;
294 result
= f_print_file (toplevel
, toplevel
->page_current
, filename
);
296 s_log_message(_("x_image_lowlevel: Unable to write eps file %s.\n"),
300 toplevel
->paper_width
= w
;
301 toplevel
->paper_height
= h
;
302 toplevel
->print_orientation
= orientation
;
303 toplevel
->print_output_type
= type
;
306 /*! \brief Write the image file, with the desired options.
307 * \par This function writes the image file, with the options set in the
308 * dialog by the user.
309 * \param w_current [in] the GSCHEM_TOPLEVEL structure.
310 * \param filename [in] the image filename.
311 * \param desired_width [in] the image width chosen by the user.
312 * \param desired_height [in] the image height chosen by the user.
313 * \param filetype [in] image filetype.
317 void x_image_lowlevel(GSCHEM_TOPLEVEL
*w_current
, const char* filename
,
318 int desired_width
, int desired_height
, char *filetype
)
320 TOPLEVEL
*toplevel
= w_current
->toplevel
;
322 int save_height
, save_width
;
323 int save_page_left
, save_page_right
, save_page_top
, save_page_bottom
;
324 int page_width
, page_height
, page_center_left
, page_center_top
;
326 GError
*gerror
= NULL
;
330 w_current
->image_width
= width
= desired_width
;
331 w_current
->image_height
= height
= desired_height
;
333 save_width
= toplevel
->width
;
334 save_height
= toplevel
->height
;
336 toplevel
->width
= width
;
337 toplevel
->height
= height
;
339 save_page_left
= toplevel
->page_current
->left
;
340 save_page_right
= toplevel
->page_current
->right
;
341 save_page_top
= toplevel
->page_current
->top
;
342 save_page_bottom
= toplevel
->page_current
->bottom
;
344 page_width
= save_page_right
- save_page_left
;
345 page_height
= save_page_bottom
- save_page_top
;
347 page_center_left
= save_page_left
+ (page_width
/ 2);
348 page_center_top
= save_page_top
+ (page_height
/ 2);
350 /* Preserve proportions */
351 prop
= (float)width
/ height
;
352 if (((float)page_width
/ page_height
) > prop
) {
353 page_height
= (page_width
/ prop
);
355 page_width
= (page_height
* prop
);
358 /* need to do this every time you change width / height */
359 set_window(toplevel
, toplevel
->page_current
,
360 page_center_left
- (page_width
/ 2),
361 page_center_left
+ (page_width
/ 2),
362 page_center_top
- (page_height
/ 2),
363 page_center_top
+ (page_height
/ 2));
365 /* de select everything first */
366 o_select_unselect_all( w_current
);
368 if (strcmp(filetype
, "eps") == 0) /*WK - catch EPS export case*/
369 x_image_write_eps(w_current
, filename
);
371 pixbuf
= x_image_get_pixbuf(w_current
);
372 if (pixbuf
!= NULL
) {
373 if (!gdk_pixbuf_save(pixbuf
, filename
, filetype
, &gerror
, NULL
)) {
374 s_log_message(_("x_image_lowlevel: Unable to write %s file %s.\n"),
376 s_log_message("%s", gerror
->message
);
379 dialog
= gtk_message_dialog_new (GTK_WINDOW(w_current
->main_window
),
381 | GTK_DIALOG_DESTROY_WITH_PARENT
,
384 _("There was the following error when saving image with type %s to filename:\n%s\n\n%s.\n"),
385 filetype
, filename
, gerror
->message
388 gtk_dialog_run (GTK_DIALOG (dialog
));
389 gtk_widget_destroy (dialog
);
391 /* Free the gerror */
392 g_error_free(gerror
);
395 /* Unlink the output file */
396 /* It's not safe to unlink the file if there was an error.
397 For example: if the operation was not allowed due to permissions,
398 the _previous existing_ file will be removed */
399 /* unlink(filename); */
402 if (toplevel
->image_color
== TRUE
) {
403 s_log_message(_("Wrote color image to [%s] [%d x %d]\n"), filename
, width
, height
);
405 s_log_message(_("Wrote black and white image to [%s] [%d x %d]\n"), filename
, width
, height
);
410 g_object_unref(pixbuf
);
413 s_log_message(_("x_image_lowlevel: Unable to get pixbuf from gschem's window.\n"));
417 toplevel
->width
= save_width
;
418 toplevel
->height
= save_height
;
420 /* need to do this every time you change width / height */
421 set_window(toplevel
, toplevel
->page_current
,
427 o_invalidate_all (w_current
);
431 /*! \brief Display the image file selection dialog.
432 * \par Display the image file selection dialog, allowing the user to
433 * set several options, like image size and image type.
434 * When the user hits "ok", then it writes the image file.
435 * \param w_current [in] the GSCHEM_TOPLEVEL structure.
438 void x_image_setup (GSCHEM_TOPLEVEL
*w_current
)
444 GtkWidget
*size_combo
;
447 GtkWidget
*type_combo
;
448 char *image_type_descr
;
454 hbox
= gtk_hbox_new(FALSE
, 0);
456 /* Image size selection */
457 vbox1
= gtk_vbox_new(TRUE
, 0);
458 label1
= gtk_label_new (_("Width x Height"));
459 gtk_widget_show (label1
);
460 gtk_misc_set_alignment( GTK_MISC (label1
), 0, 0);
461 gtk_misc_set_padding (GTK_MISC (label1
), 0, 0);
462 gtk_box_pack_start (GTK_BOX (vbox1
),
463 label1
, FALSE
, FALSE
, 0);
465 size_combo
= gtk_combo_box_new_text ();
466 create_size_menu (GTK_COMBO_BOX(size_combo
));
468 gtk_widget_show (size_combo
);
469 gtk_box_pack_start (GTK_BOX (vbox1
), size_combo
, TRUE
, TRUE
, 0);
470 gtk_widget_show(vbox1
);
472 /* Image type selection */
473 vbox2
= gtk_vbox_new(TRUE
, 0);
474 label2
= gtk_label_new (_("Image type"));
475 gtk_widget_show (label2
);
476 gtk_misc_set_alignment( GTK_MISC (label2
), 0, 0);
477 gtk_misc_set_padding (GTK_MISC (label2
), 0, 0);
478 gtk_box_pack_start (GTK_BOX (vbox2
),
479 label2
, FALSE
, FALSE
, 0);
481 type_combo
= gtk_combo_box_new_text ();
482 gtk_box_pack_start (GTK_BOX (vbox2
), type_combo
, TRUE
, TRUE
, 0);
483 create_type_menu (GTK_COMBO_BOX(type_combo
));
485 /* Connect the changed signal to the callback, so the filename
486 gets updated every time the image type is changed */
487 g_signal_connect (type_combo
, "changed",
488 G_CALLBACK(x_image_update_dialog_filename
),
491 gtk_widget_show (type_combo
);
492 gtk_widget_show(vbox2
);
494 /* Create the dialog */
495 dialog
= gtk_file_chooser_dialog_new (_("Write image..."),
496 GTK_WINDOW(w_current
->main_window
),
497 GTK_FILE_CHOOSER_ACTION_SAVE
,
498 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
499 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
502 /* Set the alternative button order (ok, cancel, help) for other systems */
503 gtk_dialog_set_alternative_button_order(GTK_DIALOG(dialog
),
508 /* Add the extra widgets to the dialog*/
509 gtk_box_pack_start(GTK_BOX(hbox
), vbox1
, FALSE
, FALSE
, 10);
510 gtk_box_pack_start(GTK_BOX(hbox
), vbox2
, FALSE
, FALSE
, 10);
512 gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER(dialog
), hbox
);
514 g_object_set (dialog
,
516 "select-multiple", FALSE
,
517 #if ((GTK_MAJOR_VERSION > 2) || ((GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION >=8)))
518 /* only in GTK 2.8 */
519 "do-overwrite-confirmation", TRUE
,
523 /* Update the filename */
524 x_image_update_dialog_filename(GTK_COMBO_BOX(type_combo
), w_current
);
526 gtk_dialog_set_default_response(GTK_DIALOG(dialog
),
527 GTK_RESPONSE_ACCEPT
);
529 gtk_window_position (GTK_WINDOW (dialog
),
532 gtk_container_set_border_width(GTK_CONTAINER(dialog
),
533 DIALOG_BORDER_SPACING
);
534 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog
)->vbox
),
537 gtk_widget_show (dialog
);
539 if (gtk_dialog_run((GTK_DIALOG(dialog
))) == GTK_RESPONSE_ACCEPT
) {
540 #if ((GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION < 6))
542 x_image_sizes
[gtk_combo_box_get_active(GTK_COMBO_BOX(size_combo
))];
544 image_size
= gtk_combo_box_get_active_text(GTK_COMBO_BOX(size_combo
));
547 #if ((GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION < 6))
549 /* If GTK < 2.6, get the description from the descriptions list */
550 ptr
= g_slist_nth(image_type_descriptions
,
551 gtk_combo_box_get_active(GTK_COMBO_BOX(type_combo
)));
552 image_type_descr
= (char *) (ptr
->data
);
554 image_type_descr
= gtk_combo_box_get_active_text(GTK_COMBO_BOX(type_combo
));
557 image_type
= x_image_get_type_from_description(image_type_descr
);
558 sscanf(image_size
, "%ix%i", &width
, &height
);
559 filename
= gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog
));
561 x_image_lowlevel(w_current
, filename
, width
, height
, image_type
);
564 #if ((GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION < 6))
565 /* If GTK < 2.6, free the descriptions list */
566 free_image_type_descriptions_list();
569 gtk_widget_destroy (dialog
);
572 /*! \todo Finish function documentation!!!
574 * \par Function Description
577 static void x_image_convert_to_greyscale(GdkPixbuf
*pixbuf
)
579 int width
, height
, rowstride
, n_channels
;
580 guchar
*pixels
, *p
, new_value
;
583 n_channels
= gdk_pixbuf_get_n_channels (pixbuf
);
590 if (gdk_pixbuf_get_colorspace (pixbuf
) != GDK_COLORSPACE_RGB
)
595 if (gdk_pixbuf_get_bits_per_sample (pixbuf
) != 8)
600 width
= gdk_pixbuf_get_width (pixbuf
);
601 height
= gdk_pixbuf_get_height (pixbuf
);
603 rowstride
= gdk_pixbuf_get_rowstride (pixbuf
);
604 pixels
= gdk_pixbuf_get_pixels (pixbuf
);
606 for (j
= 0; j
< height
; j
++)
608 for (i
= 0; i
< width
; i
++)
610 p
= pixels
+ j
* rowstride
+ i
* n_channels
;
612 new_value
= 0.3 * p
[0] + 0.59 * p
[1] + 0.11 * p
[2];
620 /*! \todo Finish function documentation!!!
622 * \par Function Description
625 GdkPixbuf
*x_image_get_pixbuf (GSCHEM_TOPLEVEL
*w_current
)
628 int origin_x
, origin_y
, bottom
, right
;
629 int size_x
, size_y
, s_right
, s_left
, s_top
,s_bottom
;
630 GSCHEM_TOPLEVEL new_w_current
;
634 /* Do a copy of the w_current struct and work with it */
635 memcpy(&new_w_current
, w_current
, sizeof(GSCHEM_TOPLEVEL
));
636 /* Do a copy of the toplevel struct and work with it */
637 memcpy(&toplevel
, w_current
->toplevel
, sizeof(TOPLEVEL
));
639 new_w_current
.toplevel
= &toplevel
;
641 WORLDtoSCREEN (&new_w_current
, toplevel
.page_current
->right
,
642 toplevel
.page_current
->left
, &s_right
, &s_left
);
643 WORLDtoSCREEN (&new_w_current
, toplevel
.page_current
->bottom
,
644 toplevel
.page_current
->top
, &s_bottom
, &s_top
);
646 size_x
= s_left
- s_right
;
647 size_y
= s_bottom
- s_top
;
649 size_x
= new_w_current
.image_width
;
650 size_y
= new_w_current
.image_height
;
652 new_w_current
.window
= gdk_pixmap_new (w_current
->window
, size_x
, size_y
, -1);
653 new_w_current
.drawable
= new_w_current
.window
;
654 new_w_current
.cr
= gdk_cairo_create (new_w_current
.window
);
655 new_w_current
.pl
= pango_cairo_create_layout (new_w_current
.cr
);
657 new_w_current
.grid
= 0;
658 new_w_current
.text_origin_marker
= FALSE
;
660 new_w_current
.win_width
= new_w_current
.image_width
;
661 new_w_current
.win_height
= new_w_current
.image_height
;
663 if (toplevel
.image_color
== FALSE
)
665 /* FIXME this assumes -- not necessarily correctly! -- that the
666 * color at index 0 in the color map is black, and the color at
667 * index 1 is white! */
669 /* We are going to be doing black&white (grayscale) output, so change the */
670 /* color of all objects to a nice and dark color, say black */
671 toplevel
.override_color
= 0;
673 /* also reset the background to white */
674 toplevel
.background_color
= 1;
677 origin_x
= origin_y
= 0;
681 /* ------------------ Begin optional code ------------------------ */
682 /* If the the code in this region is commented, the PNG returned will
683 be the same as the one returned using libgd.
684 I mean: there will be some border all around the schematic.
685 This code is used to adjust the schematic to the border of the image */
688 /* Do a zoom extents to get fit all the schematic in the window */
689 /* Commented so the image returned will be the same as with libgd */
690 a_zoom_extents (&toplevel
,
691 toplevel
.page_current
->object_list
,
695 /* See if there are objects */
697 aux
= toplevel
->page_current
->object_list
;
698 while (aux
!= NULL
) {
699 if (aux
->type
!= -1) {
707 /* If there are no objects, can't use zoom_extents */
709 get_object_glist_bounds (&toplevel
,
710 toplevel
.page_current
->object_list
,
711 &origin_x
, &origin_y
,
715 /* ------------------ End optional code ------------------------ */
719 rect
.width
= right
- origin_x
;
720 rect
.height
= bottom
- origin_y
;
722 o_redraw_rects (&new_w_current
, &rect
, 1);
725 pixbuf
= gdk_pixbuf_get_from_drawable (NULL
,new_w_current
.drawable
, NULL
,
726 origin_x
, origin_y
, 0, 0,
730 if (toplevel
.image_color
== FALSE
)
732 x_image_convert_to_greyscale(pixbuf
);
735 if (new_w_current
.cr
!= NULL
) cairo_destroy (new_w_current
.cr
);
736 if (new_w_current
.pl
!= NULL
) g_object_unref (new_w_current
.pl
);
737 if (new_w_current
.window
!= NULL
) {
738 g_object_unref(new_w_current
.window
);