diff --git a/ChangeLog b/ChangeLog index f1972a9849..5ccd3eb154 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +Sun Dec 7 17:05:32 PST 1997 + + * added Sven Neumann's patch to the scale and resize dialogs for ratio input fields (app/resize.h, app/resize.c) + + * added Raph Levien's patch for the transperancy blur problem + (app/convolve.c, app/gimage.h, app/gimage.c, paint_core.h, + paint_core.c, paint_funcs.h, paint_funcs.c) + Sun Dec 7 15:27:14 EST 1997 Adrian Likins * fixed refract.c to look for megawidget.h in the proper place diff --git a/app/convolve.c b/app/convolve.c index 406f724a75..0d35899fa9 100644 --- a/app/convolve.c +++ b/app/convolve.c @@ -266,6 +266,11 @@ convolve_motion (PaintCore *paint_core, /* if the source has no alpha, then add alpha pixels */ if (!drawable_has_alpha (drawable_id)) { + /* note: this architecture needlessly convolves the totally- + opaque alpha channel. A faster approach would be to keep + tempPR the same number of bytes as srcPR, and extend the + paint_core_replace_canvas API to handle non-alpha images. */ + tempPR.bytes = srcPR.bytes + 1; tempPR.x = 0; tempPR.y = 0; tempPR.w = area->width; @@ -287,6 +292,13 @@ convolve_motion (PaintCore *paint_core, tempPR.data = temp_data; copy_region (&srcPR, &tempPR); + + tempPR.x = 0; tempPR.y = 0; + tempPR.w = area->width; + tempPR.h = area->height; + tempPR.data = temp_data; + + multiply_alpha_region (&tempPR); } tempPR.x = 0; tempPR.y = 0; @@ -298,6 +310,9 @@ convolve_motion (PaintCore *paint_core, convolve_region (&tempPR, &destPR, matrix, matrix_size, matrix_divisor, NORMAL); + if (drawable_has_alpha (drawable_id)) + separate_alpha_region (&destPR); + /* Free the allocated temp space */ g_free (temp_data); } @@ -311,9 +326,9 @@ convolve_motion (PaintCore *paint_core, } /* paste the newly painted canvas to the gimage which is being worked on */ - paint_core_paste_canvas (paint_core, drawable_id, OPAQUE, + paint_core_replace_canvas (paint_core, drawable_id, OPAQUE, (int) (get_brush_opacity () * 255), - NORMAL_MODE, SOFT, INCREMENTAL); + SOFT, INCREMENTAL); } static void diff --git a/app/dialogs/resize-dialog.c b/app/dialogs/resize-dialog.c index aae92283ff..fe97e8bb05 100644 --- a/app/dialogs/resize-dialog.c +++ b/app/dialogs/resize-dialog.c @@ -31,6 +31,8 @@ struct _ResizePrivate { GtkWidget *width_text; GtkWidget *height_text; + GtkWidget *ratio_x_text; + GtkWidget *ratio_y_text; GtkWidget *off_x_text; GtkWidget *off_y_text; GtkWidget *drawing_area; @@ -50,6 +52,8 @@ static void off_x_update (GtkWidget *w, gpointer data); static void off_y_update (GtkWidget *w, gpointer data); static void width_update (GtkWidget *w, gpointer data); static void height_update (GtkWidget *w, gpointer data); +static void ratio_x_update (GtkWidget *w, gpointer data); +static void ratio_y_update (GtkWidget *w, gpointer data); static void constrain_update (GtkWidget *w, gpointer data); static gint resize_events (GtkWidget *area, GdkEvent *event); @@ -68,6 +72,7 @@ resize_widget_new (ResizeType type, GtkWidget *constrain; GtkWidget *table; char size[12]; + char ratio_text[12]; table = NULL; @@ -77,6 +82,8 @@ resize_widget_new (ResizeType type, resize->private_part = private; resize->width = width; resize->height = height; + resize->ratio_x = 1.0; + resize->ratio_y = 1.0; resize->off_x = 0; resize->off_y = 0; private->old_width = width; @@ -95,11 +102,11 @@ resize_widget_new (ResizeType type, { case ScaleWidget: resize->resize_widget = gtk_frame_new ("Scale"); - table = gtk_table_new (2, 2, TRUE); + table = gtk_table_new (4, 2, TRUE); break; case ResizeWidget: resize->resize_widget = gtk_frame_new ("Resize"); - table = gtk_table_new (4, 2, TRUE); + table = gtk_table_new (6, 2, TRUE); break; } gtk_frame_set_shadow_type (GTK_FRAME (resize->resize_widget), GTK_SHADOW_ETCHED_IN); @@ -146,17 +153,51 @@ resize_widget_new (ResizeType type, resize); gtk_widget_show (private->height_text); + /* the x scale ratio label and entry */ + sprintf (ratio_text, "%0.4f", resize->ratio_x); + label = gtk_label_new ("X ratio:"); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); + gtk_widget_show (label); + private->ratio_x_text = gtk_entry_new (); + gtk_table_attach (GTK_TABLE (table), private->ratio_x_text, 1, 2, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); + gtk_widget_set_usize (private->ratio_x_text, TEXT_WIDTH, 25); + gtk_entry_set_text (GTK_ENTRY (private->ratio_x_text), ratio_text); + gtk_signal_connect (GTK_OBJECT (private->ratio_x_text), "changed", + (GtkSignalFunc) ratio_x_update, + resize); + gtk_widget_show (private->ratio_x_text); + + /* the y scale ratio label and entry */ + sprintf (ratio_text, "%0.4f", resize->ratio_y); + label = gtk_label_new ("Y ratio:"); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); + gtk_widget_show (label); + private->ratio_y_text = gtk_entry_new (); + gtk_table_attach (GTK_TABLE (table), private->ratio_y_text, 1, 2, 3, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); + gtk_widget_set_usize (private->ratio_y_text, TEXT_WIDTH, 25); + gtk_entry_set_text (GTK_ENTRY (private->ratio_y_text), ratio_text); + gtk_signal_connect (GTK_OBJECT (private->ratio_y_text), "changed", + (GtkSignalFunc) ratio_y_update, + resize); + gtk_widget_show (private->ratio_y_text); + if (type == ResizeWidget) { /* the off_x label and entry */ sprintf (size, "%d", 0); label = gtk_label_new ("X Offset:"); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); - gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 4, 5, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); gtk_widget_show (label); private->off_x_text = gtk_entry_new (); - gtk_table_attach (GTK_TABLE (table), private->off_x_text, 1, 2, 2, 3, + gtk_table_attach (GTK_TABLE (table), private->off_x_text, 1, 2, 4, 5, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); gtk_widget_set_usize (private->off_x_text, TEXT_WIDTH, 25); gtk_entry_set_text (GTK_ENTRY (private->off_x_text), size); @@ -169,11 +210,11 @@ resize_widget_new (ResizeType type, sprintf (size, "%d", 0); label = gtk_label_new ("Y Offset:"); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); - gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4, + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 5, 6, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); gtk_widget_show (label); private->off_y_text = gtk_entry_new (); - gtk_table_attach (GTK_TABLE (table), private->off_y_text, 1, 2, 3, 4, + gtk_table_attach (GTK_TABLE (table), private->off_y_text, 1, 2, 5, 6, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); gtk_widget_set_usize (private->off_y_text, TEXT_WIDTH, 25); gtk_entry_set_text (GTK_ENTRY (private->off_y_text), size); @@ -425,6 +466,7 @@ width_update (GtkWidget *w, double ratio; int new_height; char size[12]; + char ratio_text[12]; resize = (Resize *) data; private = (ResizePrivate *) resize->private_part; @@ -432,6 +474,14 @@ width_update (GtkWidget *w, resize->width = atoi (str); + ratio = (double) resize->width / (double) private->old_width; + resize->ratio_x = ratio; + sprintf (ratio_text, "%0.4f", ratio); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_x_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_x_text), data); + if (resize->type == ResizeWidget) { resize->off_x = resize_bound_off_x (resize, (resize->width - private->old_width) / 2); @@ -445,7 +495,6 @@ width_update (GtkWidget *w, if (private->constrain && resize->width != 0) { private->constrain = FALSE; - ratio = (double) resize->width / (double) private->old_width; new_height = (int) (private->old_height * ratio); if (new_height == 0) new_height = 1; @@ -458,6 +507,12 @@ width_update (GtkWidget *w, gtk_entry_set_text (GTK_ENTRY (private->height_text), size); gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->height_text), data); + resize->ratio_y = ratio; + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_y_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_y_text), data); + if (resize->type == ResizeWidget) { resize->off_y = resize_bound_off_y (resize, (resize->height - private->old_height) / 2); @@ -485,6 +540,7 @@ height_update (GtkWidget *w, double ratio; int new_width; char size[12]; + char ratio_text[12]; resize = (Resize *) data; private = (ResizePrivate *) resize->private_part; @@ -492,6 +548,13 @@ height_update (GtkWidget *w, resize->height = atoi (str); + ratio = (double) resize->height / (double) private->old_height; + resize->ratio_y = ratio; + sprintf (ratio_text, "%0.4f", ratio); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_y_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_y_text), data); if (resize->type == ResizeWidget) { resize->off_y = resize_bound_off_y (resize, (resize->height - private->old_height) / 2); @@ -517,6 +580,176 @@ height_update (GtkWidget *w, gtk_signal_handler_block_by_data (GTK_OBJECT (private->width_text), data); gtk_entry_set_text (GTK_ENTRY (private->width_text), size); gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->width_text), data); + + resize->ratio_x = ratio; + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_x_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_x_text), data); + + if (resize->type == ResizeWidget) + { + resize->off_x = resize_bound_off_x (resize, (resize->width - private->old_width) / 2); + sprintf (size, "%d", resize->off_x); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->off_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->off_x_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->off_x_text), data); + } + } + + private->constrain = TRUE; + } + + resize_draw (resize); +} + +static void +ratio_x_update (GtkWidget *w, + gpointer data) +{ + Resize *resize; + ResizePrivate *private; + char *str; + int new_width; + int new_height; + char size[12]; + char ratio_text[12]; + + resize = (Resize *) data; + private = (ResizePrivate *) resize->private_part; + str = gtk_entry_get_text (GTK_ENTRY (w)); + + resize->ratio_x = atof (str); + + new_width = (int) ((double) private->old_width * resize->ratio_x); + + if (new_width != resize->width) + { + resize->width = new_width; + sprintf (size, "%d", new_width); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->width_text), data); + gtk_entry_set_text (GTK_ENTRY (private->width_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->width_text), data); + + if (resize->type == ResizeWidget) + { + resize->off_x = resize_bound_off_x (resize, (resize->width - private->old_width) / 2); + sprintf (size, "%d", resize->off_x); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->off_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->off_x_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->off_x_text), data); + } + } + + if (private->constrain && resize->width != 0) + { + private->constrain = FALSE; + + resize->ratio_y = resize->ratio_x; + + new_height = (int) (private->old_height * resize->ratio_y); + if (new_height == 0) new_height = 1; + + if (new_height != resize->height) + { + resize->height = new_height; + + sprintf (size, "%d", resize->height); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->height_text), data); + gtk_entry_set_text (GTK_ENTRY (private->height_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->height_text), data); + + sprintf (ratio_text, "%0.4f", resize->ratio_y); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_y_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_y_text), data); + + if (resize->type == ResizeWidget) + { + resize->off_y = resize_bound_off_y (resize, (resize->height - private->old_height) / 2); + sprintf (size, "%d", resize->off_y); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->off_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->off_y_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->off_y_text), data); + } + } + + private->constrain = TRUE; + } + + resize_draw (resize); +} + +static void +ratio_y_update (GtkWidget *w, + gpointer data) +{ + Resize *resize; + ResizePrivate *private; + char *str; + int new_width; + int new_height; + char size[12]; + char ratio_text[12]; + + resize = (Resize *) data; + private = (ResizePrivate *) resize->private_part; + str = gtk_entry_get_text (GTK_ENTRY (w)); + + resize->ratio_y = atof (str); + + new_height = (int) ((double) private->old_height * resize->ratio_y); + + if (new_height != resize->height) + { + resize->height = new_height; + sprintf (size, "%d", new_height); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->height_text), data); + gtk_entry_set_text (GTK_ENTRY (private->height_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->height_text), data); + + if (resize->type == ResizeWidget) + { + resize->off_y = resize_bound_off_y (resize, (resize->height - private->old_height) / 2); + sprintf (size, "%d", resize->off_y); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->off_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->off_y_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->off_y_text), data); + } + } + + if (private->constrain && resize->height != 0) + { + private->constrain = FALSE; + + resize->ratio_x = resize->ratio_y; + + new_width = (int) (private->old_width * resize->ratio_x); + if (new_width == 0) new_width = 1; + + if (new_width != resize->width) + { + resize->width = new_width; + + sprintf (size, "%d", resize->width); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->width_text), data); + gtk_entry_set_text (GTK_ENTRY (private->width_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->width_text), data); + + sprintf (ratio_text, "%0.4f", resize->ratio_x); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_x_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_x_text), data); if (resize->type == ResizeWidget) { diff --git a/app/dialogs/resize-dialog.h b/app/dialogs/resize-dialog.h index a14db9ae55..551b688dae 100644 --- a/app/dialogs/resize-dialog.h +++ b/app/dialogs/resize-dialog.h @@ -34,6 +34,8 @@ struct _Resize ResizeType type; int width; int height; + double ratio_x; + double ratio_y; int off_x; int off_y; diff --git a/app/gimage.c b/app/gimage.c index 2fe7ad927a..f56d98c97e 100644 --- a/app/gimage.c +++ b/app/gimage.c @@ -584,6 +584,126 @@ gimage_apply_image (GImage *gimage, int drawable_id, PixelRegion *src2PR, opacity, mode, active, operation); } +/* Similar to gimage_apply_image but works in "replace" mode (i.e. + transparent pixels in src2 make the result transparent rather + than opaque. + + Takes an additional mask pixel region as well. + +*/ +void +gimage_replace_image (GImage *gimage, int drawable_id, PixelRegion *src2PR, + int undo, int opacity, + PixelRegion *maskPR, + int x, int y) +{ + Channel * mask; + int x1, y1, x2, y2; + int offset_x, offset_y; + PixelRegion src1PR, destPR; + PixelRegion mask2PR, tempPR; + unsigned char *temp_data; + int operation; + int active [MAX_CHANNELS]; + + /* get the selection mask if one exists */ + mask = (gimage_mask_is_empty (gimage)) ? + NULL : gimage_get_mask (gimage); + + /* configure the active channel array */ + gimage_get_active_channels (gimage, drawable_id, active); + + /* determine what sort of operation is being attempted and + * if it's actually legal... + */ + operation = valid_combinations [drawable_type (drawable_id)][src2PR->bytes]; + if (operation == -1) + { + warning ("gimage_apply_image sent illegal parameters\n"); + return; + } + + /* get the layer offsets */ + drawable_offsets (drawable_id, &offset_x, &offset_y); + + /* make sure the image application coordinates are within gimage bounds */ + x1 = BOUNDS (x, 0, drawable_width (drawable_id)); + y1 = BOUNDS (y, 0, drawable_height (drawable_id)); + x2 = BOUNDS (x + src2PR->w, 0, drawable_width (drawable_id)); + y2 = BOUNDS (y + src2PR->h, 0, drawable_height (drawable_id)); + + if (mask) + { + /* make sure coordinates are in mask bounds ... + * we need to add the layer offset to transform coords + * into the mask coordinate system + */ + x1 = BOUNDS (x1, -offset_x, mask->width - offset_x); + y1 = BOUNDS (y1, -offset_y, mask->height - offset_y); + x2 = BOUNDS (x2, -offset_x, mask->width - offset_x); + y2 = BOUNDS (y2, -offset_y, mask->height - offset_y); + } + + /* If the calling procedure specified an undo step... */ + if (undo) + drawable_apply_image (drawable_id, x1, y1, x2, y2, NULL, FALSE); + + /* configure the pixel regions + * If an alternative to using the drawable's data as src1 was provided... + */ + pixel_region_init (&src1PR, drawable_data (drawable_id), x1, y1, (x2 - x1), (y2 - y1), FALSE); + pixel_region_init (&destPR, drawable_data (drawable_id), x1, y1, (x2 - x1), (y2 - y1), TRUE); + pixel_region_resize (src2PR, src2PR->x + (x1 - x), src2PR->y + (y1 - y), (x2 - x1), (y2 - y1)); + + if (mask) + { + int mx, my; + + /* configure the mask pixel region + * don't use x1 and y1 because they are in layer + * coordinate system. Need mask coordinate system + */ + mx = x1 + offset_x; + my = y1 + offset_y; + + pixel_region_init (&mask2PR, mask->tiles, mx, my, (x2 - x1), (y2 - y1), FALSE); + + tempPR.bytes = 1; + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.rowstride = mask2PR.rowstride; + temp_data = g_malloc (tempPR.h * tempPR.rowstride); + tempPR.data = temp_data; + + copy_region (&mask2PR, &tempPR); + + /* apparently, region operations can mutate some PR data. */ + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + apply_mask_to_region (&tempPR, maskPR, OPAQUE); + + tempPR.x = 0; + tempPR.y = 0; + tempPR.w = x2 - x1; + tempPR.h = y2 - y1; + tempPR.data = temp_data; + + combine_regions_replace (&src1PR, src2PR, &destPR, &tempPR, NULL, + opacity, active, operation); + + g_free (temp_data); + } + else + combine_regions_replace (&src1PR, src2PR, &destPR, maskPR, NULL, + opacity, active, operation); +} + void gimage_get_foreground (GImage *gimage, int drawable_id, unsigned char *fg) diff --git a/app/gimage.h b/app/gimage.h index eba28ae6be..9411330141 100644 --- a/app/gimage.h +++ b/app/gimage.h @@ -163,6 +163,8 @@ void gimage_free_shadow (GImage *); void gimage_delete (GImage *); void gimage_apply_image (GImage *, int, PixelRegion *, int, int, int, TileManager *, int, int); +void gimage_replace_image (GImage *, int, PixelRegion *, int, int, + PixelRegion *, int, int); void gimage_get_foreground (GImage *, int, unsigned char *); void gimage_get_background (GImage *, int, unsigned char *); void gimage_get_color (GImage *, int, unsigned char *, diff --git a/app/gui/resize-dialog.c b/app/gui/resize-dialog.c index aae92283ff..fe97e8bb05 100644 --- a/app/gui/resize-dialog.c +++ b/app/gui/resize-dialog.c @@ -31,6 +31,8 @@ struct _ResizePrivate { GtkWidget *width_text; GtkWidget *height_text; + GtkWidget *ratio_x_text; + GtkWidget *ratio_y_text; GtkWidget *off_x_text; GtkWidget *off_y_text; GtkWidget *drawing_area; @@ -50,6 +52,8 @@ static void off_x_update (GtkWidget *w, gpointer data); static void off_y_update (GtkWidget *w, gpointer data); static void width_update (GtkWidget *w, gpointer data); static void height_update (GtkWidget *w, gpointer data); +static void ratio_x_update (GtkWidget *w, gpointer data); +static void ratio_y_update (GtkWidget *w, gpointer data); static void constrain_update (GtkWidget *w, gpointer data); static gint resize_events (GtkWidget *area, GdkEvent *event); @@ -68,6 +72,7 @@ resize_widget_new (ResizeType type, GtkWidget *constrain; GtkWidget *table; char size[12]; + char ratio_text[12]; table = NULL; @@ -77,6 +82,8 @@ resize_widget_new (ResizeType type, resize->private_part = private; resize->width = width; resize->height = height; + resize->ratio_x = 1.0; + resize->ratio_y = 1.0; resize->off_x = 0; resize->off_y = 0; private->old_width = width; @@ -95,11 +102,11 @@ resize_widget_new (ResizeType type, { case ScaleWidget: resize->resize_widget = gtk_frame_new ("Scale"); - table = gtk_table_new (2, 2, TRUE); + table = gtk_table_new (4, 2, TRUE); break; case ResizeWidget: resize->resize_widget = gtk_frame_new ("Resize"); - table = gtk_table_new (4, 2, TRUE); + table = gtk_table_new (6, 2, TRUE); break; } gtk_frame_set_shadow_type (GTK_FRAME (resize->resize_widget), GTK_SHADOW_ETCHED_IN); @@ -146,17 +153,51 @@ resize_widget_new (ResizeType type, resize); gtk_widget_show (private->height_text); + /* the x scale ratio label and entry */ + sprintf (ratio_text, "%0.4f", resize->ratio_x); + label = gtk_label_new ("X ratio:"); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); + gtk_widget_show (label); + private->ratio_x_text = gtk_entry_new (); + gtk_table_attach (GTK_TABLE (table), private->ratio_x_text, 1, 2, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); + gtk_widget_set_usize (private->ratio_x_text, TEXT_WIDTH, 25); + gtk_entry_set_text (GTK_ENTRY (private->ratio_x_text), ratio_text); + gtk_signal_connect (GTK_OBJECT (private->ratio_x_text), "changed", + (GtkSignalFunc) ratio_x_update, + resize); + gtk_widget_show (private->ratio_x_text); + + /* the y scale ratio label and entry */ + sprintf (ratio_text, "%0.4f", resize->ratio_y); + label = gtk_label_new ("Y ratio:"); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); + gtk_widget_show (label); + private->ratio_y_text = gtk_entry_new (); + gtk_table_attach (GTK_TABLE (table), private->ratio_y_text, 1, 2, 3, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); + gtk_widget_set_usize (private->ratio_y_text, TEXT_WIDTH, 25); + gtk_entry_set_text (GTK_ENTRY (private->ratio_y_text), ratio_text); + gtk_signal_connect (GTK_OBJECT (private->ratio_y_text), "changed", + (GtkSignalFunc) ratio_y_update, + resize); + gtk_widget_show (private->ratio_y_text); + if (type == ResizeWidget) { /* the off_x label and entry */ sprintf (size, "%d", 0); label = gtk_label_new ("X Offset:"); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); - gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 4, 5, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); gtk_widget_show (label); private->off_x_text = gtk_entry_new (); - gtk_table_attach (GTK_TABLE (table), private->off_x_text, 1, 2, 2, 3, + gtk_table_attach (GTK_TABLE (table), private->off_x_text, 1, 2, 4, 5, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); gtk_widget_set_usize (private->off_x_text, TEXT_WIDTH, 25); gtk_entry_set_text (GTK_ENTRY (private->off_x_text), size); @@ -169,11 +210,11 @@ resize_widget_new (ResizeType type, sprintf (size, "%d", 0); label = gtk_label_new ("Y Offset:"); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); - gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4, + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 5, 6, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); gtk_widget_show (label); private->off_y_text = gtk_entry_new (); - gtk_table_attach (GTK_TABLE (table), private->off_y_text, 1, 2, 3, 4, + gtk_table_attach (GTK_TABLE (table), private->off_y_text, 1, 2, 5, 6, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); gtk_widget_set_usize (private->off_y_text, TEXT_WIDTH, 25); gtk_entry_set_text (GTK_ENTRY (private->off_y_text), size); @@ -425,6 +466,7 @@ width_update (GtkWidget *w, double ratio; int new_height; char size[12]; + char ratio_text[12]; resize = (Resize *) data; private = (ResizePrivate *) resize->private_part; @@ -432,6 +474,14 @@ width_update (GtkWidget *w, resize->width = atoi (str); + ratio = (double) resize->width / (double) private->old_width; + resize->ratio_x = ratio; + sprintf (ratio_text, "%0.4f", ratio); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_x_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_x_text), data); + if (resize->type == ResizeWidget) { resize->off_x = resize_bound_off_x (resize, (resize->width - private->old_width) / 2); @@ -445,7 +495,6 @@ width_update (GtkWidget *w, if (private->constrain && resize->width != 0) { private->constrain = FALSE; - ratio = (double) resize->width / (double) private->old_width; new_height = (int) (private->old_height * ratio); if (new_height == 0) new_height = 1; @@ -458,6 +507,12 @@ width_update (GtkWidget *w, gtk_entry_set_text (GTK_ENTRY (private->height_text), size); gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->height_text), data); + resize->ratio_y = ratio; + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_y_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_y_text), data); + if (resize->type == ResizeWidget) { resize->off_y = resize_bound_off_y (resize, (resize->height - private->old_height) / 2); @@ -485,6 +540,7 @@ height_update (GtkWidget *w, double ratio; int new_width; char size[12]; + char ratio_text[12]; resize = (Resize *) data; private = (ResizePrivate *) resize->private_part; @@ -492,6 +548,13 @@ height_update (GtkWidget *w, resize->height = atoi (str); + ratio = (double) resize->height / (double) private->old_height; + resize->ratio_y = ratio; + sprintf (ratio_text, "%0.4f", ratio); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_y_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_y_text), data); if (resize->type == ResizeWidget) { resize->off_y = resize_bound_off_y (resize, (resize->height - private->old_height) / 2); @@ -517,6 +580,176 @@ height_update (GtkWidget *w, gtk_signal_handler_block_by_data (GTK_OBJECT (private->width_text), data); gtk_entry_set_text (GTK_ENTRY (private->width_text), size); gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->width_text), data); + + resize->ratio_x = ratio; + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_x_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_x_text), data); + + if (resize->type == ResizeWidget) + { + resize->off_x = resize_bound_off_x (resize, (resize->width - private->old_width) / 2); + sprintf (size, "%d", resize->off_x); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->off_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->off_x_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->off_x_text), data); + } + } + + private->constrain = TRUE; + } + + resize_draw (resize); +} + +static void +ratio_x_update (GtkWidget *w, + gpointer data) +{ + Resize *resize; + ResizePrivate *private; + char *str; + int new_width; + int new_height; + char size[12]; + char ratio_text[12]; + + resize = (Resize *) data; + private = (ResizePrivate *) resize->private_part; + str = gtk_entry_get_text (GTK_ENTRY (w)); + + resize->ratio_x = atof (str); + + new_width = (int) ((double) private->old_width * resize->ratio_x); + + if (new_width != resize->width) + { + resize->width = new_width; + sprintf (size, "%d", new_width); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->width_text), data); + gtk_entry_set_text (GTK_ENTRY (private->width_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->width_text), data); + + if (resize->type == ResizeWidget) + { + resize->off_x = resize_bound_off_x (resize, (resize->width - private->old_width) / 2); + sprintf (size, "%d", resize->off_x); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->off_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->off_x_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->off_x_text), data); + } + } + + if (private->constrain && resize->width != 0) + { + private->constrain = FALSE; + + resize->ratio_y = resize->ratio_x; + + new_height = (int) (private->old_height * resize->ratio_y); + if (new_height == 0) new_height = 1; + + if (new_height != resize->height) + { + resize->height = new_height; + + sprintf (size, "%d", resize->height); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->height_text), data); + gtk_entry_set_text (GTK_ENTRY (private->height_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->height_text), data); + + sprintf (ratio_text, "%0.4f", resize->ratio_y); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_y_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_y_text), data); + + if (resize->type == ResizeWidget) + { + resize->off_y = resize_bound_off_y (resize, (resize->height - private->old_height) / 2); + sprintf (size, "%d", resize->off_y); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->off_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->off_y_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->off_y_text), data); + } + } + + private->constrain = TRUE; + } + + resize_draw (resize); +} + +static void +ratio_y_update (GtkWidget *w, + gpointer data) +{ + Resize *resize; + ResizePrivate *private; + char *str; + int new_width; + int new_height; + char size[12]; + char ratio_text[12]; + + resize = (Resize *) data; + private = (ResizePrivate *) resize->private_part; + str = gtk_entry_get_text (GTK_ENTRY (w)); + + resize->ratio_y = atof (str); + + new_height = (int) ((double) private->old_height * resize->ratio_y); + + if (new_height != resize->height) + { + resize->height = new_height; + sprintf (size, "%d", new_height); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->height_text), data); + gtk_entry_set_text (GTK_ENTRY (private->height_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->height_text), data); + + if (resize->type == ResizeWidget) + { + resize->off_y = resize_bound_off_y (resize, (resize->height - private->old_height) / 2); + sprintf (size, "%d", resize->off_y); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->off_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->off_y_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->off_y_text), data); + } + } + + if (private->constrain && resize->height != 0) + { + private->constrain = FALSE; + + resize->ratio_x = resize->ratio_y; + + new_width = (int) (private->old_width * resize->ratio_x); + if (new_width == 0) new_width = 1; + + if (new_width != resize->width) + { + resize->width = new_width; + + sprintf (size, "%d", resize->width); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->width_text), data); + gtk_entry_set_text (GTK_ENTRY (private->width_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->width_text), data); + + sprintf (ratio_text, "%0.4f", resize->ratio_x); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_x_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_x_text), data); if (resize->type == ResizeWidget) { diff --git a/app/gui/resize-dialog.h b/app/gui/resize-dialog.h index a14db9ae55..551b688dae 100644 --- a/app/gui/resize-dialog.h +++ b/app/gui/resize-dialog.h @@ -34,6 +34,8 @@ struct _Resize ResizeType type; int width; int height; + double ratio_x; + double ratio_y; int off_x; int off_y; diff --git a/app/paint-funcs/paint-funcs.c b/app/paint-funcs/paint-funcs.c index 87fe60cc8d..24961bf4e1 100644 --- a/app/paint-funcs/paint-funcs.c +++ b/app/paint-funcs/paint-funcs.c @@ -94,6 +94,10 @@ static int * make_curve (double, int *); static void run_length_encode (unsigned char *, int *, int, int); static void draw_segments (PixelRegion *, BoundSeg *, int, int, int, int); static double cubic (double, int, int, int, int); +static void apply_layer_mode_replace (unsigned char *, unsigned char *, + unsigned char *, unsigned char *, + int, int, int, + int, int, int, int *); static unsigned char * @@ -850,6 +854,59 @@ dissolve_pixels (unsigned char *src, } } +void +replace_pixels (unsigned char *src1, + unsigned char *src2, + unsigned char *dest, + unsigned char *mask, + int length, + int opacity, + int *affect, + int b1, + int b2) +{ + int alpha; + int b; + double a_val, a_recip, mask_val; + double norm_opacity; + int s1_a, s2_a; + int new_val; + + if (b1 != b2) + { + g_warning ("replace_pixels only works on commensurate pixel regions"); + return; + } + + alpha = b1 - 1; + norm_opacity = opacity * (1.0 / 65025.0); + + while (length --) + { + mask_val = mask[0] * norm_opacity; + /* calculate new alpha first. */ + s1_a = src1[alpha]; + s2_a = src2[alpha]; + a_val = s1_a + mask_val * (s2_a - s1_a); + if (a_val == 0) + a_recip = 0; + else + a_recip = 1.0 / a_val; + /* possible optimization: fold a_recip into s1_a and s2_a */ + for (b = 0; b < alpha; b++) + { + new_val = 0.5 + a_recip * (src1[b] * s1_a + mask_val * + (src2[b] * s2_a - src1[b] * s1_a)); + dest[b] = affect[b] ? MIN (new_val, 255) : src1[b]; + } + dest[alpha] = affect[alpha] ? a_val + 0.5: s1_a; + src1 += b1; + src2 += b2; + dest += b2; + mask++; + } +} + void swap_pixels (unsigned char *src, @@ -2544,6 +2601,78 @@ convolve_region (PixelRegion *srcR, } } +/* Convert from separated alpha to premultiplied alpha. Only works on + non-tiled regions! */ +void +multiply_alpha_region (PixelRegion *srcR) +{ + unsigned char *src, *s; + int x, y; + int width, height; + int b, bytes; + double alpha_val; + + width = srcR->w; + height = srcR->h; + bytes = srcR->bytes; + + src = srcR->data; + + for (y = 0; y < height; y++) + { + s = src; + for (x = 0; x < width; x++) + { + alpha_val = s[bytes - 1] * (1.0 / 255.0); + for (b = 0; b < bytes - 1; b++) + s[b] = 0.5 + s[b] * alpha_val; + s += bytes; + } + src += srcR->rowstride; + } +} + +/* Convert from premultiplied alpha to separated alpha. Only works on + non-tiled regions! */ +void +separate_alpha_region (PixelRegion *srcR) +{ + unsigned char *src, *s; + int x, y; + int width, height; + int b, bytes; + double alpha_recip; + int new_val; + + width = srcR->w; + height = srcR->h; + bytes = srcR->bytes; + + src = srcR->data; + + for (y = 0; y < height; y++) + { + s = src; + for (x = 0; x < width; x++) + { + /* predicate is equivalent to: + (((s[bytes - 1] - 1) & 255) + 2) & 256 + */ + if (s[bytes - 1] != 0 && s[bytes - 1] != 255) + { + alpha_recip = 255.0 / s[bytes - 1]; + for (b = 0; b < bytes - 1; b++) + { + new_val = 0.5 + s[b] * alpha_recip; + new_val = MIN (new_val, 255); + s[b] = new_val; + } + } + s += bytes; + } + src += srcR->rowstride; + } +} void gaussian_blur_region (PixelRegion *srcR, @@ -3792,6 +3921,43 @@ combine_regions (PixelRegion *src1, } } +void +combine_regions_replace (PixelRegion *src1, + PixelRegion *src2, + PixelRegion *dest, + PixelRegion *mask, + unsigned char *data, + int opacity, + int *affect, + int type) +{ + int h; + unsigned char * s1, * s2; + unsigned char * d, * m; + void * pr; + + for (pr = pixel_regions_register (4, src1, src2, dest, mask); pr != NULL; pr = pixel_regions_process (pr)) + { + s1 = src1->data; + s2 = src2->data; + d = dest->data; + m = mask->data; + + for (h = 0; h < src1->h; h++) + { + + /* Now, apply the paint mode */ + apply_layer_mode_replace (s1, s2, d, m, src1->x, src1->y + h, opacity, src1->w, + src1->bytes, src2->bytes, affect); + + s1 += src1->rowstride; + s2 += src2->rowstride; + d += dest->rowstride; + m += mask->rowstride; + } + } +} + /********************************* * color conversion routines * @@ -4246,3 +4412,19 @@ apply_indexed_layer_mode (unsigned char *src1, return combine; } + +static void +apply_layer_mode_replace (unsigned char *src1, + unsigned char *src2, + unsigned char *dest, + unsigned char *mask, + int x, + int y, + int opacity, + int length, + int b1, /* bytes */ + int b2, /* bytes */ + int *affect) +{ + replace_pixels (src1, src2, dest, mask, length, opacity, affect, b1, b2); +} diff --git a/app/paint-funcs/paint-funcs.h b/app/paint-funcs/paint-funcs.h index 160008e4b6..d684755c79 100644 --- a/app/paint-funcs/paint-funcs.h +++ b/app/paint-funcs/paint-funcs.h @@ -324,6 +324,10 @@ void extract_from_region (PixelRegion *, PixelRegion *, void convolve_region (PixelRegion *, PixelRegion *, int *, int, int, int); +void multiply_alpha_region (PixelRegion *); + +void separate_alpha_region (PixelRegion *); + void gaussian_blur_region (PixelRegion *, double); void border_region (PixelRegion *, void *, @@ -402,6 +406,11 @@ void combine_regions (PixelRegion *, PixelRegion *, unsigned char *, int, int, int *, int); +void combine_regions_replace (PixelRegion *, PixelRegion *, + PixelRegion *, PixelRegion *, + unsigned char *, + int, int *, int); + /* Color conversion routines */ void rgb_to_hsv (int *, int *, int *); diff --git a/app/paint/gimpconvolve.c b/app/paint/gimpconvolve.c index 406f724a75..0d35899fa9 100644 --- a/app/paint/gimpconvolve.c +++ b/app/paint/gimpconvolve.c @@ -266,6 +266,11 @@ convolve_motion (PaintCore *paint_core, /* if the source has no alpha, then add alpha pixels */ if (!drawable_has_alpha (drawable_id)) { + /* note: this architecture needlessly convolves the totally- + opaque alpha channel. A faster approach would be to keep + tempPR the same number of bytes as srcPR, and extend the + paint_core_replace_canvas API to handle non-alpha images. */ + tempPR.bytes = srcPR.bytes + 1; tempPR.x = 0; tempPR.y = 0; tempPR.w = area->width; @@ -287,6 +292,13 @@ convolve_motion (PaintCore *paint_core, tempPR.data = temp_data; copy_region (&srcPR, &tempPR); + + tempPR.x = 0; tempPR.y = 0; + tempPR.w = area->width; + tempPR.h = area->height; + tempPR.data = temp_data; + + multiply_alpha_region (&tempPR); } tempPR.x = 0; tempPR.y = 0; @@ -298,6 +310,9 @@ convolve_motion (PaintCore *paint_core, convolve_region (&tempPR, &destPR, matrix, matrix_size, matrix_divisor, NORMAL); + if (drawable_has_alpha (drawable_id)) + separate_alpha_region (&destPR); + /* Free the allocated temp space */ g_free (temp_data); } @@ -311,9 +326,9 @@ convolve_motion (PaintCore *paint_core, } /* paste the newly painted canvas to the gimage which is being worked on */ - paint_core_paste_canvas (paint_core, drawable_id, OPAQUE, + paint_core_replace_canvas (paint_core, drawable_id, OPAQUE, (int) (get_brush_opacity () * 255), - NORMAL_MODE, SOFT, INCREMENTAL); + SOFT, INCREMENTAL); } static void diff --git a/app/paint_core.c b/app/paint_core.c index b0975289b6..e7df5d55b5 100644 --- a/app/paint_core.c +++ b/app/paint_core.c @@ -43,6 +43,7 @@ static MaskBuf * paint_core_subsample_mask (MaskBuf *, double, double); static MaskBuf * paint_core_solidify_mask (MaskBuf *); static MaskBuf * paint_core_get_brush_mask (PaintCore *, int); static void paint_core_paste (PaintCore *, MaskBuf *, int, int, int, int, int); +static void paint_core_replace (PaintCore *, MaskBuf *, int, int, int, int); static void paint_to_canvas_tiles (PaintCore *, MaskBuf *, int); static void paint_to_canvas_buf (PaintCore *, MaskBuf *, int); static void set_undo_tiles (int, int, int, int, int); @@ -638,6 +639,29 @@ paint_core_paste_canvas (paint_core, drawable_id, brush_opacity, image_opacity, brush_opacity, image_opacity, paint_mode, mode); } +/* Similar to paint_core_paste_canvas, but replaces the alpha channel + rather than using it to composite (i.e. transparent over opaque + becomes transparent rather than opauqe. */ +void +paint_core_replace_canvas (paint_core, drawable_id, brush_opacity, image_opacity, + brush_hardness, mode) + PaintCore *paint_core; + int drawable_id; + int brush_opacity; + int image_opacity; + int brush_hardness; + int mode; +{ + MaskBuf *brush_mask; + + /* get the brush mask */ + brush_mask = paint_core_get_brush_mask (paint_core, brush_hardness); + + /* paste the canvas buf */ + paint_core_replace (paint_core, brush_mask, drawable_id, + brush_opacity, image_opacity, mode); +} + /************************************************************ * LOCAL FUNCTION DEFINITIONS * ************************************************************/ @@ -834,6 +858,85 @@ paint_core_paste (paint_core, brush_mask, drawable_id, brush_opacity, image_opac canvas_buf->width, canvas_buf->height); } +/* This works similarly to paint_core_paste. However, instead of combining + the canvas to the paint core drawable using one of the combination + modes, it uses a "replace" mode (i.e. transparent pixels in the + canvas erase the paint core drawable). + + When not drawing on alpha-enabled images, it just paints using NORMAL + mode. +*/ +static void +paint_core_replace (paint_core, brush_mask, drawable_id, brush_opacity, image_opacity, mode) + PaintCore *paint_core; + MaskBuf *brush_mask; + int drawable_id; + int brush_opacity; + int image_opacity; + int mode; +{ + GImage *gimage; + PixelRegion srcPR, maskPR; + int offx, offy; + + if (!drawable_has_alpha (drawable_id)) + { + paint_core_paste (paint_core, brush_mask, drawable_id, + brush_opacity, image_opacity, NORMAL_MODE, + mode); + return; + } + + if (mode != INCREMENTAL) + { + g_warning ("paint_core_replace only works in INCREMENTAL mode"); + return; + } + + if (! (gimage = drawable_gimage (drawable_id))) + return; + + /* set undo blocks */ + set_undo_tiles (drawable_id, + canvas_buf->x, canvas_buf->y, + canvas_buf->width, canvas_buf->height); + + maskPR.bytes = 1; + maskPR.x = 0; maskPR.y = 0; + maskPR.w = srcPR.w; + maskPR.h = srcPR.h; + maskPR.rowstride = maskPR.bytes * brush_mask->width; + maskPR.data = mask_buf_data (brush_mask); + + /* intialize canvas buf source pixel regions */ + srcPR.bytes = canvas_buf->bytes; + srcPR.x = 0; srcPR.y = 0; + srcPR.w = canvas_buf->width; + srcPR.h = canvas_buf->height; + srcPR.rowstride = canvas_buf->width * canvas_buf->bytes; + srcPR.data = temp_buf_data (canvas_buf); + + /* apply the paint area to the gimage */ + gimage_replace_image (gimage, drawable_id, &srcPR, + FALSE, image_opacity, + &maskPR, + canvas_buf->x, canvas_buf->y); + + /* Update the undo extents */ + paint_core->x1 = MINIMUM (paint_core->x1, canvas_buf->x); + paint_core->y1 = MINIMUM (paint_core->y1, canvas_buf->y); + paint_core->x2 = MAXIMUM (paint_core->x2, (canvas_buf->x + canvas_buf->width)); + paint_core->y2 = MAXIMUM (paint_core->y2, (canvas_buf->y + canvas_buf->height)); + + /* Update the gimage--it is important to call gdisplays_update_area + * instead of drawable_update because we don't want the drawable + * preview to be constantly invalidated + */ + drawable_offsets (drawable_id, &offx, &offy); + gdisplays_update_area (gimage->ID, canvas_buf->x + offx, canvas_buf->y + offy, + canvas_buf->width, canvas_buf->height); +} + static void paint_to_canvas_tiles (paint_core, brush_mask, brush_opacity) PaintCore *paint_core; diff --git a/app/paint_core.h b/app/paint_core.h index e689c7f1dc..f65fedd634 100644 --- a/app/paint_core.h +++ b/app/paint_core.h @@ -96,6 +96,7 @@ void paint_core_cleanup (void); TempBuf * paint_core_get_paint_area (PaintCore *, int); TempBuf * paint_core_get_orig_image (PaintCore *, int, int, int, int, int); void paint_core_paste_canvas (PaintCore *, int, int, int, int, int, int); +void paint_core_replace_canvas (PaintCore *, int, int, int, int, int); #endif /* __PAINT_CORE_H__ */ diff --git a/app/paint_funcs.c b/app/paint_funcs.c index 87fe60cc8d..24961bf4e1 100644 --- a/app/paint_funcs.c +++ b/app/paint_funcs.c @@ -94,6 +94,10 @@ static int * make_curve (double, int *); static void run_length_encode (unsigned char *, int *, int, int); static void draw_segments (PixelRegion *, BoundSeg *, int, int, int, int); static double cubic (double, int, int, int, int); +static void apply_layer_mode_replace (unsigned char *, unsigned char *, + unsigned char *, unsigned char *, + int, int, int, + int, int, int, int *); static unsigned char * @@ -850,6 +854,59 @@ dissolve_pixels (unsigned char *src, } } +void +replace_pixels (unsigned char *src1, + unsigned char *src2, + unsigned char *dest, + unsigned char *mask, + int length, + int opacity, + int *affect, + int b1, + int b2) +{ + int alpha; + int b; + double a_val, a_recip, mask_val; + double norm_opacity; + int s1_a, s2_a; + int new_val; + + if (b1 != b2) + { + g_warning ("replace_pixels only works on commensurate pixel regions"); + return; + } + + alpha = b1 - 1; + norm_opacity = opacity * (1.0 / 65025.0); + + while (length --) + { + mask_val = mask[0] * norm_opacity; + /* calculate new alpha first. */ + s1_a = src1[alpha]; + s2_a = src2[alpha]; + a_val = s1_a + mask_val * (s2_a - s1_a); + if (a_val == 0) + a_recip = 0; + else + a_recip = 1.0 / a_val; + /* possible optimization: fold a_recip into s1_a and s2_a */ + for (b = 0; b < alpha; b++) + { + new_val = 0.5 + a_recip * (src1[b] * s1_a + mask_val * + (src2[b] * s2_a - src1[b] * s1_a)); + dest[b] = affect[b] ? MIN (new_val, 255) : src1[b]; + } + dest[alpha] = affect[alpha] ? a_val + 0.5: s1_a; + src1 += b1; + src2 += b2; + dest += b2; + mask++; + } +} + void swap_pixels (unsigned char *src, @@ -2544,6 +2601,78 @@ convolve_region (PixelRegion *srcR, } } +/* Convert from separated alpha to premultiplied alpha. Only works on + non-tiled regions! */ +void +multiply_alpha_region (PixelRegion *srcR) +{ + unsigned char *src, *s; + int x, y; + int width, height; + int b, bytes; + double alpha_val; + + width = srcR->w; + height = srcR->h; + bytes = srcR->bytes; + + src = srcR->data; + + for (y = 0; y < height; y++) + { + s = src; + for (x = 0; x < width; x++) + { + alpha_val = s[bytes - 1] * (1.0 / 255.0); + for (b = 0; b < bytes - 1; b++) + s[b] = 0.5 + s[b] * alpha_val; + s += bytes; + } + src += srcR->rowstride; + } +} + +/* Convert from premultiplied alpha to separated alpha. Only works on + non-tiled regions! */ +void +separate_alpha_region (PixelRegion *srcR) +{ + unsigned char *src, *s; + int x, y; + int width, height; + int b, bytes; + double alpha_recip; + int new_val; + + width = srcR->w; + height = srcR->h; + bytes = srcR->bytes; + + src = srcR->data; + + for (y = 0; y < height; y++) + { + s = src; + for (x = 0; x < width; x++) + { + /* predicate is equivalent to: + (((s[bytes - 1] - 1) & 255) + 2) & 256 + */ + if (s[bytes - 1] != 0 && s[bytes - 1] != 255) + { + alpha_recip = 255.0 / s[bytes - 1]; + for (b = 0; b < bytes - 1; b++) + { + new_val = 0.5 + s[b] * alpha_recip; + new_val = MIN (new_val, 255); + s[b] = new_val; + } + } + s += bytes; + } + src += srcR->rowstride; + } +} void gaussian_blur_region (PixelRegion *srcR, @@ -3792,6 +3921,43 @@ combine_regions (PixelRegion *src1, } } +void +combine_regions_replace (PixelRegion *src1, + PixelRegion *src2, + PixelRegion *dest, + PixelRegion *mask, + unsigned char *data, + int opacity, + int *affect, + int type) +{ + int h; + unsigned char * s1, * s2; + unsigned char * d, * m; + void * pr; + + for (pr = pixel_regions_register (4, src1, src2, dest, mask); pr != NULL; pr = pixel_regions_process (pr)) + { + s1 = src1->data; + s2 = src2->data; + d = dest->data; + m = mask->data; + + for (h = 0; h < src1->h; h++) + { + + /* Now, apply the paint mode */ + apply_layer_mode_replace (s1, s2, d, m, src1->x, src1->y + h, opacity, src1->w, + src1->bytes, src2->bytes, affect); + + s1 += src1->rowstride; + s2 += src2->rowstride; + d += dest->rowstride; + m += mask->rowstride; + } + } +} + /********************************* * color conversion routines * @@ -4246,3 +4412,19 @@ apply_indexed_layer_mode (unsigned char *src1, return combine; } + +static void +apply_layer_mode_replace (unsigned char *src1, + unsigned char *src2, + unsigned char *dest, + unsigned char *mask, + int x, + int y, + int opacity, + int length, + int b1, /* bytes */ + int b2, /* bytes */ + int *affect) +{ + replace_pixels (src1, src2, dest, mask, length, opacity, affect, b1, b2); +} diff --git a/app/paint_funcs.h b/app/paint_funcs.h index 160008e4b6..d684755c79 100644 --- a/app/paint_funcs.h +++ b/app/paint_funcs.h @@ -324,6 +324,10 @@ void extract_from_region (PixelRegion *, PixelRegion *, void convolve_region (PixelRegion *, PixelRegion *, int *, int, int, int); +void multiply_alpha_region (PixelRegion *); + +void separate_alpha_region (PixelRegion *); + void gaussian_blur_region (PixelRegion *, double); void border_region (PixelRegion *, void *, @@ -402,6 +406,11 @@ void combine_regions (PixelRegion *, PixelRegion *, unsigned char *, int, int, int *, int); +void combine_regions_replace (PixelRegion *, PixelRegion *, + PixelRegion *, PixelRegion *, + unsigned char *, + int, int *, int); + /* Color conversion routines */ void rgb_to_hsv (int *, int *, int *); diff --git a/app/resize.c b/app/resize.c index aae92283ff..fe97e8bb05 100644 --- a/app/resize.c +++ b/app/resize.c @@ -31,6 +31,8 @@ struct _ResizePrivate { GtkWidget *width_text; GtkWidget *height_text; + GtkWidget *ratio_x_text; + GtkWidget *ratio_y_text; GtkWidget *off_x_text; GtkWidget *off_y_text; GtkWidget *drawing_area; @@ -50,6 +52,8 @@ static void off_x_update (GtkWidget *w, gpointer data); static void off_y_update (GtkWidget *w, gpointer data); static void width_update (GtkWidget *w, gpointer data); static void height_update (GtkWidget *w, gpointer data); +static void ratio_x_update (GtkWidget *w, gpointer data); +static void ratio_y_update (GtkWidget *w, gpointer data); static void constrain_update (GtkWidget *w, gpointer data); static gint resize_events (GtkWidget *area, GdkEvent *event); @@ -68,6 +72,7 @@ resize_widget_new (ResizeType type, GtkWidget *constrain; GtkWidget *table; char size[12]; + char ratio_text[12]; table = NULL; @@ -77,6 +82,8 @@ resize_widget_new (ResizeType type, resize->private_part = private; resize->width = width; resize->height = height; + resize->ratio_x = 1.0; + resize->ratio_y = 1.0; resize->off_x = 0; resize->off_y = 0; private->old_width = width; @@ -95,11 +102,11 @@ resize_widget_new (ResizeType type, { case ScaleWidget: resize->resize_widget = gtk_frame_new ("Scale"); - table = gtk_table_new (2, 2, TRUE); + table = gtk_table_new (4, 2, TRUE); break; case ResizeWidget: resize->resize_widget = gtk_frame_new ("Resize"); - table = gtk_table_new (4, 2, TRUE); + table = gtk_table_new (6, 2, TRUE); break; } gtk_frame_set_shadow_type (GTK_FRAME (resize->resize_widget), GTK_SHADOW_ETCHED_IN); @@ -146,17 +153,51 @@ resize_widget_new (ResizeType type, resize); gtk_widget_show (private->height_text); + /* the x scale ratio label and entry */ + sprintf (ratio_text, "%0.4f", resize->ratio_x); + label = gtk_label_new ("X ratio:"); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); + gtk_widget_show (label); + private->ratio_x_text = gtk_entry_new (); + gtk_table_attach (GTK_TABLE (table), private->ratio_x_text, 1, 2, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); + gtk_widget_set_usize (private->ratio_x_text, TEXT_WIDTH, 25); + gtk_entry_set_text (GTK_ENTRY (private->ratio_x_text), ratio_text); + gtk_signal_connect (GTK_OBJECT (private->ratio_x_text), "changed", + (GtkSignalFunc) ratio_x_update, + resize); + gtk_widget_show (private->ratio_x_text); + + /* the y scale ratio label and entry */ + sprintf (ratio_text, "%0.4f", resize->ratio_y); + label = gtk_label_new ("Y ratio:"); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); + gtk_widget_show (label); + private->ratio_y_text = gtk_entry_new (); + gtk_table_attach (GTK_TABLE (table), private->ratio_y_text, 1, 2, 3, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); + gtk_widget_set_usize (private->ratio_y_text, TEXT_WIDTH, 25); + gtk_entry_set_text (GTK_ENTRY (private->ratio_y_text), ratio_text); + gtk_signal_connect (GTK_OBJECT (private->ratio_y_text), "changed", + (GtkSignalFunc) ratio_y_update, + resize); + gtk_widget_show (private->ratio_y_text); + if (type == ResizeWidget) { /* the off_x label and entry */ sprintf (size, "%d", 0); label = gtk_label_new ("X Offset:"); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); - gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 4, 5, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); gtk_widget_show (label); private->off_x_text = gtk_entry_new (); - gtk_table_attach (GTK_TABLE (table), private->off_x_text, 1, 2, 2, 3, + gtk_table_attach (GTK_TABLE (table), private->off_x_text, 1, 2, 4, 5, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); gtk_widget_set_usize (private->off_x_text, TEXT_WIDTH, 25); gtk_entry_set_text (GTK_ENTRY (private->off_x_text), size); @@ -169,11 +210,11 @@ resize_widget_new (ResizeType type, sprintf (size, "%d", 0); label = gtk_label_new ("Y Offset:"); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); - gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4, + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 5, 6, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); gtk_widget_show (label); private->off_y_text = gtk_entry_new (); - gtk_table_attach (GTK_TABLE (table), private->off_y_text, 1, 2, 3, 4, + gtk_table_attach (GTK_TABLE (table), private->off_y_text, 1, 2, 5, 6, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 2, 2); gtk_widget_set_usize (private->off_y_text, TEXT_WIDTH, 25); gtk_entry_set_text (GTK_ENTRY (private->off_y_text), size); @@ -425,6 +466,7 @@ width_update (GtkWidget *w, double ratio; int new_height; char size[12]; + char ratio_text[12]; resize = (Resize *) data; private = (ResizePrivate *) resize->private_part; @@ -432,6 +474,14 @@ width_update (GtkWidget *w, resize->width = atoi (str); + ratio = (double) resize->width / (double) private->old_width; + resize->ratio_x = ratio; + sprintf (ratio_text, "%0.4f", ratio); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_x_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_x_text), data); + if (resize->type == ResizeWidget) { resize->off_x = resize_bound_off_x (resize, (resize->width - private->old_width) / 2); @@ -445,7 +495,6 @@ width_update (GtkWidget *w, if (private->constrain && resize->width != 0) { private->constrain = FALSE; - ratio = (double) resize->width / (double) private->old_width; new_height = (int) (private->old_height * ratio); if (new_height == 0) new_height = 1; @@ -458,6 +507,12 @@ width_update (GtkWidget *w, gtk_entry_set_text (GTK_ENTRY (private->height_text), size); gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->height_text), data); + resize->ratio_y = ratio; + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_y_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_y_text), data); + if (resize->type == ResizeWidget) { resize->off_y = resize_bound_off_y (resize, (resize->height - private->old_height) / 2); @@ -485,6 +540,7 @@ height_update (GtkWidget *w, double ratio; int new_width; char size[12]; + char ratio_text[12]; resize = (Resize *) data; private = (ResizePrivate *) resize->private_part; @@ -492,6 +548,13 @@ height_update (GtkWidget *w, resize->height = atoi (str); + ratio = (double) resize->height / (double) private->old_height; + resize->ratio_y = ratio; + sprintf (ratio_text, "%0.4f", ratio); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_y_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_y_text), data); if (resize->type == ResizeWidget) { resize->off_y = resize_bound_off_y (resize, (resize->height - private->old_height) / 2); @@ -517,6 +580,176 @@ height_update (GtkWidget *w, gtk_signal_handler_block_by_data (GTK_OBJECT (private->width_text), data); gtk_entry_set_text (GTK_ENTRY (private->width_text), size); gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->width_text), data); + + resize->ratio_x = ratio; + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_x_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_x_text), data); + + if (resize->type == ResizeWidget) + { + resize->off_x = resize_bound_off_x (resize, (resize->width - private->old_width) / 2); + sprintf (size, "%d", resize->off_x); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->off_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->off_x_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->off_x_text), data); + } + } + + private->constrain = TRUE; + } + + resize_draw (resize); +} + +static void +ratio_x_update (GtkWidget *w, + gpointer data) +{ + Resize *resize; + ResizePrivate *private; + char *str; + int new_width; + int new_height; + char size[12]; + char ratio_text[12]; + + resize = (Resize *) data; + private = (ResizePrivate *) resize->private_part; + str = gtk_entry_get_text (GTK_ENTRY (w)); + + resize->ratio_x = atof (str); + + new_width = (int) ((double) private->old_width * resize->ratio_x); + + if (new_width != resize->width) + { + resize->width = new_width; + sprintf (size, "%d", new_width); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->width_text), data); + gtk_entry_set_text (GTK_ENTRY (private->width_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->width_text), data); + + if (resize->type == ResizeWidget) + { + resize->off_x = resize_bound_off_x (resize, (resize->width - private->old_width) / 2); + sprintf (size, "%d", resize->off_x); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->off_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->off_x_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->off_x_text), data); + } + } + + if (private->constrain && resize->width != 0) + { + private->constrain = FALSE; + + resize->ratio_y = resize->ratio_x; + + new_height = (int) (private->old_height * resize->ratio_y); + if (new_height == 0) new_height = 1; + + if (new_height != resize->height) + { + resize->height = new_height; + + sprintf (size, "%d", resize->height); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->height_text), data); + gtk_entry_set_text (GTK_ENTRY (private->height_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->height_text), data); + + sprintf (ratio_text, "%0.4f", resize->ratio_y); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_y_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_y_text), data); + + if (resize->type == ResizeWidget) + { + resize->off_y = resize_bound_off_y (resize, (resize->height - private->old_height) / 2); + sprintf (size, "%d", resize->off_y); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->off_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->off_y_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->off_y_text), data); + } + } + + private->constrain = TRUE; + } + + resize_draw (resize); +} + +static void +ratio_y_update (GtkWidget *w, + gpointer data) +{ + Resize *resize; + ResizePrivate *private; + char *str; + int new_width; + int new_height; + char size[12]; + char ratio_text[12]; + + resize = (Resize *) data; + private = (ResizePrivate *) resize->private_part; + str = gtk_entry_get_text (GTK_ENTRY (w)); + + resize->ratio_y = atof (str); + + new_height = (int) ((double) private->old_height * resize->ratio_y); + + if (new_height != resize->height) + { + resize->height = new_height; + sprintf (size, "%d", new_height); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->height_text), data); + gtk_entry_set_text (GTK_ENTRY (private->height_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->height_text), data); + + if (resize->type == ResizeWidget) + { + resize->off_y = resize_bound_off_y (resize, (resize->height - private->old_height) / 2); + sprintf (size, "%d", resize->off_y); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->off_y_text), data); + gtk_entry_set_text (GTK_ENTRY (private->off_y_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->off_y_text), data); + } + } + + if (private->constrain && resize->height != 0) + { + private->constrain = FALSE; + + resize->ratio_x = resize->ratio_y; + + new_width = (int) (private->old_width * resize->ratio_x); + if (new_width == 0) new_width = 1; + + if (new_width != resize->width) + { + resize->width = new_width; + + sprintf (size, "%d", resize->width); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->width_text), data); + gtk_entry_set_text (GTK_ENTRY (private->width_text), size); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->width_text), data); + + sprintf (ratio_text, "%0.4f", resize->ratio_x); + + gtk_signal_handler_block_by_data (GTK_OBJECT (private->ratio_x_text), data); + gtk_entry_set_text (GTK_ENTRY (private->ratio_x_text), ratio_text); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (private->ratio_x_text), data); if (resize->type == ResizeWidget) { diff --git a/app/resize.h b/app/resize.h index a14db9ae55..551b688dae 100644 --- a/app/resize.h +++ b/app/resize.h @@ -34,6 +34,8 @@ struct _Resize ResizeType type; int width; int height; + double ratio_x; + double ratio_y; int off_x; int off_y; diff --git a/app/tools/convolve.c b/app/tools/convolve.c index 406f724a75..0d35899fa9 100644 --- a/app/tools/convolve.c +++ b/app/tools/convolve.c @@ -266,6 +266,11 @@ convolve_motion (PaintCore *paint_core, /* if the source has no alpha, then add alpha pixels */ if (!drawable_has_alpha (drawable_id)) { + /* note: this architecture needlessly convolves the totally- + opaque alpha channel. A faster approach would be to keep + tempPR the same number of bytes as srcPR, and extend the + paint_core_replace_canvas API to handle non-alpha images. */ + tempPR.bytes = srcPR.bytes + 1; tempPR.x = 0; tempPR.y = 0; tempPR.w = area->width; @@ -287,6 +292,13 @@ convolve_motion (PaintCore *paint_core, tempPR.data = temp_data; copy_region (&srcPR, &tempPR); + + tempPR.x = 0; tempPR.y = 0; + tempPR.w = area->width; + tempPR.h = area->height; + tempPR.data = temp_data; + + multiply_alpha_region (&tempPR); } tempPR.x = 0; tempPR.y = 0; @@ -298,6 +310,9 @@ convolve_motion (PaintCore *paint_core, convolve_region (&tempPR, &destPR, matrix, matrix_size, matrix_divisor, NORMAL); + if (drawable_has_alpha (drawable_id)) + separate_alpha_region (&destPR); + /* Free the allocated temp space */ g_free (temp_data); } @@ -311,9 +326,9 @@ convolve_motion (PaintCore *paint_core, } /* paste the newly painted canvas to the gimage which is being worked on */ - paint_core_paste_canvas (paint_core, drawable_id, OPAQUE, + paint_core_replace_canvas (paint_core, drawable_id, OPAQUE, (int) (get_brush_opacity () * 255), - NORMAL_MODE, SOFT, INCREMENTAL); + SOFT, INCREMENTAL); } static void diff --git a/app/tools/gimpconvolvetool.c b/app/tools/gimpconvolvetool.c index 406f724a75..0d35899fa9 100644 --- a/app/tools/gimpconvolvetool.c +++ b/app/tools/gimpconvolvetool.c @@ -266,6 +266,11 @@ convolve_motion (PaintCore *paint_core, /* if the source has no alpha, then add alpha pixels */ if (!drawable_has_alpha (drawable_id)) { + /* note: this architecture needlessly convolves the totally- + opaque alpha channel. A faster approach would be to keep + tempPR the same number of bytes as srcPR, and extend the + paint_core_replace_canvas API to handle non-alpha images. */ + tempPR.bytes = srcPR.bytes + 1; tempPR.x = 0; tempPR.y = 0; tempPR.w = area->width; @@ -287,6 +292,13 @@ convolve_motion (PaintCore *paint_core, tempPR.data = temp_data; copy_region (&srcPR, &tempPR); + + tempPR.x = 0; tempPR.y = 0; + tempPR.w = area->width; + tempPR.h = area->height; + tempPR.data = temp_data; + + multiply_alpha_region (&tempPR); } tempPR.x = 0; tempPR.y = 0; @@ -298,6 +310,9 @@ convolve_motion (PaintCore *paint_core, convolve_region (&tempPR, &destPR, matrix, matrix_size, matrix_divisor, NORMAL); + if (drawable_has_alpha (drawable_id)) + separate_alpha_region (&destPR); + /* Free the allocated temp space */ g_free (temp_data); } @@ -311,9 +326,9 @@ convolve_motion (PaintCore *paint_core, } /* paste the newly painted canvas to the gimage which is being worked on */ - paint_core_paste_canvas (paint_core, drawable_id, OPAQUE, + paint_core_replace_canvas (paint_core, drawable_id, OPAQUE, (int) (get_brush_opacity () * 255), - NORMAL_MODE, SOFT, INCREMENTAL); + SOFT, INCREMENTAL); } static void diff --git a/app/tools/paint_core.c b/app/tools/paint_core.c index b0975289b6..e7df5d55b5 100644 --- a/app/tools/paint_core.c +++ b/app/tools/paint_core.c @@ -43,6 +43,7 @@ static MaskBuf * paint_core_subsample_mask (MaskBuf *, double, double); static MaskBuf * paint_core_solidify_mask (MaskBuf *); static MaskBuf * paint_core_get_brush_mask (PaintCore *, int); static void paint_core_paste (PaintCore *, MaskBuf *, int, int, int, int, int); +static void paint_core_replace (PaintCore *, MaskBuf *, int, int, int, int); static void paint_to_canvas_tiles (PaintCore *, MaskBuf *, int); static void paint_to_canvas_buf (PaintCore *, MaskBuf *, int); static void set_undo_tiles (int, int, int, int, int); @@ -638,6 +639,29 @@ paint_core_paste_canvas (paint_core, drawable_id, brush_opacity, image_opacity, brush_opacity, image_opacity, paint_mode, mode); } +/* Similar to paint_core_paste_canvas, but replaces the alpha channel + rather than using it to composite (i.e. transparent over opaque + becomes transparent rather than opauqe. */ +void +paint_core_replace_canvas (paint_core, drawable_id, brush_opacity, image_opacity, + brush_hardness, mode) + PaintCore *paint_core; + int drawable_id; + int brush_opacity; + int image_opacity; + int brush_hardness; + int mode; +{ + MaskBuf *brush_mask; + + /* get the brush mask */ + brush_mask = paint_core_get_brush_mask (paint_core, brush_hardness); + + /* paste the canvas buf */ + paint_core_replace (paint_core, brush_mask, drawable_id, + brush_opacity, image_opacity, mode); +} + /************************************************************ * LOCAL FUNCTION DEFINITIONS * ************************************************************/ @@ -834,6 +858,85 @@ paint_core_paste (paint_core, brush_mask, drawable_id, brush_opacity, image_opac canvas_buf->width, canvas_buf->height); } +/* This works similarly to paint_core_paste. However, instead of combining + the canvas to the paint core drawable using one of the combination + modes, it uses a "replace" mode (i.e. transparent pixels in the + canvas erase the paint core drawable). + + When not drawing on alpha-enabled images, it just paints using NORMAL + mode. +*/ +static void +paint_core_replace (paint_core, brush_mask, drawable_id, brush_opacity, image_opacity, mode) + PaintCore *paint_core; + MaskBuf *brush_mask; + int drawable_id; + int brush_opacity; + int image_opacity; + int mode; +{ + GImage *gimage; + PixelRegion srcPR, maskPR; + int offx, offy; + + if (!drawable_has_alpha (drawable_id)) + { + paint_core_paste (paint_core, brush_mask, drawable_id, + brush_opacity, image_opacity, NORMAL_MODE, + mode); + return; + } + + if (mode != INCREMENTAL) + { + g_warning ("paint_core_replace only works in INCREMENTAL mode"); + return; + } + + if (! (gimage = drawable_gimage (drawable_id))) + return; + + /* set undo blocks */ + set_undo_tiles (drawable_id, + canvas_buf->x, canvas_buf->y, + canvas_buf->width, canvas_buf->height); + + maskPR.bytes = 1; + maskPR.x = 0; maskPR.y = 0; + maskPR.w = srcPR.w; + maskPR.h = srcPR.h; + maskPR.rowstride = maskPR.bytes * brush_mask->width; + maskPR.data = mask_buf_data (brush_mask); + + /* intialize canvas buf source pixel regions */ + srcPR.bytes = canvas_buf->bytes; + srcPR.x = 0; srcPR.y = 0; + srcPR.w = canvas_buf->width; + srcPR.h = canvas_buf->height; + srcPR.rowstride = canvas_buf->width * canvas_buf->bytes; + srcPR.data = temp_buf_data (canvas_buf); + + /* apply the paint area to the gimage */ + gimage_replace_image (gimage, drawable_id, &srcPR, + FALSE, image_opacity, + &maskPR, + canvas_buf->x, canvas_buf->y); + + /* Update the undo extents */ + paint_core->x1 = MINIMUM (paint_core->x1, canvas_buf->x); + paint_core->y1 = MINIMUM (paint_core->y1, canvas_buf->y); + paint_core->x2 = MAXIMUM (paint_core->x2, (canvas_buf->x + canvas_buf->width)); + paint_core->y2 = MAXIMUM (paint_core->y2, (canvas_buf->y + canvas_buf->height)); + + /* Update the gimage--it is important to call gdisplays_update_area + * instead of drawable_update because we don't want the drawable + * preview to be constantly invalidated + */ + drawable_offsets (drawable_id, &offx, &offy); + gdisplays_update_area (gimage->ID, canvas_buf->x + offx, canvas_buf->y + offy, + canvas_buf->width, canvas_buf->height); +} + static void paint_to_canvas_tiles (paint_core, brush_mask, brush_opacity) PaintCore *paint_core; diff --git a/app/tools/paint_core.h b/app/tools/paint_core.h index e689c7f1dc..f65fedd634 100644 --- a/app/tools/paint_core.h +++ b/app/tools/paint_core.h @@ -96,6 +96,7 @@ void paint_core_cleanup (void); TempBuf * paint_core_get_paint_area (PaintCore *, int); TempBuf * paint_core_get_orig_image (PaintCore *, int, int, int, int, int); void paint_core_paste_canvas (PaintCore *, int, int, int, int, int, int); +void paint_core_replace_canvas (PaintCore *, int, int, int, int, int); #endif /* __PAINT_CORE_H__ */