/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * IfsCompose is a interface for creating IFS fractals by * direct manipulation. * Copyright (C) 1997 Owen Taylor * * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include "ifs-compose.h" typedef struct { GdkPoint point; gdouble angle; } SortPoint; /* local functions */ static void aff_element_compute_click_boundary (AffElement *elem, gint num_elements, gdouble *points_x, gdouble *points_y); static guchar * create_brush (IfsComposeVals *ifsvals, gint *brush_size, gdouble *brush_offset); void aff2_translate (Aff2 *naff, gdouble x, gdouble y) { naff->a11 = 1.0; naff->a12 = 0; naff->a21 = 0; naff->a22 = 1.0; naff->b1 = x; naff->b2 = y; } void aff2_rotate (Aff2 *naff, gdouble theta) { naff->a11 = cos(theta); naff->a12 = sin(theta); naff->a21 = -naff->a12; naff->a22 = naff->a11; naff->b1 = 0; naff->b2 = 0; } void aff2_scale (Aff2 *naff, gdouble s, gboolean flip) { if (flip) naff->a11 = -s; else naff->a11 = s; naff->a12 = 0; naff->a21 = 0; naff->a22 = s; naff->b1 = 0; naff->b2 = 0; } /* Create a unitary transform with given x-y asymmetry and shear */ void aff2_distort (Aff2 *naff, gdouble asym, gdouble shear) { naff->a11 = asym; naff->a22 = 1/asym; naff->a12 = shear; naff->a21 = 0; naff->b1 = 0; naff->b2 = 0; } /* Find a pure stretch in some direction that brings xo,yo to xn,yn */ void aff2_compute_stretch (Aff2 *naff, gdouble xo, gdouble yo, gdouble xn, gdouble yn) { gdouble denom = xo*xn + yo*yn; if (denom == 0.0) /* singular */ { naff->a11 = 1.0; naff->a12 = 0.0; naff->a21 = 0.0; naff->a22 = 1.0; } else { naff->a11 = (SQR(xn) + SQR(yo)) / denom; naff->a22 = (SQR(xo) + SQR(yn)) / denom; naff->a12 = naff->a21 = (xn * yn - xo * yo) / denom; } naff->b1 = 0.0; naff->b2 = 0.0; } void aff2_compose (Aff2 *naff, Aff2 *aff1, Aff2 *aff2) { naff->a11 = aff1->a11 * aff2->a11 + aff1->a12 * aff2->a21; naff->a12 = aff1->a11 * aff2->a12 + aff1->a12 * aff2->a22; naff->b1 = aff1->a11 * aff2->b1 + aff1->a12 * aff2->b2 + aff1->b1; naff->a21 = aff1->a21 * aff2->a11 + aff1->a22 * aff2->a21; naff->a22 = aff1->a21 * aff2->a12 + aff1->a22 * aff2->a22; naff->b2 = aff1->a21 * aff2->b1 + aff1->a22 * aff2->b2 + aff1->b2; } /* Returns the identity matrix if the original matrix was singular */ void aff2_invert (Aff2 *naff, Aff2 *aff) { gdouble det = aff->a11 * aff->a22 - aff->a12 * aff->a21; if (det==0) { aff2_scale (naff, 1.0, 0); } else { naff->a11 = aff->a22 / det; naff->a22 = aff->a11 / det; naff->a21 = - aff->a21 / det; naff->a12 = - aff->a12 / det; naff->b1 = - naff->a11 * aff->b1 - naff->a12 * aff->b2; naff->b2 = - naff->a21 * aff->b1 - naff->a22 * aff->b2; } } void aff2_apply (Aff2 *aff, gdouble x, gdouble y, gdouble *xf, gdouble *yf) { gdouble xt = aff->a11 * x + aff->a12 * y + aff->b1; gdouble yt = aff->a21 * x + aff->a22 * y + aff->b2; *xf = xt; *yf = yt; } /* Find the fixed point of an affine transformation (Will return garbage for pure translations) */ void aff2_fixed_point (Aff2 *aff, gdouble *xf, gdouble *yf) { Aff2 t1,t2; t1.a11 = 1 - aff->a11; t1.a22 = 1 - aff->a22; t1.a12 = -aff->a12; t1.a21 = -aff->a21; t1.b1 = 0; t1.b2 = 0; aff2_invert (&t2, &t1); aff2_apply (&t2, aff->b1, aff->b2, xf, yf); } void aff3_apply (Aff3 *t, gdouble x, gdouble y, gdouble z, gdouble *xf, gdouble *yf, gdouble *zf) { gdouble xt = (t->vals[0][0] * x + t->vals[0][1] * y + t->vals[0][2] * z + t->vals[0][3]); gdouble yt = (t->vals[1][0] * x + t->vals[1][1] * y + t->vals[1][2] * z + t->vals[1][3]); gdouble zt = (t->vals[2][0] * x + t->vals[2][1] * y + t->vals[2][2] * z + t->vals[2][3]); *xf = xt; *yf = yt; *zf = zt; } static int ipolygon_sort_func (const void *a, const void *b) { if (((SortPoint *)a)->angle < ((SortPoint *)b)->angle) return -1; else if (((SortPoint *)a)->angle > ((SortPoint *)b)->angle) return 1; else return 0; } /* Return a newly-allocated polygon which is the convex hull of the given polygon. Uses the Graham scan. see http://www.cs.curtin.edu.au/units/cg201/notes/node77.html for a description */ IPolygon * ipolygon_convex_hull (IPolygon *poly) { gint num_new = poly->npoints; GdkPoint *new_points = g_new (GdkPoint, num_new); SortPoint *sort_points = g_new (SortPoint, num_new); IPolygon *new_poly = g_new (IPolygon, 1); gint i, j; gint x1, x2, y1, y2; gint lowest; GdkPoint lowest_pt; new_poly->points = new_points; if (num_new <= 3) { memcpy (new_points, poly->points, num_new * sizeof (GdkPoint)); new_poly->npoints = num_new; g_free (sort_points); return new_poly; } /* scan for the lowest point */ lowest_pt = poly->points[0]; lowest = 0; for (i = 1; i < num_new; i++) if (poly->points[i].y < lowest_pt.y) { lowest_pt = poly->points[i]; lowest = i; } /* sort by angle from lowest point */ for (i = 0, j = 0; i < num_new; i++, j++) { if (i==lowest) j--; else { gdouble dy = poly->points[i].y - lowest_pt.y; gdouble dx = poly->points[i].x - lowest_pt.x; if (dy == 0 && dx == 0) { j--; num_new--; continue; } sort_points[j].point = poly->points[i]; sort_points[j].angle = atan2 (dy, dx); } } qsort (sort_points, num_new - 1, sizeof (SortPoint), ipolygon_sort_func); /* now ensure that all turns as we trace the perimeter are counter-clockwise */ new_points[0] = lowest_pt; new_points[1] = sort_points[0].point; x1 = new_points[1].x - new_points[0].x; y1 = new_points[1].y - new_points[0].y; for (i = 1, j = 2; j < num_new; i++, j++) { x2 = sort_points[i].point.x - new_points[j - 1].x; y2 = sort_points[i].point.y - new_points[j - 1].y; if (x2 == 0 && y2 == 0) { num_new--; j--; continue; } while (x1 * y2 - x2 * y1 < 0) /* clockwise rotation */ { num_new--; j--; x1 = new_points[j - 1].x - new_points[j - 2].x; y1 = new_points[j - 1].y - new_points[j - 2].y; x2 = sort_points[i].point.x - new_points[j - 1].x; y2 = sort_points[i].point.y - new_points[j - 1].y; } new_points[j] = sort_points[i].point; x1 = x2; y1 = y2; } g_free (sort_points); new_poly->npoints = num_new; return new_poly; } /* Determines whether a specified point is in the given polygon. Based on inpoly.c by Bob Stein and Craig Yap. (Linux Journal, Issue 35 (March 1997), p 68) */ gint ipolygon_contains (IPolygon *poly, gint xt, gint yt) { gint xnew, ynew; gint xold, yold; gint x1,y1; gint x2,y2; gint i; gint inside = 0; if (poly->npoints < 3) return 0; xold=poly->points[poly->npoints - 1].x; yold=poly->points[poly->npoints - 1].y; for (i = 0; i < poly->npoints; i++) { xnew = poly->points[i].x; ynew = poly->points[i].y; if (xnew > xold) { x1 = xold; x2 = xnew; y1 = yold; y2 = ynew; } else { x1 = xnew; x2 = xold; y1 = ynew; y2 = yold; } if ((xnew < xt) == (xt <= xold) && (yt - y1)*(x2 - x1) < (y2 - y1)*(xt - x1)) inside = !inside; xold = xnew; yold = ynew; } return inside; } void aff_element_compute_color_trans (AffElement *elem) { int i, j; if (elem->v.simple_color) { gdouble mag2; mag2 = SQR (elem->v.target_color.r); mag2 += SQR (elem->v.target_color.g); mag2 += SQR (elem->v.target_color.b); /* For mag2 == 0, the transformation blows up in general but is well defined for hue_scale == value_scale, so we assume that special case. */ if (mag2 == 0) for (i = 0; i < 3; i++) { for (j = 0; j < 4; j++) elem->color_trans.vals[i][j] = 0.0; elem->color_trans.vals[i][i] = elem->v.hue_scale; } else { /* red */ for (j = 0; j < 3; j++) { elem->color_trans.vals[0][j] = elem->v.target_color.r / mag2 * (elem->v.value_scale - elem->v.hue_scale); } /* green */ for (j = 0; j < 3; j++) { elem->color_trans.vals[1][j] = elem->v.target_color.g / mag2 * (elem->v.value_scale - elem->v.hue_scale); } /* blue */ for (j = 0; j < 3; j++) { elem->color_trans.vals[2][j] = elem->v.target_color.g / mag2 * (elem->v.value_scale - elem->v.hue_scale); } elem->color_trans.vals[0][0] += elem->v.hue_scale; elem->color_trans.vals[1][1] += elem->v.hue_scale; elem->color_trans.vals[2][2] += elem->v.hue_scale; elem->color_trans.vals[0][3] = (1 - elem->v.value_scale) * elem->v.target_color.r; elem->color_trans.vals[1][3] = (1 - elem->v.value_scale) * elem->v.target_color.g; elem->color_trans.vals[2][3] = (1 - elem->v.value_scale) * elem->v.target_color.b; } aff3_apply (&elem->color_trans, 1.0, 0.0, 0.0, &elem->v.red_color.r, &elem->v.red_color.g, &elem->v.red_color.b); aff3_apply (&elem->color_trans, 0.0, 1.0, 0.0, &elem->v.green_color.r, &elem->v.green_color.g, &elem->v.green_color.b); aff3_apply (&elem->color_trans, 0.0, 0.0, 1.0, &elem->v.blue_color.r, &elem->v.blue_color.g, &elem->v.blue_color.b); aff3_apply (&elem->color_trans, 0.0, 0.0, 0.0, &elem->v.black_color.r, &elem->v.black_color.g, &elem->v.black_color.b); } else { elem->color_trans.vals[0][0] = elem->v.red_color.r - elem->v.black_color.r; elem->color_trans.vals[1][0] = elem->v.red_color.g - elem->v.black_color.g; elem->color_trans.vals[2][0] = elem->v.red_color.b - elem->v.black_color.b; elem->color_trans.vals[0][1] = elem->v.green_color.r - elem->v.black_color.r; elem->color_trans.vals[1][1] = elem->v.green_color.g - elem->v.black_color.g; elem->color_trans.vals[2][1] = elem->v.green_color.b - elem->v.black_color.b; elem->color_trans.vals[0][2] = elem->v.blue_color.r - elem->v.black_color.r; elem->color_trans.vals[1][2] = elem->v.blue_color.g - elem->v.black_color.g; elem->color_trans.vals[2][2] = elem->v.blue_color.b - elem->v.black_color.b; elem->color_trans.vals[0][3] = elem->v.black_color.r; elem->color_trans.vals[1][3] = elem->v.black_color.g; elem->color_trans.vals[2][3] = elem->v.black_color.b; } } void aff_element_compute_trans (AffElement *elem, gdouble width, gdouble height, gdouble center_x, gdouble center_y) { Aff2 t1, t2, t3; /* create the rotation, scaling and shearing part of the transform */ aff2_distort (&t1, elem->v.asym, elem->v.shear); aff2_scale (&t2, elem->v.scale, elem->v.flip); aff2_compose (&t3, &t2, &t1); aff2_rotate (&t2, elem->v.theta); aff2_compose (&t1, &t2, &t3); /* now create the translational part */ aff2_translate (&t2, -center_x*width, -center_y*width); aff2_compose (&t3, &t1, &t2); aff2_translate (&t2, elem->v.x*width, elem->v.y*width); aff2_compose (&elem->trans, &t2, &t3); } void aff_element_decompose_trans (AffElement *elem, Aff2 *aff, gdouble width, gdouble height, gdouble center_x, gdouble center_y) { Aff2 t1, t2; gdouble det, scale, sign; /* pull of the translational parts */ aff2_translate (&t1,center_x * width, center_y * width); aff2_compose (&t2, aff, &t1); elem->v.x = t2.b1 / width; elem->v.y = t2.b2 / width; det = t2.a11 * t2.a22 - t2.a12 * t2.a21; if (det == 0.0) { elem->v.scale = 0.0; elem->v.theta = 0.0; elem->v.asym = 1.0; elem->v.shear = 0.0; elem->v.flip = 0; } else { if (det >= 0) { scale = elem->v.scale = sqrt (det); sign = 1; elem->v.flip = 0; } else { scale = elem->v.scale = sqrt (-det); sign = -1; elem->v.flip = 1; } elem->v.theta = atan2 (-t2.a21, t2.a11); if (cos (elem->v.theta) == 0.0) { elem->v.asym = - t2.a21 / scale / sin (elem->v.theta); elem->v.shear = - sign * t2.a22 / scale / sin (elem->v.theta); } else { elem->v.asym = sign * t2.a11 / scale / cos (elem->v.theta); elem->v.shear = sign * (t2.a12/scale - sin (elem->v.theta)/elem->v.asym) / cos (elem->v.theta); } } } static void aff_element_compute_click_boundary (AffElement *elem, int num_elements, gdouble *points_x, gdouble *points_y) { gint i; gdouble xtot = 0; gdouble ytot = 0; gdouble xc, yc; gdouble theta; gdouble sth, cth; /* sin(theta), cos(theta) */ gdouble axis1, axis2; gdouble axis1max, axis2max, axis1min, axis2min; /* compute the center of mass of the points */ for (i = 0; i < num_elements; i++) { xtot += points_x[i]; ytot += points_y[i]; } xc = xtot / num_elements; yc = ytot / num_elements; /* compute the sum of the (x+iy)^2, and take half the the resulting angle (xtot+iytot = A*exp(2i*theta)), to get an average direction */ xtot = 0; ytot = 0; for (i = 0; i < num_elements; i++) { xtot += SQR (points_x[i] - xc) - SQR (points_y[i] - yc); ytot += 2 * (points_x[i] - xc) * (points_y[i] - yc); } theta = 0.5 * atan2 (ytot, xtot); sth = sin (theta); cth = cos (theta); /* compute the minimum rectangle at angle theta that bounds the points, 1/2 side lengths left in axis1, axis2, center in xc, yc */ axis1max = axis1min = 0.0; axis2max = axis2min = 0.0; for (i = 0; i < num_elements; i++) { gdouble proj1 = (points_x[i] - xc) * cth + (points_y[i] - yc) * sth; gdouble proj2 = -(points_x[i] - xc) * sth + (points_y[i] - yc) * cth; if (proj1 < axis1min) axis1min = proj1; if (proj1 > axis1max) axis1max = proj1; if (proj2 < axis2min) axis2min = proj2; if (proj2 > axis2max) axis2max = proj2; } axis1 = 0.5 * (axis1max - axis1min); axis2 = 0.5 * (axis2max - axis2min); xc += 0.5 * ((axis1max + axis1min) * cth - (axis2max + axis2min) * sth); yc += 0.5 * ((axis1max + axis1min) * sth + (axis2max + axis2min) * cth); /* if the the rectangle is less than 10 pixels in any dimension, make it click_boundary, otherwise set click_boundary = draw_boundary */ if (axis1 < 8.0 || axis2 < 8.0) { GdkPoint *points = g_new (GdkPoint, 4); elem->click_boundary = g_new (IPolygon, 1); elem->click_boundary->points = points; elem->click_boundary->npoints = 4; if (axis1 < 8.0) axis1 = 8.0; if (axis2 < 8.0) axis2 = 8.0; points[0].x = xc + axis1 * cth - axis2 * sth; points[0].y = yc + axis1 * sth + axis2 * cth; points[1].x = xc - axis1 * cth - axis2 * sth; points[1].y = yc - axis1 * sth + axis2 * cth; points[2].x = xc - axis1 * cth + axis2 * sth; points[2].y = yc - axis1 * sth - axis2 * cth; points[3].x = xc + axis1 * cth + axis2 * sth; points[3].y = yc + axis1 * sth - axis2 * cth; } else elem->click_boundary = elem->draw_boundary; } void aff_element_compute_boundary (AffElement *elem, gint width, gint height, AffElement **elements, gint num_elements) { gint i; IPolygon tmp_poly; gdouble *points_x; gdouble *points_y; if (elem->click_boundary && elem->click_boundary != elem->draw_boundary) g_free (elem->click_boundary); if (elem->draw_boundary) g_free (elem->draw_boundary); tmp_poly.npoints = num_elements; tmp_poly.points = g_new (GdkPoint, num_elements); points_x = g_new (gdouble, num_elements); points_y = g_new (gdouble, num_elements); for (i = 0; i < num_elements; i++) { aff2_apply (&elem->trans, elements[i]->v.x * width, elements[i]->v.y * width, &points_x[i],&points_y[i]); tmp_poly.points[i].x = (gint)points_x[i]; tmp_poly.points[i].y = (gint)points_y[i]; } elem->draw_boundary = ipolygon_convex_hull (&tmp_poly); aff_element_compute_click_boundary (elem, num_elements, points_x, points_y); g_free (tmp_poly.points); } void aff_element_draw (AffElement *elem, gboolean selected, gint width, gint height, cairo_t *cr, GdkColor *color, PangoLayout *layout) { PangoRectangle rect; gint i; pango_layout_set_text (layout, elem->name, -1); pango_layout_get_pixel_extents (layout, NULL, &rect); gdk_cairo_set_source_color (cr, color); cairo_move_to (cr, elem->v.x * width - rect.width / 2, elem->v.y * width + rect.height / 2); pango_cairo_show_layout (cr, layout); cairo_fill (cr); cairo_set_line_width (cr, 1.0); if (elem->click_boundary != elem->draw_boundary) { cairo_move_to (cr, elem->click_boundary->points[0].x, elem->click_boundary->points[0].y); for (i = 1; i < elem->click_boundary->npoints; i++) cairo_line_to (cr, elem->click_boundary->points[i].x, elem->click_boundary->points[i].y); cairo_close_path (cr); cairo_stroke (cr); } if (selected) cairo_set_line_width (cr, 3.0); cairo_move_to (cr, elem->draw_boundary->points[0].x, elem->draw_boundary->points[0].y); for (i = 1; i < elem->draw_boundary->npoints; i++) cairo_line_to (cr, elem->draw_boundary->points[i].x, elem->draw_boundary->points[i].y); cairo_close_path (cr); cairo_stroke (cr); } AffElement * aff_element_new (gdouble x, gdouble y, GimpRGB *color, gint count) { AffElement *elem = g_new (AffElement, 1); gchar buffer[16]; elem->v.x = x; elem->v.y = y; elem->v.theta = 0.0; elem->v.scale = 0.5; elem->v.asym = 1.0; elem->v.shear = 0.0; elem->v.flip = 0; elem->v.red_color = *color; elem->v.blue_color = *color; elem->v.green_color = *color; elem->v.black_color = *color; elem->v.target_color = *color; elem->v.hue_scale = 0.5; elem->v.value_scale = 0.5; elem->v.simple_color = TRUE; elem->draw_boundary = NULL; elem->click_boundary = NULL; aff_element_compute_color_trans (elem); elem->v.prob = 1.0; sprintf (buffer,"%d", count); elem->name = g_strdup (buffer); return elem; } void aff_element_free (AffElement *elem) { if (elem->click_boundary != elem->draw_boundary) g_free (elem->click_boundary); g_free (elem->draw_boundary); g_free (elem); } #ifdef DEBUG_BRUSH static brush_chars[] = {' ',':','*','@'}; #endif static guchar * create_brush (IfsComposeVals *ifsvals, gint *brush_size, gdouble *brush_offset) { gint i, j; gint ii, jj; guchar *brush; #ifdef DEBUG_BRUSH gdouble totpix = 0.0; #endif gdouble radius = ifsvals->radius * ifsvals->subdivide; *brush_size = ceil (2 * radius); *brush_offset = 0.5 * (*brush_size - 1); brush = g_new (guchar, SQR (*brush_size)); for (i = 0; i < *brush_size; i++) { for (j = 0; j < *brush_size; j++) { gdouble pixel = 0.0; gdouble d = sqrt (SQR (i - *brush_offset) + SQR (j - *brush_offset)); if (d - 0.5 * G_SQRT2 > radius) pixel = 0.0; else if (d + 0.5 * G_SQRT2 < radius) pixel = 1.0; else for (ii = 0; ii < 10; ii++) for (jj = 0; jj < 10; jj++) { d = sqrt (SQR (i - *brush_offset + ii * 0.1 - 0.45) + SQR (j - *brush_offset + jj * 0.1 - 0.45)); pixel += (d < radius) / 100.0; } brush[i * *brush_size + j] = 255.999 * pixel; #ifdef DEBUG_BRUSH putchar(brush_chars[(gint)(pixel * 3.999)]); totpix += pixel; #endif /* DEBUG_BRUSH */ } #ifdef DEBUG_BRUSH putchar('\n'); #endif /* DEBUG_BRUSH */ } #ifdef DEBUG_BRUSH printf ("Brush total / area = %f\n", totpix / SQR (ifsvals->subdivide)); #endif /* DEBUG_BRUSH */ return brush; } void ifs_render (AffElement **elements, gint num_elements, gint width, gint height, gint nsteps, IfsComposeVals *vals, gint band_y, gint band_height, guchar *data, guchar *mask, guchar *nhits, gboolean preview) { gint i, k, n; gdouble x, y; gdouble r, g, b; gint ri, gi, bi; guint32 p0, psum; gdouble pt; guchar *ptr; guint32 *prob; gdouble *fprob; gint subdivide; guchar *brush = NULL; gint brush_size = 1; gdouble brush_offset = 0.0; if (preview) subdivide = 1; else subdivide = vals->subdivide; /* compute the probabilities and transforms */ fprob = g_new (gdouble, num_elements); prob = g_new (guint32, num_elements); pt = 0.0; for (i = 0; i < num_elements; i++) { aff_element_compute_trans(elements[i], width * subdivide, height * subdivide, vals->center_x, vals->center_y); fprob[i] = fabs( elements[i]->trans.a11 * elements[i]->trans.a22 - elements[i]->trans.a12 * elements[i]->trans.a21); /* As a heuristic, if the determinant is really small, it's probably a line element, so increase the probability so it gets rendered */ /* FIXME: figure out what 0.01 really should be */ if (fprob[i] < 0.01) fprob[i] = 0.01; fprob[i] *= elements[i]->v.prob; pt += fprob[i]; } psum = 0; for (i = 0; i < num_elements; i++) { psum += (guint32) -1 * (fprob[i] / pt); prob[i] = psum; } prob[i - 1] = (guint32) -1; /* make sure we don't get bitten by roundoff */ /* create the brush */ if (!preview) brush = create_brush (vals, &brush_size, &brush_offset); x = y = 0; r = g = b = 0; /* n is used to limit the number of progress updates */ n = nsteps / 32; /* now run the iteration */ for (i = 0; i < nsteps; i++) { if (!preview && ((i % n) == 0)) gimp_progress_update ((gdouble) i / (gdouble) nsteps); p0 = g_random_int (); k = 0; while (p0 > prob[k]) k++; aff2_apply (&elements[k]->trans, x, y, &x, &y); aff3_apply (&elements[k]->color_trans, r, g, b, &r, &g, &b); if (i < 50) continue; ri = (gint) (255.0 * r + 0.5); gi = (gint) (255.0 * g + 0.5); bi = (gint) (255.0 * b + 0.5); if ((ri < 0) || (ri > 255) || (gi < 0) || (gi > 255) || (bi < 0) || (bi > 255)) continue; if (preview) { if ((x < width) && (y < (band_y + band_height)) && (x >= 0) && (y >= band_y)) { ptr = data + 3 * (((gint) (y - band_y)) * width + (gint) x); *ptr++ = ri; *ptr++ = gi; *ptr = bi; } } else { if ((x < width * subdivide) && (y < height * subdivide) && (x >= 0) && (y >= 0)) { gint ii; gint jj; gint jj0 = floor (y - brush_offset - band_y * subdivide); gint ii0 = floor (x - brush_offset); gint jjmin = 0; gint iimin = 0; gint jjmax; gint iimax; if (ii0 < 0) iimin = - ii0; else iimin = 0; if (jj0 < 0) jjmin = - jj0; else jjmin = 0; if (jj0 + brush_size >= subdivide * band_height) jjmax = subdivide * band_height - jj0; else jjmax = brush_size; if (ii0 + brush_size >= subdivide * width) iimax = subdivide * width - ii0; else iimax = brush_size; for (jj = jjmin; jj < jjmax; jj++) for (ii = iimin; ii < iimax; ii++) { guint m_old; guint m_new; guint m_pix; guint n_hits; guint old_scale; guint pix_scale; gint index = (jj0 + jj) * width * subdivide + ii0 + ii; n_hits = nhits[index]; if (n_hits == 255) continue; m_pix = brush[jj * brush_size + ii]; if (!m_pix) continue; nhits[index] = ++n_hits; m_old = mask[index]; m_new = m_old + m_pix - m_old * m_pix / 255; mask[index] = m_new; /* relative probability that old colored pixel is on top */ old_scale = m_old * (255 * n_hits - m_pix); /* relative probability that new colored pixel is on top */ pix_scale = m_pix * ((255 - m_old) * n_hits + m_old); ptr = data + 3 * index; *ptr = ((old_scale * (*ptr) + pix_scale * ri) / (old_scale + pix_scale)); ptr++; *ptr = ((old_scale * (*ptr) + pix_scale * gi) / (old_scale + pix_scale)); ptr++; *ptr = ((old_scale * (*ptr) + pix_scale * bi) / (old_scale + pix_scale)); } } } } /* main iteration */ if (!preview ) gimp_progress_update (1.0); g_free (brush); g_free (prob); g_free (fprob); }