Update upstream

This commit is contained in:
Cassowary 2023-12-02 11:03:24 -08:00
parent 84ea557696
commit d472f6348d
129 changed files with 17814 additions and 14162 deletions

View File

@ -184,19 +184,31 @@ static const PikaEnumActionEntry edit_paste_actions[] =
PIKA_HELP_EDIT_PASTE_IN_PLACE }, PIKA_HELP_EDIT_PASTE_IN_PLACE },
{ "edit-paste-into", PIKA_ICON_EDIT_PASTE_INTO, { "edit-paste-into", PIKA_ICON_EDIT_PASTE_INTO,
NC_("edit-action", "Paste _Into Selection"), NULL, { NULL }, NC_("edit-action", "Paste as Floating Data _Into Selection"), NULL, { NULL },
NC_("edit-action", NC_("edit-action",
"Paste the content of the clipboard into the current selection"), "Paste the content of the clipboard into the current selection"),
PIKA_PASTE_TYPE_FLOATING_INTO, FALSE, PIKA_PASTE_TYPE_FLOATING_INTO, FALSE,
PIKA_HELP_EDIT_PASTE_INTO }, PIKA_HELP_EDIT_PASTE_INTO },
{ "edit-paste-into-in-place", PIKA_ICON_EDIT_PASTE_INTO, { "edit-paste-into-in-place", PIKA_ICON_EDIT_PASTE_INTO,
NC_("edit-action", "Paste Int_o Selection In Place"), NULL, { NULL }, NC_("edit-action", "Paste as Floating Data Int_o Selection In Place"), NULL, { NULL },
NC_("edit-action", NC_("edit-action",
"Paste the content of the clipboard into the current selection " "Paste the content of the clipboard into the current selection "
"at its original position"), "at its original position"),
PIKA_PASTE_TYPE_FLOATING_INTO_IN_PLACE, FALSE, PIKA_PASTE_TYPE_FLOATING_INTO_IN_PLACE, FALSE,
PIKA_HELP_EDIT_PASTE_INTO_IN_PLACE } PIKA_HELP_EDIT_PASTE_INTO_IN_PLACE },
{ "edit-paste-float", PIKA_ICON_EDIT_PASTE,
NC_("edit-action", "Paste as _Floating Data"), NULL, { NULL },
NC_("edit-action", "Paste the content of the clipboard as Floating Data"),
PIKA_PASTE_TYPE_FLOATING, FALSE,
PIKA_HELP_EDIT_PASTE },
{ "edit-paste-float-in-place", PIKA_ICON_EDIT_PASTE,
NC_("edit-action", "Paste as Floa_ting Data In Place"), NULL, { NULL },
NC_("edit-action", "Paste the content of the clipboard as Floating Data at its original position"),
PIKA_PASTE_TYPE_FLOATING_IN_PLACE, FALSE,
PIKA_HELP_EDIT_PASTE }
}; };
static const PikaEnumActionEntry edit_fill_actions[] = static const PikaEnumActionEntry edit_fill_actions[] =

View File

@ -972,7 +972,7 @@ layers_actions_update (PikaActionGroup *group,
SET_SENSITIVE ("layers-new", image); SET_SENSITIVE ("layers-new", image);
SET_SENSITIVE ("layers-new-last-values", image); SET_SENSITIVE ("layers-new-last-values", image);
SET_SENSITIVE ("layers-new-from-visible", image); SET_SENSITIVE ("layers-new-from-visible", image);
SET_SENSITIVE ("layers-new-group", image && !indexed); SET_SENSITIVE ("layers-new-group", image && !indexed && !fs);
SET_SENSITIVE ("layers-duplicate", n_selected_layers > 0 && !fs && !ac); SET_SENSITIVE ("layers-duplicate", n_selected_layers > 0 && !fs && !ac);
SET_SENSITIVE ("layers-delete", n_selected_layers > 0 && !ac); SET_SENSITIVE ("layers-delete", n_selected_layers > 0 && !ac);

View File

@ -62,10 +62,16 @@ static const PikaActionEntry select_actions[] =
select_invert_cmd_callback, select_invert_cmd_callback,
PIKA_HELP_SELECTION_INVERT }, PIKA_HELP_SELECTION_INVERT },
{ "select-float", PIKA_ICON_LAYER_FLOATING_SELECTION, { "select-cut-float", PIKA_ICON_LAYER_FLOATING_SELECTION,
NC_("select-action", "_Float"), NULL, { "<primary><shift>L", NULL }, NC_("select-action", "Cu_t and Float"), NULL, { "<primary><shift>L", NULL },
NC_("select-action", "Create a floating selection"), NC_("select-action", "Cut the selection directly into a floating selection"),
select_float_cmd_callback, select_cut_float_cmd_callback,
PIKA_HELP_SELECTION_FLOAT },
{ "select-copy-float", PIKA_ICON_LAYER_FLOATING_SELECTION,
NC_("select-action", "_Copy and Float"), NULL, { NULL },
NC_("select-action", "Copy the selection directly into a floating selection"),
select_copy_float_cmd_callback,
PIKA_HELP_SELECTION_FLOAT }, PIKA_HELP_SELECTION_FLOAT },
{ "select-feather", NULL, { "select-feather", NULL,
@ -186,9 +192,13 @@ select_actions_update (PikaActionGroup *group,
SET_SENSITIVE ("select-all", image && ! sel_all); SET_SENSITIVE ("select-all", image && ! sel_all);
SET_SENSITIVE ("select-none", image && sel); SET_SENSITIVE ("select-none", image && sel);
SET_SENSITIVE ("select-invert", image); SET_SENSITIVE ("select-invert", image);
SET_SENSITIVE ("select-float", g_list_length (drawables) == 1 && sel &&
! pika_item_is_content_locked (drawables->data, NULL) && SET_SENSITIVE ("select-cut-float", g_list_length (drawables) == 1 && sel &&
! pika_viewable_get_children (drawables->data)); ! pika_item_is_content_locked (drawables->data, NULL) &&
! pika_viewable_get_children (drawables->data));
SET_SENSITIVE ("select-copy-float", g_list_length (drawables) == 1 && sel &&
! pika_item_is_content_locked (drawables->data, NULL) &&
! pika_viewable_get_children (drawables->data));
SET_SENSITIVE ("select-feather", image && sel); SET_SENSITIVE ("select-feather", image && sel);
SET_SENSITIVE ("select-sharpen", image && sel); SET_SENSITIVE ("select-sharpen", image && sel);

View File

@ -71,6 +71,10 @@ static void select_shrink_callback (GtkWidget *widget,
gdouble size, gdouble size,
PikaUnit unit, PikaUnit unit,
gpointer data); gpointer data);
static void select_float (PikaAction *action,
GVariant *value,
gboolean cut,
gpointer data);
/* public functions */ /* public functions */
@ -112,33 +116,19 @@ select_invert_cmd_callback (PikaAction *action,
} }
void void
select_float_cmd_callback (PikaAction *action, select_cut_float_cmd_callback (PikaAction *action,
GVariant *value, GVariant *value,
gpointer data) gpointer data)
{ {
PikaImage *image; select_float (action, value, TRUE, data);
GtkWidget *widget; }
GList *drawables;
GError *error = NULL;
return_if_no_image (image, data);
return_if_no_widget (widget, data);
drawables = pika_image_get_selected_drawables (image); void
if (pika_selection_float (PIKA_SELECTION (pika_image_get_mask (image)), select_copy_float_cmd_callback (PikaAction *action,
drawables, GVariant *value,
action_data_get_context (data), gpointer data)
TRUE, 0, 0, &error)) {
{ select_float (action, value, FALSE, data);
pika_image_flush (image);
}
else
{
pika_message_literal (image->pika,
G_OBJECT (widget), PIKA_MESSAGE_WARNING,
error->message);
g_clear_error (&error);
}
g_list_free (drawables);
} }
void void
@ -696,3 +686,34 @@ select_shrink_callback (GtkWidget *widget,
TRUE); TRUE);
pika_image_flush (image); pika_image_flush (image);
} }
static void
select_float (PikaAction *action,
GVariant *value,
gboolean cut,
gpointer data)
{
PikaImage *image;
GtkWidget *widget;
GList *drawables;
GError *error = NULL;
return_if_no_image (image, data);
return_if_no_widget (widget, data);
drawables = pika_image_get_selected_drawables (image);
if (pika_selection_float (PIKA_SELECTION (pika_image_get_mask (image)),
drawables,
action_data_get_context (data),
cut, 0, 0, &error))
{
pika_image_flush (image);
}
else
{
pika_message_literal (image->pika,
G_OBJECT (widget), PIKA_MESSAGE_WARNING,
error->message);
g_clear_error (&error);
}
g_list_free (drawables);
}

View File

