/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others * * This program is free software: you can redistribute it and/or modify * 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 "libgimpmath/gimpmath.h" #include "core-types.h" #include "gimp-transform-utils.h" #include "gimpcoords.h" #include "gimpcoords-interpolate.h" #define EPSILON 1e-6 void gimp_transform_get_rotate_center (gint x, gint y, gint width, gint height, gboolean auto_center, gdouble *center_x, gdouble *center_y) { g_return_if_fail (center_x != NULL); g_return_if_fail (center_y != NULL); if (auto_center) { *center_x = (gdouble) x + (gdouble) width / 2.0; *center_y = (gdouble) y + (gdouble) height / 2.0; } } void gimp_transform_get_flip_axis (gint x, gint y, gint width, gint height, GimpOrientationType flip_type, gboolean auto_center, gdouble *axis) { g_return_if_fail (axis != NULL); if (auto_center) { switch (flip_type) { case GIMP_ORIENTATION_HORIZONTAL: *axis = ((gdouble) x + (gdouble) width / 2.0); break; case GIMP_ORIENTATION_VERTICAL: *axis = ((gdouble) y + (gdouble) height / 2.0); break; default: g_return_if_reached (); break; } } } void gimp_transform_matrix_flip (GimpMatrix3 *matrix, GimpOrientationType flip_type, gdouble axis) { g_return_if_fail (matrix != NULL); switch (flip_type) { case GIMP_ORIENTATION_HORIZONTAL: gimp_matrix3_translate (matrix, - axis, 0.0); gimp_matrix3_scale (matrix, -1.0, 1.0); gimp_matrix3_translate (matrix, axis, 0.0); break; case GIMP_ORIENTATION_VERTICAL: gimp_matrix3_translate (matrix, 0.0, - axis); gimp_matrix3_scale (matrix, 1.0, -1.0); gimp_matrix3_translate (matrix, 0.0, axis); break; case GIMP_ORIENTATION_UNKNOWN: break; } } void gimp_transform_matrix_flip_free (GimpMatrix3 *matrix, gdouble x1, gdouble y1, gdouble x2, gdouble y2) { gdouble angle; g_return_if_fail (matrix != NULL); angle = atan2 (y2 - y1, x2 - x1); gimp_matrix3_identity (matrix); gimp_matrix3_translate (matrix, -x1, -y1); gimp_matrix3_rotate (matrix, -angle); gimp_matrix3_scale (matrix, 1.0, -1.0); gimp_matrix3_rotate (matrix, angle); gimp_matrix3_translate (matrix, x1, y1); } void gimp_transform_matrix_rotate (GimpMatrix3 *matrix, GimpRotationType rotate_type, gdouble center_x, gdouble center_y) { gdouble angle = 0; switch (rotate_type) { case GIMP_ROTATE_90: angle = G_PI_2; break; case GIMP_ROTATE_180: angle = G_PI; break; case GIMP_ROTATE_270: angle = - G_PI_2; break; } gimp_transform_matrix_rotate_center (matrix, center_x, center_y, angle); } void gimp_transform_matrix_rotate_rect (GimpMatrix3 *matrix, gint x, gint y, gint width, gint height, gdouble angle) { gdouble center_x; gdouble center_y; g_return_if_fail (matrix != NULL); center_x = (gdouble) x + (gdouble) width / 2.0; center_y = (gdouble) y + (gdouble) height / 2.0; gimp_matrix3_translate (matrix, -center_x, -center_y); gimp_matrix3_rotate (matrix, angle); gimp_matrix3_translate (matrix, +center_x, +center_y); } void gimp_transform_matrix_rotate_center (GimpMatrix3 *matrix, gdouble center_x, gdouble center_y, gdouble angle) { g_return_if_fail (matrix != NULL); gimp_matrix3_translate (matrix, -center_x, -center_y); gimp_matrix3_rotate (matrix, angle); gimp_matrix3_translate (matrix, +center_x, +center_y); } void gimp_transform_matrix_scale (GimpMatrix3 *matrix, gint x, gint y, gint width, gint height, gdouble t_x, gdouble t_y, gdouble t_width, gdouble t_height) { gdouble scale_x = 1.0; gdouble scale_y = 1.0; g_return_if_fail (matrix != NULL); if (width > 0) scale_x = t_width / (gdouble) width; if (height > 0) scale_y = t_height / (gdouble) height; gimp_matrix3_identity (matrix); gimp_matrix3_translate (matrix, -x, -y); gimp_matrix3_scale (matrix, scale_x, scale_y); gimp_matrix3_translate (matrix, t_x, t_y); } void gimp_transform_matrix_shear (GimpMatrix3 *matrix, gint x, gint y, gint width, gint height, GimpOrientationType orientation, gdouble amount) { gdouble center_x; gdouble center_y; g_return_if_fail (matrix != NULL); if (width == 0) width = 1; if (height == 0) height = 1; center_x = (gdouble) x + (gdouble) width / 2.0; center_y = (gdouble) y + (gdouble) height / 2.0; gimp_matrix3_identity (matrix); gimp_matrix3_translate (matrix, -center_x, -center_y); if (orientation == GIMP_ORIENTATION_HORIZONTAL) gimp_matrix3_xshear (matrix, amount / height); else gimp_matrix3_yshear (matrix, amount / width); gimp_matrix3_translate (matrix, +center_x, +center_y); } void gimp_transform_matrix_perspective (GimpMatrix3 *matrix, gint x, gint y, gint width, gint height, gdouble t_x1, gdouble t_y1, gdouble t_x2, gdouble t_y2, gdouble t_x3, gdouble t_y3, gdouble t_x4, gdouble t_y4) { GimpMatrix3 trafo; gdouble scalex; gdouble scaley; g_return_if_fail (matrix != NULL); scalex = scaley = 1.0; if (width > 0) scalex = 1.0 / (gdouble) width; if (height > 0) scaley = 1.0 / (gdouble) height; gimp_matrix3_translate (matrix, -x, -y); gimp_matrix3_scale (matrix, scalex, scaley); /* Determine the perspective transform that maps from * the unit cube to the transformed coordinates */ { gdouble dx1, dx2, dx3, dy1, dy2, dy3; dx1 = t_x2 - t_x4; dx2 = t_x3 - t_x4; dx3 = t_x1 - t_x2 + t_x4 - t_x3; dy1 = t_y2 - t_y4; dy2 = t_y3 - t_y4; dy3 = t_y1 - t_y2 + t_y4 - t_y3; /* Is the mapping affine? */ if ((dx3 == 0.0) && (dy3 == 0.0)) { trafo.coeff[0][0] = t_x2 - t_x1; trafo.coeff[0][1] = t_x4 - t_x2; trafo.coeff[0][2] = t_x1; trafo.coeff[1][0] = t_y2 - t_y1; trafo.coeff[1][1] = t_y4 - t_y2; trafo.coeff[1][2] = t_y1; trafo.coeff[2][0] = 0.0; trafo.coeff[2][1] = 0.0; } else { gdouble det1, det2; det1 = dx3 * dy2 - dy3 * dx2; det2 = dx1 * dy2 - dy1 * dx2; trafo.coeff[2][0] = (det2 == 0.0) ? 1.0 : det1 / det2; det1 = dx1 * dy3 - dy1 * dx3; trafo.coeff[2][1] = (det2 == 0.0) ? 1.0 : det1 / det2; trafo.coeff[0][0] = t_x2 - t_x1 + trafo.coeff[2][0] * t_x2; trafo.coeff[0][1] = t_x3 - t_x1 + trafo.coeff[2][1] * t_x3; trafo.coeff[0][2] = t_x1; trafo.coeff[1][0] = t_y2 - t_y1 + trafo.coeff[2][0] * t_y2; trafo.coeff[1][1] = t_y3 - t_y1 + trafo.coeff[2][1] * t_y3; trafo.coeff[1][2] = t_y1; } trafo.coeff[2][2] = 1.0; } gimp_matrix3_mult (&trafo, matrix); } /* modified gaussian algorithm * solves a system of linear equations * * Example: * 1x + 2y + 4z = 25 * 2x + 1y = 4 * 3x + 5y + 2z = 23 * Solution: x=1, y=2, z=5 * * Input: * matrix = { 1,2,4,25,2,1,0,4,3,5,2,23 } * s = 3 (Number of variables) * Output: * return value == TRUE (TRUE, if there is a single unique solution) * solution == { 1,2,5 } (if the return value is FALSE, the content * of solution is of no use) */ static gboolean mod_gauss (gdouble matrix[], gdouble solution[], gint s) { gint p[s]; /* row permutation */ gint i, j, r, temp; gdouble q; gint t = s + 1; for (i = 0; i < s; i++) { p[i] = i; } for (r = 0; r < s; r++) { /* make sure that (r,r) is not 0 */ if (fabs (matrix[p[r] * t + r]) <= EPSILON) { /* we need to permutate rows */ for (i = r + 1; i <= s; i++) { if (i == s) { /* if this happens, the linear system has zero or * more than one solutions. */ return FALSE; } if (fabs (matrix[p[i] * t + r]) > EPSILON) break; } temp = p[r]; p[r] = p[i]; p[i] = temp; } /* make (r,r) == 1 */ q = 1.0 / matrix[p[r] * t + r]; matrix[p[r] * t + r] = 1.0; for (j = r + 1; j < t; j++) { matrix[p[r] * t + j] *= q; } /* make that all entries in column r are 0 (except (r,r)) */ for (i = 0; i < s; i++) { if (i == r) continue; for (j = r + 1; j < t ; j++) { matrix[p[i] * t + j] -= matrix[p[r] * t + j] * matrix[p[i] * t + r]; } /* we don't need to execute the following line * since we won't access this element again: * * matrix[p[i] * t + r] = 0.0; */ } } for (i = 0; i < s; i++) { solution[i] = matrix[p[i] * t + s]; } return TRUE; } /* multiplies 'matrix' by the matrix that transforms a set of 4 'input_points' * to corresponding 'output_points', if such matrix exists, and is valid (i.e., * keeps the output points in front of the camera). * * returns TRUE if successful. */ gboolean gimp_transform_matrix_generic (GimpMatrix3 *matrix, const GimpVector2 input_points[4], const GimpVector2 output_points[4]) { GimpMatrix3 trafo; gdouble coeff[8 * 9]; gboolean negative; gint i; g_return_val_if_fail (matrix != NULL, FALSE); g_return_val_if_fail (input_points != NULL, FALSE); g_return_val_if_fail (output_points != NULL, FALSE); /* find the matrix that transforms 'input_points' to 'output_points', whose * (3, 3) coeffcient is 1, by solving a system of linear equations whose * solution is the remaining 8 coefficients. */ for (i = 0; i < 4; i++) { coeff[i * 9 + 0] = input_points[i].x; coeff[i * 9 + 1] = input_points[i].y; coeff[i * 9 + 2] = 1.0; coeff[i * 9 + 3] = 0.0; coeff[i * 9 + 4] = 0.0; coeff[i * 9 + 5] = 0.0; coeff[i * 9 + 6] = -input_points[i].x * output_points[i].x; coeff[i * 9 + 7] = -input_points[i].y * output_points[i].x; coeff[i * 9 + 8] = output_points[i].x; coeff[(i + 4) * 9 + 0] = 0.0; coeff[(i + 4) * 9 + 1] = 0.0; coeff[(i + 4) * 9 + 2] = 0.0; coeff[(i + 4) * 9 + 3] = input_points[i].x; coeff[(i + 4) * 9 + 4] = input_points[i].y; coeff[(i + 4) * 9 + 5] = 1.0; coeff[(i + 4) * 9 + 6] = -input_points[i].x * output_points[i].y; coeff[(i + 4) * 9 + 7] = -input_points[i].y * output_points[i].y; coeff[(i + 4) * 9 + 8] = output_points[i].y; } /* if there is no solution, bail */ if (! mod_gauss (coeff, (gdouble *) trafo.coeff, 8)) return FALSE; trafo.coeff[2][2] = 1.0; /* make sure that none of the input points maps to a point at infinity, and * that all output points are on the same side of the camera. */ for (i = 0; i < 4; i++) { gdouble w; gboolean neg; w = trafo.coeff[2][0] * input_points[i].x + trafo.coeff[2][1] * input_points[i].y + trafo.coeff[2][2]; if (fabs (w) <= EPSILON) return FALSE; neg = (w < 0.0); if (i == 0) negative = neg; else if (neg != negative) return FALSE; } /* if the output points are all behind the camera, negate the matrix, which * would map the input points to the corresponding points in front of the * camera. */ if (negative) { gint r; gint c; for (r = 0; r < 3; r++) { for (c = 0; c < 3; c++) { trafo.coeff[r][c] = -trafo.coeff[r][c]; } } } /* append the transformation to 'matrix' */ gimp_matrix3_mult (&trafo, matrix); return TRUE; } gboolean gimp_transform_polygon_is_convex (gdouble x1, gdouble y1, gdouble x2, gdouble y2, gdouble x3, gdouble y3, gdouble x4, gdouble y4) { gdouble z1, z2, z3, z4; /* We test if the transformed polygon is convex. if z1 and z2 have * the same sign as well as z3 and z4 the polygon is convex. */ z1 = ((x2 - x1) * (y4 - y1) - (x4 - x1) * (y2 - y1)); z2 = ((x4 - x1) * (y3 - y1) - (x3 - x1) * (y4 - y1)); z3 = ((x4 - x2) * (y3 - y2) - (x3 - x2) * (y4 - y2)); z4 = ((x3 - x2) * (y1 - y2) - (x1 - x2) * (y3 - y2)); return (z1 * z2 > 0) && (z3 * z4 > 0); } /* transforms the polygon or polyline, whose vertices are given by 'vertices', * by 'matrix', performing clipping by the near plane. 'closed' indicates * whether the vertices represent a polygon ('closed == TRUE') or a polyline * ('closed == FALSE'). * * returns the transformed vertices in 't_vertices', and their count in * 'n_t_vertices'. the minimal possible number of transformed vertices is 0, * which happens when the entire input is clipped. in general, the maximal * possible number of transformed vertices is '3 * n_vertices / 2' (rounded * down), however, for convex polygons the number is 'n_vertices + 1', and for * a single line segment ('n_vertices == 2' and 'closed == FALSE') the number * is 2. * * 't_vertices' may not alias 'vertices', except when transforming a single * line segment. */ void gimp_transform_polygon (const GimpMatrix3 *matrix, const GimpVector2 *vertices, gint n_vertices, gboolean closed, GimpVector2 *t_vertices, gint *n_t_vertices) { GimpVector3 curr; gboolean curr_visible; gint i; g_return_if_fail (matrix != NULL); g_return_if_fail (vertices != NULL); g_return_if_fail (n_vertices >= 0); g_return_if_fail (t_vertices != NULL); g_return_if_fail (n_t_vertices != NULL); *n_t_vertices = 0; if (n_vertices == 0) return; curr.x = matrix->coeff[0][0] * vertices[0].x + matrix->coeff[0][1] * vertices[0].y + matrix->coeff[0][2]; curr.y = matrix->coeff[1][0] * vertices[0].x + matrix->coeff[1][1] * vertices[0].y + matrix->coeff[1][2]; curr.z = matrix->coeff[2][0] * vertices[0].x + matrix->coeff[2][1] * vertices[0].y + matrix->coeff[2][2]; curr_visible = (curr.z >= GIMP_TRANSFORM_NEAR_Z); for (i = 0; i < n_vertices; i++) { if (curr_visible) { t_vertices[(*n_t_vertices)++] = (GimpVector2) { curr.x / curr.z, curr.y / curr.z }; } if (i < n_vertices - 1 || closed) { GimpVector3 next; gboolean next_visible; gint j = (i + 1) % n_vertices; next.x = matrix->coeff[0][0] * vertices[j].x + matrix->coeff[0][1] * vertices[j].y + matrix->coeff[0][2]; next.y = matrix->coeff[1][0] * vertices[j].x + matrix->coeff[1][1] * vertices[j].y + matrix->coeff[1][2]; next.z = matrix->coeff[2][0] * vertices[j].x + matrix->coeff[2][1] * vertices[j].y + matrix->coeff[2][2]; next_visible = (next.z >= GIMP_TRANSFORM_NEAR_Z); if (next_visible != curr_visible) { gdouble ratio = (curr.z - GIMP_TRANSFORM_NEAR_Z) / (curr.z - next.z); t_vertices[(*n_t_vertices)++] = (GimpVector2) { (curr.x + (next.x - curr.x) * ratio) / GIMP_TRANSFORM_NEAR_Z, (curr.y + (next.y - curr.y) * ratio) / GIMP_TRANSFORM_NEAR_Z }; } curr = next; curr_visible = next_visible; } } } /* same as gimp_transform_polygon(), but using GimpCoords as the vertex type, * instead of GimpVector2. */ void gimp_transform_polygon_coords (const GimpMatrix3 *matrix, const GimpCoords *vertices, gint n_vertices, gboolean closed, GimpCoords *t_vertices, gint *n_t_vertices) { GimpVector3 curr; gboolean curr_visible; gint i; g_return_if_fail (matrix != NULL); g_return_if_fail (vertices != NULL); g_return_if_fail (n_vertices >= 0); g_return_if_fail (t_vertices != NULL); g_return_if_fail (n_t_vertices != NULL); *n_t_vertices = 0; if (n_vertices == 0) return; curr.x = matrix->coeff[0][0] * vertices[0].x + matrix->coeff[0][1] * vertices[0].y + matrix->coeff[0][2]; curr.y = matrix->coeff[1][0] * vertices[0].x + matrix->coeff[1][1] * vertices[0].y + matrix->coeff[1][2]; curr.z = matrix->coeff[2][0] * vertices[0].x + matrix->coeff[2][1] * vertices[0].y + matrix->coeff[2][2]; curr_visible = (curr.z >= GIMP_TRANSFORM_NEAR_Z); for (i = 0; i < n_vertices; i++) { if (curr_visible) { t_vertices[*n_t_vertices] = vertices[i]; t_vertices[*n_t_vertices].x = curr.x / curr.z; t_vertices[*n_t_vertices].y = curr.y / curr.z; (*n_t_vertices)++; } if (i < n_vertices - 1 || closed) { GimpVector3 next; gboolean next_visible; gint j = (i + 1) % n_vertices; next.x = matrix->coeff[0][0] * vertices[j].x + matrix->coeff[0][1] * vertices[j].y + matrix->coeff[0][2]; next.y = matrix->coeff[1][0] * vertices[j].x + matrix->coeff[1][1] * vertices[j].y + matrix->coeff[1][2]; next.z = matrix->coeff[2][0] * vertices[j].x + matrix->coeff[2][1] * vertices[j].y + matrix->coeff[2][2]; next_visible = (next.z >= GIMP_TRANSFORM_NEAR_Z); if (next_visible != curr_visible) { gdouble ratio = (curr.z - GIMP_TRANSFORM_NEAR_Z) / (curr.z - next.z); gimp_coords_mix (1.0 - ratio, &vertices[i], ratio, &vertices[j], &t_vertices[*n_t_vertices]); t_vertices[*n_t_vertices].x = (curr.x + (next.x - curr.x) * ratio) / GIMP_TRANSFORM_NEAR_Z; t_vertices[*n_t_vertices].y = (curr.y + (next.y - curr.y) * ratio) / GIMP_TRANSFORM_NEAR_Z; (*n_t_vertices)++; } curr = next; curr_visible = next_visible; } } } /* returns the value of the polynomial 'poly', of degree 'degree', at 'x'. the * coefficients of 'poly' should be specified in descending-degree order. */ static gdouble polynomial_eval (const gdouble *poly, gint degree, gdouble x) { gdouble y = poly[0]; gint i; for (i = 1; i <= degree; i++) y = y * x + poly[i]; return y; } /* derives the polynomial 'poly', of degree 'degree'. * * returns the derivative in 'result'. */ static void polynomial_derive (const gdouble *poly, gint degree, gdouble *result) { while (degree) *result++ = *poly++ * degree--; } /* finds the real odd-multiplicity root of the polynomial 'poly', of degree * 'degree', inside the range '(x1, x2)'. * * returns TRUE if such a root exists, and stores its value in '*root'. * * 'poly' shall be monotonic in the range '(x1, x2)'. */ static gboolean polynomial_odd_root (const gdouble *poly, gint degree, gdouble x1, gdouble x2, gdouble *root) { gdouble y1; gdouble y2; gint i; y1 = polynomial_eval (poly, degree, x1); y2 = polynomial_eval (poly, degree, x2); if (y1 * y2 > -EPSILON) { /* the two endpoints have the same sign, or one of them is zero. there's * no root inside the range. */ return FALSE; } else if (y1 > 0.0) { gdouble t; /* if the first endpoint is positive, swap the endpoints, so that the * first endpoint is always negative, and the second endpoint is always * positive. */ t = x1; x1 = x2; x2 = t; } /* approximate the root using binary search */ for (i = 0; i < 53; i++) { gdouble x = (x1 + x2) / 2.0; gdouble y = polynomial_eval (poly, degree, x); if (y > 0.0) x2 = x; else x1 = x; } *root = (x1 + x2) / 2.0; return TRUE; } /* finds the real odd-multiplicity roots of the polynomial 'poly', of degree * 'degree', inside the range '(x1, x2)'. * * returns the roots in 'roots', in ascending order, and their count in * 'n_roots'. */ static void polynomial_odd_roots (const gdouble *poly, gint degree, gdouble x1, gdouble x2, gdouble *roots, gint *n_roots) { *n_roots = 0; /* find the real degree of the polynomial (skip any leading coefficients that * are 0) */ for (; degree && fabs (*poly) < EPSILON; poly++, degree--); #define ADD_ROOT(root) \ do \ { \ gdouble r = (root); \ \ if (r > x1 && r < x2) \ roots[(*n_roots)++] = r; \ } \ while (FALSE) switch (degree) { /* constant case */ case 0: break; /* linear case */ case 1: ADD_ROOT (-poly[1] / poly[0]); break; /* quadratic case */ case 2: { gdouble s = SQR (poly[1]) - 4 * poly[0] * poly[2]; if (s > EPSILON) { s = sqrt (s); if (poly[0] < 0.0) s = -s; ADD_ROOT ((-poly[1] - s) / (2.0 * poly[0])); ADD_ROOT ((-poly[1] + s) / (2.0 * poly[0])); } break; } /* general case */ default: { gdouble deriv[degree - 1]; gdouble deriv_roots[degree - 1]; gint n_deriv_roots; gdouble a; gdouble b; gint i; /* find the odd roots of the derivative, i.e., the local extrema of the * polynomial */ polynomial_derive (poly, degree, deriv); polynomial_odd_roots (deriv, degree - 1, x1, x2, deriv_roots, &n_deriv_roots); /* search for roots between each consecutive pair of extrema, including * the endpoints */ a = x1; for (i = 0; i <= n_deriv_roots; i++) { if (i < n_deriv_roots) b = deriv_roots[i]; else b = x2; *n_roots += polynomial_odd_root (poly, degree, a, b, &roots[*n_roots]); a = b; } break; } } #undef ADD_ROOT } /* clips the cubic bezier segment, defined by the four control points 'bezier', * to the halfplane 'ax + by + c >= 0'. * * returns the clipped set of bezier segments in 'c_bezier', and their count in * 'n_c_bezier'. the minimal possible number of clipped segments is 0, which * happens when the entire segment is clipped. the maximal possible number of * clipped segments is 2. * * if the first clipped segment is an initial segment of 'bezier', sets * '*start_in' to TRUE, otherwise to FALSE. if the last clipped segment is a * final segment of 'bezier', sets '*end_in' to TRUE, otherwise to FALSE. * * 'c_bezier' may not alias 'bezier'. */ static void clip_bezier (const GimpCoords bezier[4], gdouble a, gdouble b, gdouble c, GimpCoords c_bezier[2][4], gint *n_c_bezier, gboolean *start_in, gboolean *end_in) { gdouble dot[4]; gdouble poly[4]; gdouble roots[5]; gint n_roots; gint n_positive; gint i; n_positive = 0; for (i = 0; i < 4; i++) { dot[i] = a * bezier[i].x + b * bezier[i].y + c; n_positive += (dot[i] >= 0.0); } if (n_positive == 0) { /* all points are out -- the entire segment is out */ *n_c_bezier = 0; *start_in = FALSE; *end_in = FALSE; return; } else if (n_positive == 4) { /* all points are in -- the entire segment is in */ memcpy (c_bezier[0], bezier, sizeof (GimpCoords[4])); *n_c_bezier = 1; *start_in = TRUE; *end_in = TRUE; return; } /* find the points of intersection of the segment with the 'ax + by + c = 0' * line */ poly[0] = dot[3] - 3.0 * dot[2] + 3.0 * dot[1] - dot[0]; poly[1] = 3.0 * (dot[2] - 2.0 * dot[1] + dot[0]); poly[2] = 3.0 * (dot[1] - dot[0]); poly[3] = dot[0]; roots[0] = 0.0; polynomial_odd_roots (poly, 3, 0.0, 1.0, roots + 1, &n_roots); roots[++n_roots] = 1.0; /* construct the list of segments that are inside the halfplane */ *n_c_bezier = 0; *start_in = (polynomial_eval (poly, 3, roots[1] / 2.0) > 0.0); *end_in = (*start_in + n_roots + 1) % 2; for (i = ! *start_in; i < n_roots; i += 2) { gdouble t0 = roots[i]; gdouble t1 = roots[i + 1]; gimp_coords_interpolate_bezier_at (bezier, t0, &c_bezier[*n_c_bezier][0], &c_bezier[*n_c_bezier][1]); gimp_coords_interpolate_bezier_at (bezier, t1, &c_bezier[*n_c_bezier][3], &c_bezier[*n_c_bezier][2]); gimp_coords_mix (1.0, &c_bezier[*n_c_bezier][0], (t1 - t0) / 3.0, &c_bezier[*n_c_bezier][1], &c_bezier[*n_c_bezier][1]); gimp_coords_mix (1.0, &c_bezier[*n_c_bezier][3], (t0 - t1) / 3.0, &c_bezier[*n_c_bezier][2], &c_bezier[*n_c_bezier][2]); (*n_c_bezier)++; } } /* transforms the cubic bezier segment, defined by the four control points * 'bezier', by 'matrix', subdividing it as necessary to avoid diverging too * much from the real transformed curve. at most 'depth' subdivisions are * performed. * * appends the transformed sequence of bezier segments to 't_beziers'. * * 'bezier' shall be fully clipped to the near plane. */ static void transform_bezier_coords (const GimpMatrix3 *matrix, const GimpCoords bezier[4], GQueue *t_beziers, gint depth) { GimpCoords *t_bezier; gint n; /* check if we need to split the segment */ if (depth > 0) { GimpVector2 v[4]; GimpVector2 c[2]; GimpVector2 b; gint i; for (i = 0; i < 4; i++) v[i] = (GimpVector2) { bezier[i].x, bezier[i].y }; gimp_vector2_sub (&c[0], &v[1], &v[0]); gimp_vector2_sub (&c[1], &v[2], &v[3]); gimp_vector2_sub (&b, &v[3], &v[0]); gimp_vector2_mul (&b, 1.0 / gimp_vector2_inner_product (&b, &b)); for (i = 0; i < 2; i++) { /* split the segment if one of the control points is too far from the * line connecting the anchors */ if (fabs (gimp_vector2_cross_product (&c[i], &b).x) > 0.5) { GimpCoords mid_position; GimpCoords mid_velocity; GimpCoords sub[4]; gimp_coords_interpolate_bezier_at (bezier, 0.5, &mid_position, &mid_velocity); /* first half */ sub[0] = bezier[0]; sub[3] = mid_position; gimp_coords_mix (0.5, &sub[0], 0.5, &bezier[1], &sub[1]); gimp_coords_mix (1.0, &sub[3], -1.0 / 6.0, &mid_velocity, &sub[2]); transform_bezier_coords (matrix, sub, t_beziers, depth - 1); /* second half */ sub[0] = mid_position; sub[3] = bezier[3]; gimp_coords_mix (1.0, &sub[0], +1.0 / 6.0, &mid_velocity, &sub[1]); gimp_coords_mix (0.5, &sub[3], 0.5, &bezier[2], &sub[2]); transform_bezier_coords (matrix, sub, t_beziers, depth - 1); return; } } } /* transform the segment by transforming each of the individual points. note * that, for non-affine transforms, this is only an approximation of the real * transformed curve, but due to subdivision it should be good enough. */ t_bezier = g_new (GimpCoords, 4); /* note that while the segments themselves are clipped to the near plane, * their control points may still get transformed behind the camera. we * therefore clip the control points to the near plane as well, which is not * too meaningful, but avoids erroneously transforming them behind the * camera. */ gimp_transform_polygon_coords (matrix, bezier, 2, FALSE, t_bezier, &n); gimp_transform_polygon_coords (matrix, bezier + 2, 2, FALSE, t_bezier + 2, &n); g_queue_push_tail (t_beziers, t_bezier); } /* transforms the cubic bezier segment, defined by the four control points * 'bezier', by 'matrix', performing clipping by the near plane and subdividing * as necessary. * * returns the transformed set of bezier-segment sequences in 't_beziers', as * GQueues of GimpCoords[4] bezier-segments, and the number of sequences in * 'n_t_beziers'. the minimal possible number of transformed sequences is 0, * which happens when the entire segment is clipped. the maximal possible * number of transformed sequences is 2. each sequence has at least one * segment. * * if the first transformed segment is an initial segment of 'bezier', sets * '*start_in' to TRUE, otherwise to FALSE. if the last transformed segment is * a final segment of 'bezier', sets '*end_in' to TRUE, otherwise to FALSE. */ void gimp_transform_bezier_coords (const GimpMatrix3 *matrix, const GimpCoords bezier[4], GQueue *t_beziers[2], gint *n_t_beziers, gboolean *start_in, gboolean *end_in) { GimpCoords c_bezier[2][4]; gint i; g_return_if_fail (matrix != NULL); g_return_if_fail (bezier != NULL); g_return_if_fail (t_beziers != NULL); g_return_if_fail (n_t_beziers != NULL); g_return_if_fail (start_in != NULL); g_return_if_fail (end_in != NULL); /* if the matrix is affine, transform the easy way */ if (gimp_matrix3_is_affine (matrix)) { GimpCoords *t_bezier; t_beziers[0] = g_queue_new (); *n_t_beziers = 1; t_bezier = g_new (GimpCoords, 1); g_queue_push_tail (t_beziers[0], t_bezier); for (i = 0; i < 4; i++) { t_bezier[i] = bezier[i]; gimp_matrix3_transform_point (matrix, bezier[i].x, bezier[i].y, &t_bezier[i].x, &t_bezier[i].y); } return; } /* clip the segment to the near plane */ clip_bezier (bezier, matrix->coeff[2][0], matrix->coeff[2][1], matrix->coeff[2][2] - GIMP_TRANSFORM_NEAR_Z, c_bezier, n_t_beziers, start_in, end_in); /* transform each of the resulting segments */ for (i = 0; i < *n_t_beziers; i++) { t_beziers[i] = g_queue_new (); transform_bezier_coords (matrix, c_bezier[i], t_beziers[i], 3); } }