/* Copyright (C) 1998, 1999 artofcode LLC. All rights reserved. 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA, 02111-1307. */ /*$Id: gxshade4.c,v 1.6.2.1.2.1 2003/01/17 00:49:04 giles Exp $ */ /* Rendering for Gouraud triangle shadings */ #include "math_.h" #include "memory_.h" #include "gx.h" #include "gserrors.h" #include "gsmatrix.h" /* for gscoord.h */ #include "gscoord.h" #include "gxcspace.h" #include "gxdcolor.h" #include "gxdevcli.h" #include "gxistate.h" #include "gxpath.h" #include "gxshade.h" #include "gxshade4.h" /* ---------------- Triangle mesh filling ---------------- */ /* Initialize the fill state for triangle shading. */ void mesh_init_fill_state(mesh_fill_state_t * pfs, const gs_shading_mesh_t * psh, const gs_rect * rect, gx_device * dev, gs_imager_state * pis) { shade_init_fill_state((shading_fill_state_t *) pfs, (const gs_shading_t *)psh, dev, pis); pfs->pshm = psh; shade_bbox_transform2fixed(rect, pis, &pfs->rect); } /* Initialize the recursion state for filling one triangle. */ void mesh_init_fill_triangle(mesh_fill_state_t * pfs, const mesh_vertex_t *va, const mesh_vertex_t *vb, const mesh_vertex_t *vc, bool check_clipping) { pfs->depth = 1; pfs->frames[0].va = *va; pfs->frames[0].vb = *vb; pfs->frames[0].vc = *vc; pfs->frames[0].check_clipping = check_clipping; } #define SET_MIN_MAX_3(vmin, vmax, a, b, c)\ if ( a < b ) vmin = a, vmax = b; else vmin = b, vmax = a;\ if ( c < vmin ) vmin = c; else if ( c > vmax ) vmax = c #define MIDPOINT_FAST(a,b) arith_rshift_1((a) + (b) + 1) private mesh_frame_t * mesh_subdivide_triangle(mesh_fill_state_t *pfs, mesh_frame_t *fp) { int i; float dabx, daby, dbcx, dbcy, dacx, dacy; float r2ab, r2bc, r2ac; float r2min, r2max; float tri_area_2; dabx = fp->vb.p.x - fp->va.p.x; daby = fp->vb.p.y - fp->va.p.y; dbcx = fp->vc.p.x - fp->vb.p.x; dbcy = fp->vc.p.y - fp->vb.p.y; dacx = (dabx + dbcx); dacy = (daby + dbcy); r2ab = dabx * dabx + daby * daby; r2bc = dbcx * dbcx + dbcy * dbcy; r2ac = dacx * dacx + dacy * dacy; SET_MIN_MAX_3(r2min, r2max, r2ab, r2bc, r2ac); tri_area_2 = fp->va.p.y * (fp->vc.p.x - fp->vb.p.x) + fp->vb.p.y * (fp->va.p.x - fp->vc.p.x) + fp->vc.p.y * (fp->vb.p.x - fp->va.p.x); if (fabs(tri_area_2) < 0.5 * r2max) { /* skinny triangle, subdivide longest edge */ mesh_vertex_t tmp; /* first, sort by length, so that AB is longest edge */ if (r2bc > r2ac) { if (r2bc > r2ab) { /* BC is longest edge, rotate */ tmp = fp->va; fp->va = fp->vb; fp->vb = fp->vc; fp->vc = tmp; } } else { if (r2ac > r2ab) { /* AC is longest edge, rotate */ tmp = fp->va; fp->va = fp->vc; fp->vc = fp->vb; fp->vb = tmp; } } #define VAB fp[1].va VAB.p.x = MIDPOINT_FAST(fp->va.p.x, fp->vb.p.x); VAB.p.y = MIDPOINT_FAST(fp->va.p.y, fp->vb.p.y); for (i = 0; i < pfs->num_components; ++i) { float ta = fp->va.cc[i], tb = fp->vb.cc[i]; VAB.cc[i] = (ta + tb) * 0.5; } /* Fill in the rest of the triangles. */ fp[1].vb = fp->vb; fp[1].vc = fp->vc; fp->vb = VAB; fp[1].check_clipping = fp->check_clipping; return fp + 1; #undef VAB } else { /* Reasonably shaped, subdivide into 4 triangles at midpoints */ /* * Subdivide the triangle and recur. The only subdivision method * that doesn't seem to create anomalous shapes divides the * triangle in 4, using the midpoints of each side. * * If the original vertices are A, B, C, we fill the sub-triangles * in the following order: * (A, AB, AC) - fp[3] * (AB, AC, BC) - fp[2] * (AC, BC, C) - fp[1] * (AB, B, BC) - fp[0] */ #define VAB fp[3].vb #define VAC fp[2].vb #define VBC fp[1].vb VAB.p.x = MIDPOINT_FAST(fp->va.p.x, fp->vb.p.x); VAB.p.y = MIDPOINT_FAST(fp->va.p.y, fp->vb.p.y); VAC.p.x = MIDPOINT_FAST(fp->va.p.x, fp->vc.p.x); VAC.p.y = MIDPOINT_FAST(fp->va.p.y, fp->vc.p.y); VBC.p.x = MIDPOINT_FAST(fp->vb.p.x, fp->vc.p.x); VBC.p.y = MIDPOINT_FAST(fp->vb.p.y, fp->vc.p.y); for (i = 0; i < pfs->num_components; ++i) { float ta = fp->va.cc[i], tb = fp->vb.cc[i], tc = fp->vc.cc[i]; VAB.cc[i] = (ta + tb) * 0.5; VAC.cc[i] = (ta + tc) * 0.5; VBC.cc[i] = (tb + tc) * 0.5; } /* Fill in the rest of the triangles. */ fp[3].va = fp->va; fp[3].vc = VAC; fp[2].va = VAB; fp[2].vc = VBC; fp[1].va = VAC; fp[1].vc = fp->vc; fp->va = VAB; fp->vc = VBC; #undef VAB #undef VAC #undef VBC fp[3].check_clipping = fp[2].check_clipping = fp[1].check_clipping = fp->check_clipping; return fp + 3; } } #undef MIDPOINT_FAST int mesh_fill_triangle(mesh_fill_state_t *pfs) { const gs_shading_mesh_t *psh = pfs->pshm; gs_imager_state *pis = pfs->pis; mesh_frame_t *fp = &pfs->frames[pfs->depth - 1]; int ci; for (;;) { bool check = fp->check_clipping; /* * Fill the triangle with vertices at fp->va.p, fp->vb.p, and * fp->vc.p with color fp->va.cc. If check is true, check for * whether the triangle is entirely inside the rectangle, entirely * outside, or partly inside; if check is false, assume the triangle * is entirely inside. */ if (check) { fixed xmin, ymin, xmax, ymax; SET_MIN_MAX_3(xmin, xmax, fp->va.p.x, fp->vb.p.x, fp->vc.p.x); SET_MIN_MAX_3(ymin, ymax, fp->va.p.y, fp->vb.p.y, fp->vc.p.y); if (xmin >= pfs->rect.p.x && xmax <= pfs->rect.q.x && ymin >= pfs->rect.p.y && ymax <= pfs->rect.q.y ) { /* The triangle is entirely inside the rectangle. */ check = false; } else if (xmin >= pfs->rect.q.x || xmax <= pfs->rect.p.x || ymin >= pfs->rect.q.y || ymax <= pfs->rect.p.y ) { /* The triangle is entirely outside the rectangle. */ goto next; } } if (fp < &pfs->frames[countof(pfs->frames) - 3]) { /* Check whether the colors fall within the smoothness criterion. */ for (ci = 0; ci < pfs->num_components; ++ci) { float c0 = fp->va.cc[ci], c1 = fp->vb.cc[ci], c2 = fp->vc.cc[ci]; float cmin, cmax; SET_MIN_MAX_3(cmin, cmax, c0, c1, c2); if (cmax - cmin > pfs->cc_max_error[ci]) goto nofill; } } fill: /* Fill the triangle with the color. */ { gx_device_color dev_color; const gs_color_space *pcs = psh->params.ColorSpace; gs_client_color fcc; int code; #if 0 memcpy(&fcc.paint, fp->va.cc, sizeof(fcc.paint)); #else /* Average the colors at the vertices. */ { int ci; for (ci = 0; ci < pfs->num_components; ++ci) fcc.paint.values[ci] = (fp->va.cc[ci] + fp->vb.cc[ci] + fp->vc.cc[ci]) / 3.0; } #endif (*pcs->type->restrict_color)(&fcc, pcs); (*pcs->type->remap_color)(&fcc, pcs, &dev_color, pis, pfs->dev, gs_color_select_texture); /****** SHOULD ADD adjust ON ANY OUTSIDE EDGES ******/ /* * See the comment in gx_dc_pattern2_fill_rectangle in gsptype2.c * re the choice of path filling vs. direct triangle fill. */ if (pis->fill_adjust.x != 0 || pis->fill_adjust.y != 0) { gx_path *ppath = gx_path_alloc(pis->memory, "Gt_fill"); gx_path_add_point(ppath, fp->va.p.x, fp->va.p.y); gx_path_add_line(ppath, fp->vb.p.x, fp->vb.p.y); gx_path_add_line(ppath, fp->vc.p.x, fp->vc.p.y); code = shade_fill_path((const shading_fill_state_t *)pfs, ppath, &dev_color); gx_path_free(ppath, "Gt_fill"); } else { code = (*dev_proc(pfs->dev, fill_triangle)) (pfs->dev, fp->va.p.x, fp->va.p.y, fp->vb.p.x - fp->va.p.x, fp->vb.p.y - fp->va.p.y, fp->vc.p.x - fp->va.p.x, fp->vc.p.y - fp->va.p.y, &dev_color, pis->log_op); if (code < 0) return code; } } next: if (fp == &pfs->frames[0]) return 0; --fp; continue; nofill: /* * The colors don't converge. Does the region color more than * a single pixel? */ { gs_fixed_rect region; SET_MIN_MAX_3(region.p.x, region.q.x, fp->va.p.x, fp->vb.p.x, fp->vc.p.x); SET_MIN_MAX_3(region.p.y, region.q.y, fp->va.p.y, fp->vb.p.y, fp->vc.p.y); if (region.q.x - region.p.x <= fixed_1 && region.q.y - region.p.y <= fixed_1) { /* * More precisely, does the bounding box of the region * span more than 1 pixel center in either X or Y? */ int nx = fixed2int_pixround(region.q.x) - fixed2int_pixround(region.p.x); int ny = fixed2int_pixround(region.q.y) - fixed2int_pixround(region.p.y); if (!(nx > 1 && ny != 0) || (ny > 1 && nx != 0)) goto fill; } } fp = mesh_subdivide_triangle(pfs, fp); } } /* ---------------- Gouraud triangle shadings ---------------- */ private int Gt_next_vertex(const gs_shading_mesh_t * psh, shade_coord_stream_t * cs, mesh_vertex_t * vertex) { int code = shade_next_vertex(cs, vertex); if (code >= 0 && psh->params.Function) { /* Decode the color with the function. */ code = gs_function_evaluate(psh->params.Function, vertex->cc, vertex->cc); } return code; } inline private int Gt_fill_triangle(mesh_fill_state_t * pfs, const mesh_vertex_t * va, const mesh_vertex_t * vb, const mesh_vertex_t * vc) { mesh_init_fill_triangle(pfs, va, vb, vc, true); return mesh_fill_triangle(pfs); } int gs_shading_FfGt_fill_rectangle(const gs_shading_t * psh0, const gs_rect * rect, gx_device * dev, gs_imager_state * pis) { const gs_shading_FfGt_t * const psh = (const gs_shading_FfGt_t *)psh0; mesh_fill_state_t state; shade_coord_stream_t cs; int num_bits = psh->params.BitsPerFlag; int flag; mesh_vertex_t va, vb, vc; mesh_init_fill_state(&state, (const gs_shading_mesh_t *)psh, rect, dev, pis); shade_next_init(&cs, (const gs_shading_mesh_params_t *)&psh->params, pis); while ((flag = shade_next_flag(&cs, num_bits)) >= 0) { int code; switch (flag) { default: return_error(gs_error_rangecheck); case 0: if ((code = Gt_next_vertex(state.pshm, &cs, &va)) < 0 || (code = shade_next_flag(&cs, num_bits)) < 0 || (code = Gt_next_vertex(state.pshm, &cs, &vb)) < 0 || (code = shade_next_flag(&cs, num_bits)) < 0 ) return code; goto v2; case 1: va = vb; case 2: vb = vc; v2: if ((code = Gt_next_vertex(state.pshm, &cs, &vc)) < 0 || (code = Gt_fill_triangle(&state, &va, &vb, &vc)) < 0 ) return code; } } return 0; } int gs_shading_LfGt_fill_rectangle(const gs_shading_t * psh0, const gs_rect * rect, gx_device * dev, gs_imager_state * pis) { const gs_shading_LfGt_t * const psh = (const gs_shading_LfGt_t *)psh0; mesh_fill_state_t state; shade_coord_stream_t cs; mesh_vertex_t *vertex; mesh_vertex_t next; int per_row = psh->params.VerticesPerRow; int i, code = 0; mesh_init_fill_state(&state, (const gs_shading_mesh_t *)psh, rect, dev, pis); shade_next_init(&cs, (const gs_shading_mesh_params_t *)&psh->params, pis); vertex = (mesh_vertex_t *) gs_alloc_byte_array(pis->memory, per_row, sizeof(*vertex), "gs_shading_LfGt_render"); if (vertex == 0) return_error(gs_error_VMerror); for (i = 0; i < per_row; ++i) if ((code = Gt_next_vertex(state.pshm, &cs, &vertex[i])) < 0) goto out; while (!seofp(cs.s)) { code = Gt_next_vertex(state.pshm, &cs, &next); if (code < 0) goto out; for (i = 1; i < per_row; ++i) { code = Gt_fill_triangle(&state, &vertex[i - 1], &vertex[i], &next); if (code < 0) goto out; vertex[i - 1] = next; code = Gt_next_vertex(state.pshm, &cs, &next); if (code < 0) goto out; code = Gt_fill_triangle(&state, &vertex[i], &vertex[i - 1], &next); if (code < 0) goto out; } vertex[per_row - 1] = next; } out: gs_free_object(pis->memory, vertex, "gs_shading_LfGt_render"); return code; }