@ -32,7 +32,10 @@ void select_none_cmd_callback (PikaAction *action,
void select_invert_cmd_callback (PikaAction *action, void select_invert_cmd_callback (PikaAction *action,
GVariant *value, GVariant *value,
gpointer data); gpointer data);
void select_float_cmd_callback (PikaAction *action, void select_cut_float_cmd_callback (PikaAction *action,
GVariant *value,
gpointer data);
void select_copy_float_cmd_callback (PikaAction *action,
GVariant *value, GVariant *value,
gpointer data); gpointer data);
void select_feather_cmd_callback (PikaAction *action, void select_feather_cmd_callback (PikaAction *action,

View File

@ -98,6 +98,7 @@ enum
PROP_GLOBAL_PALETTE, PROP_GLOBAL_PALETTE,
PROP_GLOBAL_GRADIENT, PROP_GLOBAL_GRADIENT,
PROP_GLOBAL_FONT, PROP_GLOBAL_FONT,
PROP_GLOBAL_EXPAND,
PROP_DEFAULT_IMAGE, PROP_DEFAULT_IMAGE,
PROP_DEFAULT_GRID, PROP_DEFAULT_GRID,
PROP_UNDO_LEVELS, PROP_UNDO_LEVELS,
@ -540,6 +541,13 @@ pika_core_config_class_init (PikaCoreConfigClass *klass)
TRUE, TRUE,
PIKA_PARAM_STATIC_STRINGS); PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_GLOBAL_EXPAND,
"global-expand",
"Global expand",
GLOBAL_EXPAND_BLURB,
TRUE,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_OBJECT (object_class, PROP_DEFAULT_IMAGE, PIKA_CONFIG_PROP_OBJECT (object_class, PROP_DEFAULT_IMAGE,
"default-image", "default-image",
"Default image", "Default image",
@ -1061,6 +1069,9 @@ pika_core_config_set_property (GObject *object,
case PROP_GLOBAL_FONT: case PROP_GLOBAL_FONT:
core_config->global_font = g_value_get_boolean (value); core_config->global_font = g_value_get_boolean (value);
break; break;
case PROP_GLOBAL_EXPAND:
core_config->global_expand = g_value_get_boolean (value);
break;
case PROP_DEFAULT_IMAGE: case PROP_DEFAULT_IMAGE:
if (g_value_get_object (value)) if (g_value_get_object (value))
pika_config_sync (g_value_get_object (value) , pika_config_sync (g_value_get_object (value) ,
@ -1337,6 +1348,9 @@ pika_core_config_get_property (GObject *object,
case PROP_GLOBAL_FONT: case PROP_GLOBAL_FONT:
g_value_set_boolean (value, core_config->global_font); g_value_set_boolean (value, core_config->global_font);
break; break;
case PROP_GLOBAL_EXPAND:
g_value_set_boolean (value, core_config->global_expand);
break;
case PROP_DEFAULT_IMAGE: case PROP_DEFAULT_IMAGE:
g_value_set_object (value, core_config->default_image); g_value_set_object (value, core_config->default_image);
break; break;

View File

@ -82,6 +82,7 @@ struct _PikaCoreConfig
gboolean global_palette; gboolean global_palette;
gboolean global_gradient; gboolean global_gradient;
gboolean global_font; gboolean global_font;
gboolean global_expand;
PikaTemplate *default_image; PikaTemplate *default_image;
PikaGrid *default_grid; PikaGrid *default_grid;
gint levels_of_undo; gint levels_of_undo;

View File

@ -181,6 +181,9 @@ _("When enabled, the selected pattern will be used for all tools.")
#define GLOBAL_PALETTE_BLURB \ #define GLOBAL_PALETTE_BLURB \
"When enabled, the selected palette will be used for all tools." "When enabled, the selected palette will be used for all tools."
#define GLOBAL_EXPAND_BLURB \
"When enabled, the selected auto expand layer settings will be used for all tools."
#define GRADIENT_PATH_BLURB \ #define GRADIENT_PATH_BLURB \
"Sets the gradient search path." "Sets the gradient search path."

View File

@ -715,6 +715,7 @@ typedef enum /*< pdb-skip, skip >*/
PIKA_CONTEXT_PROP_BUFFER = 18, PIKA_CONTEXT_PROP_BUFFER = 18,
PIKA_CONTEXT_PROP_IMAGEFILE = 19, PIKA_CONTEXT_PROP_IMAGEFILE = 19,
PIKA_CONTEXT_PROP_TEMPLATE = 20, PIKA_CONTEXT_PROP_TEMPLATE = 20,
PIKA_CONTEXT_PROP_EXPAND = 21,
PIKA_CONTEXT_PROP_LAST = PIKA_CONTEXT_PROP_TEMPLATE PIKA_CONTEXT_PROP_LAST = PIKA_CONTEXT_PROP_TEMPLATE
} PikaContextPropType; } PikaContextPropType;
@ -741,6 +742,7 @@ typedef enum /*< pdb-skip, skip >*/
PIKA_CONTEXT_PROP_MASK_BUFFER = 1 << 18, PIKA_CONTEXT_PROP_MASK_BUFFER = 1 << 18,
PIKA_CONTEXT_PROP_MASK_IMAGEFILE = 1 << 19, PIKA_CONTEXT_PROP_MASK_IMAGEFILE = 1 << 19,
PIKA_CONTEXT_PROP_MASK_TEMPLATE = 1 << 20, PIKA_CONTEXT_PROP_MASK_TEMPLATE = 1 << 20,
PIKA_CONTEXT_PROP_MASK_EXPAND = 1 << 21,
/* aliases */ /* aliases */
PIKA_CONTEXT_PROP_MASK_PAINT = (PIKA_CONTEXT_PROP_MASK_FOREGROUND | PIKA_CONTEXT_PROP_MASK_PAINT = (PIKA_CONTEXT_PROP_MASK_FOREGROUND |
@ -750,7 +752,8 @@ typedef enum /*< pdb-skip, skip >*/
PIKA_CONTEXT_PROP_MASK_BRUSH | PIKA_CONTEXT_PROP_MASK_BRUSH |
PIKA_CONTEXT_PROP_MASK_DYNAMICS | PIKA_CONTEXT_PROP_MASK_DYNAMICS |
PIKA_CONTEXT_PROP_MASK_PATTERN | PIKA_CONTEXT_PROP_MASK_PATTERN |
PIKA_CONTEXT_PROP_MASK_GRADIENT), PIKA_CONTEXT_PROP_MASK_GRADIENT |
PIKA_CONTEXT_PROP_MASK_EXPAND),
PIKA_CONTEXT_PROP_MASK_ALL = (PIKA_CONTEXT_PROP_MASK_IMAGE | PIKA_CONTEXT_PROP_MASK_ALL = (PIKA_CONTEXT_PROP_MASK_IMAGE |
PIKA_CONTEXT_PROP_MASK_DISPLAY | PIKA_CONTEXT_PROP_MASK_DISPLAY |

View File

@ -557,6 +557,9 @@ user_update_menurc_over20 (const GMatchInfo *matched_value,
else if (g_strcmp0 (action_match, "view-rotate-reset") == 0 && else if (g_strcmp0 (action_match, "view-rotate-reset") == 0 &&
install->old_major == 2) install->old_major == 2)
new_action_name = g_strdup ("view-reset"); new_action_name = g_strdup ("view-reset");
/* select-float became select-cut-float in 3.0 (select-copy-float added). */
else if (g_strcmp0 (action_match, "select-float") == 0)
new_action_name = g_strdup ("select-cut-float");
if (new_action_name == NULL) if (new_action_name == NULL)
new_action_name = g_strdup (action_match); new_action_name = g_strdup (action_match);

View File

@ -286,7 +286,8 @@ pika_brush_get_new_preview (PikaViewable *viewable,
guchar *mask; guchar *mask;
guchar *buf; guchar *buf;
gint x, y; gint x, y;
gboolean scaled = FALSE; gboolean free_mask = FALSE;
gdouble scale = 1.0;
mask_width = pika_temp_buf_get_width (mask_buf); mask_width = pika_temp_buf_get_width (mask_buf);
mask_height = pika_temp_buf_get_height (mask_buf); mask_height = pika_temp_buf_get_height (mask_buf);
@ -295,45 +296,47 @@ pika_brush_get_new_preview (PikaViewable *viewable,
{ {
gdouble ratio_x = (gdouble) width / (gdouble) mask_width; gdouble ratio_x = (gdouble) width / (gdouble) mask_width;
gdouble ratio_y = (gdouble) height / (gdouble) mask_height; gdouble ratio_y = (gdouble) height / (gdouble) mask_height;
gdouble scale = MIN (ratio_x, ratio_y);
if (scale != 1.0) scale = MIN (ratio_x, ratio_y);
}
if (PIKA_IS_BRUSH_GENERATED (brush) || scale != 1.0)
{
pika_brush_begin_use (brush);
if (PIKA_IS_BRUSH_GENERATED (brush))
{ {
pika_brush_begin_use (brush); PikaBrushGenerated *gen_brush = PIKA_BRUSH_GENERATED (brush);
if (PIKA_IS_BRUSH_GENERATED (brush)) mask_buf = pika_brush_transform_mask (brush, scale,
{ (pika_brush_generated_get_aspect_ratio (gen_brush) - 1.0) * 20.0 / 19.0,
PikaBrushGenerated *gen_brush = PIKA_BRUSH_GENERATED (brush); pika_brush_generated_get_angle (gen_brush) / -360.0,
FALSE,
pika_brush_generated_get_hardness (gen_brush));
}
else
{
mask_buf = pika_brush_transform_mask (brush, scale, 0.0, 0.0, FALSE, 1.0);
}
mask_buf = pika_brush_transform_mask (brush, scale, if (! mask_buf)
(pika_brush_generated_get_aspect_ratio (gen_brush) - 1.0) * 20.0 / 19.0, {
pika_brush_generated_get_angle (gen_brush) / 360.0, mask_buf = pika_temp_buf_new (1, 1, babl_format ("Y u8"));
FALSE, pika_temp_buf_data_clear ((PikaTempBuf *) mask_buf);
pika_brush_generated_get_hardness (gen_brush)); }
} else
else {
mask_buf = pika_brush_transform_mask (brush, scale, pika_temp_buf_ref ((PikaTempBuf *) mask_buf);
}
if (pixmap_buf)
pixmap_buf = pika_brush_transform_pixmap (brush, scale,
0.0, 0.0, FALSE, 1.0); 0.0, 0.0, FALSE, 1.0);
if (! mask_buf) mask_width = pika_temp_buf_get_width (mask_buf);
{ mask_height = pika_temp_buf_get_height (mask_buf);
mask_buf = pika_temp_buf_new (1, 1, babl_format ("Y u8"));
pika_temp_buf_data_clear ((PikaTempBuf *) mask_buf);
}
else
{
pika_temp_buf_ref ((PikaTempBuf *) mask_buf);
}
if (pixmap_buf) free_mask = TRUE;
pixmap_buf = pika_brush_transform_pixmap (brush, scale,
0.0, 0.0, FALSE, 1.0);
mask_width = pika_temp_buf_get_width (mask_buf);
mask_height = pika_temp_buf_get_height (mask_buf);
scaled = TRUE;
}
} }
return_buf = pika_temp_buf_new (mask_width, mask_height, return_buf = pika_temp_buf_new (mask_width, mask_height,
@ -381,7 +384,7 @@ pika_brush_get_new_preview (PikaViewable *viewable,
pika_temp_buf_unlock (mask_buf, mask_data); pika_temp_buf_unlock (mask_buf, mask_data);
if (scaled) if (free_mask)
{ {
pika_temp_buf_unref ((PikaTempBuf *) mask_buf); pika_temp_buf_unref ((PikaTempBuf *) mask_buf);

View File

@ -747,7 +747,7 @@ pika_channel_resize (PikaItem *item,
gint offset_x, gint offset_x,
gint offset_y) gint offset_y)
{ {
PIKA_ITEM_CLASS (parent_class)->resize (item, context, PIKA_FILL_TRANSPARENT, PIKA_ITEM_CLASS (parent_class)->resize (item, context, fill_type,
new_width, new_height, new_width, new_height,
offset_x, offset_y); offset_x, offset_y);

View File

@ -45,6 +45,8 @@ struct _PikaDrawablePrivate
GeglBuffer *paint_buffer; GeglBuffer *paint_buffer;
cairo_region_t *paint_copy_region; cairo_region_t *paint_copy_region;
cairo_region_t *paint_update_region; cairo_region_t *paint_update_region;
gboolean push_resize_undo;
}; };
#endif /* __PIKA_DRAWABLE_PRIVATE_H__ */ #endif /* __PIKA_DRAWABLE_PRIVATE_H__ */

View File

@ -632,7 +632,10 @@ pika_drawable_resize (PikaItem *item,
copy_y - new_offset_y, 0, 0)); copy_y - new_offset_y, 0, 0));
} }
pika_drawable_set_buffer_full (drawable, pika_item_is_attached (item), NULL, pika_drawable_set_buffer_full (drawable,
pika_item_is_attached (item) &&
drawable->private->push_resize_undo,
NULL,
new_buffer, new_buffer,
GEGL_RECTANGLE (new_offset_x, new_offset_y, GEGL_RECTANGLE (new_offset_x, new_offset_y,
0, 0), 0, 0),
@ -910,6 +913,10 @@ pika_drawable_real_set_buffer (PikaDrawable *drawable,
} }
g_set_object (&drawable->private->buffer, buffer); g_set_object (&drawable->private->buffer, buffer);
if (pika_drawable_is_painting (drawable))
g_set_object (&drawable->private->paint_buffer, buffer);
g_clear_object (&drawable->private->format_profile); g_clear_object (&drawable->private->format_profile);
if (drawable->private->buffer_source_node) if (drawable->private->buffer_source_node)
@ -1076,6 +1083,8 @@ pika_drawable_new (GType type,
pika_drawable_set_buffer (drawable, FALSE, NULL, buffer); pika_drawable_set_buffer (drawable, FALSE, NULL, buffer);
g_object_unref (buffer); g_object_unref (buffer);
pika_drawable_enable_resize_undo (drawable);
return drawable; return drawable;
} }
@ -1676,6 +1685,22 @@ pika_drawable_push_undo (PikaDrawable *drawable,
x, y, width, height); x, y, width, height);
} }
void
pika_drawable_disable_resize_undo (PikaDrawable *drawable)
{
g_return_if_fail (PIKA_IS_DRAWABLE (drawable));
drawable->private->push_resize_undo = FALSE;
}
void
pika_drawable_enable_resize_undo (PikaDrawable *drawable)
{
g_return_if_fail (PIKA_IS_DRAWABLE (drawable));
drawable->private->push_resize_undo = TRUE;
}
const Babl * const Babl *
pika_drawable_get_space (PikaDrawable *drawable) pika_drawable_get_space (PikaDrawable *drawable)
{ {

View File

@ -209,6 +209,9 @@ void pika_drawable_push_undo (PikaDrawable *drawable,
gint width, gint width,
gint height); gint height);
void pika_drawable_disable_resize_undo (PikaDrawable *drawable);
void pika_drawable_enable_resize_undo (PikaDrawable *drawable);
const Babl * pika_drawable_get_space (PikaDrawable *drawable); const Babl * pika_drawable_get_space (PikaDrawable *drawable);
const Babl * pika_drawable_get_format (PikaDrawable *drawable); const Babl * pika_drawable_get_format (PikaDrawable *drawable);
const Babl * pika_drawable_get_format_with_alpha(PikaDrawable *drawable); const Babl * pika_drawable_get_format_with_alpha(PikaDrawable *drawable);

View File

@ -1083,9 +1083,6 @@ pika_palette_load_ase (PikaContext *context,
} }
g_free (palette_name); g_free (palette_name);
/* Header blocks are considered a "color" so we offset the count here */
num_cols -= 1;
for (i = 0; i < num_cols; i++) for (i = 0; i < num_cols; i++)
{ {
gchar color_space[4]; gchar color_space[4];
@ -1108,6 +1105,23 @@ pika_palette_load_ase (PikaContext *context,
} }
skip_first = FALSE; skip_first = FALSE;
/* Skip group marker padding */
group = GINT16_FROM_BE (group);
if (group < 0)
{
gchar marker[4];
if (! g_input_stream_read_all (input, &marker, sizeof (marker),
&bytes_read, NULL, error))
{
g_printerr ("Invalid ASE group marker: %s.",
pika_file_get_utf8_name (file));
break;
}
num_cols--;
continue;
}
color_name = pika_palette_load_ase_block_name (input, file_size, error); color_name = pika_palette_load_ase_block_name (input, file_size, error);
if (! color_name) if (! color_name)
break; break;
@ -1148,7 +1162,7 @@ pika_palette_load_ase (PikaContext *context,
for (gint j = 0; j < components; j++) for (gint j = 0; j < components; j++)
{ {
gint tmp; gint32 tmp;
if (! g_input_stream_read_all (input, &tmp, sizeof (tmp), if (! g_input_stream_read_all (input, &tmp, sizeof (tmp),
&bytes_read, NULL, error)) &bytes_read, NULL, error))
@ -1162,7 +1176,7 @@ pika_palette_load_ase (PikaContext *context,
/* Convert 4 bytes to a 32bit float value */ /* Convert 4 bytes to a 32bit float value */
tmp = GINT32_FROM_BE (tmp); tmp = GINT32_FROM_BE (tmp);
pixels[j] = *(gfloat *) &tmp; memcpy (&pixels[j], &tmp, 4);
} }
if (! valid_color) if (! valid_color)

View File

@ -2,7 +2,7 @@
* a rebranding of The GNU Image Manipulation Program (created with heckimp) * a rebranding of The GNU Image Manipulation Program (created with heckimp)
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
* *
* Original copyright, applying to most contents (license remains unchanged): * Original copyright, applying to most contents (license remains unchanged):
* Copyright (C) 1995 Spencer Kimball and Peter Mattis * Copyright (C) 1995 Spencer Kimball and Peter Mattis
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -34,6 +34,8 @@
#include "config/pikacoreconfig.h" #include "config/pikacoreconfig.h"
#include "widgets/pikawidgets-utils.h"
#include "about.h" #include "about.h"
#include "git-version.h" #include "git-version.h"
@ -45,22 +47,34 @@
#include "pika-intl.h" #include "pika-intl.h"
/* The first authors are the creators and maintainers, don't shuffle
* them
*/
#define START_INDEX (G_N_ELEMENTS (creators) - 1 /*NULL*/ + \
G_N_ELEMENTS (maintainers) - 1 /*NULL*/)
typedef struct typedef struct
{ {
GtkWidget *dialog; GtkWidget *dialog;
Pika *pika;
GtkWidget *update_frame; GtkWidget *update_frame;
PikaCoreConfig *config; PikaCoreConfig *config;
GtkWidget *anim_area; GtkWidget *anim_area;
PangoLayout *layout; PangoLayout *layout;
guint timer; gint n_authors;
gint shuffle[G_N_ELEMENTS (authors) - 1]; /* NULL terminated */
gint index; guint timer;
gint animstep;
gint state; gint index;
gboolean visible; gint animstep;
gint state;
gboolean visible;
} PikaAboutDialog; } PikaAboutDialog;
@ -93,7 +107,8 @@ static void about_dialog_download_clicked
const gchar *link); const gchar *link);
GtkWidget * GtkWidget *
about_dialog_create (PikaCoreConfig *config) about_dialog_create (Pika *pika,
PikaCoreConfig *config)
{ {
static PikaAboutDialog dialog; static PikaAboutDialog dialog;
@ -106,6 +121,8 @@ about_dialog_create (PikaCoreConfig *config)
gchar *copyright; gchar *copyright;
gchar *version; gchar *version;
dialog.pika = pika;
dialog.n_authors = G_N_ELEMENTS (authors) - 1;
dialog.config = config; dialog.config = config;
pixbuf = about_dialog_load_logo (); pixbuf = about_dialog_load_logo ();
@ -134,6 +151,9 @@ about_dialog_create (PikaCoreConfig *config)
"logo", pixbuf, "logo", pixbuf,
"website", "https://heckin.technology/AlderconeStudio/PIKApp/", "website", "https://heckin.technology/AlderconeStudio/PIKApp/",
"website-label", _("Visit the PIKA website"), "website-label", _("Visit the PIKA website"),
"authors", authors,
"artists", artists,
"documenters", documenters,
/* Translators: insert your names here, /* Translators: insert your names here,
separated by newline */ separated by newline */
"translator-credits", _("translator-credits"), "translator-credits", _("translator-credits"),
@ -196,6 +216,10 @@ about_dialog_map (GtkWidget *widget,
dialog->timer = g_timeout_add (800, about_dialog_timer, dialog); dialog->timer = g_timeout_add (800, about_dialog_timer, dialog);
} }
#ifdef G_OS_WIN32
pika_window_set_title_bar_theme (dialog->pika, widget, FALSE);
#endif
} }
static void static void
@ -466,6 +490,27 @@ about_dialog_add_update (PikaAboutDialog *dialog,
static void static void
about_dialog_reshuffle (PikaAboutDialog *dialog) about_dialog_reshuffle (PikaAboutDialog *dialog)
{ {
GRand *gr = g_rand_new ();
gint i;
for (i = 0; i < dialog->n_authors; i++)
dialog->shuffle[i] = i;
for (i = START_INDEX; i < dialog->n_authors; i++)
{
gint j = g_rand_int_range (gr, START_INDEX, dialog->n_authors);
if (i != j)
{
gint t;
t = dialog->shuffle[j];
dialog->shuffle[j] = dialog->shuffle[i];
dialog->shuffle[i] = t;
}
}
g_rand_free (gr);
} }
static gboolean static gboolean
@ -642,11 +687,17 @@ about_dialog_timer (gpointer data)
return FALSE; return FALSE;
case 1: case 1:
text = insert_spacers (_("PIKA is brought to you by Aldercone Studio")); text = insert_spacers (_("PIKA is brought to you by"));
dialog->state += 1;
break; break;
case 2: case 2:
return FALSE; if (! (dialog->index < dialog->n_authors))
dialog->index = 0;
text = insert_spacers (authors[dialog->shuffle[dialog->index]]);
dialog->index += 1;
break;
default: default:
g_return_val_if_reached (TRUE); g_return_val_if_reached (TRUE);

View File

@ -23,7 +23,8 @@
#define __ABOUT_DIALOG_H__ #define __ABOUT_DIALOG_H__
GtkWidget * about_dialog_create (PikaCoreConfig *config); GtkWidget * about_dialog_create (Pika *pika,
PikaCoreConfig *config);
#endif /* __ABOUT_DIALOG_H__ */ #endif /* __ABOUT_DIALOG_H__ */

View File

@ -221,7 +221,7 @@ dialogs_about_get (PikaDialogFactory *factory,
PikaUIManager *ui_manager, PikaUIManager *ui_manager,
gint view_size) gint view_size)
{ {
return about_dialog_create (context->pika); return about_dialog_create (context->pika, context->pika->edit_config);
} }
GtkWidget * GtkWidget *

View File

@ -1856,6 +1856,9 @@ prefs_dialog_new (Pika *pika,
prefs_check_button_add_with_icon (object, "global-gradient", prefs_check_button_add_with_icon (object, "global-gradient",
_("_Gradient"), PIKA_ICON_GRADIENT, _("_Gradient"), PIKA_ICON_GRADIENT,
GTK_BOX (vbox2), size_group); GTK_BOX (vbox2), size_group);
prefs_check_button_add_with_icon (object, "global-expand",
_("E_xpand Layers"), PIKA_ICON_TOOL_SCALE,
GTK_BOX (vbox2), size_group);
/* Move Tool */ /* Move Tool */
vbox2 = prefs_frame_new (_("Move Tool"), vbox2 = prefs_frame_new (_("Move Tool"),

View File

@ -46,6 +46,11 @@ static void pika_display_bounds_changed_handler (PikaImage *image,
gint old_x, gint old_x,
gint old_y, gint old_y,
PikaDisplay *display); PikaDisplay *display);
static void pika_display_flush_handler (PikaImage *image,
gboolean invalidate_preview,
PikaDisplay *display);
static gboolean
pika_display_flush_handler_idle (gpointer user_data);
/* public functions */ /* public functions */
@ -122,3 +127,23 @@ pika_display_bounds_changed_handler (PikaImage *image,
{ {
pika_display_update_bounding_box (display); pika_display_update_bounding_box (display);
} }
static void
pika_display_flush_handler (PikaImage *image,
gboolean invalidate_preview,
PikaDisplay *display)
{
g_idle_add_full (G_PRIORITY_LOW,
(GSourceFunc) pika_display_flush_handler_idle,
g_object_ref (display), g_object_unref);
}
static gboolean
pika_display_flush_handler_idle (gpointer user_data)
{
PikaDisplay *display = user_data;
pika_display_flush (display);
return G_SOURCE_REMOVE;
}

View File

@ -26,13 +26,19 @@
#include <string.h> #include <string.h>
#include <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h> #include <gegl.h>
#include <gegl-plugin.h> #include <gegl-plugin.h>
#include "libpikacolor/pikacolor.h"
#include "pika-gegl-types.h" #include "pika-gegl-types.h"
#include "core/pikapattern.h"
#include "core/pikaprogress.h" #include "core/pikaprogress.h"
#include "pika-babl.h"
#include "pika-gegl-loops.h" #include "pika-gegl-loops.h"
#include "pika-gegl-utils.h" #include "pika-gegl-utils.h"
@ -337,6 +343,100 @@ pika_gegl_buffer_dup (GeglBuffer *buffer)
return new_buffer; return new_buffer;
} }
GeglBuffer *
pika_gegl_buffer_resize (GeglBuffer *buffer,
gint new_width,
gint new_height,
gint offset_x,
gint offset_y,
PikaRGB *color,
PikaPattern *pattern,
gint pattern_offset_x,
gint pattern_offset_y)
{
GeglBuffer *new_buffer;
gboolean intersect;
GeglRectangle copy_rect;
const GeglRectangle *extent;
const Babl *format;
g_return_val_if_fail (GEGL_IS_BUFFER (buffer), NULL);
extent = gegl_buffer_get_extent (buffer);
format = gegl_buffer_get_format (buffer);
new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, new_width, new_height),
format);
intersect = gegl_rectangle_intersect (&copy_rect,
GEGL_RECTANGLE (0, 0, extent->width,
extent->height),
GEGL_RECTANGLE (offset_x, offset_y,
new_width, new_height));
if (! intersect ||
copy_rect.width != new_width ||
copy_rect.height != new_height)
{
/* Clear the new buffer if needed and color/pattern is given */
if (pattern)
{
GeglBuffer *src_buffer;
GeglBuffer *dest_buffer;
PikaColorProfile *src_profile;
PikaColorProfile *dest_profile;
src_buffer = pika_pattern_create_buffer (pattern);
src_profile = pika_babl_format_get_color_profile (
gegl_buffer_get_format (src_buffer));
dest_profile = pika_babl_format_get_color_profile (
gegl_buffer_get_format (new_buffer));
if (pika_color_transform_can_gegl_copy (src_profile, dest_profile))
{
dest_buffer = g_object_ref (src_buffer);
}
else
{
dest_buffer = gegl_buffer_new (gegl_buffer_get_extent (src_buffer),
gegl_buffer_get_format (new_buffer));
pika_gegl_convert_color_profile (src_buffer, NULL, src_profile,
dest_buffer, NULL, dest_profile,
PIKA_COLOR_RENDERING_INTENT_PERCEPTUAL,
TRUE, NULL);
}
g_object_unref (src_profile);
g_object_unref (dest_profile);
gegl_buffer_set_pattern (new_buffer, NULL, dest_buffer,
pattern_offset_x, pattern_offset_y);
g_object_unref (src_buffer);
g_object_unref (dest_buffer);
}
else if (color)
{
GeglColor *gegl_color;
gegl_color = pika_gegl_color_new (color, gegl_buffer_get_format (buffer));
gegl_buffer_set_color (new_buffer, NULL, gegl_color);
g_object_unref (gegl_color);
}
}
if (intersect && copy_rect.width && copy_rect.height)
{
/* Copy the pixels in the intersection */
pika_gegl_buffer_copy (buffer, &copy_rect, GEGL_ABYSS_NONE, new_buffer,
GEGL_RECTANGLE (copy_rect.x - offset_x,
copy_rect.y - offset_y, 0, 0));
}
return new_buffer;
}
gboolean gboolean
pika_gegl_buffer_set_extent (GeglBuffer *buffer, pika_gegl_buffer_set_extent (GeglBuffer *buffer,
const GeglRectangle *extent) const GeglRectangle *extent)

View File

@ -59,6 +59,15 @@ gboolean pika_gegl_param_spec_has_key (GParamSpec *pspe
const gchar *value); const gchar *value);
GeglBuffer * pika_gegl_buffer_dup (GeglBuffer *buffer); GeglBuffer * pika_gegl_buffer_dup (GeglBuffer *buffer);
GeglBuffer * pika_gegl_buffer_resize (GeglBuffer *buffer,
gint new_width,
gint new_height,
gint offset_x,
gint offset_y,
PikaRGB *color,
PikaPattern *pattern,
gint pattern_offset_x,
gint pattern_offset_y);
gboolean pika_gegl_buffer_set_extent (GeglBuffer *buffer, gboolean pika_gegl_buffer_set_extent (GeglBuffer *buffer,
const GeglRectangle *extent); const GeglRectangle *extent);

View File

@ -85,5 +85,12 @@ typedef enum /*< skip, pdb-skip >*/
PIKA_PAINT_STATE_FINISH /* Cleanup and/or reset PaintFunc operation */ PIKA_PAINT_STATE_FINISH /* Cleanup and/or reset PaintFunc operation */
} PikaPaintState; } PikaPaintState;
/* State of lock blinking */
typedef enum /*< skip, pdb-skip >*/
{
PIKA_PAINT_LOCK_NOT_BLINKED,
PIKA_PAINT_LOCK_BLINK_PENDING,
PIKA_PAINT_LOCK_BLINKED,
} PikaPaintLockBlinkState;
#endif /* __PAINT_ENUMS_H__ */ #endif /* __PAINT_ENUMS_H__ */

View File

@ -819,6 +819,8 @@ pika_brush_core_get_paint_buffer (PikaPaintCore *paint_core,
gint x1, y1, x2, y2; gint x1, y1, x2, y2;
gint drawable_width, drawable_height; gint drawable_width, drawable_height;
gint brush_width, brush_height; gint brush_width, brush_height;
gint offset_change_x, offset_change_y;
PikaCoords new_coords;
pika_brush_transform_size (core->brush, pika_brush_transform_size (core->brush,
core->scale, core->aspect_ratio, core->scale, core->aspect_ratio,
@ -835,11 +837,31 @@ pika_brush_core_get_paint_buffer (PikaPaintCore *paint_core,
x = (gint) floor (coords->x) - (brush_width / 2); x = (gint) floor (coords->x) - (brush_width / 2);
y = (gint) floor (coords->y) - (brush_height / 2); y = (gint) floor (coords->y) - (brush_height / 2);
x1 = x - 1;
y1 = y - 1;
x2 = x + brush_width + 1;
y2 = y + brush_height + 1;
pika_paint_core_expand_drawable (paint_core, drawable, paint_options,
x1, x2, y1, y2,
&offset_change_x, &offset_change_y);
if (offset_change_x || offset_change_y)
{
x += offset_change_x;
y += offset_change_y;
new_coords = *coords;
new_coords.x = coords->x + offset_change_x;
new_coords.y = coords->y + offset_change_y;
pika_symmetry_set_origin (paint_core->sym, drawable, &new_coords);
}
drawable_width = pika_item_get_width (PIKA_ITEM (drawable)); drawable_width = pika_item_get_width (PIKA_ITEM (drawable));
drawable_height = pika_item_get_height (PIKA_ITEM (drawable)); drawable_height = pika_item_get_height (PIKA_ITEM (drawable));
x1 = CLAMP (x - 1, 0, drawable_width); x1 = CLAMP (x - 1, 0, drawable_width);
y1 = CLAMP (y - 1, 0, drawable_height); y1 = CLAMP (y - 1, 0, drawable_height);
x2 = CLAMP (x + brush_width + 1, 0, drawable_width); x2 = CLAMP (x + brush_width + 1, 0, drawable_width);
y2 = CLAMP (y + brush_height + 1, 0, drawable_height); y2 = CLAMP (y + brush_height + 1, 0, drawable_height);

View File

@ -470,6 +470,21 @@ pika_blob_duplicate (PikaBlob *b)
return g_memdup2 (b, sizeof (PikaBlob) + sizeof (PikaBlobSpan) * (b->height - 1)); return g_memdup2 (b, sizeof (PikaBlob) + sizeof (PikaBlobSpan) * (b->height - 1));
} }
void
pika_blob_move (PikaBlob *b,
gint x,
gint y)
{
gint i = 0;
b->y += y;
for (i = 0; i < b->height; i++)
{
b->data[i].left += x;
b->data[i].right += x;
}
}
#if 0 #if 0
void void
pika_blob_dump (PikaBlob *b) pika_blob_dump (PikaBlob *b)

View File

@ -86,6 +86,9 @@ void pika_blob_bounds (PikaBlob *b,
PikaBlob * pika_blob_convex_union (PikaBlob *b1, PikaBlob * pika_blob_convex_union (PikaBlob *b1,
PikaBlob *b2); PikaBlob *b2);
PikaBlob * pika_blob_duplicate (PikaBlob *b); PikaBlob * pika_blob_duplicate (PikaBlob *b);
void pika_blob_move (PikaBlob *b,
gint x,
gint y);
#endif /* __PIKA_INK_BLOB_H__ */ #endif /* __PIKA_INK_BLOB_H__ */

View File

@ -242,17 +242,49 @@ pika_ink_get_paint_buffer (PikaPaintCore *paint_core,
gint *paint_width, gint *paint_width,
gint *paint_height) gint *paint_height)
{ {
PikaInk *ink = PIKA_INK (paint_core); PikaInk *ink = PIKA_INK (paint_core);
gint x, y; gint x, y;
gint width, height; gint width, height;
gint dwidth, dheight; gint dwidth, dheight;
gint x1, y1, x2, y2; gint x1, y1, x2, y2;
gint offset_change_x, offset_change_y;
PikaCoords new_coords;
GList *iter;
pika_blob_bounds (ink->cur_blob, &x, &y, &width, &height); pika_blob_bounds (ink->cur_blob, &x, &y, &width, &height);
x1 = x / SUBSAMPLE - 1;
y1 = y / SUBSAMPLE - 1;
x2 = (x + width) / SUBSAMPLE + 2;
y2 = (y + height) / SUBSAMPLE + 2;
pika_paint_core_expand_drawable (paint_core, drawable, paint_options,
x1, x2, y1, y2,
&offset_change_x, &offset_change_y);
dwidth = pika_item_get_width (PIKA_ITEM (drawable)); dwidth = pika_item_get_width (PIKA_ITEM (drawable));
dheight = pika_item_get_height (PIKA_ITEM (drawable)); dheight = pika_item_get_height (PIKA_ITEM (drawable));
if (offset_change_x || offset_change_y)
{
x += SUBSAMPLE * offset_change_x;
y += SUBSAMPLE * offset_change_y;
new_coords = *coords;
new_coords.x = coords->x + offset_change_x;
new_coords.y = coords->y + offset_change_y;
pika_symmetry_set_origin (paint_core->sym, drawable, &new_coords);
for (iter = ink->blobs_to_render; iter; iter = g_list_next (iter))
pika_blob_move (iter->data,
SUBSAMPLE * offset_change_x,
SUBSAMPLE * offset_change_y);
for (iter = ink->last_blobs; iter; iter = g_list_next (iter))
pika_blob_move (iter->data,
SUBSAMPLE * offset_change_x,
SUBSAMPLE * offset_change_y);
}
x1 = CLAMP (x / SUBSAMPLE - 1, 0, dwidth); x1 = CLAMP (x / SUBSAMPLE - 1, 0, dwidth);
y1 = CLAMP (y / SUBSAMPLE - 1, 0, dheight); y1 = CLAMP (y / SUBSAMPLE - 1, 0, dheight);
x2 = CLAMP ((x + width) / SUBSAMPLE + 2, 0, dwidth); x2 = CLAMP ((x + width) / SUBSAMPLE + 2, 0, dwidth);
@ -319,7 +351,6 @@ pika_ink_motion (PikaPaintCore *paint_core,
PikaInk *ink = PIKA_INK (paint_core); PikaInk *ink = PIKA_INK (paint_core);
PikaInkOptions *options = PIKA_INK_OPTIONS (paint_options); PikaInkOptions *options = PIKA_INK_OPTIONS (paint_options);
PikaContext *context = PIKA_CONTEXT (paint_options); PikaContext *context = PIKA_CONTEXT (paint_options);
GList *blob_unions = NULL;
GList *blobs_to_render = NULL; GList *blobs_to_render = NULL;
GeglBuffer *paint_buffer; GeglBuffer *paint_buffer;
gint paint_buffer_x; gint paint_buffer_x;
@ -338,6 +369,7 @@ pika_ink_motion (PikaPaintCore *paint_core,
coords.x -= off_x; coords.x -= off_x;
coords.y -= off_y; coords.y -= off_y;
pika_symmetry_set_origin (sym, drawable, &coords); pika_symmetry_set_origin (sym, drawable, &coords);
paint_core->sym = sym;
n_strokes = pika_symmetry_get_size (sym); n_strokes = pika_symmetry_get_size (sym);
@ -377,7 +409,8 @@ pika_ink_motion (PikaPaintCore *paint_core,
last_blob); last_blob);
ink->start_blobs = g_list_prepend (ink->start_blobs, ink->start_blobs = g_list_prepend (ink->start_blobs,
pika_blob_duplicate (last_blob)); pika_blob_duplicate (last_blob));
blobs_to_render = g_list_prepend (blobs_to_render, last_blob); blobs_to_render = g_list_prepend (blobs_to_render,
pika_blob_duplicate (last_blob));
} }
ink->start_blobs = g_list_reverse (ink->start_blobs); ink->start_blobs = g_list_reverse (ink->start_blobs);
ink->last_blobs = g_list_reverse (ink->last_blobs); ink->last_blobs = g_list_reverse (ink->last_blobs);
@ -411,7 +444,6 @@ pika_ink_motion (PikaPaintCore *paint_core,
g_list_nth (ink->last_blobs, i)->data = blob; g_list_nth (ink->last_blobs, i)->data = blob;
blobs_to_render = g_list_prepend (blobs_to_render, blob_union); blobs_to_render = g_list_prepend (blobs_to_render, blob_union);
blob_unions = g_list_prepend (blob_unions, blob_union);
} }
blobs_to_render = g_list_reverse (blobs_to_render); blobs_to_render = g_list_reverse (blobs_to_render);
} }
@ -422,6 +454,7 @@ pika_ink_motion (PikaPaintCore *paint_core,
pika_pickable_srgb_to_image_color (PIKA_PICKABLE (drawable), pika_pickable_srgb_to_image_color (PIKA_PICKABLE (drawable),
&foreground, &foreground); &foreground, &foreground);
color = pika_gegl_color_new (&foreground, pika_drawable_get_space (drawable)); color = pika_gegl_color_new (&foreground, pika_drawable_get_space (drawable));
ink->blobs_to_render = blobs_to_render;
for (i = 0; i < n_strokes; i++) for (i = 0; i < n_strokes; i++)
{ {
@ -467,8 +500,7 @@ pika_ink_motion (PikaPaintCore *paint_core,
g_object_unref (color); g_object_unref (color);
g_list_free_full (blob_unions, g_free); g_list_free_full (blobs_to_render, g_free);
g_list_free (blobs_to_render);
} }
static PikaBlob * static PikaBlob *

View File

@ -41,10 +41,11 @@ struct _PikaInk
{ {
PikaPaintCore parent_instance; PikaPaintCore parent_instance;
GList *start_blobs; /* starting blobs per stroke (for undo) */ GList *start_blobs; /* starting blobs per stroke (for undo) */
PikaBlob *cur_blob; /* current blob */ PikaBlob *cur_blob; /* current blob */
GList *last_blobs; /* blobs for last stroke positions */ GList *last_blobs; /* blobs for last stroke positions */
GList *blobs_to_render;
}; };
struct _PikaInkClass struct _PikaInkClass

View File

@ -259,7 +259,7 @@ pika_mybrush_core_motion (PikaPaintCore *paint_core,
{ {
PikaMybrushCore *mybrush = PIKA_MYBRUSH_CORE (paint_core); PikaMybrushCore *mybrush = PIKA_MYBRUSH_CORE (paint_core);
MyPaintRectangle rect; MyPaintRectangle rect;
PikaCoords coords; PikaCoords origin;
GList *iter; GList *iter;
gdouble dt = 0.0; gdouble dt = 0.0;
gint off_x, off_y; gint off_x, off_y;
@ -269,10 +269,10 @@ pika_mybrush_core_motion (PikaPaintCore *paint_core,
pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y);
n_strokes = pika_symmetry_get_size (sym); n_strokes = pika_symmetry_get_size (sym);
coords = *(pika_symmetry_get_origin (sym)); origin = *(pika_symmetry_get_origin (sym));
coords.x -= off_x; origin.x -= off_x;
coords.y -= off_y; origin.y -= off_y;
pika_symmetry_set_origin (sym, drawable, &coords); pika_symmetry_set_origin (sym, drawable, &origin);
/* The number of strokes may change during a motion, depending on /* The number of strokes may change during a motion, depending on
* the type of symmetry. When that happens, reset the brushes. * the type of symmetry. When that happens, reset the brushes.
@ -291,9 +291,8 @@ pika_mybrush_core_motion (PikaPaintCore *paint_core,
iter; iter;
iter = g_list_next (iter), i++) iter = g_list_next (iter), i++)
{ {
MyPaintBrush *brush = iter->data; MyPaintBrush *brush = iter->data;
PikaCoords coords = *(pika_symmetry_get_coords (sym, i));
coords = *(pika_symmetry_get_coords (sym, i));
mypaint_brush_stroke_to (brush, mypaint_brush_stroke_to (brush,
(MyPaintSurface *) mybrush->private->surface, (MyPaintSurface *) mybrush->private->surface,
@ -326,6 +325,42 @@ pika_mybrush_core_motion (PikaPaintCore *paint_core,
MyPaintBrush *brush = iter->data; MyPaintBrush *brush = iter->data;
PikaCoords coords = *(pika_symmetry_get_coords (sym, i)); PikaCoords coords = *(pika_symmetry_get_coords (sym, i));
gdouble pressure = coords.pressure; gdouble pressure = coords.pressure;
gboolean expanded;
gfloat radius = 100;
gint x1, x2, y1, y2;
gint offset_change_x, offset_change_y;
gint off_x_surf, off_y_surf;
gint off_x, off_y;
x1 = coords.x - radius;
y1 = coords.y - radius;
x2 = coords.x + radius;
y2 = coords.y + radius;
expanded = pika_paint_core_expand_drawable (paint_core, drawable, paint_options,
x1, x2, y1, y2,
&offset_change_x, &offset_change_y);
pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y);
if (expanded)
pika_mypaint_surface_set_buffer (mybrush->private->surface, pika_drawable_get_buffer (drawable),
off_x, off_y);
pika_mypaint_surface_get_offset (mybrush->private->surface, &off_x_surf, &off_y_surf);
coords.x -= off_x_surf;
coords.y -= off_y_surf;
if (offset_change_x || offset_change_y)
{
pika_mypaint_surface_set_offset (mybrush->private->surface,
off_x_surf + offset_change_x,
off_y_surf + offset_change_y);
origin = *(pika_symmetry_get_origin (sym));
origin.x += offset_change_x;
origin.y += offset_change_y;
pika_symmetry_set_origin (sym, drawable, &origin);
}
mypaint_brush_stroke_to (brush, mypaint_brush_stroke_to (brush,
(MyPaintSurface *) mybrush->private->surface, (MyPaintSurface *) mybrush->private->surface,

View File

@ -38,13 +38,15 @@
struct _PikaMybrushSurface struct _PikaMybrushSurface
{ {
MyPaintSurface surface; MyPaintSurface surface;
GeglBuffer *buffer; GeglBuffer *buffer;
GeglBuffer *paint_mask; GeglBuffer *paint_mask;
gint paint_mask_x; gint paint_mask_x;
gint paint_mask_y; gint paint_mask_y;
GeglRectangle dirty; gint off_x;
PikaComponentMask component_mask; gint off_y;
GeglRectangle dirty;
PikaComponentMask component_mask;
PikaMybrushOptions *options; PikaMybrushOptions *options;
}; };
@ -382,6 +384,8 @@ pika_mypaint_surface_draw_dab (MyPaintSurface *base_surface,
colorize = opaque * colorize; colorize = opaque * colorize;
/* FIXME: This should use the real matrix values to trim aspect_ratio dabs */ /* FIXME: This should use the real matrix values to trim aspect_ratio dabs */
x += surface->off_x;
y += surface->off_y;
dabRect = calculate_dab_roi (x, y, radius); dabRect = calculate_dab_roi (x, y, radius);
gegl_rectangle_intersect (&dabRect, &dabRect, gegl_buffer_get_extent (surface->buffer)); gegl_rectangle_intersect (&dabRect, &dabRect, gegl_buffer_get_extent (surface->buffer));
@ -561,5 +565,40 @@ pika_mypaint_surface_new (GeglBuffer *buffer,
surface->paint_mask_y = paint_mask_y; surface->paint_mask_y = paint_mask_y;
surface->dirty = *GEGL_RECTANGLE (0, 0, 0, 0); surface->dirty = *GEGL_RECTANGLE (0, 0, 0, 0);
surface->off_x = 0;
surface->off_y = 0;
return surface; return surface;
} }
void
pika_mypaint_surface_set_buffer (PikaMybrushSurface *surface,
GeglBuffer *buffer,
gint paint_mask_x,
gint paint_mask_y)
{
g_object_unref (surface->buffer);
surface->buffer = g_object_ref (buffer);
surface->paint_mask_x = paint_mask_x;
surface->paint_mask_y = paint_mask_y;
}
void
pika_mypaint_surface_set_offset (PikaMybrushSurface *surface,
gint off_x,
gint off_y)
{
surface->off_x = off_x;
surface->off_y = off_y;
}
void
pika_mypaint_surface_get_offset (PikaMybrushSurface *surface,
gint *off_x,
gint *off_y)
{
*off_x = surface->off_x;
*off_y = surface->off_y;
}

View File

@ -33,5 +33,18 @@ pika_mypaint_surface_new (GeglBuffer *buffer,
gint paint_mask_y, gint paint_mask_y,
PikaMybrushOptions *options); PikaMybrushOptions *options);
void
pika_mypaint_surface_set_buffer (PikaMybrushSurface *surface,
GeglBuffer *buffer,
gint paint_mask_x,
gint paint_mask_y);
void
pika_mypaint_surface_set_offset (PikaMybrushSurface *surface,
gint off_x,
gint off_y);
void
pika_mypaint_surface_get_offset (PikaMybrushSurface *surface,
gint *off_x,
gint *off_y);
#endif /* __PIKA_MYBRUSH_SURFACE_H__ */ #endif /* __PIKA_MYBRUSH_SURFACE_H__ */

View File

@ -193,7 +193,8 @@ pika_paintbrush_real_get_paint_params (PikaPaintbrush *paintbrush,
*paint_mode = pika_context_get_paint_mode (context); *paint_mode = pika_context_get_paint_mode (context);
if (pika_paint_options_get_gradient_color (paint_options, image, if (pika_paint_options_are_dynamics_enabled (paint_options) &&
pika_paint_options_get_gradient_color (paint_options, image,
grad_point, grad_point,
paint_core->pixel_dist, paint_core->pixel_dist,
paint_color)) paint_color))
@ -252,6 +253,7 @@ _pika_paintbrush_motion (PikaPaintCore *paint_core,
coords.x -= off_x; coords.x -= off_x;
coords.y -= off_y; coords.y -= off_y;
pika_symmetry_set_origin (sym, drawable, &coords); pika_symmetry_set_origin (sym, drawable, &coords);
paint_core->sym = sym;
/* Some settings are based on the original stroke. */ /* Some settings are based on the original stroke. */
opacity *= pika_dynamics_get_linear_value (dynamics, opacity *= pika_dynamics_get_linear_value (dynamics,
@ -313,6 +315,8 @@ _pika_paintbrush_motion (PikaPaintCore *paint_core,
&paint_buffer_y, &paint_buffer_y,
&paint_width, &paint_width,
&paint_height); &paint_height);
coords = *(pika_symmetry_get_coords (sym, i));
if (! paint_buffer) if (! paint_buffer)
continue; continue;

View File

@ -24,10 +24,12 @@
#include <string.h> #include <string.h>
#include <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h> #include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h> #include <gegl.h>
#include "libpikabase/pikabase.h" #include "libpikabase/pikabase.h"
#include "libpikacolor/pikacolor.h"
#include "libpikamath/pikamath.h" #include "libpikamath/pikamath.h"
#include "paint-types.h" #include "paint-types.h"
@ -47,6 +49,9 @@
#include "core/pikaimage-guides.h" #include "core/pikaimage-guides.h"
#include "core/pikaimage-symmetry.h" #include "core/pikaimage-symmetry.h"
#include "core/pikaimage-undo.h" #include "core/pikaimage-undo.h"
#include "core/pikaimage-undo-push.h"
#include "core/pikalayer.h"
#include "core/pikalayermask.h"
#include "core/pikapickable.h" #include "core/pikapickable.h"
#include "core/pikaprojection.h" #include "core/pikaprojection.h"
#include "core/pikasymmetry.h" #include "core/pikasymmetry.h"
@ -158,7 +163,10 @@ static void
pika_paint_core_init (PikaPaintCore *core) pika_paint_core_init (PikaPaintCore *core)
{ {
core->ID = global_core_ID++; core->ID = global_core_ID++;
core->undo_buffers = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); core->undo_buffers = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, g_object_unref);
core->original_bounds = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, g_free);
} }
static void static void
@ -170,6 +178,7 @@ pika_paint_core_finalize (GObject *object)
g_clear_pointer (&core->undo_desc, g_free); g_clear_pointer (&core->undo_desc, g_free);
g_hash_table_unref (core->undo_buffers); g_hash_table_unref (core->undo_buffers);
g_hash_table_unref (core->original_bounds);
if (core->applicators) if (core->applicators)
g_hash_table_unref (core->applicators); g_hash_table_unref (core->applicators);
@ -357,10 +366,11 @@ pika_paint_core_start (PikaPaintCore *core,
const PikaCoords *coords, const PikaCoords *coords,
GError **error) GError **error)
{ {
PikaImage *image; PikaImage *image;
PikaChannel *mask; PikaChannel *mask;
gint max_width = 0; gint max_width = 0;
gint max_height = 0; gint max_height = 0;
GeglRectangle *rect;
g_return_val_if_fail (PIKA_IS_PAINT_CORE (core), FALSE); g_return_val_if_fail (PIKA_IS_PAINT_CORE (core), FALSE);
g_return_val_if_fail (g_list_length (drawables) > 0, FALSE); g_return_val_if_fail (g_list_length (drawables) > 0, FALSE);
@ -418,8 +428,16 @@ pika_paint_core_start (PikaPaintCore *core,
for (GList *iter = drawables; iter; iter = iter->next) for (GList *iter = drawables; iter; iter = iter->next)
{ {
/* Allocate the undo structures */ /* Allocate the undo structures */
rect = g_new (GeglRectangle, 1);
rect->width = pika_item_get_width (PIKA_ITEM (iter->data));
rect->height = pika_item_get_height (PIKA_ITEM (iter->data));
pika_item_get_offset (PIKA_ITEM (iter->data), &rect->x, &rect->y);
g_hash_table_insert (core->original_bounds, iter->data,
rect);
g_hash_table_insert (core->undo_buffers, iter->data, g_hash_table_insert (core->undo_buffers, iter->data,
pika_gegl_buffer_dup (pika_drawable_get_buffer (iter->data))); pika_gegl_buffer_dup (pika_drawable_get_buffer (iter->data)));
max_width = MAX (max_width, pika_item_get_width (iter->data)); max_width = MAX (max_width, pika_item_get_width (iter->data));
max_height = MAX (max_height, pika_item_get_height (iter->data)); max_height = MAX (max_height, pika_item_get_height (iter->data));
} }
@ -484,6 +502,9 @@ pika_paint_core_start (PikaPaintCore *core,
} }
} }
/* initialize the lock_blink_state */
core->lock_blink_state = PIKA_PAINT_LOCK_NOT_BLINKED;
/* Freeze the drawable preview so that it isn't constantly updated. */ /* Freeze the drawable preview so that it isn't constantly updated. */
for (GList *iter = drawables; iter; iter = iter->next) for (GList *iter = drawables; iter; iter = iter->next)
pika_viewable_preview_freeze (PIKA_VIEWABLE (iter->data)); pika_viewable_preview_freeze (PIKA_VIEWABLE (iter->data));
@ -532,7 +553,9 @@ pika_paint_core_finish (PikaPaintCore *core,
{ {
GeglBuffer *undo_buffer; GeglBuffer *undo_buffer;
GeglBuffer *buffer; GeglBuffer *buffer;
GeglBuffer *drawable_buffer;
GeglRectangle rect; GeglRectangle rect;
GeglRectangle old_rect;
if (! g_hash_table_steal_extended (core->undo_buffers, iter->data, if (! g_hash_table_steal_extended (core->undo_buffers, iter->data,
NULL, (gpointer*) &undo_buffer)) NULL, (gpointer*) &undo_buffer))
@ -542,16 +565,6 @@ pika_paint_core_finish (PikaPaintCore *core,
continue; continue;
} }
pika_rectangle_intersect (core->x1, core->y1,
core->x2 - core->x1, core->y2 - core->y1,
0, 0,
pika_item_get_width (PIKA_ITEM (iter->data)),
pika_item_get_height (PIKA_ITEM (iter->data)),
&rect.x, &rect.y, &rect.width, &rect.height);
gegl_rectangle_align_to_buffer (&rect, &rect, undo_buffer,
GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
if (! undo_group_started) if (! undo_group_started)
{ {
pika_image_undo_group_start (image, PIKA_UNDO_GROUP_PAINT, pika_image_undo_group_start (image, PIKA_UNDO_GROUP_PAINT,
@ -559,19 +572,115 @@ pika_paint_core_finish (PikaPaintCore *core,
undo_group_started = TRUE; undo_group_started = TRUE;
} }
PIKA_PAINT_CORE_GET_CLASS (core)->push_undo (core, image, NULL); /* get new and old bounds of drawable */
old_rect = *(GeglRectangle*) g_hash_table_lookup (core->original_bounds, iter->data);
buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, rect.width, rect.height), pika_item_get_offset (PIKA_ITEM (iter->data), &rect.x, &rect.y);
pika_drawable_get_format (iter->data)); rect.width = pika_item_get_width (PIKA_ITEM (iter->data));
rect.height = pika_item_get_height (PIKA_ITEM (iter->data));
pika_gegl_buffer_copy (undo_buffer, /* Making copy of entire buffer consumes more memory, so do that only when buffer has resized */
&rect, if (rect.x == old_rect.x &&
GEGL_ABYSS_NONE, rect.y == old_rect.y &&
buffer, rect.width == old_rect.width &&
GEGL_RECTANGLE (0, 0, 0, 0)); rect.height == old_rect.height)
{
pika_rectangle_intersect (core->x1, core->y1, core->x2 - core->x1,
core->y2 - core->y1, 0, 0,
pika_item_get_width (PIKA_ITEM (iter->data)),
pika_item_get_height (PIKA_ITEM (iter->data)),
&rect.x, &rect.y, &rect.width, &rect.height);
pika_drawable_push_undo (iter->data, NULL, gegl_rectangle_align_to_buffer (&rect, &rect, undo_buffer,
buffer, rect.x, rect.y, rect.width, rect.height); GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
PIKA_PAINT_CORE_GET_CLASS (core)->push_undo (core, image, NULL);
buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, rect.width, rect.height),
pika_drawable_get_format (iter->data));
pika_gegl_buffer_copy (undo_buffer,
&rect,
GEGL_ABYSS_NONE,
buffer,
GEGL_RECTANGLE (0, 0, 0, 0));
pika_drawable_push_undo (iter->data, NULL,
buffer, rect.x, rect.y, rect.width, rect.height);
}
else
{
/* drawable is expanded only if drawable is layer or layer mask*/
g_return_if_fail (PIKA_IS_LAYER (iter->data) || PIKA_IS_LAYER_MASK (iter->data));
/* create a copy of original buffer from undo data */
buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
old_rect.width,
old_rect.height),
pika_drawable_get_format (iter->data));
pika_gegl_buffer_copy (undo_buffer,
GEGL_RECTANGLE (old_rect.x - rect.x,
old_rect.y - rect.y,
old_rect.width,
old_rect.height),
GEGL_ABYSS_NONE,
buffer,
GEGL_RECTANGLE (0, 0, 0, 0));
/* make a backup copy of drawable to restore */
drawable_buffer = g_object_ref (pika_drawable_get_buffer (PIKA_DRAWABLE (iter->data)));
if (PIKA_IS_LAYER_MASK (drawables->data) || PIKA_LAYER (drawables->data)->mask)
{
GeglBuffer *other_new;
GeglBuffer *other_old;
PikaDrawable *other_drawable;
if (PIKA_IS_LAYER_MASK (drawables->data))
other_drawable = PIKA_DRAWABLE ((PIKA_LAYER_MASK (drawables->data))->layer);
else
other_drawable = PIKA_DRAWABLE (PIKA_LAYER (drawables->data)->mask);
/* create a copy of original buffer by taking the required area */
other_old = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
old_rect.width,
old_rect.height),
pika_drawable_get_format (other_drawable));
pika_gegl_buffer_copy (pika_drawable_get_buffer (other_drawable),
GEGL_RECTANGLE (old_rect.x - rect.x,
old_rect.y - rect.y,
old_rect.width,
old_rect.height),
GEGL_ABYSS_NONE,
other_old,
GEGL_RECTANGLE (0, 0, 0, 0));
/* make a backup copy of drawable to restore */
other_new = g_object_ref (pika_drawable_get_buffer (other_drawable));
pika_drawable_set_buffer_full (other_drawable, FALSE, NULL,
other_old, &old_rect,
FALSE);
pika_drawable_set_buffer_full (other_drawable, TRUE, NULL,
other_new, &rect,
FALSE);
g_object_unref (other_new);
g_object_unref (other_old);
}
/* Restore drawable to state before painting started */
pika_drawable_set_buffer_full (iter->data, FALSE, NULL,
buffer, &old_rect,
FALSE);
/* Change the drawable again but push undo this time */
pika_drawable_set_buffer_full (iter->data, TRUE, NULL,
drawable_buffer, &rect,
FALSE);
g_object_unref (drawable_buffer);
}
g_object_unref (buffer); g_object_unref (buffer);
g_object_unref (undo_buffer); g_object_unref (undo_buffer);
@ -614,7 +723,8 @@ pika_paint_core_cancel (PikaPaintCore *core,
&x, &y, &width, &height)) &x, &y, &width, &height))
{ {
GeglBuffer *undo_buffer; GeglBuffer *undo_buffer;
GeglRectangle rect; GeglRectangle new_rect;
GeglRectangle old_rect;
if (! g_hash_table_steal_extended (core->undo_buffers, iter->data, if (! g_hash_table_steal_extended (core->undo_buffers, iter->data,
NULL, (gpointer*) &undo_buffer)) NULL, (gpointer*) &undo_buffer))
@ -624,21 +734,108 @@ pika_paint_core_cancel (PikaPaintCore *core,
continue; continue;
} }
gegl_rectangle_align_to_buffer (&rect, old_rect = *(GeglRectangle*) g_hash_table_lookup (core->original_bounds, iter->data);
GEGL_RECTANGLE (x, y, width, height),
pika_drawable_get_buffer (iter->data), pika_item_get_offset (PIKA_ITEM (iter->data), &new_rect.x, &new_rect.y);
GEGL_RECTANGLE_ALIGNMENT_SUPERSET); new_rect.width = pika_item_get_width (PIKA_ITEM (iter->data));
new_rect.height = pika_item_get_height (PIKA_ITEM (iter->data));
if (new_rect.x == old_rect.x &&
new_rect.y == old_rect.y &&
new_rect.width == old_rect.width &&
new_rect.height == old_rect.height)
{
GeglRectangle rect;
gegl_rectangle_align_to_buffer (&rect,
GEGL_RECTANGLE (x, y, width, height),
pika_drawable_get_buffer (iter->data),
GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
pika_gegl_buffer_copy (undo_buffer,
&rect,
GEGL_ABYSS_NONE,
pika_drawable_get_buffer (iter->data),
&rect);
pika_drawable_update (iter->data, x, y, width, height);
}
else
{
GeglBuffer *buffer;
GeglRectangle bbox;
/* drawable is expanded only if drawable is layer or layer mask,
* so drawable cannot be anything else */
g_return_if_fail (PIKA_IS_LAYER (iter->data) || PIKA_IS_LAYER_MASK (iter->data));
/* When canceling painting with drawable expansion, ensure that
* the drawable display outside the reverted size is not shown. We
* cannot use pika_drawable_update() because it won't work while
* painting. Directly emit the "update" signal.
* */
bbox = pika_drawable_get_bounding_box (iter->data);
g_signal_emit_by_name (iter->data, "update", bbox.x, bbox.y, bbox.width, bbox.height);
/* create a copy of original buffer from undo data */
buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
old_rect.width,
old_rect.height),
pika_drawable_get_format (iter->data));
pika_gegl_buffer_copy (undo_buffer,
GEGL_RECTANGLE (old_rect.x - new_rect.x,
old_rect.y - new_rect.y,
old_rect.width,
old_rect.height),
GEGL_ABYSS_NONE,
buffer,
GEGL_RECTANGLE (0, 0, 0, 0));
if (PIKA_IS_LAYER_MASK (drawables->data) || PIKA_LAYER (drawables->data)->mask)
{
GeglBuffer *other_old;
PikaDrawable *other_drawable;
if (PIKA_IS_LAYER_MASK (drawables->data))
other_drawable = PIKA_DRAWABLE ((PIKA_LAYER_MASK (drawables->data))->layer);
else
other_drawable = PIKA_DRAWABLE (PIKA_LAYER (drawables->data)->mask);
/* create a copy of original buffer by taking the required area */
other_old = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
old_rect.width,
old_rect.height),
pika_drawable_get_format (other_drawable));
pika_gegl_buffer_copy (pika_drawable_get_buffer (other_drawable),
GEGL_RECTANGLE (old_rect.x - new_rect.x,
old_rect.y - new_rect.y,
old_rect.width,
old_rect.height),
GEGL_ABYSS_NONE,
other_old,
GEGL_RECTANGLE (0, 0, 0, 0));
pika_drawable_set_buffer_full (other_drawable, FALSE, NULL,
other_old, &old_rect,
FALSE);
g_object_unref (other_old);
}
/* Restore drawable to state before painting started */
pika_drawable_set_buffer_full (iter->data, FALSE, NULL,
buffer, &old_rect,
FALSE);
pika_drawable_update (iter->data, 0, 0, -1, -1);
g_object_unref (buffer);
}
pika_gegl_buffer_copy (undo_buffer,
&rect,
GEGL_ABYSS_NONE,
pika_drawable_get_buffer (iter->data),
&rect);
g_object_unref (undo_buffer); g_object_unref (undo_buffer);
} }
pika_drawable_update (iter->data, x, y, width, height);
pika_viewable_preview_thaw (PIKA_VIEWABLE (iter->data)); pika_viewable_preview_thaw (PIKA_VIEWABLE (iter->data));
} }
@ -651,6 +848,7 @@ pika_paint_core_cleanup (PikaPaintCore *core)
g_return_if_fail (PIKA_IS_PAINT_CORE (core)); g_return_if_fail (PIKA_IS_PAINT_CORE (core));
g_hash_table_remove_all (core->undo_buffers); g_hash_table_remove_all (core->undo_buffers);
g_hash_table_remove_all (core->original_bounds);
g_clear_object (&core->saved_proj_buffer); g_clear_object (&core->saved_proj_buffer);
g_clear_object (&core->canvas_buffer); g_clear_object (&core->canvas_buffer);
@ -692,6 +890,227 @@ pika_paint_core_get_show_all (PikaPaintCore *core)
return core->show_all; return core->show_all;
} }
gboolean
pika_paint_core_expand_drawable (PikaPaintCore *core,
PikaDrawable *drawable,
PikaPaintOptions *options,
gint x1,
gint x2,
gint y1,
gint y2,
gint *new_off_x,
gint *new_off_y)
{
gint drawable_width, drawable_height;
gint drawable_offset_x, drawable_offset_y;
gint image_width, image_height;
gint new_width, new_height;
gint expand_amount;
gboolean show_all;
gboolean outside_image;
PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable));
PikaLayer *layer;
drawable_width = pika_item_get_width (PIKA_ITEM (drawable));
drawable_height = pika_item_get_height (PIKA_ITEM (drawable));
pika_item_get_offset (PIKA_ITEM (drawable), &drawable_offset_x, &drawable_offset_y);
new_width = drawable_width;
new_height = drawable_height;
*new_off_x = 0;
*new_off_y = 0;
image_width = pika_image_get_width (image);
image_height = pika_image_get_height (image);
expand_amount = options->expand_amount;
show_all = pika_paint_core_get_show_all (core);
outside_image = x2 < -drawable_offset_x || x1 > image_width - drawable_offset_x ||
y2 < -drawable_offset_y || y1 > image_height - drawable_offset_y;
/* Don't expand if drawable is anything other
* than layer or layer mask */
if (PIKA_IS_LAYER_MASK (drawable))
layer = (PIKA_LAYER_MASK (drawable))->layer;
else if (PIKA_IS_LAYER (drawable))
layer = PIKA_LAYER (drawable);
else
return FALSE;
if (!pika_paint_core_get_show_all (core) && outside_image)
return FALSE;
if (!options->expand_use)
return FALSE;
if (x1 < 0)
{
if (show_all)
{
new_width += expand_amount - x1;
*new_off_x += expand_amount - x1;
}
else if (drawable_offset_x > 0)
{
new_width += expand_amount - x1;
*new_off_x += expand_amount - x1;
if (*new_off_x > drawable_offset_x)
{
new_width -= *new_off_x - drawable_offset_x;
*new_off_x = drawable_offset_x;
}
}
}
if (y1 < 0)
{
if (show_all)
{
new_height += expand_amount - y1;
*new_off_y += expand_amount - y1;
}
else if (drawable_offset_y > 0)
{
new_height += expand_amount - y1;
*new_off_y += expand_amount - y1;
if (*new_off_y > drawable_offset_y)
{
new_height -= *new_off_y - drawable_offset_y;
*new_off_y = drawable_offset_y;
}
}
}
if (x2 > drawable_width)
{
if (show_all)
{
new_width += x2 - drawable_width + expand_amount;
}
else if (drawable_width + drawable_offset_x < image_width)
{
new_width += x2 - drawable_width + expand_amount;
if (new_width + drawable_offset_x - *new_off_x > image_width)
new_width = image_width + *new_off_x - drawable_offset_x;
}
}
if (y2 > drawable_height)
{
if (show_all)
{
new_height += y2 - drawable_height + expand_amount;
}
else if (drawable_height + drawable_offset_y < image_height)
{
new_height += y2 - drawable_height + expand_amount;
if (new_height + drawable_offset_y - *new_off_y > image_height)
new_height = image_height + *new_off_y - drawable_offset_y;
}
}
if (new_width != drawable_width || *new_off_x ||
new_height != drawable_height || *new_off_y)
{
PikaRGB color;
PikaPattern *pattern;
PikaContext *context = PIKA_CONTEXT (options);
PikaFillType fill_type = options->expand_fill_type;
gboolean context_has_image;
PikaFillType mask_fill_type;
GeglBuffer *undo_buffer;
GeglBuffer *new_buffer;
if (pika_item_get_lock_position (PIKA_ITEM (layer)))
{
if (core->lock_blink_state == PIKA_PAINT_LOCK_NOT_BLINKED)
core->lock_blink_state = PIKA_PAINT_LOCK_BLINK_PENDING;
/* Since we are not expanding, set new offset to zero */
*new_off_x = 0;
*new_off_y = 0;
return FALSE;
}
mask_fill_type = options->expand_mask_fill_type == PIKA_ADD_MASK_BLACK ?
PIKA_FILL_TRANSPARENT :
PIKA_FILL_WHITE;
/* The image field of context is null but
* is required for Filling with Middle Gray */
context_has_image = context->image != NULL;
if (!context_has_image)
context->image = image;
/* ensure that every expansion is pushed to undo stack */
if (core->x2 == core->x1)
core->x2++;
if (core->y2 == core->y1)
core->y2++;
g_object_freeze_notify (G_OBJECT (layer));
pika_drawable_disable_resize_undo (PIKA_DRAWABLE (layer));
PIKA_LAYER_GET_CLASS (layer)->resize (layer, context, fill_type,
new_width, new_height,
*new_off_x, *new_off_y);
pika_drawable_enable_resize_undo (PIKA_DRAWABLE (layer));
if (layer->mask)
{
g_object_freeze_notify (G_OBJECT (layer->mask));
pika_drawable_disable_resize_undo (PIKA_DRAWABLE (layer->mask));
PIKA_ITEM_GET_CLASS (layer->mask)->resize (PIKA_ITEM (layer->mask), context,
mask_fill_type, new_width, new_height,
*new_off_x, *new_off_y);
pika_drawable_enable_resize_undo (PIKA_DRAWABLE (layer->mask));
g_object_thaw_notify (G_OBJECT (layer->mask));
}
g_object_thaw_notify (G_OBJECT (layer));
pika_image_flush (image);
if (PIKA_IS_LAYER_MASK (drawable))
{
fill_type = mask_fill_type;
}
else if (fill_type == PIKA_FILL_TRANSPARENT &&
! pika_drawable_has_alpha (drawable))
{
fill_type = PIKA_FILL_BACKGROUND;
}
new_buffer = pika_gegl_buffer_resize (core->canvas_buffer, new_width, new_height,
-(*new_off_x), -(*new_off_y), NULL, NULL, 0, 0);
g_object_unref (core->canvas_buffer);
core->canvas_buffer = new_buffer;
pika_get_fill_params (context, fill_type, &color, &pattern, NULL);
pika_pickable_srgb_to_image_color (PIKA_PICKABLE (drawable),
&color, &color);
if (! pika_drawable_has_alpha (drawable))
pika_rgb_set_alpha (&color, 1.0);
undo_buffer = g_hash_table_lookup (core->undo_buffers, drawable);
g_object_ref (undo_buffer);
new_buffer = pika_gegl_buffer_resize (undo_buffer, new_width, new_height,
-(*new_off_x), -(*new_off_y), &color,
pattern, 0, 0);
g_hash_table_insert (core->undo_buffers, drawable, new_buffer);
g_object_unref (undo_buffer);
/* Restore context to its original state */
if (!context_has_image)
context->image = NULL;
return TRUE;
}
return FALSE;
}
void void
pika_paint_core_set_current_coords (PikaPaintCore *core, pika_paint_core_set_current_coords (PikaPaintCore *core,
const PikaCoords *coords) const PikaCoords *coords)

View File

@ -69,12 +69,17 @@ struct _PikaPaintCore
GeglBuffer *paint_buffer; /* the buffer to paint pixels to */ GeglBuffer *paint_buffer; /* the buffer to paint pixels to */
gint paint_buffer_x; gint paint_buffer_x;
gint paint_buffer_y; gint paint_buffer_y;
GHashTable *original_bounds; /* the original bounds of drawables */
GeglBuffer *mask_buffer; /* the target drawable's mask */ GeglBuffer *mask_buffer; /* the target drawable's mask */
GHashTable *applicators; GHashTable *applicators;
GArray *stroke_buffer; GArray *stroke_buffer;
PikaSymmetry *sym;
PikaPaintLockBlinkState
lock_blink_state;
}; };
struct _PikaPaintCoreClass struct _PikaPaintCoreClass
@ -156,6 +161,16 @@ void pika_paint_core_set_show_all (PikaPaintCore *core,
gboolean show_all); gboolean show_all);
gboolean pika_paint_core_get_show_all (PikaPaintCore *core); gboolean pika_paint_core_get_show_all (PikaPaintCore *core);
gboolean pika_paint_core_expand_drawable (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
gint x1,
gint x2,
gint y1,
gint y2,
gint *x,
gint *y);
void pika_paint_core_set_current_coords (PikaPaintCore *core, void pika_paint_core_set_current_coords (PikaPaintCore *core,
const PikaCoords *coords); const PikaCoords *coords);
void pika_paint_core_get_current_coords (PikaPaintCore *core, void pika_paint_core_get_current_coords (PikaPaintCore *core,

View File

@ -83,6 +83,11 @@
#define DEFAULT_SMOOTHING_QUALITY 20 #define DEFAULT_SMOOTHING_QUALITY 20
#define DEFAULT_SMOOTHING_FACTOR 50 #define DEFAULT_SMOOTHING_FACTOR 50
#define DEFAULT_EXPAND_USE FALSE
#define DEFAULT_EXPAND_AMOUNT 100.0
#define DEFAULT_EXPAND_FILL_TYPE PIKA_FILL_TRANSPARENT
#define DEFAULT_EXPAND_MASK_FILL_TYPE PIKA_ADD_MASK_WHITE
enum enum
{ {
PROP_0, PROP_0,
@ -134,7 +139,12 @@ enum
PROP_USE_SMOOTHING, PROP_USE_SMOOTHING,
PROP_SMOOTHING_QUALITY, PROP_SMOOTHING_QUALITY,
PROP_SMOOTHING_FACTOR PROP_SMOOTHING_FACTOR,
PROP_EXPAND_USE,
PROP_EXPAND_AMOUNT,
PROP_EXPAND_FILL_TYPE,
PROP_EXPAND_MASK_FILL_TYPE
}; };
@ -308,6 +318,36 @@ pika_paint_options_class_init (PikaPaintOptionsClass *klass)
DEFAULT_USE_JITTER, DEFAULT_USE_JITTER,
PIKA_PARAM_STATIC_STRINGS); PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_EXPAND_USE,
"expand-use",
_("Expand Layers"),
_("Expand active layer as you paint"),
DEFAULT_EXPAND_USE,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_EXPAND_AMOUNT,
"expand-amount",
_("Amount"),
_("Amount of expansion"),
1.0, 1000.0, DEFAULT_EXPAND_AMOUNT,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_ENUM (object_class, PROP_EXPAND_FILL_TYPE,
"expand-fill-type",
_("Fill With"),
_("Fill layer with"),
PIKA_TYPE_FILL_TYPE,
DEFAULT_EXPAND_FILL_TYPE,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_ENUM (object_class, PROP_EXPAND_MASK_FILL_TYPE,
"expand-mask-fill-type",
_("Fill Mask With"),
_("Fill layer mask with"),
PIKA_TYPE_ADD_MASK_TYPE,
DEFAULT_EXPAND_MASK_FILL_TYPE,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_DYNAMICS_ENABLED, PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_DYNAMICS_ENABLED,
"dynamics-enabled", "dynamics-enabled",
_("Enable dynamics"), _("Enable dynamics"),
@ -642,6 +682,19 @@ pika_paint_options_set_property (GObject *object,
smoothing_options->smoothing_factor = g_value_get_double (value); smoothing_options->smoothing_factor = g_value_get_double (value);
break; break;
case PROP_EXPAND_USE:
options->expand_use = g_value_get_boolean (value);
break;
case PROP_EXPAND_AMOUNT:
options->expand_amount = g_value_get_double (value);
break;
case PROP_EXPAND_FILL_TYPE:
options->expand_fill_type = g_value_get_enum (value);
break;
case PROP_EXPAND_MASK_FILL_TYPE:
options->expand_mask_fill_type = g_value_get_enum (value);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break; break;
@ -788,6 +841,19 @@ pika_paint_options_get_property (GObject *object,
g_value_set_double (value, smoothing_options->smoothing_factor); g_value_set_double (value, smoothing_options->smoothing_factor);
break; break;
case PROP_EXPAND_USE:
g_value_set_boolean (value, options->expand_use);
break;
case PROP_EXPAND_AMOUNT:
g_value_set_double (value, options->expand_amount);
break;
case PROP_EXPAND_FILL_TYPE:
g_value_set_enum (value, options->expand_fill_type);
break;
case PROP_EXPAND_MASK_FILL_TYPE:
g_value_set_enum (value, options->expand_mask_fill_type);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break; break;
@ -1221,9 +1287,18 @@ static const gchar *gradient_props[] =
"gradient-repeat" "gradient-repeat"
}; };
static const gchar *expand_props[] =
{
"expand-use",
"expand-amount",
"expand-fill-type",
"expand-mask-fill-type",
};
static const gint max_n_props = (G_N_ELEMENTS (brush_props) + static const gint max_n_props = (G_N_ELEMENTS (brush_props) +
G_N_ELEMENTS (dynamics_props) + G_N_ELEMENTS (dynamics_props) +
G_N_ELEMENTS (gradient_props)); G_N_ELEMENTS (gradient_props) +
G_N_ELEMENTS (expand_props));
gboolean gboolean
pika_paint_options_is_prop (const gchar *prop_name, pika_paint_options_is_prop (const gchar *prop_name,
@ -1254,6 +1329,13 @@ pika_paint_options_is_prop (const gchar *prop_name,
return TRUE; return TRUE;
} }
if (prop_mask & PIKA_CONTEXT_PROP_MASK_EXPAND)
{
for (i = 0; i < G_N_ELEMENTS (expand_props); i++)
if (! strcmp (prop_name, expand_props[i]))
return TRUE;
}
return FALSE; return FALSE;
} }
@ -1288,6 +1370,12 @@ pika_paint_options_copy_props (PikaPaintOptions *src,
names[n_props++] = gradient_props[i]; names[n_props++] = gradient_props[i];
} }
if (prop_mask & PIKA_CONTEXT_PROP_MASK_EXPAND)
{
for (i = 0; i < G_N_ELEMENTS (expand_props); i++)
names[n_props++] = expand_props[i];
}
if (n_props > 0) if (n_props > 0)
{ {
g_object_getv (G_OBJECT (src), n_props, names, values); g_object_getv (G_OBJECT (src), n_props, names, values);

View File

@ -109,6 +109,11 @@ struct _PikaPaintOptions
gboolean hard; gboolean hard;
gboolean expand_use;
gdouble expand_amount;
PikaFillType expand_fill_type;
PikaAddMaskType expand_mask_fill_type;
PikaJitterOptions *jitter_options; PikaJitterOptions *jitter_options;
gboolean dynamics_enabled; gboolean dynamics_enabled;

View File

@ -406,6 +406,7 @@ pika_smudge_motion (PikaPaintCore *paint_core,
coords.x -= off_x; coords.x -= off_x;
coords.y -= off_y; coords.y -= off_y;
pika_symmetry_set_origin (sym, drawable, &coords); pika_symmetry_set_origin (sym, drawable, &coords);
paint_core->sym = sym;
opacity = pika_dynamics_get_linear_value (dynamics, opacity = pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_OPACITY, PIKA_DYNAMICS_OUTPUT_OPACITY,

View File

@ -347,6 +347,7 @@ pika_source_core_motion (PikaSourceCore *source_core,
coords.x -= off_x; coords.x -= off_x;
coords.y -= off_y; coords.y -= off_y;
pika_symmetry_set_origin (sym, drawable, &coords); pika_symmetry_set_origin (sym, drawable, &coords);
paint_core->sym = sym;
/* Some settings are based on the original stroke. */ /* Some settings are based on the original stroke. */
opacity = pika_dynamics_get_linear_value (dynamics, opacity = pika_dynamics_get_linear_value (dynamics,

View File

@ -5101,7 +5101,7 @@ register_image_procs (PikaPDB *pdb)
"pika-image-get-imported-file"); "pika-image-get-imported-file");
pika_procedure_set_static_help (procedure, pika_procedure_set_static_help (procedure,
"Returns the imported file for the specified image.", "Returns the imported file for the specified image.",
"This procedure returns the file associated with the specified image if the image was imported from a non-native Pika format. If the image was not imported, or has since been saved in the native Gimp format, this procedure returns %NULL.", "This procedure returns the file associated with the specified image if the image was imported from a non-native Pika format. If the image was not imported, or has since been saved in the native Pika format, this procedure returns %NULL.",
NULL); NULL);
pika_procedure_set_static_attribution (procedure, pika_procedure_set_static_attribution (procedure,
"Eric Grivel <pika@lumenssolutions.com>", "Eric Grivel <pika@lumenssolutions.com>",

View File

@ -1297,7 +1297,10 @@ vectors_export_to_file_invoker (PikaProcedure *procedure,
if (success) if (success)
{ {
GList *vectors_list = g_list_prepend (NULL, vectors); GList *vectors_list = NULL;
if (vectors != NULL)
vectors_list = g_list_prepend (vectors_list, vectors);
success = pika_vectors_export_file (image, vectors_list, file, error); success = pika_vectors_export_file (image, vectors_list, file, error);
@ -1327,7 +1330,10 @@ vectors_export_to_string_invoker (PikaProcedure *procedure,
if (success) if (success)
{ {
GList *vectors_list = g_list_prepend (NULL, vectors); GList *vectors_list = NULL;
if (vectors != NULL)
vectors_list = g_list_prepend (vectors_list, vectors);
string = pika_vectors_export_string (image, vectors_list); string = pika_vectors_export_string (image, vectors_list);
g_list_free (vectors_list); g_list_free (vectors_list);
@ -2440,7 +2446,7 @@ register_vectors_procs (PikaPDB *pdb)
"pika-vectors-export-to-file"); "pika-vectors-export-to-file");
pika_procedure_set_static_help (procedure, pika_procedure_set_static_help (procedure,
"save a path as an SVG file.", "save a path as an SVG file.",
"This procedure creates an SVG file to save a Vectors object, that is, a path. The resulting file can be edited using a vector graphics application, or later reloaded into PIKA. If you pass 0 as the 'vectors' argument, then all paths in the image will be exported.", "This procedure creates an SVG file to save a Vectors object, that is, a path. The resulting file can be edited using a vector graphics application, or later reloaded into PIKA. Pass %NULL as the 'vectors' argument to export all paths in the image.",
NULL); NULL);
pika_procedure_set_static_attribution (procedure, pika_procedure_set_static_attribution (procedure,
"Bill Skaggs <weskaggs@primate.ucdavis.edu>", "Bill Skaggs <weskaggs@primate.ucdavis.edu>",
@ -2461,7 +2467,7 @@ register_vectors_procs (PikaPDB *pdb)
pika_procedure_add_argument (procedure, pika_procedure_add_argument (procedure,
pika_param_spec_vectors ("vectors", pika_param_spec_vectors ("vectors",
"vectors", "vectors",
"The vectors object to be saved, or 0 for all in the image", "The vectors object to export, or %NULL for all in the image",
FALSE, FALSE,
PIKA_PARAM_READWRITE | PIKA_PARAM_NO_VALIDATE)); PIKA_PARAM_READWRITE | PIKA_PARAM_NO_VALIDATE));
pika_pdb_register_procedure (pdb, procedure); pika_pdb_register_procedure (pdb, procedure);
@ -2475,7 +2481,7 @@ register_vectors_procs (PikaPDB *pdb)
"pika-vectors-export-to-string"); "pika-vectors-export-to-string");
pika_procedure_set_static_help (procedure, pika_procedure_set_static_help (procedure,
"Save a path as an SVG string.", "Save a path as an SVG string.",
"This procedure works like 'pika-vectors-export-to-file' but creates a string rather than a file. The contents are a NUL-terminated string that holds a complete XML document. If you pass 0 as the 'vectors' argument, then all paths in the image will be exported.", "This procedure works like 'pika-vectors-export-to-file' but creates a string rather than a file. The string is NULL-terminated and holds a complete XML document. Pass %NULL as the 'vectors' argument to export all paths in the image.",
NULL); NULL);
pika_procedure_set_static_attribution (procedure, pika_procedure_set_static_attribution (procedure,
"Bill Skaggs <weskaggs@primate.ucdavis.edu>", "Bill Skaggs <weskaggs@primate.ucdavis.edu>",
@ -2490,7 +2496,7 @@ register_vectors_procs (PikaPDB *pdb)
pika_procedure_add_argument (procedure, pika_procedure_add_argument (procedure,
pika_param_spec_vectors ("vectors", pika_param_spec_vectors ("vectors",
"vectors", "vectors",
"The vectors object to save, or 0 for all in the image", "The vectors object to export, or %NULL for all in the image",
FALSE, FALSE,
PIKA_PARAM_READWRITE | PIKA_PARAM_NO_VALIDATE)); PIKA_PARAM_READWRITE | PIKA_PARAM_NO_VALIDATE));
pika_procedure_add_return_value (procedure, pika_procedure_add_return_value (procedure,

View File

@ -450,7 +450,7 @@ pika_update_about_dialog (PikaCoreConfig *config,
if (config->last_known_release != NULL) if (config->last_known_release != NULL)
{ {
#ifndef PIKA_CONSOLE_COMPILATION #ifndef PIKA_CONSOLE_COMPILATION
gtk_widget_show (about_dialog_create (pika)); gtk_widget_show (about_dialog_create (pika, config));
#else #else
g_printerr (_("A new version of PIKA (%s) was released.\n" g_printerr (_("A new version of PIKA (%s) was released.\n"
"It is recommended to update."), "It is recommended to update."),

View File

@ -170,6 +170,9 @@ pika_tool_options_manager_init (Pika *pika)
g_signal_connect (pika->config, "notify::global-font", g_signal_connect (pika->config, "notify::global-font",
G_CALLBACK (tool_options_manager_global_notify), G_CALLBACK (tool_options_manager_global_notify),
manager); manager);
g_signal_connect (pika->config, "notify::global-expand",
G_CALLBACK (tool_options_manager_global_notify),
manager);
g_signal_connect (user_context, "tool-changed", g_signal_connect (user_context, "tool-changed",
G_CALLBACK (tool_options_manager_tool_changed), G_CALLBACK (tool_options_manager_tool_changed),
@ -254,6 +257,8 @@ tool_options_manager_get_global_props (PikaCoreConfig *config)
global_props |= PIKA_CONTEXT_PROP_MASK_GRADIENT; global_props |= PIKA_CONTEXT_PROP_MASK_GRADIENT;
if (config->global_font) if (config->global_font)
global_props |= PIKA_CONTEXT_PROP_MASK_FONT; global_props |= PIKA_CONTEXT_PROP_MASK_FONT;
if (config->global_expand)
global_props |= PIKA_CONTEXT_PROP_MASK_EXPAND;
return global_props; return global_props;
} }
@ -358,6 +363,12 @@ tool_options_manager_paint_options_notify (PikaPaintOptions *src,
prop_mask |= PIKA_CONTEXT_PROP_MASK_GRADIENT; prop_mask |= PIKA_CONTEXT_PROP_MASK_GRADIENT;
} }
if ((active || config->global_expand) &&
tool_info->context_props & PIKA_CONTEXT_PROP_MASK_EXPAND)
{
prop_mask |= PIKA_CONTEXT_PROP_MASK_EXPAND;
}
if (pika_paint_options_is_prop (pspec->name, prop_mask)) if (pika_paint_options_is_prop (pspec->name, prop_mask))
{ {
GValue value = G_VALUE_INIT; GValue value = G_VALUE_INIT;

View File

@ -66,6 +66,8 @@ pika_airbrush_tool_register (PikaToolRegisterCallback callback,
PIKA_TYPE_AIRBRUSH_OPTIONS, PIKA_TYPE_AIRBRUSH_OPTIONS,
pika_airbrush_options_gui, pika_airbrush_options_gui,
PIKA_PAINT_OPTIONS_CONTEXT_MASK | PIKA_PAINT_OPTIONS_CONTEXT_MASK |
PIKA_CONTEXT_PROP_MASK_EXPAND |
PIKA_CONTEXT_PROP_MASK_PATTERN |
PIKA_CONTEXT_PROP_MASK_GRADIENT, PIKA_CONTEXT_PROP_MASK_GRADIENT,
"pika-airbrush-tool", "pika-airbrush-tool",
_("Airbrush"), _("Airbrush"),

View File

@ -61,6 +61,7 @@ pika_clone_tool_register (PikaToolRegisterCallback callback,
PIKA_TYPE_CLONE_OPTIONS, PIKA_TYPE_CLONE_OPTIONS,
pika_clone_options_gui, pika_clone_options_gui,
PIKA_PAINT_OPTIONS_CONTEXT_MASK | PIKA_PAINT_OPTIONS_CONTEXT_MASK |
PIKA_CONTEXT_PROP_MASK_EXPAND |
PIKA_CONTEXT_PROP_MASK_PATTERN, PIKA_CONTEXT_PROP_MASK_PATTERN,
"pika-clone-tool", "pika-clone-tool",
_("Clone"), _("Clone"),

View File

@ -73,7 +73,9 @@ pika_convolve_tool_register (PikaToolRegisterCallback callback,
(* callback) (PIKA_TYPE_CONVOLVE_TOOL, (* callback) (PIKA_TYPE_CONVOLVE_TOOL,
PIKA_TYPE_CONVOLVE_OPTIONS, PIKA_TYPE_CONVOLVE_OPTIONS,
pika_convolve_options_gui, pika_convolve_options_gui,
PIKA_PAINT_OPTIONS_CONTEXT_MASK, PIKA_PAINT_OPTIONS_CONTEXT_MASK |
PIKA_CONTEXT_PROP_MASK_PATTERN |
PIKA_CONTEXT_PROP_MASK_EXPAND,
"pika-convolve-tool", "pika-convolve-tool",
_("Blur / Sharpen"), _("Blur / Sharpen"),
_("Blur / Sharpen Tool: Selective blurring or unblurring using a brush"), _("Blur / Sharpen Tool: Selective blurring or unblurring using a brush"),

View File

@ -52,7 +52,9 @@ pika_heal_tool_register (PikaToolRegisterCallback callback,
(* callback) (PIKA_TYPE_HEAL_TOOL, (* callback) (PIKA_TYPE_HEAL_TOOL,
PIKA_TYPE_SOURCE_OPTIONS, PIKA_TYPE_SOURCE_OPTIONS,
pika_heal_options_gui, pika_heal_options_gui,
PIKA_PAINT_OPTIONS_CONTEXT_MASK, PIKA_PAINT_OPTIONS_CONTEXT_MASK |
PIKA_CONTEXT_PROP_MASK_PATTERN |
PIKA_CONTEXT_PROP_MASK_EXPAND,
"pika-heal-tool", "pika-heal-tool",
_("Healing"), _("Healing"),
_("Healing Tool: Heal image irregularities"), _("Healing Tool: Heal image irregularities"),

View File

@ -50,6 +50,7 @@ pika_ink_options_gui (PikaToolOptions *tool_options)
GtkWidget *frame; GtkWidget *frame;
GtkWidget *vbox2; GtkWidget *vbox2;
GtkWidget *scale; GtkWidget *scale;
GtkWidget *combo_box;
GtkWidget *blob_box; GtkWidget *blob_box;
GtkWidget *hbox; GtkWidget *hbox;
GtkWidget *editor; GtkWidget *editor;
@ -133,6 +134,28 @@ pika_ink_options_gui (PikaToolOptions *tool_options)
gtk_container_add (GTK_CONTAINER (frame), editor); gtk_container_add (GTK_CONTAINER (frame), editor);
gtk_widget_show (editor); gtk_widget_show (editor);
/* Expand layer options */
vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
scale = pika_prop_spin_scale_new (config, "expand-amount",
1, 10, 2);
pika_spin_scale_set_constrain_drag (PIKA_SPIN_SCALE (scale), TRUE);
pika_spin_scale_set_scale_limits (PIKA_SPIN_SCALE (scale), 1.0, 1000.0);
pika_spin_scale_set_gamma (PIKA_SPIN_SCALE (scale), 1.0);
gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0);
combo_box = pika_prop_enum_combo_box_new (config, "expand-fill-type", 0, 0);
gtk_box_pack_start (GTK_BOX (vbox2), combo_box, FALSE, FALSE, 0);
frame = pika_prop_enum_radio_frame_new (config, "expand-mask-fill-type",
"Fill Layer Mask With", 0, 1);
gtk_box_pack_start (GTK_BOX (vbox2), frame, FALSE, FALSE, 0);
frame = pika_prop_expanding_frame_new (config, "expand-use", NULL,
vbox2, NULL);
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
gtk_widget_show (frame);
pika_config_connect (config, G_OBJECT (editor), "blob-type"); pika_config_connect (config, G_OBJECT (editor), "blob-type");
pika_config_connect (config, G_OBJECT (editor), "blob-aspect"); pika_config_connect (config, G_OBJECT (editor), "blob-aspect");
pika_config_connect (config, G_OBJECT (editor), "blob-angle"); pika_config_connect (config, G_OBJECT (editor), "blob-angle");

View File

@ -68,7 +68,9 @@ pika_ink_tool_register (PikaToolRegisterCallback callback,
PIKA_CONTEXT_PROP_MASK_FOREGROUND | PIKA_CONTEXT_PROP_MASK_FOREGROUND |
PIKA_CONTEXT_PROP_MASK_BACKGROUND | PIKA_CONTEXT_PROP_MASK_BACKGROUND |
PIKA_CONTEXT_PROP_MASK_OPACITY | PIKA_CONTEXT_PROP_MASK_OPACITY |
PIKA_CONTEXT_PROP_MASK_PAINT_MODE, PIKA_CONTEXT_PROP_MASK_PAINT_MODE |
PIKA_CONTEXT_PROP_MASK_PATTERN |
PIKA_CONTEXT_PROP_MASK_EXPAND,
"pika-ink-tool", "pika-ink-tool",
_("Ink"), _("Ink"),
_("Ink Tool: Calligraphy-style painting"), _("Ink Tool: Calligraphy-style painting"),

View File

@ -50,8 +50,11 @@ pika_mybrush_options_gui (PikaToolOptions *tool_options)
{ {
GObject *config = G_OBJECT (tool_options); GObject *config = G_OBJECT (tool_options);
GtkWidget *vbox = pika_paint_options_gui (tool_options); GtkWidget *vbox = pika_paint_options_gui (tool_options);
GtkWidget *vbox2;
GtkWidget *button; GtkWidget *button;
GtkWidget *scale; GtkWidget *scale;
GtkWidget *combo_box;
GtkWidget *frame;
/* the brush */ /* the brush */
button = pika_prop_mybrush_box_new (NULL, PIKA_CONTEXT (tool_options), button = pika_prop_mybrush_box_new (NULL, PIKA_CONTEXT (tool_options),
@ -82,5 +85,28 @@ pika_mybrush_options_gui (PikaToolOptions *tool_options)
0.1, 1.0, 2); 0.1, 1.0, 2);
gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
/* Expand layer options */
vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
scale = pika_prop_spin_scale_new (config, "expand-amount",
1, 10, 2);
pika_spin_scale_set_constrain_drag (PIKA_SPIN_SCALE (scale), TRUE);
pika_spin_scale_set_scale_limits (PIKA_SPIN_SCALE (scale), 1.0, 1000.0);
pika_spin_scale_set_gamma (PIKA_SPIN_SCALE (scale), 1.0);
gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0);
combo_box = pika_prop_enum_combo_box_new (config, "expand-fill-type", 0, 0);
gtk_box_pack_start (GTK_BOX (vbox2), combo_box, FALSE, FALSE, 0);
frame = pika_prop_enum_radio_frame_new (config, "expand-mask-fill-type",
"Fill Layer Mask With", 0, 1);
gtk_box_pack_start (GTK_BOX (vbox2), frame, FALSE, FALSE, 0);
frame = pika_prop_expanding_frame_new (config, "expand-use", NULL,
vbox2, NULL);
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
gtk_widget_show (frame);
return vbox; return vbox;
} }

View File

@ -72,7 +72,9 @@ pika_mybrush_tool_register (PikaToolRegisterCallback callback,
PIKA_CONTEXT_PROP_MASK_BACKGROUND | PIKA_CONTEXT_PROP_MASK_BACKGROUND |
PIKA_CONTEXT_PROP_MASK_OPACITY | PIKA_CONTEXT_PROP_MASK_OPACITY |
PIKA_CONTEXT_PROP_MASK_PAINT_MODE | PIKA_CONTEXT_PROP_MASK_PAINT_MODE |
PIKA_CONTEXT_PROP_MASK_MYBRUSH, PIKA_CONTEXT_PROP_MASK_MYBRUSH |
PIKA_CONTEXT_PROP_MASK_PATTERN |
PIKA_CONTEXT_PROP_MASK_EXPAND,
"pika-mypaint-brush-tool", "pika-mypaint-brush-tool",
_("MyPaint Brush"), _("MyPaint Brush"),
_("MyPaint Brush Tool: Use MyPaint brushes in PIKA"), _("MyPaint Brush Tool: Use MyPaint brushes in PIKA"),

View File

@ -56,7 +56,9 @@ pika_paintbrush_tool_register (PikaToolRegisterCallback callback,
PIKA_TYPE_PAINT_OPTIONS, PIKA_TYPE_PAINT_OPTIONS,
pika_paint_options_gui, pika_paint_options_gui,
PIKA_PAINT_OPTIONS_CONTEXT_MASK | PIKA_PAINT_OPTIONS_CONTEXT_MASK |
PIKA_CONTEXT_PROP_MASK_GRADIENT, PIKA_CONTEXT_PROP_MASK_EXPAND |
PIKA_CONTEXT_PROP_MASK_GRADIENT |
PIKA_CONTEXT_PROP_MASK_PATTERN,
"pika-paintbrush-tool", "pika-paintbrush-tool",
_("Paintbrush"), _("Paintbrush"),
_("Paintbrush Tool: Paint smooth strokes using a brush"), _("Paintbrush Tool: Paint smooth strokes using a brush"),

View File

@ -77,6 +77,8 @@ static GtkWidget * jitter_options_gui (PikaPaintOptions *paint_options,
GType tool_type); GType tool_type);
static GtkWidget * smoothing_options_gui (PikaPaintOptions *paint_options, static GtkWidget * smoothing_options_gui (PikaPaintOptions *paint_options,
GType tool_type); GType tool_type);
static GtkWidget * expand_options_gui (PikaPaintOptions *paint_options,
GType tool_type);
static GtkWidget * pika_paint_options_gui_scale_with_buttons static GtkWidget * pika_paint_options_gui_scale_with_buttons
(GObject *config, (GObject *config,
@ -274,6 +276,22 @@ pika_paint_options_gui (PikaToolOptions *tool_options)
gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
} }
/* the "expand layers" options */
if (tool_type == PIKA_TYPE_PAINTBRUSH_TOOL ||
tool_type == PIKA_TYPE_PENCIL_TOOL ||
tool_type == PIKA_TYPE_AIRBRUSH_TOOL ||
tool_type == PIKA_TYPE_CLONE_TOOL ||
tool_type == PIKA_TYPE_HEAL_TOOL ||
tool_type == PIKA_TYPE_CONVOLVE_TOOL ||
tool_type == PIKA_TYPE_SMUDGE_TOOL)
{
GtkWidget *frame;
frame = expand_options_gui (options, tool_type);
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
gtk_widget_show (frame);
}
return vbox; return vbox;
} }
@ -489,6 +507,39 @@ pika_paint_options_gui_reset_force (GtkWidget *button,
NULL); NULL);
} }
static GtkWidget *
expand_options_gui (PikaPaintOptions *paint_options,
GType tool_type)
{
GObject *config = G_OBJECT (paint_options);
GtkWidget *frame;
GtkWidget *scale;
GtkWidget *combo_box;
GtkWidget *vbox;
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
scale = pika_prop_spin_scale_new (config, "expand-amount",
1, 10, 2);
pika_spin_scale_set_constrain_drag (PIKA_SPIN_SCALE (scale), TRUE);
pika_spin_scale_set_scale_limits (PIKA_SPIN_SCALE (scale), 1.0, 1000.0);
pika_spin_scale_set_gamma (PIKA_SPIN_SCALE (scale), 1.0);
gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
combo_box = pika_prop_enum_combo_box_new (config, "expand-fill-type", 0, 0);
gtk_box_pack_start (GTK_BOX (vbox), combo_box, FALSE, FALSE, 0);
frame = pika_prop_enum_radio_frame_new (config, "expand-mask-fill-type",
"Fill Layer Mask With", 0, 1);
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
frame = pika_prop_expanding_frame_new (config, "expand-use", NULL,
vbox, NULL);
return frame;
}
static GtkWidget * static GtkWidget *
pika_paint_options_gui_scale_with_buttons (GObject *config, pika_paint_options_gui_scale_with_buttons (GObject *config,
gchar *prop_name, gchar *prop_name,

View File

@ -30,6 +30,8 @@
#include "core/pikadrawable.h" #include "core/pikadrawable.h"
#include "core/pikaimage.h" #include "core/pikaimage.h"
#include "core/pikalayer.h"
#include "core/pikalayermask.h"
#include "core/pikaprojection.h" #include "core/pikaprojection.h"
#include "paint/pikapaintcore.h" #include "paint/pikapaintcore.h"
@ -41,6 +43,7 @@
#include "pikapainttool.h" #include "pikapainttool.h"
#include "pikapainttool-paint.h" #include "pikapainttool-paint.h"
#include "pikatools-utils.h"
#define DISPLAY_UPDATE_INTERVAL 10000 /* microseconds */ #define DISPLAY_UPDATE_INTERVAL 10000 /* microseconds */
@ -218,6 +221,27 @@ pika_paint_tool_paint_interpolate (PikaPaintTool *paint_tool,
pika_paint_core_interpolate (core, data->drawables, paint_options, pika_paint_core_interpolate (core, data->drawables, paint_options,
&data->coords, data->time); &data->coords, data->time);
/* Blink the lock box if required */
if (core->lock_blink_state == PIKA_PAINT_LOCK_BLINK_PENDING)
{
GList *iter;
PikaLayer *layer;
/* Blink the lock only once per stroke */
core->lock_blink_state = PIKA_PAINT_LOCK_BLINKED;
for (iter = data->drawables; iter; iter = g_list_next (iter))
{
layer = PIKA_IS_LAYER_MASK (iter->data) ?
PIKA_LAYER_MASK (iter->data)->layer :
PIKA_LAYER (iter->data);
if (pika_item_get_lock_position (PIKA_ITEM (layer)))
pika_tools_blink_lock_box (PIKA_CONTEXT (paint_options)->pika,
PIKA_ITEM (layer));
}
}
g_list_free (data->drawables); g_list_free (data->drawables);
g_slice_free (InterpolateData, data); g_slice_free (InterpolateData, data);
} }

View File

@ -50,6 +50,8 @@ pika_pencil_tool_register (PikaToolRegisterCallback callback,
PIKA_TYPE_PENCIL_OPTIONS, PIKA_TYPE_PENCIL_OPTIONS,
pika_paint_options_gui, pika_paint_options_gui,
PIKA_PAINT_OPTIONS_CONTEXT_MASK | PIKA_PAINT_OPTIONS_CONTEXT_MASK |
PIKA_CONTEXT_PROP_MASK_EXPAND |
PIKA_CONTEXT_PROP_MASK_PATTERN |
PIKA_CONTEXT_PROP_MASK_GRADIENT, PIKA_CONTEXT_PROP_MASK_GRADIENT,
"pika-pencil-tool", "pika-pencil-tool",
_("Pencil"), _("Pencil"),

View File

@ -54,6 +54,8 @@ pika_smudge_tool_register (PikaToolRegisterCallback callback,
PIKA_TYPE_SMUDGE_OPTIONS, PIKA_TYPE_SMUDGE_OPTIONS,
pika_smudge_options_gui, pika_smudge_options_gui,
PIKA_PAINT_OPTIONS_CONTEXT_MASK | PIKA_PAINT_OPTIONS_CONTEXT_MASK |
PIKA_CONTEXT_PROP_MASK_EXPAND |
PIKA_CONTEXT_PROP_MASK_PATTERN |
PIKA_CONTEXT_PROP_MASK_GRADIENT, PIKA_CONTEXT_PROP_MASK_GRADIENT,
"pika-smudge-tool", "pika-smudge-tool",
_("Smudge"), _("Smudge"),

View File

@ -51,19 +51,26 @@ static gchar * pika_vectors_export_path_data (PikaVectors *vectors);
/** /**
* pika_vectors_export_file: * pika_vectors_export_file:
* @image: the #PikaImage from which to export vectors * @image: the #PikaImage from which to export
* @vectors: a #GList of #PikaVectors objects or %NULL to export all vectors in @image * @path_list: a #GList of #PikaVectors objects or %NULL to export all paths in @image
* @file: the file to write * @file: the file to write
* @error: return location for errors * @error: return location for errors
* *
* Exports one or more vectors to a SVG file. * Exports one or more vectors aka path to an SVG file aka XML doc.
*
* When @path_list is %NULL aka empty list, exports all paths in image.
*
* When @path_list is empty and image has no paths,
* this still writes a non-empty file containing an XML doc.
*
* Will overwrite any existing file.
* *
* Returns: %TRUE on success, * Returns: %TRUE on success,
* %FALSE if there was an error writing the file * %FALSE when there was an error writing the file
**/ **/
gboolean gboolean
pika_vectors_export_file (PikaImage *image, pika_vectors_export_file (PikaImage *image,
GList *vectors, GList *path_list,
GFile *file, GFile *file,
GError **error) GError **error)
{ {
@ -81,7 +88,7 @@ pika_vectors_export_file (PikaImage *image,
if (! output) if (! output)
return FALSE; return FALSE;
string = pika_vectors_export (image, vectors); string = pika_vectors_export (image, path_list);
if (! g_output_stream_write_all (output, string->str, string->len, if (! g_output_stream_write_all (output, string->str, string->len,
NULL, NULL, &my_error)) NULL, NULL, &my_error))
@ -111,20 +118,25 @@ pika_vectors_export_file (PikaImage *image,
/** /**
* pika_vectors_export_string: * pika_vectors_export_string:
* @image: the #PikaImage from which to export vectors * @image: the #PikaImage from which to export
* @vectors: a #PikaVectors object or %NULL to export all vectors in @image * @path_list: a #GList of #PikaVectors objects, or %NULL to export all paths in @image
* *
* Exports one or more vectors to a SVG string. * Exports one or more vectors aka path to a SVG string.
* *
* Returns: a %NUL-terminated string that holds a complete XML document * When @path_list is %NULL aka empty list, exports all paths in image.
*
* When @path_list is empty and image has no paths,
* this still returns a string for an empty XML doc.
*
* Returns: a NULL-terminated string that holds a complete XML document
**/ **/
gchar * gchar *
pika_vectors_export_string (PikaImage *image, pika_vectors_export_string (PikaImage *image,
GList *vectors) GList *path_list)
{ {
g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);
return g_string_free (pika_vectors_export (image, vectors), FALSE); return g_string_free (pika_vectors_export (image, path_list), FALSE);
} }
static GString * static GString *

View File

@ -104,6 +104,8 @@ static void pika_action_proxy_button_activate (GtkButton *button,
static void pika_action_update_proxy_sensitive (PikaAction *action, static void pika_action_update_proxy_sensitive (PikaAction *action,
GtkWidget *proxy); GtkWidget *proxy);
static void pika_action_update_proxy_visible (PikaAction *action,
GtkWidget *proxy);
static void pika_action_update_proxy_tooltip (PikaAction *action, static void pika_action_update_proxy_tooltip (PikaAction *action,
GtkWidget *proxy); GtkWidget *proxy);
@ -446,9 +448,20 @@ void
pika_action_set_visible (PikaAction *action, pika_action_set_visible (PikaAction *action,
gboolean visible) gboolean visible)
{ {
g_object_set (action, PikaActionPrivate *priv = GET_PRIVATE (action);
"visible", visible,
NULL); /* Only notify when the state actually changed. This is important for
* handlers such as visibility of menu items in PikaMenuModel which
* will assume that the action visibility changed. Otherwise we might
* remove items by mistake.
*/
if (priv->visible != visible)
{
priv->visible = visible;
pika_action_update_proxy_visible (action, NULL);
g_object_notify (G_OBJECT (action), "visible");
}
} }
gboolean gboolean
@ -929,16 +942,8 @@ pika_action_set_property (GObject *object,
NULL); NULL);
break; break;
case PIKA_ACTION_PROP_VISIBLE: case PIKA_ACTION_PROP_VISIBLE:
if (priv->visible != g_value_get_boolean (value)) pika_action_set_visible (PIKA_ACTION (object),
{ g_value_get_boolean (value));
priv->visible = g_value_get_boolean (value);
/* Only notify when the state actually changed. This is important for
* handlers such as visibility of menu items in PikaMenuModel which
* will assume that the action visibility changed. Otherwise we might
* remove items by mistake.
*/
g_object_notify (object, "visible");
}
break; break;
case PIKA_ACTION_PROP_LABEL: case PIKA_ACTION_PROP_LABEL:
@ -1391,6 +1396,24 @@ pika_action_update_proxy_sensitive (PikaAction *action,
} }
} }
static void
pika_action_update_proxy_visible (PikaAction *action,
GtkWidget *proxy)
{
PikaActionPrivate *priv = GET_PRIVATE (action);
gboolean visible = pika_action_is_visible (action);
if (proxy)
{
gtk_widget_set_visible (proxy, visible);
}
else
{
for (GList *list = priv->proxies; list; list = list->next)
gtk_widget_set_visible (list->data, visible);
}
}
static void static void
pika_action_update_proxy_tooltip (PikaAction *action, pika_action_update_proxy_tooltip (PikaAction *action,
GtkWidget *proxy) GtkWidget *proxy)

View File

@ -102,10 +102,6 @@ static void pika_container_tree_view_set_view_size (PikaContainerVi
static void pika_container_tree_view_real_edit_name (PikaContainerTreeView *tree_view); static void pika_container_tree_view_real_edit_name (PikaContainerTreeView *tree_view);
static void pika_container_tree_view_selection_label_notify (GtkLabel *label,
GParamSpec *pspec,
PikaItemTreeView *view);
static gboolean pika_container_tree_view_edit_focus_out (GtkWidget *widget, static gboolean pika_container_tree_view_edit_focus_out (GtkWidget *widget,
GdkEvent *event, GdkEvent *event,
gpointer user_data); gpointer user_data);
@ -296,12 +292,6 @@ pika_container_tree_view_constructed (GObject *object)
gtk_label_set_selectable (GTK_LABEL (tree_view->priv->multi_selection_label), TRUE); gtk_label_set_selectable (GTK_LABEL (tree_view->priv->multi_selection_label), TRUE);
gtk_tree_view_column_set_widget (tree_view->main_column, gtk_tree_view_column_set_widget (tree_view->main_column,
tree_view->priv->multi_selection_label); tree_view->priv->multi_selection_label);
g_signal_connect (tree_view->priv->multi_selection_label, "notify::label",
G_CALLBACK (pika_container_tree_view_selection_label_notify),
tree_view);
g_signal_connect (tree_view->priv->multi_selection_label, "notify::selection-bound",
G_CALLBACK (pika_container_tree_view_selection_label_notify),
tree_view);
gtk_widget_show (tree_view->priv->multi_selection_label); gtk_widget_show (tree_view->priv->multi_selection_label);
gtk_tree_view_insert_column (tree_view->view, tree_view->main_column, 0); gtk_tree_view_insert_column (tree_view->view, tree_view->main_column, 0);
@ -1204,20 +1194,6 @@ pika_container_tree_view_real_edit_name (PikaContainerTreeView *tree_view)
/* callbacks */ /* callbacks */
static void
pika_container_tree_view_selection_label_notify (GtkLabel *label,
GParamSpec *pspec,
PikaItemTreeView *view)
{
/* This is a weird trick to make the label follow the color scheme of
* selected items in whatever theme is selected. It seems we cannot
* link to the color of another widget whose theme we don't control.
* Faking selection is the only way I found, though it is quite ugly
* semantically.
*/
gtk_label_select_region (label, 0, -1);
}
static gboolean static gboolean
pika_container_tree_view_edit_focus_out (GtkWidget *widget, pika_container_tree_view_edit_focus_out (GtkWidget *widget,
GdkEvent *event, GdkEvent *event,
@ -1354,10 +1330,12 @@ pika_container_tree_view_button (GtkWidget *widget,
GdkEventButton *bevent, GdkEventButton *bevent,
PikaContainerTreeView *tree_view) PikaContainerTreeView *tree_view)
{ {
PikaContainerView *container_view = PIKA_CONTAINER_VIEW (tree_view); PikaContainerView *container_view = PIKA_CONTAINER_VIEW (tree_view);
GtkTreeViewColumn *column; GtkTreeViewColumn *column;
GtkTreePath *path; GtkTreePath *path;
gboolean handled = TRUE; gboolean handled = TRUE;
GtkCellRenderer *toggled_cell = NULL;
PikaCellRendererViewable *clicked_cell = NULL;
tree_view->priv->dnd_renderer = NULL; tree_view->priv->dnd_renderer = NULL;
@ -1366,8 +1344,6 @@ pika_container_tree_view_button (GtkWidget *widget,
&path, &column, NULL, NULL)) &path, &column, NULL, NULL))
{ {
PikaViewRenderer *renderer; PikaViewRenderer *renderer;
GtkCellRenderer *toggled_cell = NULL;
PikaCellRendererViewable *clicked_cell = NULL;
GtkCellRenderer *edit_cell = NULL; GtkCellRenderer *edit_cell = NULL;
GdkRectangle column_area; GdkRectangle column_area;
GtkTreeIter iter; GtkTreeIter iter;
@ -1389,15 +1365,21 @@ pika_container_tree_view_button (GtkWidget *widget,
multisel_mode = FALSE; multisel_mode = FALSE;
} }
/* We need to grab focus after a button click, in order to make keyboard /* We need to grab focus at button click, in order to make keyboard
* navigation possible. For multi-selection though, actual selection will * navigation possible; yet the timing matters:
* happen in pika_container_tree_view_selection_changed() but the widget * 1. For multi-selection, actual selection will happen in
* must already be focused or the handler won't run. * pika_container_tree_view_selection_changed() but the widget
* Whereas for single selection, grab must happen after we changed the * must already be focused or the handler won't run. So we grab first.
* selection (which will happen in this function) otherwise we end up * 2. For toggled and clicked cells, we must also grab first (see code
* first scrolling to the current selection. So we have a separate * below), and absolutely not in the end, because some toggle cells may
* gtk_widget_grab_focus() at the end of the function. * trigger a popup (and the grab on the source widget would close the
* See also commit 3e101922 and MR !1128. * popup).
* 3. Finally for single selection, grab must happen after we changed
* the selection (which will happen in this function) otherwise we
* end up first scrolling to the current selection.
* This is why we have a few separate calls to gtk_widget_grab_focus()
* in this function.
* See also commit 3e101922, MR !1128 and #10281.
*/ */
if (multisel_mode && bevent->type == GDK_BUTTON_PRESS && ! gtk_widget_has_focus (widget)) if (multisel_mode && bevent->type == GDK_BUTTON_PRESS && ! gtk_widget_has_focus (widget))
gtk_widget_grab_focus (widget); gtk_widget_grab_focus (widget);
@ -1524,6 +1506,10 @@ pika_container_tree_view_button (GtkWidget *widget,
column, &column_area, column, &column_area,
bevent->x, bevent->y); bevent->x, bevent->y);
if ((toggled_cell || clicked_cell) &&
bevent->type == GDK_BUTTON_PRESS && ! gtk_widget_has_focus (widget))
gtk_widget_grab_focus (widget);
if (! toggled_cell && ! clicked_cell) if (! toggled_cell && ! clicked_cell)
{ {
edit_cell = edit_cell =
@ -1720,7 +1706,8 @@ pika_container_tree_view_button (GtkWidget *widget,
handled = TRUE; handled = TRUE;
} }
if (handled && bevent->type == GDK_BUTTON_PRESS && ! gtk_widget_has_focus (widget)) if (handled && bevent->type == GDK_BUTTON_PRESS && ! gtk_widget_has_focus (widget) &&
! toggled_cell && ! clicked_cell)
gtk_widget_grab_focus (widget); gtk_widget_grab_focus (widget);
return handled; return handled;

View File

@ -46,6 +46,9 @@ static void pika_image_editor_real_set_image (PikaImageEditor *editor,
static void pika_image_editor_image_flush (PikaImage *image, static void pika_image_editor_image_flush (PikaImage *image,
gboolean invalidate_preview, gboolean invalidate_preview,
PikaImageEditor *editor); PikaImageEditor *editor);
static gboolean
pika_image_editor_image_flush_idle
(gpointer user_data);
G_DEFINE_TYPE_WITH_CODE (PikaImageEditor, pika_image_editor, PIKA_TYPE_EDITOR, G_DEFINE_TYPE_WITH_CODE (PikaImageEditor, pika_image_editor, PIKA_TYPE_EDITOR,
@ -177,7 +180,19 @@ pika_image_editor_image_flush (PikaImage *image,
gboolean invalidate_preview, gboolean invalidate_preview,
PikaImageEditor *editor) PikaImageEditor *editor)
{ {
g_idle_add_full (G_PRIORITY_LOW,
(GSourceFunc) pika_image_editor_image_flush_idle,
g_object_ref (editor), g_object_unref);
}
static gboolean
pika_image_editor_image_flush_idle (gpointer user_data)
{
PikaImageEditor *editor = user_data;
if (pika_editor_get_ui_manager (PIKA_EDITOR (editor))) if (pika_editor_get_ui_manager (PIKA_EDITOR (editor)))
pika_ui_manager_update (pika_editor_get_ui_manager (PIKA_EDITOR (editor)), pika_ui_manager_update (pika_editor_get_ui_manager (PIKA_EDITOR (editor)),
pika_editor_get_popup_data (PIKA_EDITOR (editor))); pika_editor_get_popup_data (PIKA_EDITOR (editor)));
return G_SOURCE_REMOVE;
} }

View File

@ -1224,6 +1224,8 @@ pika_layer_tree_view_floating_selection_changed (PikaImage *image,
g_list_free (all_layers); g_list_free (all_layers);
} }
gtk_widget_set_sensitive (layer_view->priv->link_button, ! floating_sel);
pika_layer_tree_view_update_highlight (layer_view); pika_layer_tree_view_update_highlight (layer_view);
} }

View File

@ -86,13 +86,13 @@
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://github.com/Exiv2/exiv2/releases/download/v0.28.0/exiv2-0.28.0-Source.tar.gz", "url": "https://github.com/Exiv2/exiv2/archive/refs/tags/v0.28.1.tar.gz",
"sha256": "89af3b5ef7277753ef7a7b5374ae017c6b9e304db3b688f1948e73e103491f3d", "sha256": "3078651f995cb6313b1041f07f4dd1bf0e9e4d394d6e2adc6e92ad0b621291fa",
"x-checker-data": { "x-checker-data": {
"type": "anitya", "type": "anitya",
"project-id": 769, "project-id": 769,
"stable-only": true, "stable-only": true,
"url-template": "https://github.com/Exiv2/exiv2/releases/download/v$version/exiv2-$version-Source.tar.gz" "url-template": "https://github.com/Exiv2/exiv2/archive/refs/tags/v$version.tar.gz"
} }
} }
] ]
@ -119,7 +119,7 @@
] ]
}, },
{ {
"name" : "openexr", "name": "openexr",
"config-opts": [ "config-opts": [
"-DBUILD_SHARED_LIBS=ON", "-DBUILD_SHARED_LIBS=ON",
"-DOPENEXR_INSTALL_EXAMPLES=OFF", "-DOPENEXR_INSTALL_EXAMPLES=OFF",
@ -201,7 +201,8 @@
"-DOpenJPEG_DIR=/usr/lib64/openjpeg-2.3", "-DOpenJPEG_DIR=/usr/lib64/openjpeg-2.3",
"-DENABLE_BOOST=OFF", "-DENABLE_BOOST=OFF",
"-DWITH_NSS3:BOOL=OFF", "-DWITH_NSS3:BOOL=OFF",
"-DENABLE_QT5:BOOL=OFF" "-DENABLE_QT5:BOOL=OFF",
"-DENABLE_QT6:BOOL=OFF"
], ],
"cleanup": [ "cleanup": [
"/bin", "/bin",
@ -212,8 +213,8 @@
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://poppler.freedesktop.org/poppler-23.08.0.tar.xz", "url": "https://poppler.freedesktop.org/poppler-23.11.0.tar.xz",
"sha256": "4a4bf7fc903b9f1a2ab7d04b7c5d8220db9bc6261cc73fdb9a826dc272f49aa8", "sha256": "f99cca6799cb9cb6c92fc1e0eb78547b611cb733750ab7cb047cb0e6c246539c",
"x-checker-data": { "x-checker-data": {
"type": "anitya", "type": "anitya",
"project-id": 3686, "project-id": 3686,
@ -314,8 +315,8 @@
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs10012/ghostscript-10.01.2.tar.gz", "url": "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs10021/ghostscript-10.02.1.tar.gz",
"sha512": "11887850d440b8bbd8b83b8b58df8c743cf94be8fdbcfbda1a5fce55ce7c6a963d94b41a4ebcf1ba48f64f8af01e76dec789e2711212b1f7a32f98c42d75e0ff", "sha512": "24eeff047a24085413283ee42539a2feea4d3de81077664861399055a9d30349a0ef0950746bb0b0ee2237b4b9ee215340204ab0e33347b53dc2acee85fcfc19",
"x-checker-data": { "x-checker-data": {
"//": "Bypass broken url-template with anitya checker - See https://github.com/flathub/flatpak-external-data-checker/issues/360", "//": "Bypass broken url-template with anitya checker - See https://github.com/flathub/flatpak-external-data-checker/issues/360",
"type": "json", "type": "json",
@ -365,8 +366,8 @@
{ {
"type": "git", "type": "git",
"url": "https://github.com/json-c/json-c.git", "url": "https://github.com/json-c/json-c.git",
"tag": "json-c-0.16-20220414", "tag": "json-c-0.17-20230812",
"commit": "2f2ddc1f2dbca56c874e8f9c31b5b963202d80e7", "commit": "b4c371fa0cbc4dcbaccc359ce9e957a22988fb34",
"x-checker-data": { "x-checker-data": {
"type": "git", "type": "git",
"tag-pattern": "^json-c-([\\d.]+)-[\\d]+$" "tag-pattern": "^json-c-([\\d.]+)-[\\d]+$"
@ -469,8 +470,8 @@
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://github.com/xianyi/OpenBLAS/archive/v0.3.23.tar.gz", "url": "https://github.com/xianyi/OpenBLAS/archive/v0.3.24.tar.gz",
"sha256": "5d9491d07168a5d00116cdc068a40022c3455bf9293c7cb86a65b1054d7e5114", "sha256": "ceadc5065da97bd92404cac7254da66cc6eb192679cf1002098688978d4d5132",
"x-checker-data": { "x-checker-data": {
"type": "anitya", "type": "anitya",
"project-id": 2540, "project-id": 2540,
@ -616,8 +617,8 @@
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://github.com/strukturag/libheif/releases/download/v1.16.2/libheif-1.16.2.tar.gz", "url": "https://github.com/strukturag/libheif/releases/download/v1.17.3/libheif-1.17.3.tar.gz",
"sha256": "7f97e4205c0bd9f9b8560536c8bd2e841d1c9a6d610401eb3eb87ed9cdfe78ea", "sha256": "8d5b6292e7931324f81f871f250ecbb9f874aa3c66b4f6f35ceb0bf3163b53ea",
"x-checker-data": { "x-checker-data": {
"type": "anitya", "type": "anitya",
"project-id": 64439, "project-id": 64439,

View File

@ -57,7 +57,6 @@ download_lang Galician.isl
download_lang Georgian.isl download_lang Georgian.isl
download_lang Greek.isl download_lang Greek.isl
download_lang Indonesian.isl download_lang Indonesian.isl
download_lang Korean.isl
download_lang Latvian.isl download_lang Latvian.isl
download_lang Lithuanian.isl download_lang Lithuanian.isl
download_lang Malaysian.isl download_lang Malaysian.isl
@ -67,9 +66,9 @@ download_lang Swedish.isl
download_lang Vietnamese.isl download_lang Vietnamese.isl
cd - cd -
# Hungarian is not in a release yet, but was moved from Unofficial # Any language not in a release yet, but moved from Unofficial, should be added here
cd "${ISCCDIR}/Languages/" cd "${ISCCDIR}/Languages/"
download_lang_official Hungarian.isl download_lang_official Korean.isl
cd - cd -
# Copy generated language files into the source directory. # Copy generated language files into the source directory.

View File

@ -157,6 +157,9 @@ cp -fr ${MSYS_PREFIX}/share/xml/iso-codes/iso_639.xml ${PIKA_DISTRIB}/share/xml/
# Adwaita can be used as the base icon set. # Adwaita can be used as the base icon set.
cp -fr ${MSYS_PREFIX}/share/icons/Adwaita ${PIKA_DISTRIB}/share/icons/ cp -fr ${MSYS_PREFIX}/share/icons/Adwaita ${PIKA_DISTRIB}/share/icons/
# Gdbus is needed to avoid warnings in CMD.
cp -fr ${MSYS_PREFIX}/bin/gdbus.exe ${PIKA_DISTRIB}/bin
# XXX Why are these for exactly? # XXX Why are these for exactly?
cp -fr ${MSYS_PREFIX}/bin/gspawn*.exe ${PIKA_DISTRIB}/bin/ cp -fr ${MSYS_PREFIX}/bin/gspawn*.exe ${PIKA_DISTRIB}/bin/

View File

@ -13,6 +13,7 @@ exr
gif gif
heif heif
heic heic
hej2
icns icns
jp2 jp2
j2k j2k

View File

@ -216,7 +216,7 @@ Name: "it"; MessagesFile: "compiler:Languages\Italian.isl,lang\it.setup.isl"
Name: "ja"; MessagesFile: "compiler:Languages\Japanese.isl,lang\ja.setup.isl" Name: "ja"; MessagesFile: "compiler:Languages\Japanese.isl,lang\ja.setup.isl"
Name: "ka"; MessagesFile: "compiler:Languages\Unofficial\Georgian.isl,lang\ka.setup.isl" Name: "ka"; MessagesFile: "compiler:Languages\Unofficial\Georgian.isl,lang\ka.setup.isl"
Name: "kab"; MessagesFile: "compiler:Default.isl,lang\kab.isl,lang\kab.setup.isl" Name: "kab"; MessagesFile: "compiler:Default.isl,lang\kab.isl,lang\kab.setup.isl"
Name: "ko"; MessagesFile: "compiler:Languages\Unofficial\Korean.isl,lang\ko.setup.isl" Name: "ko"; MessagesFile: "compiler:Languages\Korean.isl,lang\ko.setup.isl"
Name: "lt"; MessagesFile: "compiler:Languages\Unofficial\Lithuanian.isl,lang\lt.setup.isl" Name: "lt"; MessagesFile: "compiler:Languages\Unofficial\Lithuanian.isl,lang\lt.setup.isl"
Name: "lv"; MessagesFile: "compiler:Languages\Unofficial\Latvian.isl,lang\lv.setup.isl" Name: "lv"; MessagesFile: "compiler:Languages\Unofficial\Latvian.isl,lang\lv.setup.isl"
Name: "mr"; MessagesFile: "compiler:Languages\Unofficial\Marathi.islu,lang\mr.setup.isl" Name: "mr"; MessagesFile: "compiler:Languages\Unofficial\Marathi.islu,lang\mr.setup.isl"

View File

@ -407,6 +407,32 @@
</description> </description>
</release> </release>
<release version="2.10.36" date="2023-11-05">
<description>
<p>
This new version of PIKA comes with many fixes, including
vulnerability corrections. It is highly recommended to update. It
also provides a few interesting changes, such as:
</p>
<ul>
<li>
Adobe ACB and ASE palette support
</li>
<li>
New gradient: FG to Transparent (Hardedge)
</li>
<li>
Some text tool algorithm enhancement when replacing formatted text
</li>
<li>
Better theming of item locks (when hovering them or setting them active)
</li>
<li>
Improvements in handling a few specific metadata
</li>
</ul>
</description>
</release>
<release version="2.10.34" date="2023-02-21"> <release version="2.10.34" date="2023-02-21">
<description> <description>
<p> <p>

View File

@ -47,6 +47,7 @@
#include <libpika/pikafont_pdb.h> #include <libpika/pikafont_pdb.h>
#include <libpika/pikafonts_pdb.h> #include <libpika/pikafonts_pdb.h>
#include <libpika/pikafontselect_pdb.h> #include <libpika/pikafontselect_pdb.h>
#include <libpika/pikapikarc_pdb.h>
#include <libpika/pikagradient_pdb.h> #include <libpika/pikagradient_pdb.h>
#include <libpika/pikagradients_pdb.h> #include <libpika/pikagradients_pdb.h>
#include <libpika/pikagradientselect_pdb.h> #include <libpika/pikagradientselect_pdb.h>
@ -71,7 +72,6 @@
#include <libpika/pikapattern_pdb.h> #include <libpika/pikapattern_pdb.h>
#include <libpika/pikapatterns_pdb.h> #include <libpika/pikapatterns_pdb.h>
#include <libpika/pikapatternselect_pdb.h> #include <libpika/pikapatternselect_pdb.h>
#include <libpika/pikapikarc_pdb.h>
#include <libpika/pikaprogress_pdb.h> #include <libpika/pikaprogress_pdb.h>
#include <libpika/pikaresource_pdb.h> #include <libpika/pikaresource_pdb.h>
#include <libpika/pikaselection_pdb.h> #include <libpika/pikaselection_pdb.h>

View File

@ -1191,14 +1191,14 @@ pika_vectors_import_from_string (PikaImage *image,
* pika_vectors_export_to_file: * pika_vectors_export_to_file:
* @image: The image. * @image: The image.
* @file: The SVG file to create. * @file: The SVG file to create.
* @vectors: The vectors object to be saved, or 0 for all in the image. * @vectors: The vectors object to export, or %NULL for all in the image.
* *
* save a path as an SVG file. * save a path as an SVG file.
* *
* This procedure creates an SVG file to save a Vectors object, that * This procedure creates an SVG file to save a Vectors object, that
* is, a path. The resulting file can be edited using a vector graphics * is, a path. The resulting file can be edited using a vector graphics
* application, or later reloaded into PIKA. If you pass 0 as the * application, or later reloaded into PIKA. Pass %NULL as the
* 'vectors' argument, then all paths in the image will be exported. * 'vectors' argument to export all paths in the image.
* *
* Returns: TRUE on success. * Returns: TRUE on success.
* *
@ -1234,14 +1234,14 @@ pika_vectors_export_to_file (PikaImage *image,
/** /**
* pika_vectors_export_to_string: * pika_vectors_export_to_string:
* @image: The image. * @image: The image.
* @vectors: The vectors object to save, or 0 for all in the image. * @vectors: The vectors object to export, or %NULL for all in the image.
* *
* Save a path as an SVG string. * Save a path as an SVG string.
* *
* This procedure works like pika_vectors_export_to_file() but creates * This procedure works like pika_vectors_export_to_file() but creates
* a string rather than a file. The contents are a NUL-terminated * a string rather than a file. The string is NULL-terminated and holds
* string that holds a complete XML document. If you pass 0 as the * a complete XML document. Pass %NULL as the 'vectors' argument to
* 'vectors' argument, then all paths in the image will be exported. * export all paths in the image.
* *
* Returns: (transfer full): * Returns: (transfer full):
* A string whose contents are a complete SVG document. * A string whose contents are a complete SVG document.

View File

@ -351,6 +351,7 @@ pika_config_param_spec_duplicate (GParamSpec *pspec)
* with type names instead. * with type names instead.
*/ */
g_strcmp0 (type_name, "PikaImage") == 0 || g_strcmp0 (type_name, "PikaImage") == 0 ||
g_strcmp0 (type_name, "PikaDisplay") == 0 ||
g_strcmp0 (type_name, "PikaDrawable") == 0 || g_strcmp0 (type_name, "PikaDrawable") == 0 ||
g_strcmp0 (type_name, "PikaLayer") == 0 || g_strcmp0 (type_name, "PikaLayer") == 0 ||
g_strcmp0 (type_name, "PikaTextLayer") == 0 || g_strcmp0 (type_name, "PikaTextLayer") == 0 ||

View File

@ -96,7 +96,9 @@ static void pika_dialog_close (GtkDialog *dialog);
static void pika_dialog_response (GtkDialog *dialog, static void pika_dialog_response (GtkDialog *dialog,
gint response_id); gint response_id);
#ifdef G_OS_WIN32
static void pika_dialog_set_title_bar_theme (GtkWidget *dialog); static void pika_dialog_set_title_bar_theme (GtkWidget *dialog);
#endif
G_DEFINE_TYPE_WITH_PRIVATE (PikaDialog, pika_dialog, GTK_TYPE_DIALOG) G_DEFINE_TYPE_WITH_PRIVATE (PikaDialog, pika_dialog, GTK_TYPE_DIALOG)
@ -770,10 +772,10 @@ pika_dialogs_show_help_button (gboolean show)
show_help_button = show ? TRUE : FALSE; show_help_button = show ? TRUE : FALSE;
} }
#ifdef G_OS_WIN32
void void
pika_dialog_set_title_bar_theme (GtkWidget *dialog) pika_dialog_set_title_bar_theme (GtkWidget *dialog)
{ {
#ifdef G_OS_WIN32
HWND hwnd; HWND hwnd;
gboolean use_dark_mode = FALSE; gboolean use_dark_mode = FALSE;
GdkWindow *window = NULL; GdkWindow *window = NULL;
@ -810,5 +812,5 @@ pika_dialog_set_title_bar_theme (GtkWidget *dialog)
gdk_window_hide (window); gdk_window_hide (window);
gdk_window_show (window); gdk_window_show (window);
} }
#endif
} }
#endif

View File

@ -90,10 +90,16 @@
<item><attribute name="action">app.edit-paste-in-place</attribute></item> <item><attribute name="action">app.edit-paste-in-place</attribute></item>
<submenu> <submenu>
<attribute name="label" translatable="yes" context="edit-action">Paste _as</attribute> <attribute name="label" translatable="yes" context="edit-action">Paste _as</attribute>
<item><attribute name="action">app.edit-paste-merged</attribute></item> <section>
<item><attribute name="action">app.edit-paste-merged-in-place</attribute></item> <item><attribute name="action">app.edit-paste-merged</attribute></item>
<item><attribute name="action">app.edit-paste-into</attribute></item> <item><attribute name="action">app.edit-paste-merged-in-place</attribute></item>
<item><attribute name="action">app.edit-paste-into-in-place</attribute></item> </section>
<section>
<item><attribute name="action">app.edit-paste-float</attribute></item>
<item><attribute name="action">app.edit-paste-float-in-place</attribute></item>
<item><attribute name="action">app.edit-paste-into</attribute></item>
<item><attribute name="action">app.edit-paste-into-in-place</attribute></item>
</section>
<item><attribute name="action">app.edit-paste-as-new-image</attribute><attribute name="label-variant">long</attribute></item> <item><attribute name="action">app.edit-paste-as-new-image</attribute><attribute name="label-variant">long</attribute></item>
</submenu> </submenu>
<submenu> <submenu>
@ -143,7 +149,11 @@
<item><attribute name="action">app.select-all</attribute></item> <item><attribute name="action">app.select-all</attribute></item>
<item><attribute name="action">app.select-none</attribute></item> <item><attribute name="action">app.select-none</attribute></item>
<item><attribute name="action">app.select-invert</attribute></item> <item><attribute name="action">app.select-invert</attribute></item>
<item><attribute name="action">app.select-float</attribute></item> <submenu>
<attribute name="label" translatable="yes" context="view-action">_Float</attribute>
<item><attribute name="action">app.select-cut-float</attribute></item>
<item><attribute name="action">app.select-copy-float</attribute></item>
</submenu>
<item><attribute name="action">app.tools-by-color-select-short</attribute></item> <item><attribute name="action">app.tools-by-color-select-short</attribute></item>
<item><attribute name="action">app.vectors-selection-from-vectors</attribute></item> <item><attribute name="action">app.vectors-selection-from-vectors</attribute></item>
<item><attribute name="action">app.dialogs-selection-editor</attribute></item> <item><attribute name="action">app.dialogs-selection-editor</attribute></item>

View File

@ -10,7 +10,10 @@
<item><attribute name="action">app.select-none</attribute></item> <item><attribute name="action">app.select-none</attribute></item>
<item><attribute name="action">app.select-invert</attribute></item> <item><attribute name="action">app.select-invert</attribute></item>
<item><attribute name="action">app.vectors-selection-from-vectors</attribute></item> <item><attribute name="action">app.vectors-selection-from-vectors</attribute></item>
<item><attribute name="action">app.select-float</attribute></item> <section>
<item><attribute name="action">app.select-cut-float</attribute></item>
<item><attribute name="action">app.select-copy-float</attribute></item>
</section>
<section> <section>
<item><attribute name="action">app.select-feather</attribute></item> <item><attribute name="action">app.select-feather</attribute></item>
<item><attribute name="action">app.select-sharpen</attribute></item> <item><attribute name="action">app.select-sharpen</attribute></item>

View File

@ -192,7 +192,7 @@ sub vectors_stroke_get_length {
code => <<"CODE" code => <<"CODE"
{ {
PikaStroke *stroke = pika_pdb_get_vectors_stroke (vectors, stroke_id, 0, error); PikaStroke *stroke = pika_pdb_get_vectors_stroke (vectors, stroke_id, 0, error);
if (stroke) if (stroke)
length = pika_stroke_get_length (stroke, precision); length = pika_stroke_get_length (stroke, precision);
else else
@ -207,7 +207,7 @@ sub vectors_stroke_get_point_at_dist {
$help = <<'HELP'; $help = <<'HELP';
This will return the x,y position of a point at a given distance along the This will return the x,y position of a point at a given distance along the
stroke. The distance will be obtained by first digitizing the stroke. The distance will be obtained by first digitizing the
curve internally and then walking along the curve. For a closed stroke the curve internally and then walking along the curve. For a closed stroke the
start of the path is the first point on the path that was created. This might start of the path is the first point on the path that was created. This might
not be obvious. If the stroke is not long enough, a "valid" flag will be FALSE. not be obvious. If the stroke is not long enough, a "valid" flag will be FALSE.
@ -241,7 +241,7 @@ HELP
code => <<"CODE" code => <<"CODE"
{ {
PikaStroke *stroke = pika_pdb_get_vectors_stroke (vectors, stroke_id, 0, error); PikaStroke *stroke = pika_pdb_get_vectors_stroke (vectors, stroke_id, 0, error);
if (stroke) if (stroke)
{ {
PikaCoords coord; PikaCoords coord;
@ -279,7 +279,7 @@ HELP
{ {
PikaStroke *stroke = pika_pdb_get_vectors_stroke (vectors, stroke_id, PikaStroke *stroke = pika_pdb_get_vectors_stroke (vectors, stroke_id,
PIKA_PDB_ITEM_CONTENT, error); PIKA_PDB_ITEM_CONTENT, error);
if (stroke) if (stroke)
{ {
if (pika_item_is_attached (PIKA_ITEM (vectors))) if (pika_item_is_attached (PIKA_ITEM (vectors)))
@ -317,7 +317,7 @@ HELP
{ {
PikaStroke *stroke = pika_pdb_get_vectors_stroke (vectors, stroke_id, PikaStroke *stroke = pika_pdb_get_vectors_stroke (vectors, stroke_id,
PIKA_PDB_ITEM_CONTENT, error); PIKA_PDB_ITEM_CONTENT, error);
if (stroke) if (stroke)
{ {
if (pika_item_is_attached (PIKA_ITEM (vectors))) if (pika_item_is_attached (PIKA_ITEM (vectors)))
@ -405,7 +405,7 @@ HELP
PIKA_PDB_ITEM_CONTENT | PIKA_PDB_ITEM_CONTENT |
PIKA_PDB_ITEM_POSITION, PIKA_PDB_ITEM_POSITION,
error); error);
if (stroke) if (stroke)
{ {
if (pika_item_is_attached (PIKA_ITEM (vectors))) if (pika_item_is_attached (PIKA_ITEM (vectors)))
@ -451,7 +451,7 @@ HELP
PIKA_PDB_ITEM_CONTENT | PIKA_PDB_ITEM_CONTENT |
PIKA_PDB_ITEM_POSITION, PIKA_PDB_ITEM_POSITION,
error); error);
if (stroke) if (stroke)
{ {
if (pika_item_is_attached (PIKA_ITEM (vectors))) if (pika_item_is_attached (PIKA_ITEM (vectors)))
@ -498,7 +498,7 @@ HELP
PIKA_PDB_ITEM_CONTENT | PIKA_PDB_ITEM_CONTENT |
PIKA_PDB_ITEM_POSITION, PIKA_PDB_ITEM_POSITION,
error); error);
if (stroke) if (stroke)
{ {
if (pika_item_is_attached (PIKA_ITEM (vectors))) if (pika_item_is_attached (PIKA_ITEM (vectors)))
@ -544,7 +544,7 @@ HELP
PIKA_PDB_ITEM_CONTENT | PIKA_PDB_ITEM_CONTENT |
PIKA_PDB_ITEM_POSITION, PIKA_PDB_ITEM_POSITION,
error); error);
if (stroke) if (stroke)
{ {
if (pika_item_is_attached (PIKA_ITEM (vectors))) if (pika_item_is_attached (PIKA_ITEM (vectors)))
@ -595,7 +595,7 @@ HELP
PIKA_PDB_ITEM_CONTENT | PIKA_PDB_ITEM_CONTENT |
PIKA_PDB_ITEM_POSITION, PIKA_PDB_ITEM_POSITION,
error); error);
if (stroke) if (stroke)
{ {
if (pika_item_is_attached (PIKA_ITEM (vectors))) if (pika_item_is_attached (PIKA_ITEM (vectors)))
@ -647,7 +647,7 @@ HELP
code => <<"CODE" code => <<"CODE"
{ {
PikaStroke *stroke = pika_pdb_get_vectors_stroke (vectors, stroke_id, 0, error); PikaStroke *stroke = pika_pdb_get_vectors_stroke (vectors, stroke_id, 0, error);
if (PIKA_IS_BEZIER_STROKE (stroke)) if (PIKA_IS_BEZIER_STROKE (stroke))
{ {
GArray *points_array; GArray *points_array;
@ -713,7 +713,7 @@ HELP
code => <<"CODE" code => <<"CODE"
{ {
PikaStroke *stroke = pika_pdb_get_vectors_stroke (vectors, stroke_id, 0, error); PikaStroke *stroke = pika_pdb_get_vectors_stroke (vectors, stroke_id, 0, error);
if (stroke) if (stroke)
{ {
GArray *coords_array; GArray *coords_array;
@ -1162,7 +1162,7 @@ HELP
{ {
GList *list; GList *list;
gint i; gint i;
vectors = g_new (PikaVectors *, num_vectors); vectors = g_new (PikaVectors *, num_vectors);
for (i = 0, list = vectors_list; for (i = 0, list = vectors_list;
@ -1231,9 +1231,9 @@ HELP
{ {
GList *list; GList *list;
gint i; gint i;
vectors = g_new (PikaVectors *, num_vectors); vectors = g_new (PikaVectors *, num_vectors);
for (i = 0, list = vectors_list; for (i = 0, list = vectors_list;
i < num_vectors; i < num_vectors;
i++, list = g_list_next (list)) i++, list = g_list_next (list))
@ -1255,8 +1255,8 @@ sub vectors_export_to_file {
$help = <<'HELP'; $help = <<'HELP';
This procedure creates an SVG file to save a Vectors object, that is, This procedure creates an SVG file to save a Vectors object, that is,
a path. The resulting file can be edited using a vector graphics a path. The resulting file can be edited using a vector graphics
application, or later reloaded into PIKA. If you pass 0 as the 'vectors' application, or later reloaded into PIKA. Pass %NULL as the 'vectors'
argument, then all paths in the image will be exported. argument to export all paths in the image.
HELP HELP
&bill_pdb_misc('2007', '2.6'); &bill_pdb_misc('2007', '2.6');
@ -1267,14 +1267,17 @@ HELP
{ name => 'file', type => 'file', { name => 'file', type => 'file',
desc => 'The SVG file to create.' }, desc => 'The SVG file to create.' },
{ name => 'vectors', type => 'vectors', no_validate => 1, { name => 'vectors', type => 'vectors', no_validate => 1,
desc => 'The vectors object to be saved, or 0 for all in the image' } desc => 'The vectors object to export, or %NULL for all in the image' }
); );
%invoke = ( %invoke = (
headers => [ qw("vectors/pikavectors-export.h") ], headers => [ qw("vectors/pikavectors-export.h") ],
code => <<'CODE' code => <<'CODE'
{ {
GList *vectors_list = g_list_prepend (NULL, vectors); GList *vectors_list = NULL;
if (vectors != NULL)
vectors_list = g_list_prepend (vectors_list, vectors);
success = pika_vectors_export_file (image, vectors_list, file, error); success = pika_vectors_export_file (image, vectors_list, file, error);
@ -1289,9 +1292,9 @@ sub vectors_export_to_string {
$help = <<'HELP'; $help = <<'HELP';
This procedure works like pika_vectors_export_to_file() but creates a string This procedure works like pika_vectors_export_to_file() but creates a string
rather than a file. The contents are a NUL-terminated string that holds a rather than a file. The string is NULL-terminated and holds a
complete XML document. If you pass 0 as the 'vectors' argument, then all complete XML document. Pass %NULL as the 'vectors' argument to export
paths in the image will be exported. all paths in the image.
HELP HELP
&bill_pdb_misc('2007', '2.6'); &bill_pdb_misc('2007', '2.6');
@ -1300,7 +1303,7 @@ HELP
{ name => 'image', type => 'image', { name => 'image', type => 'image',
desc => 'The image' }, desc => 'The image' },
{ name => 'vectors', type => 'vectors', no_validate => 1, { name => 'vectors', type => 'vectors', no_validate => 1,
desc => 'The vectors object to save, or 0 for all in the image' } desc => 'The vectors object to export, or %NULL for all in the image' }
); );
@outargs = ( @outargs = (
@ -1312,7 +1315,10 @@ HELP
headers => [ qw("vectors/pikavectors-export.h") ], headers => [ qw("vectors/pikavectors-export.h") ],
code => <<'CODE' code => <<'CODE'
{ {
GList *vectors_list = g_list_prepend (NULL, vectors); GList *vectors_list = NULL;
if (vectors != NULL)
vectors_list = g_list_prepend (vectors_list, vectors);
string = pika_vectors_export_string (image, vectors_list); string = pika_vectors_export_string (image, vectors_list);
g_list_free (vectors_list); g_list_free (vectors_list);

View File

@ -352,7 +352,8 @@ static struct
gint delayTime; gint delayTime;
gint inputFlag; gint inputFlag;
gint disposal; gint disposal;
} Gif89 = { -1, -1, -1, 0 }; gint num_loops;
} Gif89 = { -1, -1, -1, 0, -1 };
static void read_error (const gchar *error_type, static void read_error (const gchar *error_type,
PikaImage *image, PikaImage *image,
@ -399,9 +400,10 @@ load_image (GFile *file,
gint grayScale; gint grayScale;
gboolean useGlobalColormap; gboolean useGlobalColormap;
gint bitPixel; gint bitPixel;
gint imageCount = 0; gint imageCount = 0;
PikaImage *image = NULL; PikaImage *image = NULL;
gboolean status; gboolean status;
gboolean saved_parasite = FALSE;
pika_progress_init_printf (_("Opening '%s'"), pika_progress_init_printf (_("Opening '%s'"),
pika_file_get_utf8_name (file)); pika_file_get_utf8_name (file));
@ -590,6 +592,25 @@ load_image (GFile *file,
/* If we are loading a thumbnail, we stop after the first frame. */ /* If we are loading a thumbnail, we stop after the first frame. */
if (thumbnail) if (thumbnail)
break; break;
/* If there is more than one frame, we add a parasite so that
* we know to export as an animation on overwrite */
if (Gif89.num_loops > -1 && ! saved_parasite)
{
PikaParasite *parasite;
gchar *str;
str = g_strdup_printf ("%d", Gif89.num_loops);
parasite = pika_parasite_new ("gif/animated",
PIKA_PARASITE_PERSISTENT,
strlen (str) + 1,
(gpointer) str);
g_free (str);
pika_image_attach_parasite (image, parasite);
pika_parasite_free (parasite);
saved_parasite = TRUE;
}
} }
fclose (fd); fclose (fd);
@ -690,6 +711,15 @@ DoExtension (FILE *fd,
#ifdef GIFDEBUG #ifdef GIFDEBUG
str = "Application Extension"; str = "Application Extension";
#endif #endif
/* Animation block */
if (GetDataBlock (fd, (guchar *) buf))
{
if (strncmp ((const gchar *) buf, "NETSCAPE2.0", 8) == 0)
{
if (GetDataBlock (fd, (guchar *) buf))
Gif89.num_loops = (buf[0] << 8) | buf[1];
}
}
break; break;
case 0xfe: /* Comment Extension */ case 0xfe: /* Comment Extension */
#ifdef GIFDEBUG #ifdef GIFDEBUG

View File

@ -268,6 +268,7 @@ gif_save (PikaProcedure *procedure,
PikaExportReturn export = PIKA_EXPORT_CANCEL; PikaExportReturn export = PIKA_EXPORT_CANCEL;
PikaImage *orig_image; PikaImage *orig_image;
PikaImage *sanitized_image = NULL; PikaImage *sanitized_image = NULL;
PikaParasite *parasite = NULL;
GError *error = NULL; GError *error = NULL;
gegl_init (NULL, NULL); gegl_init (NULL, NULL);
@ -288,6 +289,34 @@ gif_save (PikaProcedure *procedure,
*/ */
sanitized_image = image; sanitized_image = image;
/* If imported as an animation, set the animation configurations
* when overwriting the file */
parasite = pika_image_get_parasite (image, "gif/animated");
if (parasite)
{
gint num_loops;
gchar *parasite_data;
guint32 parasite_size;
parasite_data = (gchar *) pika_parasite_get_data (parasite, &parasite_size);
parasite_data = g_strndup (parasite_data, parasite_size);
if (sscanf (parasite_data, "%i", &num_loops) == 1)
{
gboolean loop = (num_loops == 0);
g_object_set (config,
"as-animation", TRUE,
"loop", loop,
"number-of-repeats", num_loops,
NULL);
}
pika_image_detach_parasite (image, "gif/animated");
g_free (parasite);
g_free (parasite_data);
}
if (run_mode == PIKA_RUN_INTERACTIVE) if (run_mode == PIKA_RUN_INTERACTIVE)
{ {
if (! save_dialog (image, procedure, G_OBJECT (config))) if (! save_dialog (image, procedure, G_OBJECT (config)))

View File

@ -31,6 +31,7 @@
#define LOAD_PROC "file-heif-load" #define LOAD_PROC "file-heif-load"
#define LOAD_PROC_AV1 "file-heif-av1-load" #define LOAD_PROC_AV1 "file-heif-av1-load"
#define LOAD_PROC_HEJ2 "file-heif-hej2-load"
#define SAVE_PROC "file-heif-save" #define SAVE_PROC "file-heif-save"
#define SAVE_PROC_AV1 "file-heif-av1-save" #define SAVE_PROC_AV1 "file-heif-av1-save"
#define PLUG_IN_BINARY "file-heif" #define PLUG_IN_BINARY "file-heif"
@ -103,6 +104,8 @@ static PikaValueArray * heif_av1_save (PikaProcedure *pro
#endif #endif
static PikaImage * load_image (GFile *file, static PikaImage * load_image (GFile *file,
PikaMetadata *metadata,
PikaMetadataLoadFlags *flags,
gboolean interactive, gboolean interactive,
PikaPDBStatusType *status, PikaPDBStatusType *status,
GError **error); GError **error);
@ -172,6 +175,13 @@ heif_init_procedures (PikaPlugIn *plug_in)
} }
#endif #endif
#if LIBHEIF_HAVE_VERSION(1,17,0)
if (heif_have_decoder_for_format (heif_compression_JPEG2000))
{
list = g_list_append (list, g_strdup (LOAD_PROC_HEJ2));
}
#endif
#if LIBHEIF_HAVE_VERSION(1,13,0) #if LIBHEIF_HAVE_VERSION(1,13,0)
heif_deinit (); heif_deinit ();
#endif #endif
@ -412,6 +422,33 @@ heif_create_procedure (PikaPlugIn *plug_in,
pika_export_xmp (), pika_export_xmp (),
G_PARAM_READWRITE); G_PARAM_READWRITE);
} }
#endif
#if LIBHEIF_HAVE_VERSION(1,17,0)
else if (! strcmp (name, LOAD_PROC_HEJ2))
{
procedure = pika_load_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN,
heif_load, NULL, NULL);
pika_procedure_set_menu_label (procedure, _("JPEG 2000 encapsulated in HEIF"));
pika_procedure_set_documentation (procedure,
_("Loads HEJ2 images"),
_("Load JPEG 2000 image encapsulated in HEIF (HEJ2)"),
name);
pika_procedure_set_attribution (procedure,
"Daniel Novomesky <dnovomesky@gmail.com>",
"Daniel Novomesky <dnovomesky@gmail.com>",
"2023");
pika_file_procedure_set_handles_remote (PIKA_FILE_PROCEDURE (procedure), TRUE);
pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
"image/hej2k");
pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
"hej2");
pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure),
"4,string,ftypj2ki");
}
#endif #endif
return procedure; return procedure;
} }
@ -442,7 +479,7 @@ heif_load (PikaProcedure *procedure,
heif_init (NULL); heif_init (NULL);
#endif #endif
image = load_image (file, interactive, &status, &error); image = load_image (file, metadata, flags, interactive, &status, &error);
#if LIBHEIF_HAVE_VERSION(1,13,0) #if LIBHEIF_HAVE_VERSION(1,13,0)
heif_deinit (); heif_deinit ();
@ -836,10 +873,12 @@ nclx_to_pika_profile (const struct heif_color_profile_nclx *nclx)
#endif #endif
PikaImage * PikaImage *
load_image (GFile *file, load_image (GFile *file,
gboolean interactive, PikaMetadata *metadata,
PikaPDBStatusType *status, PikaMetadataLoadFlags *flags,
GError **error) gboolean interactive,
PikaPDBStatusType *status,
GError **error)
{ {
GInputStream *input; GInputStream *input;
goffset file_size; goffset file_size;
@ -1267,117 +1306,111 @@ load_image (GFile *file,
g_object_unref (buffer); g_object_unref (buffer);
{ if (metadata)
size_t exif_data_size = 0; {
uint8_t *exif_data = NULL; size_t exif_data_size = 0;
size_t xmp_data_size = 0; uint8_t *exif_data = NULL;
uint8_t *xmp_data = NULL; size_t xmp_data_size = 0;
gint n_metadata; uint8_t *xmp_data = NULL;
heif_item_id metadata_id; gint n_metadata;
heif_item_id metadata_id;
n_metadata = n_metadata = heif_image_handle_get_list_of_metadata_block_IDs (handle, "Exif",
heif_image_handle_get_list_of_metadata_block_IDs (handle, &metadata_id, 1);
"Exif", if (n_metadata > 0)
&metadata_id, 1); {
if (n_metadata > 0) exif_data_size = heif_image_handle_get_metadata_size (handle, metadata_id);
{
exif_data_size = heif_image_handle_get_metadata_size (handle,
metadata_id);
exif_data = g_alloca (exif_data_size);
err = heif_image_handle_get_metadata (handle, metadata_id, exif_data); exif_data = g_alloca (exif_data_size);
if (err.code != 0)
{
exif_data = NULL;
exif_data_size = 0;
}
}
n_metadata = err = heif_image_handle_get_metadata (handle, metadata_id, exif_data);
heif_image_handle_get_list_of_metadata_block_IDs (handle, if (err.code != 0)
"mime", {
&metadata_id, 1); exif_data = NULL;
if (n_metadata > 0) exif_data_size = 0;
{ }
if (g_strcmp0 ( }
heif_image_handle_get_metadata_content_type (handle, metadata_id),
"application/rdf+xml") == 0)
{
xmp_data_size = heif_image_handle_get_metadata_size (handle,
metadata_id);
xmp_data = g_alloca (xmp_data_size);
err = heif_image_handle_get_metadata (handle, metadata_id, xmp_data); n_metadata = heif_image_handle_get_list_of_metadata_block_IDs (handle, "mime",
if (err.code != 0) &metadata_id, 1);
{ if (n_metadata > 0)
xmp_data = NULL; {
xmp_data_size = 0; if (g_strcmp0 (heif_image_handle_get_metadata_content_type (handle, metadata_id), "application/rdf+xml")
} == 0)
} {
} xmp_data_size = heif_image_handle_get_metadata_size (handle, metadata_id);
if (exif_data || xmp_data) xmp_data = g_alloca (xmp_data_size);
{
PikaMetadata *metadata = pika_metadata_new ();
PikaMetadataLoadFlags flags = PIKA_METADATA_LOAD_COMMENT | PIKA_METADATA_LOAD_RESOLUTION;
if (exif_data) err = heif_image_handle_get_metadata (handle, metadata_id, xmp_data);
{ if (err.code != 0)
const guint8 tiffHeaderBE[4] = { 'M', 'M', 0, 42 }; {
const guint8 tiffHeaderLE[4] = { 'I', 'I', 42, 0 }; xmp_data = NULL;
GExiv2Metadata *exif_metadata = GEXIV2_METADATA (metadata); xmp_data_size = 0;
const guint8 *tiffheader = exif_data; }
glong new_exif_size = exif_data_size; }
}
while (new_exif_size >= 4) /*Searching for TIFF Header*/ if (exif_data || xmp_data)
{ {
if (tiffheader[0] == tiffHeaderBE[0] && tiffheader[1] == tiffHeaderBE[1] && gexiv2_metadata_clear (GEXIV2_METADATA (metadata));
tiffheader[2] == tiffHeaderBE[2] && tiffheader[3] == tiffHeaderBE[3])
{
break;
}
if (tiffheader[0] == tiffHeaderLE[0] && tiffheader[1] == tiffHeaderLE[1] &&
tiffheader[2] == tiffHeaderLE[2] && tiffheader[3] == tiffHeaderLE[3])
{
break;
}
new_exif_size--;
tiffheader++;
}
if (new_exif_size > 4) /* TIFF header + some data found*/ if (exif_data)
{ {
if (! gexiv2_metadata_open_buf (exif_metadata, tiffheader, new_exif_size, error)) const guint8 tiffHeaderBE[4] = { 'M', 'M', 0, 42 };
{ const guint8 tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
g_printerr ("%s: Failed to set EXIF metadata: %s\n", G_STRFUNC, (*error)->message); GExiv2Metadata *exif_metadata = GEXIV2_METADATA (metadata);
g_clear_error (error); const guint8 *tiffheader = exif_data;
} glong new_exif_size = exif_data_size;
}
else
{
g_printerr ("%s: EXIF metadata not set\n", G_STRFUNC);
}
}
if (xmp_data) while (new_exif_size >= 4) /*Searching for TIFF Header*/
{ {
if (!pika_metadata_set_from_xmp (metadata, xmp_data, xmp_data_size, error)) if (tiffheader[0] == tiffHeaderBE[0] && tiffheader[1] == tiffHeaderBE[1] &&
{ tiffheader[2] == tiffHeaderBE[2] && tiffheader[3] == tiffHeaderBE[3])
g_printerr ("%s: Failed to set XMP metadata: %s\n", G_STRFUNC, (*error)->message); {
g_clear_error (error); break;
} }
} if (tiffheader[0] == tiffHeaderLE[0] && tiffheader[1] == tiffHeaderLE[1] &&
tiffheader[2] == tiffHeaderLE[2] && tiffheader[3] == tiffHeaderLE[3])
{
break;
}
new_exif_size--;
tiffheader++;
}
gexiv2_metadata_try_set_orientation (GEXIV2_METADATA (metadata), if (new_exif_size > 4) /* TIFF header + some data found*/
GEXIV2_ORIENTATION_NORMAL, NULL); {
gexiv2_metadata_try_set_metadata_pixel_width (GEXIV2_METADATA (metadata), if (! gexiv2_metadata_open_buf (exif_metadata, tiffheader, new_exif_size, error))
width, NULL); {
gexiv2_metadata_try_set_metadata_pixel_height (GEXIV2_METADATA (metadata), g_printerr ("%s: Failed to set EXIF metadata: %s\n", G_STRFUNC, (*error)->message);
height, NULL); g_clear_error (error);
pika_image_metadata_load_finish (image, "image/heif", }
metadata, flags); }
} else
} {
g_printerr ("%s: EXIF metadata not set\n", G_STRFUNC);
}
}
if (xmp_data)
{
if (! pika_metadata_set_from_xmp (metadata, xmp_data, xmp_data_size, error))
{
g_printerr ("%s: Failed to set XMP metadata: %s\n", G_STRFUNC, (*error)->message);
g_clear_error (error);
}
}
}
gexiv2_metadata_try_set_orientation (GEXIV2_METADATA (metadata),
GEXIV2_ORIENTATION_NORMAL, NULL);
gexiv2_metadata_try_set_metadata_pixel_width (GEXIV2_METADATA (metadata), width, NULL);
gexiv2_metadata_try_set_metadata_pixel_height (GEXIV2_METADATA (metadata),
height, NULL);
*flags = PIKA_METADATA_LOAD_COMMENT | PIKA_METADATA_LOAD_RESOLUTION;
}
if (profile) if (profile)
g_object_unref (profile); g_object_unref (profile);

View File

@ -230,10 +230,10 @@ load_image (GFile *file,
{ {
ILBM_Image *true_image = iff_image[i]; ILBM_Image *true_image = iff_image[i];
/* Struct representing bitmap header properties */ /* Struct representing bitmap header properties */
ILBM_BitMapHeader *bitMapHeader = true_image->bitMapHeader; ILBM_BitMapHeader *bitMapHeader;
/* Struct containing the color palette */ /* Struct containing the color palette */
ILBM_ColorMap *colorMap = true_image->colorMap; ILBM_ColorMap *colorMap;
ILBM_Viewport *camg = true_image->viewport; ILBM_Viewport *camg;
IFF_UByte *bitplanes; IFF_UByte *bitplanes;
PikaImageType image_type; PikaImageType image_type;
guchar pika_cmap[768]; /* Max index is (2^nplanes) - 1 */ guchar pika_cmap[768]; /* Max index is (2^nplanes) - 1 */
@ -244,29 +244,38 @@ load_image (GFile *file,
gint row_length; gint row_length;
gint pixel_size = 1; gint pixel_size = 1;
gint y_height = 0; gint y_height = 0;
gint aspect_x = 0;
gint aspect_y = 0;
gboolean ehb_mode = FALSE; gboolean ehb_mode = FALSE;
gboolean ham_mode = FALSE; gboolean ham_mode = FALSE;
if (! true_image || ! bitMapHeader) if (! true_image)
{ {
g_message (_("Invalid or missing ILBM image")); g_message (_("Invalid or missing ILBM image"));
return image; return image;
} }
if (! true_image->body)
{ colorMap = true_image->colorMap;
g_message (_("ILBM contains no image data - likely a palette file")); camg = true_image->viewport;
return NULL;
}
/* Convert ACBM files to ILBM format */ /* Convert ACBM files to ILBM format */
if (ILBM_imageIsACBM (true_image)) if (ILBM_imageIsACBM (true_image))
ILBM_convertACBMToILBM (true_image); ILBM_convertACBMToILBM (true_image);
bitMapHeader = true_image->bitMapHeader;
if (! bitMapHeader || ! true_image->body)
{
g_message (_("ILBM contains no image data - likely a palette file"));
return NULL;
}
width = bitMapHeader->w; width = bitMapHeader->w;
height = bitMapHeader->h; height = bitMapHeader->h;
nPlanes = bitMapHeader->nPlanes; nPlanes = bitMapHeader->nPlanes;
row_length = (width + 15) / 16; row_length = (width + 15) / 16;
pixel_size = nPlanes / 8; pixel_size = nPlanes / 8;
aspect_x = bitMapHeader->xAspect;
aspect_y = bitMapHeader->yAspect;
/* Check for ILBM variants in CMAG chunk */ /* Check for ILBM variants in CMAG chunk */
if (camg) if (camg)
@ -329,8 +338,9 @@ load_image (GFile *file,
ILBM_unpackByteRun (true_image); ILBM_unpackByteRun (true_image);
image = pika_image_new (width, height, if (! image)
pixel_size == 1 ? PIKA_INDEXED : PIKA_RGB); image = pika_image_new (width, height,
pixel_size == 1 ? PIKA_INDEXED : PIKA_RGB);
layer = pika_layer_new (image, _("Background"), width, height, layer = pika_layer_new (image, _("Background"), width, height,
image_type, 100, image_type, 100,
@ -341,6 +351,24 @@ load_image (GFile *file,
bitplanes = true_image->body->chunkData; bitplanes = true_image->body->chunkData;
/* Setting resolution for non-square pixel aspect ratios */
if (aspect_x != aspect_y && aspect_x > 0 && aspect_y > 0)
{
gdouble image_xres;
gdouble image_yres;
gfloat ratio = (gfloat) aspect_x / aspect_y;
g_message (_("Non-square pixels. Image might look squashed if "
"Dot for Dot mode is enabled."));
pika_image_get_resolution (image, &image_xres, &image_yres);
if (ratio < 1)
image_xres = image_yres * (1 / ratio);
else
image_yres = image_xres * ratio;
pika_image_set_resolution (image, image_xres, image_yres);
}
/* Loading rows */ /* Loading rows */
for (gint j = 0; j < height; j++) for (gint j = 0; j < height; j++)
{ {

View File

@ -396,9 +396,11 @@ create_cmyk_layer (PikaImage *image,
} }
static PikaImage * static PikaImage *
load_image (GFile *file, load_image (GFile *file,
PikaRunMode runmode, PikaRunMode runmode,
GError **error) PikaMetadata *metadata,
PikaMetadataLoadFlags *flags,
GError **error)
{ {
FILE *inputFile = g_fopen (g_file_peek_path (file), "rb"); FILE *inputFile = g_fopen (g_file_peek_path (file), "rb");
@ -430,6 +432,7 @@ load_image (GFile *file,
const Babl *type; const Babl *type;
PikaPrecision precision_linear; PikaPrecision precision_linear;
PikaPrecision precision_non_linear; PikaPrecision precision_non_linear;
uint32_t i;
if (!inputFile) if (!inputFile)
{ {
@ -637,7 +640,7 @@ load_image (GFile *file,
} }
/* Check for extra channels */ /* Check for extra channels */
for (gint32 i = 0; i < basicinfo.num_extra_channels; i++) for (i = 0; i < basicinfo.num_extra_channels; i++)
{ {
JxlExtraChannelInfo extra; JxlExtraChannelInfo extra;
@ -936,205 +939,208 @@ load_image (GFile *file,
g_object_unref (profile); g_object_unref (profile);
} }
if (basicinfo.have_container) if (metadata)
{ {
JxlDecoderReleaseInput (decoder); if (basicinfo.have_container)
JxlDecoderRewind (decoder); {
JxlDecoderReleaseInput (decoder);
JxlDecoderRewind (decoder);
if (JxlDecoderSetInput (decoder, memory, inputFileSize) != JXL_DEC_SUCCESS) if (JxlDecoderSetInput (decoder, memory, inputFileSize) != JXL_DEC_SUCCESS)
{
g_printerr ("%s: JxlDecoderSetInput failed after JxlDecoderRewind\n", G_STRFUNC);
}
else
{
JxlDecoderCloseInput (decoder);
if (JxlDecoderSubscribeEvents (decoder, JXL_DEC_BOX) != JXL_DEC_SUCCESS)
{ {
g_printerr ("%s: JxlDecoderSubscribeEvents for JXL_DEC_BOX failed\n", G_STRFUNC); g_printerr ("%s: JxlDecoderSetInput failed after JxlDecoderRewind\n", G_STRFUNC);
} }
else else
{ {
gboolean search_exif = TRUE; JxlDecoderCloseInput (decoder);
gboolean search_xmp = TRUE; if (JxlDecoderSubscribeEvents (decoder, JXL_DEC_BOX) != JXL_DEC_SUCCESS)
gboolean success_exif = FALSE;
gboolean success_xmp = FALSE;
JxlBoxType box_type = { 0, 0, 0, 0 };
GByteArray *exif_box = NULL;
GByteArray *xml_box = NULL;
size_t exif_remains = 0;
size_t xml_remains = 0;
while (search_exif || search_xmp)
{ {
status = JxlDecoderProcessInput (decoder); g_printerr ("%s: JxlDecoderSubscribeEvents for JXL_DEC_BOX failed\n", G_STRFUNC);
switch (status) }
else
{
gboolean search_exif = TRUE;
gboolean search_xmp = TRUE;
gboolean success_exif = FALSE;
gboolean success_xmp = FALSE;
JxlBoxType box_type = { 0, 0, 0, 0 };
GByteArray *exif_box = NULL;
GByteArray *xml_box = NULL;
size_t exif_remains = 0;
size_t xml_remains = 0;
while (search_exif || search_xmp)
{ {
case JXL_DEC_SUCCESS: status = JxlDecoderProcessInput (decoder);
if (box_type[0] == 'E' && box_type[1] == 'x' && box_type[2] == 'i' && box_type[3] == 'f' && search_exif) switch (status)
{
exif_remains = JxlDecoderReleaseBoxBuffer (decoder);
g_byte_array_set_size (exif_box, exif_box->len - exif_remains);
success_exif = TRUE;
}
else if (box_type[0] == 'x' && box_type[1] == 'm' && box_type[2] == 'l' && box_type[3] == ' ' && search_xmp)
{
xml_remains = JxlDecoderReleaseBoxBuffer (decoder);
g_byte_array_set_size (xml_box, xml_box->len - xml_remains);
success_xmp = TRUE;
}
search_exif = FALSE;
search_xmp = FALSE;
break;
case JXL_DEC_ERROR:
search_exif = FALSE;
search_xmp = FALSE;
g_printerr ("%s: Metadata decoding error\n", G_STRFUNC);
break;
case JXL_DEC_NEED_MORE_INPUT:
search_exif = FALSE;
search_xmp = FALSE;
g_printerr ("%s: JXL metadata are probably incomplete\n", G_STRFUNC);
break;
case JXL_DEC_BOX:
JxlDecoderSetDecompressBoxes (decoder, JXL_TRUE);
if (box_type[0] == 'E' && box_type[1] == 'x' && box_type[2] == 'i' && box_type[3] == 'f' && search_exif && exif_box)
{
exif_remains = JxlDecoderReleaseBoxBuffer (decoder);
g_byte_array_set_size (exif_box, exif_box->len - exif_remains);
search_exif = FALSE;
success_exif = TRUE;
}
else if (box_type[0] == 'x' && box_type[1] == 'm' && box_type[2] == 'l' && box_type[3] == ' ' && search_xmp && xml_box)
{
xml_remains = JxlDecoderReleaseBoxBuffer (decoder);
g_byte_array_set_size (xml_box, xml_box->len - xml_remains);
search_xmp = FALSE;
success_xmp = TRUE;
}
if (JxlDecoderGetBoxType (decoder, box_type, JXL_TRUE) == JXL_DEC_SUCCESS)
{ {
case JXL_DEC_SUCCESS:
if (box_type[0] == 'E' && box_type[1] == 'x' && box_type[2] == 'i' && box_type[3] == 'f' && search_exif) if (box_type[0] == 'E' && box_type[1] == 'x' && box_type[2] == 'i' && box_type[3] == 'f' && search_exif)
{ {
exif_box = g_byte_array_sized_new (4096); exif_remains = JxlDecoderReleaseBoxBuffer (decoder);
g_byte_array_set_size (exif_box, 4096); g_byte_array_set_size (exif_box, exif_box->len - exif_remains);
success_exif = TRUE;
JxlDecoderSetBoxBuffer (decoder, exif_box->data, exif_box->len);
} }
else if (box_type[0] == 'x' && box_type[1] == 'm' && box_type[2] == 'l' && box_type[3] == ' ' && search_xmp) else if (box_type[0] == 'x' && box_type[1] == 'm' && box_type[2] == 'l' && box_type[3] == ' ' && search_xmp)
{ {
xml_box = g_byte_array_sized_new (4096); xml_remains = JxlDecoderReleaseBoxBuffer (decoder);
g_byte_array_set_size (xml_box, 4096); g_byte_array_set_size (xml_box, xml_box->len - xml_remains);
success_xmp = TRUE;
JxlDecoderSetBoxBuffer (decoder, xml_box->data, xml_box->len);
} }
}
else
{
search_exif = FALSE; search_exif = FALSE;
search_xmp = FALSE; search_xmp = FALSE;
g_printerr ("%s: Error in JxlDecoderGetBoxType\n", G_STRFUNC); break;
} case JXL_DEC_ERROR:
break;
case JXL_DEC_BOX_NEED_MORE_OUTPUT:
if (box_type[0] == 'E' && box_type[1] == 'x' && box_type[2] == 'i' && box_type[3] == 'f' && search_exif)
{
exif_remains = JxlDecoderReleaseBoxBuffer (decoder);
g_byte_array_set_size (exif_box, exif_box->len + 4096);
JxlDecoderSetBoxBuffer (decoder, exif_box->data + exif_box->len - (4096 + exif_remains), 4096 + exif_remains);
}
else if (box_type[0] == 'x' && box_type[1] == 'm' && box_type[2] == 'l' && box_type[3] == ' ' && search_xmp)
{
xml_remains = JxlDecoderReleaseBoxBuffer (decoder);
g_byte_array_set_size (xml_box, xml_box->len + 4096);
JxlDecoderSetBoxBuffer (decoder, xml_box->data + xml_box->len - (4096 + xml_remains), 4096 + xml_remains);
}
else
{
search_exif = FALSE; search_exif = FALSE;
search_xmp = FALSE; search_xmp = FALSE;
g_printerr ("%s: Metadata decoding error\n", G_STRFUNC);
break;
case JXL_DEC_NEED_MORE_INPUT:
search_exif = FALSE;
search_xmp = FALSE;
g_printerr ("%s: JXL metadata are probably incomplete\n", G_STRFUNC);
break;
case JXL_DEC_BOX:
JxlDecoderSetDecompressBoxes (decoder, JXL_TRUE);
if (box_type[0] == 'E' && box_type[1] == 'x' && box_type[2] == 'i' && box_type[3] == 'f' && search_exif && exif_box)
{
exif_remains = JxlDecoderReleaseBoxBuffer (decoder);
g_byte_array_set_size (exif_box, exif_box->len - exif_remains);
search_exif = FALSE;
success_exif = TRUE;
}
else if (box_type[0] == 'x' && box_type[1] == 'm' && box_type[2] == 'l' && box_type[3] == ' ' && search_xmp && xml_box)
{
xml_remains = JxlDecoderReleaseBoxBuffer (decoder);
g_byte_array_set_size (xml_box, xml_box->len - xml_remains);
search_xmp = FALSE;
success_xmp = TRUE;
}
if (JxlDecoderGetBoxType (decoder, box_type, JXL_TRUE) == JXL_DEC_SUCCESS)
{
if (box_type[0] == 'E' && box_type[1] == 'x' && box_type[2] == 'i' && box_type[3] == 'f' && search_exif)
{
exif_box = g_byte_array_sized_new (4096);
g_byte_array_set_size (exif_box, 4096);
JxlDecoderSetBoxBuffer (decoder, exif_box->data, exif_box->len);
}
else if (box_type[0] == 'x' && box_type[1] == 'm' && box_type[2] == 'l' && box_type[3] == ' ' && search_xmp)
{
xml_box = g_byte_array_sized_new (4096);
g_byte_array_set_size (xml_box, 4096);
JxlDecoderSetBoxBuffer (decoder, xml_box->data, xml_box->len);
}
}
else
{
search_exif = FALSE;
search_xmp = FALSE;
g_printerr ("%s: Error in JxlDecoderGetBoxType\n", G_STRFUNC);
}
break;
case JXL_DEC_BOX_NEED_MORE_OUTPUT:
if (box_type[0] == 'E' && box_type[1] == 'x' && box_type[2] == 'i' && box_type[3] == 'f' && search_exif)
{
exif_remains = JxlDecoderReleaseBoxBuffer (decoder);
g_byte_array_set_size (exif_box, exif_box->len + 4096);
JxlDecoderSetBoxBuffer (decoder, exif_box->data + exif_box->len - (4096 + exif_remains), 4096 + exif_remains);
}
else if (box_type[0] == 'x' && box_type[1] == 'm' && box_type[2] == 'l' && box_type[3] == ' ' && search_xmp)
{
xml_remains = JxlDecoderReleaseBoxBuffer (decoder);
g_byte_array_set_size (xml_box, xml_box->len + 4096);
JxlDecoderSetBoxBuffer (decoder, xml_box->data + xml_box->len - (4096 + xml_remains), 4096 + xml_remains);
}
else
{
search_exif = FALSE;
search_xmp = FALSE;
}
break;
default:
break;
} }
break;
default:
break;
} }
}
if (success_exif || success_xmp) if (success_exif || success_xmp)
{
PikaMetadata *metadata = pika_metadata_new ();
if (success_exif && exif_box)
{ {
const guint8 tiffHeaderBE[4] = { 'M', 'M', 0, 42 }; gexiv2_metadata_clear (GEXIV2_METADATA (metadata));
const guint8 tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
const guint8 *tiffheader = exif_box->data;
glong new_exif_size = exif_box->len;
while (new_exif_size >= 4) /*Searching for TIFF Header*/ if (success_exif && exif_box)
{ {
if (tiffheader[0] == tiffHeaderBE[0] && tiffheader[1] == tiffHeaderBE[1] && const guint8 tiffHeaderBE[4] = { 'M', 'M', 0, 42 };
tiffheader[2] == tiffHeaderBE[2] && tiffheader[3] == tiffHeaderBE[3]) const guint8 tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
const guint8 *tiffheader = exif_box->data;
glong new_exif_size = exif_box->len;
while (new_exif_size >= 4) /*Searching for TIFF Header*/
{ {
break; if (tiffheader[0] == tiffHeaderBE[0] && tiffheader[1] == tiffHeaderBE[1] &&
tiffheader[2] == tiffHeaderBE[2] && tiffheader[3] == tiffHeaderBE[3])
{
break;
}
if (tiffheader[0] == tiffHeaderLE[0] && tiffheader[1] == tiffHeaderLE[1] &&
tiffheader[2] == tiffHeaderLE[2] && tiffheader[3] == tiffHeaderLE[3])
{
break;
}
new_exif_size--;
tiffheader++;
} }
if (tiffheader[0] == tiffHeaderLE[0] && tiffheader[1] == tiffHeaderLE[1] &&
tiffheader[2] == tiffHeaderLE[2] && tiffheader[3] == tiffHeaderLE[3]) if (new_exif_size > 4) /* TIFF header + some data found*/
{ {
break; if (! gexiv2_metadata_open_buf (GEXIV2_METADATA (metadata), tiffheader, new_exif_size, error))
{
g_printerr ("%s: Failed to set EXIF metadata: %s\n", G_STRFUNC, (*error)->message);
g_clear_error (error);
}
}
else
{
g_printerr ("%s: EXIF metadata not set\n", G_STRFUNC);
} }
new_exif_size--;
tiffheader++;
} }
if (new_exif_size > 4) /* TIFF header + some data found*/ if (success_xmp && xml_box)
{ {
if (! gexiv2_metadata_open_buf (GEXIV2_METADATA (metadata), tiffheader, new_exif_size, error)) if (! pika_metadata_set_from_xmp (metadata, xml_box->data, xml_box->len, error))
{ {
g_printerr ("%s: Failed to set EXIF metadata: %s\n", G_STRFUNC, (*error)->message); g_printerr ("%s: Failed to set XMP metadata: %s\n", G_STRFUNC, (*error)->message);
g_clear_error (error); g_clear_error (error);
} }
} }
else
{
g_printerr ("%s: EXIF metadata not set\n", G_STRFUNC);
}
} }
if (success_xmp && xml_box) if (exif_box)
{ {
if (! pika_metadata_set_from_xmp (metadata, xml_box->data, xml_box->len, error)) g_byte_array_free (exif_box, TRUE);
{
g_printerr ("%s: Failed to set XMP metadata: %s\n", G_STRFUNC, (*error)->message);
g_clear_error (error);
}
} }
gexiv2_metadata_try_set_orientation (GEXIV2_METADATA (metadata), if (xml_box)
GEXIV2_ORIENTATION_NORMAL, NULL); {
gexiv2_metadata_try_set_metadata_pixel_width (GEXIV2_METADATA (metadata), g_byte_array_free (xml_box, TRUE);
basicinfo.xsize, NULL); }
gexiv2_metadata_try_set_metadata_pixel_height (GEXIV2_METADATA (metadata),
basicinfo.ysize, NULL);
pika_image_metadata_load_finish (image, "image/jxl", metadata,
PIKA_METADATA_LOAD_COMMENT | PIKA_METADATA_LOAD_RESOLUTION);
}
if (exif_box)
{
g_byte_array_free (exif_box, TRUE);
}
if (xml_box)
{
g_byte_array_free (xml_box, TRUE);
} }
} }
} }
gexiv2_metadata_try_set_orientation (GEXIV2_METADATA (metadata),
GEXIV2_ORIENTATION_NORMAL, NULL);
gexiv2_metadata_try_set_metadata_pixel_width (GEXIV2_METADATA (metadata),
basicinfo.xsize, NULL);
gexiv2_metadata_try_set_metadata_pixel_height (GEXIV2_METADATA (metadata),
basicinfo.ysize, NULL);
*flags = PIKA_METADATA_LOAD_COMMENT | PIKA_METADATA_LOAD_RESOLUTION;
} }
JxlThreadParallelRunnerDestroy (runner); JxlThreadParallelRunnerDestroy (runner);
@ -1158,7 +1164,7 @@ jpegxl_load (PikaProcedure *procedure,
gegl_init (NULL, NULL); gegl_init (NULL, NULL);
image = load_image (file, run_mode, &error); image = load_image (file, run_mode, metadata, flags, &error);
if (! image) if (! image)
return pika_procedure_new_return_values (procedure, return pika_procedure_new_return_values (procedure,

View File

@ -886,12 +886,13 @@ gui_single (PikaProcedure *procedure,
PikaProcedureConfig *config, PikaProcedureConfig *config,
PikaImage *image) PikaImage *image)
{ {
GtkWidget *window; GtkWidget *window;
GtkWidget *widget; GtkWidget *widget;
GList *missing_fonts; PikaLayer **layers;
GList *dialog_props = NULL; GList *missing_fonts;
gboolean run; GList *dialog_props = NULL;
gint32 n_layers; gboolean run;
gint32 n_layers;
pika_ui_init (PLUG_IN_BINARY); pika_ui_init (PLUG_IN_BINARY);
@ -910,7 +911,13 @@ gui_single (PikaProcedure *procedure,
widget = pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (window), widget = pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (window),
"pages-frame", "layers-as-pages", FALSE, "pages-frame", "layers-as-pages", FALSE,
"pages-box"); "pages-box");
g_free (pika_image_get_layers (multi_page.images[0], &n_layers)); /* Enable "layers-as-pages" if more than one layer, or there's a single
* layer group has more than one layer */
layers = pika_image_get_layers (multi_page.images[0], &n_layers);
if (n_layers == 1 && pika_item_is_group (PIKA_ITEM (layers[0])))
g_free (pika_item_get_children (PIKA_ITEM (layers[0]), &n_layers));
g_free (layers);
gtk_widget_set_sensitive (widget, n_layers > 1); gtk_widget_set_sensitive (widget, n_layers > 1);
/* Warning for missing fonts (non-embeddable with rasterization /* Warning for missing fonts (non-embeddable with rasterization

View File

@ -65,6 +65,7 @@ typedef enum _PngExportformat
PNG_FORMAT_GRAYA16 PNG_FORMAT_GRAYA16
} PngExportFormat; } PngExportFormat;
static GSList *safe_to_copy_chunks;
typedef struct _Png Png; typedef struct _Png Png;
typedef struct _PngClass PngClass; typedef struct _PngClass PngClass;
@ -139,6 +140,9 @@ static gboolean ia_has_transparent_pixels (GeglBuffer *buffer);
static gint find_unused_ia_color (GeglBuffer *buffer, static gint find_unused_ia_color (GeglBuffer *buffer,
gint *colors); gint *colors);
static gint read_unknown_chunk (png_structp png_ptr,
png_unknown_chunkp chunk);
G_DEFINE_TYPE (Png, png, PIKA_TYPE_PLUG_IN) G_DEFINE_TYPE (Png, png, PIKA_TYPE_PLUG_IN)
@ -602,6 +606,7 @@ load_image (GFile *file,
const Babl *file_format; /* BABL format for layer */ const Babl *file_format; /* BABL format for layer */
png_structp pp; /* PNG read pointer */ png_structp pp; /* PNG read pointer */
png_infop info; /* PNG info pointers */ png_infop info; /* PNG info pointers */
png_voidp user_chunkp; /* PNG unknown chunk pointer */
guchar **pixels; /* Pixel rows */ guchar **pixels; /* Pixel rows */
guchar *pixel; /* Pixel data */ guchar *pixel; /* Pixel data */
guchar alpha[256]; /* Index -> Alpha */ guchar alpha[256]; /* Index -> Alpha */
@ -609,6 +614,8 @@ load_image (GFile *file,
gint num_texts; gint num_texts;
struct read_error_data error_data; struct read_error_data error_data;
safe_to_copy_chunks = NULL;
pp = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); pp = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (! pp) if (! pp)
{ {
@ -667,6 +674,11 @@ load_image (GFile *file,
png_init_io (pp, fp); png_init_io (pp, fp);
png_set_compression_buffer_size (pp, 512); png_set_compression_buffer_size (pp, 512);
/* Set up callback to save "safe to copy" chunks */
png_set_keep_unknown_chunks (pp, PNG_HANDLE_CHUNK_IF_SAFE, NULL, 0);
user_chunkp = png_get_user_chunk_ptr (pp);
png_set_read_user_chunk_fn (pp, user_chunkp, read_unknown_chunk);
/* /*
* Get the image info * Get the image info
*/ */
@ -1184,6 +1196,23 @@ load_image (GFile *file,
g_object_unref (buffer); g_object_unref (buffer);
} }
/* If any safe-to-copy chunks were saved,
* store them in the image as parasite */
if (safe_to_copy_chunks)
{
GSList *iter;
for (iter = safe_to_copy_chunks; iter; iter = iter->next)
{
PikaParasite *parasite = iter->data;
pika_image_attach_parasite ((PikaImage *) image, parasite);
pika_parasite_free (parasite);
}
g_slist_free (safe_to_copy_chunks);
}
return (PikaImage *) image; return (PikaImage *) image;
} }
@ -1292,6 +1321,7 @@ save_image (GFile *file,
gint num; /* Number of rows to load */ gint num; /* Number of rows to load */
FILE *fp; /* File pointer */ FILE *fp; /* File pointer */
PikaColorProfile *profile = NULL; /* Color profile */ PikaColorProfile *profile = NULL; /* Color profile */
gchar **parasites; /* Safe-to-copy chunks */
gboolean out_linear; /* Save linear RGB */ gboolean out_linear; /* Save linear RGB */
GeglBuffer *buffer; /* GEGL buffer for layer */ GeglBuffer *buffer; /* GEGL buffer for layer */
const Babl *file_format = NULL; /* BABL format of file */ const Babl *file_format = NULL; /* BABL format of file */
@ -1914,6 +1944,51 @@ save_image (GFile *file,
if (G_BYTE_ORDER == G_LITTLE_ENDIAN) if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
png_set_swap (pp); png_set_swap (pp);
/* Write any safe-to-copy chunks saved from import */
parasites = pika_image_get_parasite_list (image);
if (parasites)
{
gint count;
count = g_strv_length (parasites);
for (gint i = 0; i < count; i++)
{
if (strncmp (parasites[i], "png", 3) == 0)
{
PikaParasite *parasite;
parasite = pika_image_get_parasite (image, parasites[i]);
if (parasite)
{
gchar buf[1024];
gchar *chunk_name;
g_strlcpy (buf, parasites[i], sizeof (buf));
chunk_name = strchr (buf, '/');
chunk_name++;
if (chunk_name)
{
png_byte name[4];
const guint8 *data;
guint32 len;
for (gint j = 0; j < 4; j++)
name[j] = chunk_name[j];
data = (const guint8 *) pika_parasite_get_data (parasite, &len);
png_write_chunk (pp, name, data, len);
}
pika_parasite_free (parasite);
}
}
}
}
g_strfreev (parasites);
/* /*
* Turn on interlace handling... * Turn on interlace handling...
*/ */
@ -2263,6 +2338,28 @@ respin_cmap (png_structp pp,
return get_bit_depth_for_palette (colors); return get_bit_depth_for_palette (colors);
} }
static gint
read_unknown_chunk (png_structp png_ptr,
png_unknown_chunkp chunk)
{
/* Chunks with a lowercase letter in the 4th byte
* are safe to copy */
if (g_ascii_islower (chunk->name[3]))
{
PikaParasite *parasite;
gchar pname[255];
g_snprintf (pname, sizeof (pname), "png/%s", chunk->name);
if ((parasite = pika_parasite_new (pname,
PIKA_PARASITE_PERSISTENT,
chunk->size, chunk->data)))
safe_to_copy_chunks = g_slist_prepend (safe_to_copy_chunks, parasite);
}
return 0;
}
static gboolean static gboolean
save_dialog (PikaImage *image, save_dialog (PikaImage *image,
PikaProcedure *procedure, PikaProcedure *procedure,

View File

@ -944,7 +944,7 @@ process_pam_header (PNMScanner *scan,
{ {
/* skip unknown headers but recognize xv's thumbnail format */ /* skip unknown headers but recognize xv's thumbnail format */
CHECK_FOR_ERROR (g_ascii_isdigit (*buf), pnminfo->jmpbuf, CHECK_FOR_ERROR (g_ascii_isdigit (*buf), pnminfo->jmpbuf,
_("PAM: Unsupported inofficial PNM variant.")); _("PAM: Unsupported unofficial PNM variant."));
} }
} }
CHECK_FOR_ERROR (pnmscanner_eof (scan), pnminfo->jmpbuf, CHECK_FOR_ERROR (pnmscanner_eof (scan), pnminfo->jmpbuf,

View File

@ -77,7 +77,7 @@ struct _LicClass
#define LIC_TYPE (lic_get_type ()) #define LIC_TYPE (lic_get_type ())
#define LIC (obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), LIC_TYPE, Lic)) #define LIC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), LIC_TYPE, Lic))
GType lic_get_type (void) G_GNUC_CONST; GType lic_get_type (void) G_GNUC_CONST;

View File

@ -381,6 +381,7 @@ load_image (GFile *file,
_("Unsupported compression (%u) in BMP file from '%s'"), _("Unsupported compression (%u) in BMP file from '%s'"),
bitmap_head.biCompr, bitmap_head.biCompr,
pika_file_get_utf8_name (file)); pika_file_get_utf8_name (file));
goto out;
} }
#ifdef DEBUG #ifdef DEBUG

View File

@ -42,10 +42,6 @@
#define LOAD_PROC "file-dds-load" #define LOAD_PROC "file-dds-load"
#define SAVE_PROC "file-dds-save" #define SAVE_PROC "file-dds-save"
#define DECODE_YCOCG_PROC "color-decode-ycocg"
#define DECODE_YCOCG_SCALED_PROC "color-decode-ycocg-scaled"
#define DECODE_ALPHA_EXP_PROC "color-decode-alpha-exp"
typedef struct _Dds Dds; typedef struct _Dds Dds;
typedef struct _DdsClass DdsClass; typedef struct _DdsClass DdsClass;
@ -86,15 +82,6 @@ static PikaValueArray * dds_save (PikaProcedure *procedure,
PikaMetadata *metadata, PikaMetadata *metadata,
PikaProcedureConfig *config, PikaProcedureConfig *config,
gpointer run_data); gpointer run_data);
#if 0
static PikaValueArray * dds_decode (PikaProcedure *procedure,
PikaRunMode run_mode,
PikaImage *image,
gint n_drawables,
PikaDrawable **drawables,
PikaProcedureConfig *config,
gpointer run_data);
#endif
G_DEFINE_TYPE (Dds, dds, PIKA_TYPE_PLUG_IN) G_DEFINE_TYPE (Dds, dds, PIKA_TYPE_PLUG_IN)
@ -125,11 +112,6 @@ dds_query_procedures (PikaPlugIn *plug_in)
list = g_list_append (list, g_strdup (LOAD_PROC)); list = g_list_append (list, g_strdup (LOAD_PROC));
list = g_list_append (list, g_strdup (SAVE_PROC)); list = g_list_append (list, g_strdup (SAVE_PROC));
#if 0
list = g_list_append (list, g_strdup (DECODE_YCOCG_PROC));
list = g_list_append (list, g_strdup (DECODE_YCOCG_SCALED_PROC));
list = g_list_append (list, g_strdup (DECODE_ALPHA_EXP_PROC));
#endif
return list; return list;
} }
@ -170,10 +152,10 @@ dds_create_procedure (PikaPlugIn *plug_in,
TRUE, TRUE,
G_PARAM_READWRITE); G_PARAM_READWRITE);
PIKA_PROC_ARG_BOOLEAN (procedure, "decode-images", PIKA_PROC_ARG_BOOLEAN (procedure, "flip-image",
_("Automatically decode YCoCg/AE_xp images when detected"), _("Flip image _vertically"),
_("Decode YCoCg/AExp images when detected"), _("Flip the image vertically on import"),
TRUE, FALSE,
G_PARAM_READWRITE); G_PARAM_READWRITE);
} }
else if (! strcmp (name, SAVE_PROC)) else if (! strcmp (name, SAVE_PROC))
@ -249,13 +231,17 @@ dds_create_procedure (PikaPlugIn *plug_in,
"default", "default",
G_PARAM_READWRITE); G_PARAM_READWRITE);
PIKA_PROC_ARG_INT (procedure, "save-type", PIKA_PROC_ARG_CHOICE (procedure, "save-type",
"Save type", _("Sav_e type"),
"How to save the image (0 = selected layer, " _("How to save the image"),
"1 = cube map, 2 = volume map, 3 = texture array, " pika_choice_new_with_values ("layer", DDS_SAVE_SELECTED_LAYER, _("Selected layer"), NULL,
"4 = all visible layers)", "canvas", DDS_SAVE_VISIBLE_LAYERS, _("All visible layers"), NULL,
0, 4, DDS_SAVE_SELECTED_LAYER, "cube", DDS_SAVE_CUBEMAP, _("As cube map"), NULL,
G_PARAM_READWRITE); "volume", DDS_SAVE_VOLUMEMAP, _("As volume map"), NULL,
"array", DDS_SAVE_ARRAY, _("As texture array"), NULL,
NULL),
"layer",
G_PARAM_READWRITE);
PIKA_PROC_ARG_BOOLEAN (procedure, "flip-image", PIKA_PROC_ARG_BOOLEAN (procedure, "flip-image",
_("Flip image _vertically on export"), _("Flip image _vertically on export"),
@ -276,26 +262,29 @@ dds_create_procedure (PikaPlugIn *plug_in,
0, 255, 0, 0, 255, 0,
G_PARAM_READWRITE); G_PARAM_READWRITE);
PIKA_PROC_ARG_INT (procedure, "mipmaps", PIKA_PROC_ARG_CHOICE (procedure, "mipmaps",
"Mipmaps", _("_Mipmaps"),
"How to handle mipmaps (0 = No mipmaps, " _("How to handle mipmaps"),
"1 = Generate mipmaps, " pika_choice_new_with_values ("none", DDS_MIPMAP_NONE, _("No mipmaps"), NULL,
"2 = Use existing mipmaps (layers)", "generate", DDS_MIPMAP_GENERATE, _("Generate mipmaps"), NULL,
0, 2, DDS_MIPMAP_NONE, "existing", DDS_MIPMAP_EXISTING, _("Use existing mipmaps"), NULL,
G_PARAM_READWRITE); NULL),
"none",
G_PARAM_READWRITE);
PIKA_PROC_ARG_CHOICE (procedure, "mipmap-filter", PIKA_PROC_ARG_CHOICE (procedure, "mipmap-filter",
_("F_ilter"), _("F_ilter"),
_("Filtering to use when generating mipmaps"), _("Filtering to use when generating mipmaps"),
pika_choice_new_with_values ("default", DDS_MIPMAP_FILTER_DEFAULT, _("Default"), NULL, pika_choice_new_with_values ("default", DDS_MIPMAP_FILTER_DEFAULT, _("Default"), NULL,
"nearest", DDS_MIPMAP_FILTER_NEAREST, _("Nearest"), NULL, "nearest", DDS_MIPMAP_FILTER_NEAREST, _("Nearest"), NULL,
"box", DDS_MIPMAP_FILTER_BOX, _("Box"), NULL, "box", DDS_MIPMAP_FILTER_BOX, _("Box"), NULL,
"triangle", DDS_MIPMAP_FILTER_TRIANGLE, _("Triangle"), NULL, "triangle", DDS_MIPMAP_FILTER_TRIANGLE, _("Triangle"), NULL,
"quadratic", DDS_MIPMAP_FILTER_QUADRATIC, _("Quadratic"), NULL, "quadratic", DDS_MIPMAP_FILTER_QUADRATIC, _("Quadratic"), NULL,
"bspline", DDS_MIPMAP_FILTER_BSPLINE, _("B-Spline"), NULL, "bspline", DDS_MIPMAP_FILTER_BSPLINE, _("B-Spline"), NULL,
"mitchell", DDS_MIPMAP_FILTER_MITCHELL, _("Mitchell"), NULL, "mitchell", DDS_MIPMAP_FILTER_MITCHELL, _("Mitchell"), NULL,
"lanczos", DDS_MIPMAP_FILTER_LANCZOS, _("Lanczos"), NULL, "catrom", DDS_MIPMAP_FILTER_CATROM, _("Catmull-Rom"), NULL,
"kaiser", DDS_MIPMAP_FILTER_KAISER, _("Kaiser"), NULL, "lanczos", DDS_MIPMAP_FILTER_LANCZOS, _("Lanczos"), NULL,
"kaiser", DDS_MIPMAP_FILTER_KAISER, _("Kaiser"), NULL,
NULL), NULL),
"default", "default",
G_PARAM_READWRITE); G_PARAM_READWRITE);
@ -343,78 +332,6 @@ dds_create_procedure (PikaPlugIn *plug_in,
0.0, 1.0, 0.5, 0.0, 1.0, 0.5,
G_PARAM_READWRITE); G_PARAM_READWRITE);
} }
#if 0
else if (! strcmp (name, DECODE_YCOCG_PROC))
{
procedure = pika_image_procedure_new (plug_in, name,
PIKA_PDB_PROC_TYPE_PLUGIN,
dds_decode, NULL, NULL);
pika_procedure_set_image_types (procedure, "RGBA");
pika_procedure_set_sensitivity_mask (procedure,
PIKA_PROCEDURE_SENSITIVE_DRAWABLE);
pika_procedure_set_menu_label (procedure, _("Decode YCoCg"));
/* pika_procedure_add_menu_path (procedure, "<Image>/Filters/Colors"); */
pika_procedure_set_documentation (procedure,
_("Converts YCoCg encoded pixels to RGB"),
_("Converts YCoCg encoded pixels to RGB"),
name);
pika_procedure_set_attribution (procedure,
"Shawn Kirst",
"Shawn Kirst",
"2008");
}
else if (! strcmp (name, DECODE_YCOCG_SCALED_PROC))
{
procedure = pika_image_procedure_new (plug_in, name,
PIKA_PDB_PROC_TYPE_PLUGIN,
dds_decode, NULL, NULL);
pika_procedure_set_image_types (procedure, "RGBA");
pika_procedure_set_sensitivity_mask (procedure,
PIKA_PROCEDURE_SENSITIVE_DRAWABLE);
pika_procedure_set_menu_label (procedure, _("Decode YCoCg (scaled)"));
/* pika_procedure_add_menu_path (procedure, "<Image>/Filters/Colors"); */
pika_procedure_set_documentation (procedure,
_("Converts YCoCg (scaled) encoded "
"pixels to RGB"),
_("Converts YCoCg (scaled) encoded "
"pixels to RGB"),
name);
pika_procedure_set_attribution (procedure,
"Shawn Kirst",
"Shawn Kirst",
"2008");
}
else if (! strcmp (name, DECODE_ALPHA_EXP_PROC))
{
procedure = pika_image_procedure_new (plug_in, name,
PIKA_PDB_PROC_TYPE_PLUGIN,
dds_decode, NULL, NULL);
pika_procedure_set_image_types (procedure, "RGBA");
pika_procedure_set_sensitivity_mask (procedure,
PIKA_PROCEDURE_SENSITIVE_DRAWABLE);
pika_procedure_set_menu_label (procedure, _("Decode Alpha exponent"));
/* pika_procedure_add_menu_path (procedure, "<Image>/Filters/Colors"); */
pika_procedure_set_documentation (procedure,
_("Converts alpha exponent encoded "
"pixels to RGB",
_("Converts alpha exponent encoded "
"pixels to RGB"),
name);
pika_procedure_set_attribution (procedure,
"Shawn Kirst",
"Shawn Kirst",
"2008");
}
#endif
return procedure; return procedure;
} }
@ -436,7 +353,7 @@ dds_load (PikaProcedure *procedure,
gegl_init (NULL, NULL); gegl_init (NULL, NULL);
status = read_dds (file, &image, run_mode == PIKA_RUN_INTERACTIVE, status = read_dds (file, &image, run_mode == PIKA_RUN_INTERACTIVE,
procedure, G_OBJECT (config), &error); procedure, config, &error);
if (status != PIKA_PDB_SUCCESS) if (status != PIKA_PDB_SUCCESS)
return pika_procedure_new_return_values (procedure, status, error); return pika_procedure_new_return_values (procedure, status, error);
@ -520,53 +437,3 @@ dds_save (PikaProcedure *procedure,
return pika_procedure_new_return_values (procedure, status, error); return pika_procedure_new_return_values (procedure, status, error);
} }
#if 0
static PikaValueArray *
dds_decode (PikaProcedure *procedure,
PikaRunMode run_mode,
PikaImage *image,
gint n_drawables,
PikaDrawable **drawables,
PikaProcedureConfig *config,
gpointer run_data)
{
const gchar *name = pika_procedure_get_name (procedure);
PikaDrawable *drawable,
if (n_drawables != 1)
{
GError *error = NULL;
g_set_error (&error, PIKA_PLUG_IN_ERROR, 0,
_("Procedure '%s' only works with one drawable."),
name);
return pika_procedure_new_return_values (procedure,
PIKA_PDB_EXECUTION_ERROR,
error);
}
else
{
drawable = drawables[0];
}
if (! strcmp (name, DECODE_YCOCG_PROC))
{
decode_ycocg_image (drawable, TRUE);
}
else if (! strcmp (name, DECODE_YCOCG_SCALED_PROC))
{
decode_ycocg_scaled_image (drawable, TRUE);
}
else if (! strcmp (name, DECODE_ALPHA_EXP_PROC))
{
decode_alpha_exp_image (drawable, TRUE);
}
if (run_mode != PIKA_RUN_NONINTERACTIVE)
pika_displays_flush ();
return pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL);
}
#endif

View File

@ -21,6 +21,7 @@
#ifndef __DDS_H__ #ifndef __DDS_H__
#define __DDS_H__ #define __DDS_H__
#define DDS_PLUGIN_VERSION_MAJOR 3 #define DDS_PLUGIN_VERSION_MAJOR 3
#define DDS_PLUGIN_VERSION_MINOR 9 #define DDS_PLUGIN_VERSION_MINOR 9
#define DDS_PLUGIN_VERSION_REVISION 92 #define DDS_PLUGIN_VERSION_REVISION 92
@ -101,6 +102,7 @@ typedef enum
DDS_MIPMAP_FILTER_QUADRATIC, DDS_MIPMAP_FILTER_QUADRATIC,
DDS_MIPMAP_FILTER_BSPLINE, DDS_MIPMAP_FILTER_BSPLINE,
DDS_MIPMAP_FILTER_MITCHELL, DDS_MIPMAP_FILTER_MITCHELL,
DDS_MIPMAP_FILTER_CATROM,
DDS_MIPMAP_FILTER_LANCZOS, DDS_MIPMAP_FILTER_LANCZOS,
DDS_MIPMAP_FILTER_KAISER, DDS_MIPMAP_FILTER_KAISER,
DDS_MIPMAP_FILTER_MAX DDS_MIPMAP_FILTER_MAX
@ -115,6 +117,7 @@ typedef enum
DDS_MIPMAP_WRAP_MAX DDS_MIPMAP_WRAP_MAX
} DDS_MIPMAP_WRAP; } DDS_MIPMAP_WRAP;
#define DDS_HEADERSIZE 128 #define DDS_HEADERSIZE 128
#define DDS_HEADERSIZE_DX10 20 #define DDS_HEADERSIZE_DX10 20
@ -122,18 +125,43 @@ typedef enum
#define DDSD_HEIGHT 0x00000002 #define DDSD_HEIGHT 0x00000002
#define DDSD_WIDTH 0x00000004 #define DDSD_WIDTH 0x00000004
#define DDSD_PITCH 0x00000008 #define DDSD_PITCH 0x00000008
#define DDSD_BACKBUFFERCOUNT 0x00000020
#define DDSD_ZBUFFERBITDEPTH 0x00000040
#define DDSD_ALPHABITDEPTH 0x00000080
#define DDSD_LPSURFACE 0x00000800
#define DDSD_PIXELFORMAT 0x00001000 #define DDSD_PIXELFORMAT 0x00001000
#define DDSD_CKDESTOVERLAY 0x00002000
#define DDSD_CKDESTBLT 0x00004000
#define DDSD_CKSRCOVERLAY 0x00008000
#define DDSD_CKSRCBLT 0x00010000
#define DDSD_MIPMAPCOUNT 0x00020000 #define DDSD_MIPMAPCOUNT 0x00020000
#define DDSD_REFRESHRATE 0x00040000
#define DDSD_LINEARSIZE 0x00080000 #define DDSD_LINEARSIZE 0x00080000
#define DDSD_TEXTURESTAGE 0x00100000
#define DDSD_FVF 0x00200000
#define DDSD_SRCVBHANDLE 0x00400000
#define DDSD_DEPTH 0x00800000 #define DDSD_DEPTH 0x00800000
#define DDPF_ALPHAPIXELS 0x00000001 #define DDPF_ALPHAPIXELS 0x00000001
#define DDPF_ALPHA 0x00000002 #define DDPF_ALPHA 0x00000002
#define DDPF_FOURCC 0x00000004 #define DDPF_FOURCC 0x00000004
#define DDPF_PALETTEINDEXED4 0x00000008
#define DDPF_PALETTEINDEXEDTO8 0x00000010
#define DDPF_PALETTEINDEXED8 0x00000020 #define DDPF_PALETTEINDEXED8 0x00000020
#define DDPF_RGB 0x00000040 #define DDPF_RGB 0x00000040
#define DDPF_COMPRESSED 0x00000080
#define DDPF_RGBTOYUV 0x00000100
#define DDPF_YUV 0x00000200
#define DDPF_ZBUFFER 0x00000400
#define DDPF_PALETTEINDEXED1 0x00000800
#define DDPF_PALETTEINDEXED2 0x00001000
#define DDPF_ZPIXELS 0x00002000
#define DDPF_STENCILBUFFER 0x00004000
#define DDPF_ALPHAPREMULT 0x00008000
#define DDPF_LUMINANCE 0x00020000 #define DDPF_LUMINANCE 0x00020000
#define DDPF_NORMAL 0x80000000 // nvidia specific #define DDPF_BUMPLUMINANCE 0x00040000
#define DDPF_BUMPDUDV 0x00080000
#define DDPF_NORMAL 0x80000000 /* NVidia specific */
#define DDSCAPS_COMPLEX 0x00000008 #define DDSCAPS_COMPLEX 0x00000008
#define DDSCAPS_TEXTURE 0x00001000 #define DDSCAPS_TEXTURE 0x00001000
@ -159,6 +187,7 @@ typedef enum
#define D3D10_RESOURCE_DIMENSION_TEXTURE2D 3 #define D3D10_RESOURCE_DIMENSION_TEXTURE2D 3
#define D3D10_RESOURCE_DIMENSION_TEXTURE3D 4 #define D3D10_RESOURCE_DIMENSION_TEXTURE3D 4
typedef struct typedef struct
{ {
unsigned int size; unsigned int size;
@ -192,8 +221,8 @@ typedef struct
{ {
struct struct
{ {
unsigned int magic1; // FOURCC "PIKA" unsigned int magic1; /* FOURCC "PIKA" */
unsigned int magic2; // FOURCC "-DDS" unsigned int magic2; /* FOURCC "-DDS" */
unsigned int version; unsigned int version;
unsigned int extra_fourcc; unsigned int extra_fourcc;
} pika_dds_special; } pika_dds_special;
@ -204,6 +233,7 @@ typedef struct
unsigned int reserved2; unsigned int reserved2;
} dds_header_t; } dds_header_t;
typedef enum typedef enum
{ {
DXGI_FORMAT_UNKNOWN = 0, DXGI_FORMAT_UNKNOWN = 0,
@ -334,4 +364,70 @@ typedef struct
unsigned int reserved; unsigned int reserved;
} dds_header_dx10_t; } dds_header_dx10_t;
/* Format values that can be found in the FOURCC field */
typedef enum _D3DFORMAT {
D3DFMT_R8G8B8 = 20,
D3DFMT_A8R8G8B8 = 21,
D3DFMT_X8R8G8B8 = 22,
D3DFMT_R5G6B5 = 23,
D3DFMT_X1R5G5B5 = 24,
D3DFMT_A1R5G5B5 = 25,
D3DFMT_A4R4G4B4 = 26,
D3DFMT_R3G3B2 = 27,
D3DFMT_A8 = 28,
D3DFMT_A8R3G3B2 = 29,
D3DFMT_X4R4G4B4 = 30,
D3DFMT_A2B10G10R10 = 31,
D3DFMT_A8B8G8R8 = 32,
D3DFMT_X8B8G8R8 = 33,
D3DFMT_G16R16 = 34,
D3DFMT_A2R10G10B10 = 35,
D3DFMT_A16B16G16R16 = 36,
D3DFMT_A8P8 = 40,
D3DFMT_P8 = 41,
D3DFMT_L8 = 50,
D3DFMT_A8L8 = 51,
D3DFMT_A4L4 = 52,
D3DFMT_V8U8 = 60,
D3DFMT_L6V5U5 = 61,
D3DFMT_X8L8V8U8 = 62,
D3DFMT_Q8W8V8U8 = 63,
D3DFMT_V16U16 = 64,
D3DFMT_A2W10V10U10 = 67,
D3DFMT_D16_LOCKABLE = 70,
D3DFMT_D32 = 71,
D3DFMT_D15S1 = 73,
D3DFMT_D24S8 = 75,
D3DFMT_D24X8 = 77,
D3DFMT_D24X4S4 = 79,
D3DFMT_D16 = 80,
D3DFMT_L16 = 81,
D3DFMT_D32F_LOCKABLE = 82,
D3DFMT_D24FS8 = 83,
D3DFMT_D32_LOCKABLE = 84,
D3DFMT_S8_LOCKABLE = 85,
D3DFMT_VERTEXDATA = 100,
D3DFMT_INDEX16 = 101,
D3DFMT_INDEX32 = 102,
D3DFMT_Q16W16V16U16 = 110,
D3DFMT_R16F = 111,
D3DFMT_G16R16F = 112,
D3DFMT_A16B16G16R16F = 113,
D3DFMT_R32F = 114,
D3DFMT_G32R32F = 115,
D3DFMT_A32B32G32R32F = 116,
D3DFMT_CxV8U8 = 117,
D3DFMT_A1 = 118,
D3DFMT_A2B10G10R10_XR_BIAS = 119,
D3DFMT_BINARYBUFFER = 199,
} D3DFORMAT;
#endif /* __DDS_H__ */ #endif /* __DDS_H__ */

File diff suppressed because it is too large Load Diff

View File

@ -22,12 +22,12 @@
#define __DDSREAD_H__ #define __DDSREAD_H__
extern PikaPDBStatusType read_dds (GFile *file, extern PikaPDBStatusType read_dds (GFile *file,
PikaImage **image, PikaImage **image,
gboolean interactive, gboolean interactive,
PikaProcedure *procedure, PikaProcedure *procedure,
GObject *config, PikaProcedureConfig *config,
GError **error); GError **error);
#endif /* __DDSREAD_H__ */ #endif /* __DDSREAD_H__ */

File diff suppressed because it is too large Load Diff

View File

@ -34,11 +34,14 @@
#include <math.h> #include <math.h>
#include <glib.h> #include <glib.h>
#include <libpika/pika.h>
#include "dds.h" #include "dds.h"
#include "dxt.h" #include "dxt.h"
#include "endian_rw.h" #include "endian_rw.h"
#include "mipmap.h"
#include "imath.h" #include "imath.h"
#include "mipmap.h"
#include "misc.h"
#include "vec.h" #include "vec.h"
#include "dxt_tables.h" #include "dxt_tables.h"
@ -757,181 +760,6 @@ encode_color_block (unsigned char *dst,
PUTL32(dst + 4, indices); PUTL32(dst + 4, indices);
} }
static void
get_min_max_YCoCg (const unsigned char *block,
unsigned char *mincolor,
unsigned char *maxcolor)
{
int i;
mincolor[2] = mincolor[1] = 255;
maxcolor[2] = maxcolor[1] = 0;
for (i = 0; i < 16; ++i)
{
if (block[4 * i + 2] < mincolor[2]) mincolor[2] = block[4 * i + 2];
if (block[4 * i + 1] < mincolor[1]) mincolor[1] = block[4 * i + 1];
if (block[4 * i + 2] > maxcolor[2]) maxcolor[2] = block[4 * i + 2];
if (block[4 * i + 1] > maxcolor[1]) maxcolor[1] = block[4 * i + 1];
}
}
static void
scale_YCoCg (unsigned char *block,
unsigned char *mincolor,
unsigned char *maxcolor)
{
const int s0 = 128 / 2 - 1;
const int s1 = 128 / 4 - 1;
int m0, m1, m2, m3;
int mask0, mask1, scale;
int i;
m0 = abs(mincolor[2] - 128);
m1 = abs(mincolor[1] - 128);
m2 = abs(maxcolor[2] - 128);
m3 = abs(maxcolor[1] - 128);
if (m1 > m0) m0 = m1;
if (m3 > m2) m2 = m3;
if (m2 > m0) m0 = m2;
mask0 = -(m0 <= s0);
mask1 = -(m0 <= s1);
scale = 1 + (1 & mask0) + (2 & mask1);
mincolor[2] = (mincolor[2] - 128) * scale + 128;
mincolor[1] = (mincolor[1] - 128) * scale + 128;
mincolor[0] = (scale - 1) << 3;
maxcolor[2] = (maxcolor[2] - 128) * scale + 128;
maxcolor[1] = (maxcolor[1] - 128) * scale + 128;
maxcolor[0] = (scale - 1) << 3;
for (i = 0; i < 16; ++i)
{
block[i * 4 + 2] = (block[i * 4 + 2] - 128) * scale + 128;
block[i * 4 + 1] = (block[i * 4 + 1] - 128) * scale + 128;
}
}
#define INSET_SHIFT 4
static void
inset_bbox_YCoCg (unsigned char *mincolor,
unsigned char *maxcolor)
{
int inset[4], mini[4], maxi[4];
inset[2] = (maxcolor[2] - mincolor[2]) - ((1 << (INSET_SHIFT - 1)) - 1);
inset[1] = (maxcolor[1] - mincolor[1]) - ((1 << (INSET_SHIFT - 1)) - 1);
mini[2] = ((mincolor[2] << INSET_SHIFT) + inset[2]) >> INSET_SHIFT;
mini[1] = ((mincolor[1] << INSET_SHIFT) + inset[1]) >> INSET_SHIFT;
maxi[2] = ((maxcolor[2] << INSET_SHIFT) - inset[2]) >> INSET_SHIFT;
maxi[1] = ((maxcolor[1] << INSET_SHIFT) - inset[1]) >> INSET_SHIFT;
mini[2] = (mini[2] >= 0) ? mini[2] : 0;
mini[1] = (mini[1] >= 0) ? mini[1] : 0;
maxi[2] = (maxi[2] <= 255) ? maxi[2] : 255;
maxi[1] = (maxi[1] <= 255) ? maxi[1] : 255;
mincolor[2] = (mini[2] & 0xf8) | (mini[2] >> 5);
mincolor[1] = (mini[1] & 0xfc) | (mini[1] >> 6);
maxcolor[2] = (maxi[2] & 0xf8) | (maxi[2] >> 5);
maxcolor[1] = (maxi[1] & 0xfc) | (maxi[1] >> 6);
}
static void
select_diagonal_YCoCg (const unsigned char *block,
unsigned char *mincolor,
unsigned char *maxcolor)
{
unsigned char mid0, mid1, side, mask, b0, b1, c0, c1;
int i;
mid0 = ((int)mincolor[2] + maxcolor[2] + 1) >> 1;
mid1 = ((int)mincolor[1] + maxcolor[1] + 1) >> 1;
side = 0;
for (i = 0; i < 16; ++i)
{
b0 = block[i * 4 + 2] >= mid0;
b1 = block[i * 4 + 1] >= mid1;
side += (b0 ^ b1);
}
mask = -(side > 8);
mask &= -(mincolor[2] != maxcolor[2]);
c0 = mincolor[1];
c1 = maxcolor[1];
c0 ^= c1;
c1 ^= c0 & mask;
c0 ^= c1;
mincolor[1] = c0;
maxcolor[1] = c1;
}
static void
encode_YCoCg_block (unsigned char *dst,
unsigned char *block)
{
unsigned char colors[4][3], *maxcolor, *mincolor;
unsigned int mask;
int c0, c1, d0, d1, d2, d3;
int b0, b1, b2, b3, b4;
int x0, x1, x2;
int i, idx;
maxcolor = &colors[0][0];
mincolor = &colors[1][0];
get_min_max_YCoCg(block, mincolor, maxcolor);
scale_YCoCg(block, mincolor, maxcolor);
inset_bbox_YCoCg(mincolor, maxcolor);
select_diagonal_YCoCg(block, mincolor, maxcolor);
lerp_rgb13(&colors[2][0], maxcolor, mincolor);
lerp_rgb13(&colors[3][0], mincolor, maxcolor);
mask = 0;
for (i = 0; i < 16; ++i)
{
c0 = block[4 * i + 2];
c1 = block[4 * i + 1];
d0 = abs(colors[0][2] - c0) + abs(colors[0][1] - c1);
d1 = abs(colors[1][2] - c0) + abs(colors[1][1] - c1);
d2 = abs(colors[2][2] - c0) + abs(colors[2][1] - c1);
d3 = abs(colors[3][2] - c0) + abs(colors[3][1] - c1);
b0 = d0 > d3;
b1 = d1 > d2;
b2 = d0 > d2;
b3 = d1 > d3;
b4 = d2 > d3;
x0 = b1 & b2;
x1 = b0 & b3;
x2 = b0 & b4;
idx = (x2 | ((x0 | x1) << 1));
mask |= idx << (2 * i);
}
PUTL16(dst + 0, pack_rgb565(maxcolor));
PUTL16(dst + 2, pack_rgb565(mincolor));
PUTL32(dst + 4, mask);
}
/* write DXT3 alpha block */ /* write DXT3 alpha block */
static void static void
encode_alpha_block_BC2 (unsigned char *dst, encode_alpha_block_BC2 (unsigned char *dst,
@ -1288,33 +1116,35 @@ dxt_compress (unsigned char *dst,
} }
static void static void
decode_color_block (unsigned char *block, decode_color_block (guchar *block,
unsigned char *src, guchar *src,
int format) gint format)
{ {
int i, x, y; guchar *d = block;
unsigned char *d = block; guint indices, idx;
unsigned int indices, idx; guchar colors[4][3];
unsigned char colors[4][3]; gushort c0, c1;
unsigned short c0, c1; gint i, x, y;
c0 = GETL16(&src[0]); c0 = GETL16 (&src[0]);
c1 = GETL16(&src[2]); c1 = GETL16 (&src[2]);
unpack_rgb565(colors[0], c0); unpack_rgb565 (colors[0], c0);
unpack_rgb565(colors[1], c1); unpack_rgb565 (colors[1], c1);
if ((c0 > c1) || (format == DDS_COMPRESS_BC3)) if ((c0 > c1) || (format == DDS_COMPRESS_BC3))
{ {
lerp_rgb13(colors[2], colors[0], colors[1]); /* Four-color mode */
lerp_rgb13(colors[3], colors[1], colors[0]); lerp_rgb13 (colors[2], colors[0], colors[1]);
lerp_rgb13 (colors[3], colors[1], colors[0]);
} }
else else
{ {
/* Three-color mode */
for (i = 0; i < 3; ++i) for (i = 0; i < 3; ++i)
{ {
colors[2][i] = (colors[0][i] + colors[1][i] + 1) >> 1; colors[2][i] = (colors[0][i] + colors[1][i] + 1) >> 1;
colors[3][i] = 255; colors[3][i] = 0; /* Three-color mode index 11 is always black */
} }
} }

View File

@ -2,7 +2,6 @@ plugin_name = 'file-dds'
plugin_sources = [ plugin_sources = [
'dds.c', 'dds.c',
'color.c',
'ddsread.c', 'ddsread.c',
'ddswrite.c', 'ddswrite.c',
'dxt.c', 'dxt.c',

File diff suppressed because it is too large Load Diff

View File

@ -21,55 +21,56 @@
#ifndef __MIPMAP_H__ #ifndef __MIPMAP_H__
#define __MIPMAP_H__ #define __MIPMAP_H__
int get_num_mipmaps (int width,
int height);
unsigned int get_mipmapped_size (int width,
int height,
int bpp,
int level,
int num,
int format);
unsigned int get_volume_mipmapped_size (int width,
int height,
int depth,
int bpp,
int level,
int num,
int format);
int get_next_mipmap_dimensions (int *next_w,
int *next_h,
int curr_w,
int curr_h);
float cubic_interpolate (float a, gint get_num_mipmaps (gint width,
float b, gint height);
float c,
float d, guint get_mipmapped_size (gint width,
float x); gint height,
int generate_mipmaps (unsigned char *dst, gint bpp,
unsigned char *src, gint level,
unsigned int width, gint num,
unsigned int height, gint format);
int bpp,
int indexed, guint get_volume_mipmapped_size (gint width,
int mipmaps, gint height,
int filter, gint depth,
int wrap, gint bpp,
int gamma_correct, gint level,
float gamma, gint num,
int preserve_alpha_test_coverage, gint format);
float alpha_test_threshold);
int generate_volume_mipmaps (unsigned char *dst, gint get_next_mipmap_dimensions (gint *next_w,
unsigned char *src, gint *next_h,
unsigned int width, gint curr_w,
unsigned int height, gint curr_h);
unsigned int depth,
int bpp, gint generate_mipmaps (guchar *dst,
int indexed, guchar *src,
int mipmaps, guint width,
int filter, guint height,
int wrap, gint bpp,
int gamma_correct, gint indexed,
float gamma); gint mipmaps,
gint filter,
gint wrap,
gint gamma_correct,
gfloat gamma,
gint preserve_alpha_test_coverage,
gfloat alpha_test_threshold);
gint generate_volume_mipmaps (guchar *dst,
guchar *src,
guint width,
guint height,
guint depth,
gint bpp,
gint indexed,
gint mipmaps,
gint filter,
gint wrap,
gint gamma_correct,
gfloat gamma);
#endif /* __MIPMAP_H__ */ #endif /* __MIPMAP_H__ */

View File

@ -4,56 +4,53 @@
* Copyright (C) 2004-2012 Shawn Kirst <skirst@gmail.com>, * Copyright (C) 2004-2012 Shawn Kirst <skirst@gmail.com>,
* with parts (C) 2003 Arne Reuter <homepage@arnereuter.de> where specified. * with parts (C) 2003 Arne Reuter <homepage@arnereuter.de> where specified.
* *
* This program is free software; you can redistribute it and/or * This program is free software: you can redistribute it and/or modify
* modify it under the terms of the GNU General Public * it under the terms of the GNU General Public License as published by
* License as published by the Free Software Foundation; either * the Free Software Foundation; either version 3 of the License, or
* version 2 of the License, or (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to * along with this program. If not, see <https://www.gnu.org/licenses/>.
* the Free Software Foundation, 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301, USA.
*/ */
#include <libpika/pika.h> #include <libpika/pika.h>
#include "endian_rw.h"
#include "imath.h"
#include "misc.h" #include "misc.h"
static inline float
saturate (float a)
{
if(a < 0) a = 0;
if(a > 1) a = 1;
/*
* Decoding Functions
*/
static inline gfloat
saturate (gfloat a)
{
if (a < 0) a = 0;
if (a > 1) a = 1;
return a; return a;
} }
void void
decode_ycocg_image (PikaDrawable *drawable, decode_ycocg (PikaDrawable *drawable)
gboolean shadow)
{ {
GeglBuffer *buffer, *sbuffer; GeglBuffer *buffer;
const Babl *format; const Babl *format;
unsigned char *data; guchar *data;
unsigned int i, w, h, num_pixels; guint num_pixels;
guint i, w, h;
const float offset = 0.5f * 256.0f / 255.0f; const gfloat offset = 0.5f * 256.0f / 255.0f;
float Y, Co, Cg, R, G, B; gfloat Y, Co, Cg;
gfloat R, G, B;
buffer = pika_drawable_get_buffer (drawable); buffer = pika_drawable_get_buffer (drawable);
if (shadow)
{
sbuffer = pika_drawable_get_shadow_buffer (drawable);
gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, sbuffer, NULL);
g_object_unref (buffer);
buffer = sbuffer;
}
format = babl_format ("R'G'B'A u8"); format = babl_format ("R'G'B'A u8");
w = gegl_buffer_get_width (buffer); w = gegl_buffer_get_width (buffer);
@ -62,46 +59,43 @@ decode_ycocg_image (PikaDrawable *drawable,
data = g_malloc (num_pixels * 4); data = g_malloc (num_pixels * 4);
gegl_buffer_get (buffer, GEGL_RECTANGLE(0, 0, w, h), 1.0, format, data, gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, w, h), 1.0, format, data,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
pika_progress_init ("Decoding YCoCg pixels..."); pika_progress_init ("Decoding YCoCg pixels...");
for (i = 0; i < num_pixels; ++i) for (i = 0; i < num_pixels; ++i)
{ {
Y = (float)data[4 * i + 3] / 255.0f; Y = (gfloat) data[4 * i + 3] / 255.0f;
Co = (float)data[4 * i + 0] / 255.0f; Co = (gfloat) data[4 * i + 0] / 255.0f;
Cg = (float)data[4 * i + 1] / 255.0f; Cg = (gfloat) data[4 * i + 1] / 255.0f;
/* convert YCoCg to RGB */ /* convert YCoCg to RGB */
Co -= offset; Co -= offset;
Cg -= offset; Cg -= offset;
R = saturate(Y + Co - Cg); R = saturate (Y + Co - Cg);
G = saturate(Y + Cg); G = saturate (Y + Cg);
B = saturate(Y - Co - Cg); B = saturate (Y - Co - Cg);
/* copy new alpha from blue */ /* copy new alpha from blue */
data[4 * i + 3] = data[4 * i + 2]; data[4 * i + 3] = data[4 * i + 2];
data[4 * i + 0] = (unsigned char)(R * 255.0f); data[4 * i + 0] = (guchar) (R * 255.0f);
data[4 * i + 1] = (unsigned char)(G * 255.0f); data[4 * i + 1] = (guchar) (G * 255.0f);
data[4 * i + 2] = (unsigned char)(B * 255.0f); data[4 * i + 2] = (guchar) (B * 255.0f);
if ((i & 0x7fff) == 0) if ((i & 0x7fff) == 0)
pika_progress_update ((float)i / (float)num_pixels); pika_progress_update ((gdouble) i / (gdouble) num_pixels);
} }
gegl_buffer_set (buffer, GEGL_RECTANGLE(0, 0, w, h), 0, format, data, gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, w, h), 0, format, data,
GEGL_AUTO_ROWSTRIDE); GEGL_AUTO_ROWSTRIDE);
pika_progress_update (1.0); pika_progress_update (1.0);
gegl_buffer_flush (buffer); gegl_buffer_flush (buffer);
if (shadow)
pika_drawable_merge_shadow (drawable, TRUE);
pika_drawable_update (drawable, 0, 0, w, h); pika_drawable_update (drawable, 0, 0, w, h);
g_free (data); g_free (data);
@ -110,27 +104,19 @@ decode_ycocg_image (PikaDrawable *drawable,
} }
void void
decode_ycocg_scaled_image (PikaDrawable *drawable, decode_ycocg_scaled (PikaDrawable *drawable)
gboolean shadow)
{ {
GeglBuffer *buffer, *sbuffer; GeglBuffer *buffer;
const Babl *format; const Babl *format;
unsigned char *data; guchar *data;
unsigned int i, w, h, num_pixels; guint num_pixels;
guint i, w, h;
const float offset = 0.5f * 256.0f / 255.0f; const gfloat offset = 0.5f * 256.0f / 255.0f;
float Y, Co, Cg, R, G, B, s; gfloat Y, Co, Cg;
gfloat R, G, B, s;
buffer = pika_drawable_get_buffer (drawable); buffer = pika_drawable_get_buffer (drawable);
if (shadow)
{
sbuffer = pika_drawable_get_shadow_buffer (drawable);
gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, sbuffer, NULL);
g_object_unref (buffer);
buffer = sbuffer;
}
format = babl_format ("R'G'B'A u8"); format = babl_format ("R'G'B'A u8");
w = gegl_buffer_get_width (buffer); w = gegl_buffer_get_width (buffer);
@ -139,17 +125,17 @@ decode_ycocg_scaled_image (PikaDrawable *drawable,
data = g_malloc (num_pixels * 4); data = g_malloc (num_pixels * 4);
gegl_buffer_get (buffer, GEGL_RECTANGLE(0, 0, w, h), 1.0, format, data, gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, w, h), 1.0, format, data,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
pika_progress_init ("Decoding YCoCg (scaled) pixels..."); pika_progress_init ("Decoding YCoCg (scaled) pixels...");
for (i = 0; i < num_pixels; ++i) for (i = 0; i < num_pixels; ++i)
{ {
Y = (float)data[4 * i + 3] / 255.0f; Y = (gfloat) data[4 * i + 3] / 255.0f;
Co = (float)data[4 * i + 0] / 255.0f; Co = (gfloat) data[4 * i + 0] / 255.0f;
Cg = (float)data[4 * i + 1] / 255.0f; Cg = (gfloat) data[4 * i + 1] / 255.0f;
s = (float)data[4 * i + 2] / 255.0f; s = (gfloat) data[4 * i + 2] / 255.0f;
/* convert YCoCg to RGB */ /* convert YCoCg to RGB */
s = 1.0f / ((255.0f / 8.0f) * s + 1.0f); s = 1.0f / ((255.0f / 8.0f) * s + 1.0f);
@ -157,31 +143,28 @@ decode_ycocg_scaled_image (PikaDrawable *drawable,
Co = (Co - offset) * s; Co = (Co - offset) * s;
Cg = (Cg - offset) * s; Cg = (Cg - offset) * s;
R = saturate(Y + Co - Cg); R = saturate (Y + Co - Cg);
G = saturate(Y + Cg); G = saturate (Y + Cg);
B = saturate(Y - Co - Cg); B = saturate (Y - Co - Cg);
data[4 * i + 0] = (unsigned char)(R * 255.0f); data[4 * i + 0] = (guchar) (R * 255.0f);
data[4 * i + 1] = (unsigned char)(G * 255.0f); data[4 * i + 1] = (guchar) (G * 255.0f);
data[4 * i + 2] = (unsigned char)(B * 255.0f); data[4 * i + 2] = (guchar) (B * 255.0f);
/* set alpha to 1 */ /* set alpha to 1 */
data[4 * i + 3] = 255; data[4 * i + 3] = 255;
if ((i & 0x7fff) == 0) if ((i & 0x7fff) == 0)
pika_progress_update ((float)i / (float)num_pixels); pika_progress_update ((gdouble) i / (gdouble) num_pixels);
} }
gegl_buffer_set (buffer, GEGL_RECTANGLE(0, 0, w, h), 0, format, data, gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, w, h), 0, format, data,
GEGL_AUTO_ROWSTRIDE); GEGL_AUTO_ROWSTRIDE);
pika_progress_update (1.0); pika_progress_update (1.0);
gegl_buffer_flush (buffer); gegl_buffer_flush (buffer);
if (shadow)
pika_drawable_merge_shadow (drawable, TRUE);
pika_drawable_update (drawable, 0, 0, w, h); pika_drawable_update (drawable, 0, 0, w, h);
g_free (data); g_free (data);
@ -190,25 +173,17 @@ decode_ycocg_scaled_image (PikaDrawable *drawable,
} }
void void
decode_alpha_exp_image (PikaDrawable *drawable, decode_alpha_exponent (PikaDrawable *drawable)
gboolean shadow)
{ {
GeglBuffer *buffer, *sbuffer; GeglBuffer *buffer;
const Babl *format; const Babl *format;
unsigned char *data; guchar *data;
unsigned int i, w, h, num_pixels; guint num_pixels;
int R, G, B, A; guint i, w, h;
gint R, G, B, A;
buffer = pika_drawable_get_buffer (drawable); buffer = pika_drawable_get_buffer (drawable);
if (shadow)
{
sbuffer = pika_drawable_get_shadow_buffer (drawable);
gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, sbuffer, NULL);
g_object_unref (buffer);
buffer = sbuffer;
}
format = babl_format ("R'G'B'A u8"); format = babl_format ("R'G'B'A u8");
w = gegl_buffer_get_width (buffer); w = gegl_buffer_get_width (buffer);
@ -217,7 +192,7 @@ decode_alpha_exp_image (PikaDrawable *drawable,
data = g_malloc (num_pixels * 4); data = g_malloc (num_pixels * 4);
gegl_buffer_get (buffer, GEGL_RECTANGLE(0, 0, w, h), 1.0, format, data, gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, w, h), 1.0, format, data,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
pika_progress_init ("Decoding Alpha-exponent pixels..."); pika_progress_init ("Decoding Alpha-exponent pixels...");
@ -240,22 +215,268 @@ decode_alpha_exp_image (PikaDrawable *drawable,
data[4 * i + 3] = A; data[4 * i + 3] = A;
if ((i & 0x7fff) == 0) if ((i & 0x7fff) == 0)
pika_progress_update ((float)i / (float)num_pixels); pika_progress_update ((gdouble) i / (gdouble) num_pixels);
} }
gegl_buffer_set (buffer, GEGL_RECTANGLE(0, 0, w, h), 0, format, data, gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, w, h), 0, format, data,
GEGL_AUTO_ROWSTRIDE); GEGL_AUTO_ROWSTRIDE);
pika_progress_update (1.0); pika_progress_update (1.0);
gegl_buffer_flush (buffer); gegl_buffer_flush (buffer);
if (shadow)
pika_drawable_merge_shadow (drawable, TRUE);
pika_drawable_update (drawable, 0, 0, w, h); pika_drawable_update (drawable, 0, 0, w, h);
g_free (data); g_free (data);
g_object_unref (buffer); g_object_unref (buffer);
} }
/*
* Encoding Functions
*/
void
encode_ycocg (guchar *dst,
gint r,
gint g,
gint b)
{
gint y = ((r + (g << 1) + b) + 2) >> 2;
gint co = ((((r << 1) - (b << 1)) + 2) >> 2) + 128;
gint cg = (((-r + (g << 1) - b) + 2) >> 2) + 128;
dst[0] = 255;
dst[1] = (cg > 255 ? 255 : (cg < 0 ? 0 : cg));
dst[2] = (co > 255 ? 255 : (co < 0 ? 0 : co));
dst[3] = (y > 255 ? 255 : (y < 0 ? 0 : y));
}
void
encode_alpha_exponent (guchar *dst,
gint r,
gint g,
gint b,
gint a)
{
gfloat ar, ag, ab, aa;
ar = (gfloat) r / 255.0f;
ag = (gfloat) g / 255.0f;
ab = (gfloat) b / 255.0f;
aa = MAX (ar, MAX (ag, ab));
if (aa < 1e-04f)
{
dst[0] = b;
dst[1] = g;
dst[2] = r;
dst[3] = 255;
return;
}
ar /= aa;
ag /= aa;
ab /= aa;
r = (gint) floorf (255.0f * ar + 0.5f);
g = (gint) floorf (255.0f * ag + 0.5f);
b = (gint) floorf (255.0f * ab + 0.5f);
a = (gint) floorf (255.0f * aa + 0.5f);
dst[0] = MAX (0, MIN (255, b));
dst[1] = MAX (0, MIN (255, g));
dst[2] = MAX (0, MIN (255, r));
dst[3] = MAX (0, MIN (255, a));
}
/*
* Compression Functions
*/
static void
get_min_max_YCoCg (const guchar *block,
guchar *mincolor,
guchar *maxcolor)
{
gint i;
mincolor[2] = mincolor[1] = 255;
maxcolor[2] = maxcolor[1] = 0;
for (i = 0; i < 16; ++i)
{
if (block[4 * i + 2] < mincolor[2]) mincolor[2] = block[4 * i + 2];
if (block[4 * i + 1] < mincolor[1]) mincolor[1] = block[4 * i + 1];
if (block[4 * i + 2] > maxcolor[2]) maxcolor[2] = block[4 * i + 2];
if (block[4 * i + 1] > maxcolor[1]) maxcolor[1] = block[4 * i + 1];
}
}
static void
scale_YCoCg (guchar *block,
guchar *mincolor,
guchar *maxcolor)
{
const gint s0 = 128 / 2 - 1;
const gint s1 = 128 / 4 - 1;
gint m0, m1, m2, m3;
gint mask0, mask1, scale;
gint i;
m0 = abs (mincolor[2] - 128);
m1 = abs (mincolor[1] - 128);
m2 = abs (maxcolor[2] - 128);
m3 = abs (maxcolor[1] - 128);
if (m1 > m0) m0 = m1;
if (m3 > m2) m2 = m3;
if (m2 > m0) m0 = m2;
mask0 = -(m0 <= s0);
mask1 = -(m0 <= s1);
scale = 1 + (1 & mask0) + (2 & mask1);
mincolor[2] = (mincolor[2] - 128) * scale + 128;
mincolor[1] = (mincolor[1] - 128) * scale + 128;
mincolor[0] = (scale - 1) << 3;
maxcolor[2] = (maxcolor[2] - 128) * scale + 128;
maxcolor[1] = (maxcolor[1] - 128) * scale + 128;
maxcolor[0] = (scale - 1) << 3;
for (i = 0; i < 16; ++i)
{
block[i * 4 + 2] = (block[i * 4 + 2] - 128) * scale + 128;
block[i * 4 + 1] = (block[i * 4 + 1] - 128) * scale + 128;
}
}
#define INSET_SHIFT 4
static void
inset_bbox_YCoCg (guchar *mincolor,
guchar *maxcolor)
{
gint inset[4], mini[4], maxi[4];
inset[2] = (maxcolor[2] - mincolor[2]) - ((1 << (INSET_SHIFT - 1)) - 1);
inset[1] = (maxcolor[1] - mincolor[1]) - ((1 << (INSET_SHIFT - 1)) - 1);
mini[2] = ((mincolor[2] << INSET_SHIFT) + inset[2]) >> INSET_SHIFT;
mini[1] = ((mincolor[1] << INSET_SHIFT) + inset[1]) >> INSET_SHIFT;
maxi[2] = ((maxcolor[2] << INSET_SHIFT) - inset[2]) >> INSET_SHIFT;
maxi[1] = ((maxcolor[1] << INSET_SHIFT) - inset[1]) >> INSET_SHIFT;
mini[2] = (mini[2] >= 0) ? mini[2] : 0;
mini[1] = (mini[1] >= 0) ? mini[1] : 0;
maxi[2] = (maxi[2] <= 255) ? maxi[2] : 255;
maxi[1] = (maxi[1] <= 255) ? maxi[1] : 255;
mincolor[2] = (mini[2] & 0xf8) | (mini[2] >> 5);
mincolor[1] = (mini[1] & 0xfc) | (mini[1] >> 6);
maxcolor[2] = (maxi[2] & 0xf8) | (maxi[2] >> 5);
maxcolor[1] = (maxi[1] & 0xfc) | (maxi[1] >> 6);
}
static void
select_diagonal_YCoCg (const guchar *block,
guchar *mincolor,
guchar *maxcolor)
{
guchar mid0, mid1, side, mask, b0, b1, c0, c1;
gint i;
mid0 = ((gint) mincolor[2] + maxcolor[2] + 1) >> 1;
mid1 = ((gint) mincolor[1] + maxcolor[1] + 1) >> 1;
side = 0;
for (i = 0; i < 16; ++i)
{
b0 = block[i * 4 + 2] >= mid0;
b1 = block[i * 4 + 1] >= mid1;
side += (b0 ^ b1);
}
mask = -(side > 8);
mask &= -(mincolor[2] != maxcolor[2]);
c0 = mincolor[1];
c1 = maxcolor[1];
c0 ^= c1;
c1 ^= c0 & mask;
c0 ^= c1;
mincolor[1] = c0;
maxcolor[1] = c1;
}
void
encode_YCoCg_block (guchar *dst,
guchar *block)
{
guchar colors[4][3], *maxcolor, *mincolor;
guint mask;
gint c0, c1, d0, d1, d2, d3;
gint b0, b1, b2, b3, b4;
gint x0, x1, x2;
gint i, idx;
maxcolor = &colors[0][0];
mincolor = &colors[1][0];
get_min_max_YCoCg (block, mincolor, maxcolor);
scale_YCoCg (block, mincolor, maxcolor);
inset_bbox_YCoCg (mincolor, maxcolor);
select_diagonal_YCoCg (block, mincolor, maxcolor);
colors[2][0] = (2 * maxcolor[0] + mincolor[0]) / 3;
colors[2][1] = (2 * maxcolor[1] + mincolor[1]) / 3;
colors[2][2] = (2 * maxcolor[2] + mincolor[2]) / 3;
colors[3][0] = (2 * mincolor[0] + maxcolor[0]) / 3;
colors[3][1] = (2 * mincolor[1] + maxcolor[1]) / 3;
colors[3][2] = (2 * mincolor[2] + maxcolor[2]) / 3;
mask = 0;
for (i = 0; i < 16; ++i)
{
c0 = block[4 * i + 2];
c1 = block[4 * i + 1];
d0 = abs (colors[0][2] - c0) + abs (colors[0][1] - c1);
d1 = abs (colors[1][2] - c0) + abs (colors[1][1] - c1);
d2 = abs (colors[2][2] - c0) + abs (colors[2][1] - c1);
d3 = abs (colors[3][2] - c0) + abs (colors[3][1] - c1);
b0 = d0 > d3;
b1 = d1 > d2;
b2 = d0 > d2;
b3 = d1 > d3;
b4 = d2 > d3;
x0 = b1 & b2;
x1 = b0 & b3;
x2 = b0 & b4;
idx = (x2 | ((x0 | x1) << 1));
mask |= idx << (2 * i);
}
PUTL16 (dst + 0, (mul8bit (maxcolor[2], 31) << 11) |
(mul8bit (maxcolor[1], 63) << 5) |
(mul8bit (maxcolor[0], 31) ));
PUTL16 (dst + 2, (mul8bit (mincolor[2], 31) << 11) |
(mul8bit (mincolor[1], 63) << 5) |
(mul8bit (mincolor[0], 31) ));
PUTL32 (dst + 4, mask);
}

View File

@ -21,11 +21,26 @@
#ifndef __MISC_H__ #ifndef __MISC_H__
#define __MISC_H__ #define __MISC_H__
void decode_ycocg_image (PikaDrawable *drawable,
gboolean shadow); void decode_ycocg (PikaDrawable *drawable);
void decode_ycocg_scaled_image (PikaDrawable *drawable,
gboolean shadow); void decode_ycocg_scaled (PikaDrawable *drawable);
void decode_alpha_exp_image (PikaDrawable *drawable,
gboolean shadow); void decode_alpha_exponent (PikaDrawable *drawable);
void encode_ycocg (guchar *dst,
gint r,
gint g,
gint b);
void encode_alpha_exponent (guchar *dst,
gint r,
gint g,
gint b,
gint a);
void encode_YCoCg_block (guchar *dst,
guchar *block);
#endif /* __MISC_H__ */ #endif /* __MISC_H__ */

View File

@ -40,6 +40,23 @@
static gboolean callback_lock; static gboolean callback_lock;
static void select_web_cb (GtkWidget *widget,
AreaInfoDialog_t *param);
static void select_ftp_cb (GtkWidget *widget,
AreaInfoDialog_t *param);
static void select_gopher_cb (GtkWidget *widget,
AreaInfoDialog_t *param);
static void select_other_cb (GtkWidget *widget,
AreaInfoDialog_t *param);
static void select_file_cb (GtkWidget *widget,
AreaInfoDialog_t *param);
static void select_wais_cb (GtkWidget *widget,
AreaInfoDialog_t *param);
static void select_telnet_cb (GtkWidget *widget,
AreaInfoDialog_t *param);
static void select_email_cb (GtkWidget *widget,
AreaInfoDialog_t *param);
static gchar* static gchar*
relative_filter(const char *name, gpointer data) relative_filter(const char *name, gpointer data)
@ -77,7 +94,23 @@ url_changed (GtkWidget *widget,
button = param->other; button = param->other;
callback_lock = TRUE; callback_lock = TRUE;
g_signal_handlers_block_by_func (param->web_site, G_CALLBACK (select_web_cb), data);
g_signal_handlers_block_by_func (param->ftp_site, G_CALLBACK (select_ftp_cb), data);
g_signal_handlers_block_by_func (param->gopher, G_CALLBACK (select_gopher_cb), data);
g_signal_handlers_block_by_func (param->other, G_CALLBACK (select_other_cb), data);
g_signal_handlers_block_by_func (param->file, G_CALLBACK (select_file_cb), data);
g_signal_handlers_block_by_func (param->wais, G_CALLBACK (select_wais_cb), data);
g_signal_handlers_block_by_func (param->telnet, G_CALLBACK (select_telnet_cb), data);
g_signal_handlers_block_by_func (param->email, G_CALLBACK (select_email_cb), data);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
g_signal_handlers_unblock_by_func (param->web_site, G_CALLBACK (select_web_cb), data);
g_signal_handlers_unblock_by_func (param->ftp_site, G_CALLBACK (select_ftp_cb), data);
g_signal_handlers_unblock_by_func (param->gopher, G_CALLBACK (select_gopher_cb), data);
g_signal_handlers_unblock_by_func (param->other, G_CALLBACK (select_other_cb), data);
g_signal_handlers_unblock_by_func (param->file, G_CALLBACK (select_file_cb), data);
g_signal_handlers_unblock_by_func (param->wais, G_CALLBACK (select_wais_cb), data);
g_signal_handlers_unblock_by_func (param->telnet, G_CALLBACK (select_telnet_cb), data);
g_signal_handlers_unblock_by_func (param->email, G_CALLBACK (select_email_cb), data);
} }
static void static void

View File

@ -76,6 +76,10 @@ static PikaProcedure * metadata_create_procedure (PikaPlugIn *plug_in
const gchar *name); const gchar *name);
static PikaValueArray * metadata_run (PikaProcedure *procedure, static PikaValueArray * metadata_run (PikaProcedure *procedure,
PikaRunMode run_mode,
PikaImage *image,
gint n_drawables,
PikaDrawable **drawables,
PikaProcedureConfig *config, PikaProcedureConfig *config,
gpointer run_data); gpointer run_data);
@ -747,9 +751,9 @@ metadata_create_procedure (PikaPlugIn *plug_in,
if (! strcmp (name, PLUG_IN_PROC)) if (! strcmp (name, PLUG_IN_PROC))
{ {
procedure = pika_procedure_new (plug_in, name, procedure = pika_image_procedure_new (plug_in, name,
PIKA_PDB_PROC_TYPE_PLUGIN, PIKA_PDB_PROC_TYPE_PLUGIN,
metadata_run, NULL, NULL); metadata_run, NULL, NULL);
pika_procedure_set_image_types (procedure, "*"); pika_procedure_set_image_types (procedure, "*");
@ -768,19 +772,6 @@ metadata_create_procedure (PikaPlugIn *plug_in,
"Ben Touchette", "Ben Touchette",
"Ben Touchette", "Ben Touchette",
"2017"); "2017");
PIKA_PROC_ARG_ENUM (procedure, "run-mode",
"Run mode",
"The run mode",
PIKA_TYPE_RUN_MODE,
PIKA_RUN_INTERACTIVE,
G_PARAM_READWRITE);
PIKA_PROC_ARG_IMAGE (procedure, "image",
"Image",
"The input image",
FALSE,
G_PARAM_READWRITE);
} }
return procedure; return procedure;
@ -788,17 +779,18 @@ metadata_create_procedure (PikaPlugIn *plug_in,
static PikaValueArray * static PikaValueArray *
metadata_run (PikaProcedure *procedure, metadata_run (PikaProcedure *procedure,
PikaRunMode run_mode,
PikaImage *image,
gint n_drawables,
PikaDrawable **drawables,
PikaProcedureConfig *config, PikaProcedureConfig *config,
gpointer run_data) gpointer run_data)
{ {
PikaImage *image;
PikaMetadata *metadata; PikaMetadata *metadata;
GError *error = NULL; GError *error = NULL;
pika_ui_init (PLUG_IN_BINARY); pika_ui_init (PLUG_IN_BINARY);
g_object_get (config, "image", &image, NULL);
metadata = pika_image_get_metadata (image); metadata = pika_image_get_metadata (image);
/* Always show metadata dialog so we can add appropriate iptc data /* Always show metadata dialog so we can add appropriate iptc data

View File

@ -97,6 +97,10 @@ static PikaProcedure * metadata_create_procedure (PikaPlugIn *plug_in
const gchar *name); const gchar *name);
static PikaValueArray * metadata_run (PikaProcedure *procedure, static PikaValueArray * metadata_run (PikaProcedure *procedure,
PikaRunMode run_mode,
PikaImage *image,
gint n_drawables,
PikaDrawable **drawables,
PikaProcedureConfig *config, PikaProcedureConfig *config,
gpointer run_data); gpointer run_data);
@ -172,9 +176,9 @@ metadata_create_procedure (PikaPlugIn *plug_in,
if (! strcmp (name, PLUG_IN_PROC)) if (! strcmp (name, PLUG_IN_PROC))
{ {
procedure = pika_procedure_new (plug_in, name, procedure = pika_image_procedure_new (plug_in, name,
PIKA_PDB_PROC_TYPE_PLUGIN, PIKA_PDB_PROC_TYPE_PLUGIN,
metadata_run, NULL, NULL); metadata_run, NULL, NULL);
pika_procedure_set_image_types (procedure, "*"); pika_procedure_set_image_types (procedure, "*");
@ -194,19 +198,6 @@ metadata_create_procedure (PikaPlugIn *plug_in,
"Hartmut Kuhse, Michael Natterer, " "Hartmut Kuhse, Michael Natterer, "
"Ben Touchette", "Ben Touchette",
"2013, 2017"); "2013, 2017");
PIKA_PROC_ARG_ENUM (procedure, "run-mode",
"Run mode",
"The run mode",
PIKA_TYPE_RUN_MODE,
PIKA_RUN_INTERACTIVE,
G_PARAM_READWRITE);
PIKA_PROC_ARG_IMAGE (procedure, "image",
"Image",
"The input image",
FALSE,
G_PARAM_READWRITE);
} }
return procedure; return procedure;
@ -214,17 +205,18 @@ metadata_create_procedure (PikaPlugIn *plug_in,
static PikaValueArray * static PikaValueArray *
metadata_run (PikaProcedure *procedure, metadata_run (PikaProcedure *procedure,
PikaRunMode run_mode,
PikaImage *image,
gint n_drawables,
PikaDrawable **drawables,
PikaProcedureConfig *config, PikaProcedureConfig *config,
gpointer run_data) gpointer run_data)
{ {
PikaImage *image;
PikaMetadata *metadata; PikaMetadata *metadata;
GError *error = NULL; GError *error = NULL;
pika_ui_init (PLUG_IN_BINARY); pika_ui_init (PLUG_IN_BINARY);
g_object_get (config, "image", &image, NULL);
metadata = pika_image_get_metadata (image); metadata = pika_image_get_metadata (image);
/* Always show metadata dialog so we can add appropriate iptc data /* Always show metadata dialog so we can add appropriate iptc data

View File

@ -77,6 +77,7 @@ marshal_vector_to_drawable_array (scheme *sc,
PikaDrawable **drawable_array; PikaDrawable **drawable_array;
gint id; gint id;
pointer error; pointer error;
GType actual_type = PIKA_TYPE_DRAWABLE;
guint num_elements = sc->vptr->vector_length (vector); guint num_elements = sc->vptr->vector_length (vector);
g_debug ("vector has %d elements", num_elements); g_debug ("vector has %d elements", num_elements);
@ -104,10 +105,15 @@ marshal_vector_to_drawable_array (scheme *sc,
g_free (drawable_array); g_free (drawable_array);
return error; return error;
} }
/* Parameters are validated based on the actual type inside the object
array. So we set that type here instead of a generic drawable. */
if (j == 0)
actual_type = G_OBJECT_TYPE (drawable_array[j]);
} }
/* Shallow copy. */ /* Shallow copy. */
pika_value_set_object_array (value, PIKA_TYPE_DRAWABLE, (GObject**)drawable_array, num_elements); pika_value_set_object_array (value, actual_type, (GObject**)drawable_array, num_elements);
g_free (drawable_array); g_free (drawable_array);

View File

@ -615,7 +615,7 @@ script_fu_resource_widget (const gchar *title,
{ {
GtkWidget *result_widget = NULL; GtkWidget *result_widget = NULL;
g_debug ("%s type: %ld", G_STRFUNC, resource_type); g_debug ("%s type: %" G_GSIZE_FORMAT, G_STRFUNC, resource_type);
/* Passing NULL resource sets initial choice to resource from context. /* Passing NULL resource sets initial choice to resource from context.
* Passing empty string for outer widget label, since we provide our own. * Passing empty string for outer widget label, since we provide our own.
@ -824,6 +824,10 @@ script_fu_update_models (SFScript *script)
FALSE); FALSE);
} }
break; break;
default:
/* Silence warnings about unhandled enumeration values */
break;
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More