Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/fitz/shade.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/source/fitz/shade.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1136 @@ +// Copyright (C) 2004-2021 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF 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 Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "mupdf/fitz.h" + +#include <string.h> +#include <math.h> + +typedef struct +{ + fz_shade *shade; + fz_shade_prepare_fn *prepare; + fz_shade_process_fn *process; + void *process_arg; + int ncomp; +} fz_mesh_processor; + +#define SWAP(a,b) {fz_vertex *t = (a); (a) = (b); (b) = t;} + +static inline void +paint_tri(fz_context *ctx, fz_mesh_processor *painter, fz_vertex *v0, fz_vertex *v1, fz_vertex *v2) +{ + if (painter->process) + { + painter->process(ctx, painter->process_arg, v0, v1, v2); + } +} + +static inline void +paint_quad(fz_context *ctx, fz_mesh_processor *painter, fz_vertex *v0, fz_vertex *v1, fz_vertex *v2, fz_vertex *v3) +{ + /* For a quad with corners (in clockwise or anticlockwise order) are + * v0, v1, v2, v3. We can choose to split in in various different ways. + * Arbitrarily we can pick v0, v1, v3 for the first triangle. We then + * have to choose between v1, v2, v3 or v3, v2, v1 (or their equivalent + * rotations) for the second triangle. + * + * v1, v2, v3 has the property that both triangles share the same + * winding (useful if we were ever doing simple back face culling). + * + * v3, v2, v1 has the property that all the 'shared' edges (both + * within this quad, and with adjacent quads) are walked in the same + * direction every time. This can be useful in that depending on the + * implementation/rounding etc walking from A -> B can hit different + * pixels than walking from B->A. + * + * In the event neither of these things matter at the moment, as all + * the process functions where it matters order the edges from top to + * bottom before walking them. + */ + if (painter->process) + { + painter->process(ctx, painter->process_arg, v0, v1, v3); + painter->process(ctx, painter->process_arg, v3, v2, v1); + } +} + +static inline void +fz_prepare_color(fz_context *ctx, fz_mesh_processor *painter, fz_vertex *v, float *c) +{ + if (painter->prepare) + { + painter->prepare(ctx, painter->process_arg, v, c); + } +} + +static inline void +fz_prepare_vertex(fz_context *ctx, fz_mesh_processor *painter, fz_vertex *v, fz_matrix ctm, float x, float y, float *c) +{ + v->p = fz_transform_point_xy(x, y, ctm); + if (painter->prepare) + { + painter->prepare(ctx, painter->process_arg, v, c); + } +} + +static void +fz_process_shade_type1(fz_context *ctx, fz_shade *shade, fz_matrix ctm, fz_mesh_processor *painter) +{ + float *p = shade->u.f.fn_vals; + int xdivs = shade->u.f.xdivs; + int ydivs = shade->u.f.ydivs; + float x0 = shade->u.f.domain[0][0]; + float y0 = shade->u.f.domain[0][1]; + float x1 = shade->u.f.domain[1][0]; + float y1 = shade->u.f.domain[1][1]; + int xx, yy; + float y, yn, x; + fz_vertex vs[2][2]; + fz_vertex *v = vs[0]; + fz_vertex *vn = vs[1]; + int n = fz_colorspace_n(ctx, shade->colorspace); + + ctm = fz_concat(shade->u.f.matrix, ctm); + + y = y0; + for (yy = 0; yy < ydivs; yy++) + { + yn = y0 + (y1 - y0) * (yy + 1) / ydivs; + + x = x0; + + fz_prepare_vertex(ctx, painter, &v[0], ctm, x, y, p); + p += n; + fz_prepare_vertex(ctx, painter, &v[1], ctm, x, yn, p + xdivs * n); + + for (xx = 0; xx < xdivs; xx++) + { + x = x0 + (x1 - x0) * (xx + 1) / xdivs; + + fz_prepare_vertex(ctx, painter, &vn[0], ctm, x, y, p); + p += n; + fz_prepare_vertex(ctx, painter, &vn[1], ctm, x, yn, p + xdivs * n); + + paint_quad(ctx, painter, &v[0], &vn[0], &vn[1], &v[1]); + SWAP(v,vn); + } + y = yn; + } +} + +#define HUGENUM 32000 /* how far to extend linear/radial shadings */ + +static fz_point +fz_point_on_circle(fz_point p, float r, float theta) +{ + p.x = p.x + cosf(theta) * r; + p.y = p.y + sinf(theta) * r; + return p; +} + +static void +fz_process_shade_type2(fz_context *ctx, fz_shade *shade, fz_matrix ctm, fz_mesh_processor *painter, fz_rect scissor) +{ + fz_point p0, p1, dir; + fz_vertex v0, v1, v2, v3; + fz_vertex e0, e1; + float theta; + float zero = 0; + float one = 1; + float r; + + p0.x = shade->u.l_or_r.coords[0][0]; + p0.y = shade->u.l_or_r.coords[0][1]; + p1.x = shade->u.l_or_r.coords[1][0]; + p1.y = shade->u.l_or_r.coords[1][1]; + dir.x = p0.y - p1.y; + dir.y = p1.x - p0.x; + p0 = fz_transform_point(p0, ctm); + p1 = fz_transform_point(p1, ctm); + dir = fz_transform_vector(dir, ctm); + theta = atan2f(dir.y, dir.x); + + if (fz_is_infinite_rect(scissor)) { + r = HUGENUM; /* Not ideal, but it'll do for now */ + } else { + float x = p0.x - scissor.x0; + float y = p0.y - scissor.y0; + if (x < scissor.x1 - p0.x) + x = scissor.x1 - p0.x; + if (x < p0.x - scissor.x1) + x = p0.x - scissor.x1; + if (x < scissor.x1 - p1.x) + x = scissor.x1 - p1.x; + if (y < scissor.y1 - p0.y) + y = scissor.y1 - p0.y; + if (y < p0.y - scissor.y1) + y = p0.y - scissor.y1; + if (y < scissor.y1 - p1.y) + y = scissor.y1 - p1.y; + r = x+y; + } + v0.p = fz_point_on_circle(p0, r, theta); + v1.p = fz_point_on_circle(p1, r, theta); + v2.p.x = 2*p0.x - v0.p.x; + v2.p.y = 2*p0.y - v0.p.y; + v3.p.x = 2*p1.x - v1.p.x; + v3.p.y = 2*p1.y - v1.p.y; + + fz_prepare_color(ctx, painter, &v0, &zero); + fz_prepare_color(ctx, painter, &v1, &one); + fz_prepare_color(ctx, painter, &v2, &zero); + fz_prepare_color(ctx, painter, &v3, &one); + + paint_quad(ctx, painter, &v0, &v2, &v3, &v1); + + if (shade->u.l_or_r.extend[0] || shade->u.l_or_r.extend[1]) { + float d = fabsf(p1.x - p0.x); + float e = fabsf(p1.y - p0.y); + if (d < e) + d = e; + if (d != 0) + r /= d; + } + if (shade->u.l_or_r.extend[0]) + { + e0.p.x = v0.p.x - (p1.x - p0.x) * r; + e0.p.y = v0.p.y - (p1.y - p0.y) * r; + fz_prepare_color(ctx, painter, &e0, &zero); + + e1.p.x = v2.p.x - (p1.x - p0.x) * r; + e1.p.y = v2.p.y - (p1.y - p0.y) * r; + fz_prepare_color(ctx, painter, &e1, &zero); + + paint_quad(ctx, painter, &e0, &v0, &v2, &e1); + } + + if (shade->u.l_or_r.extend[1]) + { + e0.p.x = v1.p.x + (p1.x - p0.x) * r; + e0.p.y = v1.p.y + (p1.y - p0.y) * r; + fz_prepare_color(ctx, painter, &e0, &one); + + e1.p.x = v3.p.x + (p1.x - p0.x) * r; + e1.p.y = v3.p.y + (p1.y - p0.y) * r; + fz_prepare_color(ctx, painter, &e1, &one); + + paint_quad(ctx, painter, &e0, &v1, &v3, &e1); + } +} + +static void +fz_paint_annulus(fz_context *ctx, fz_matrix ctm, + fz_point p0, float r0, float c0, + fz_point p1, float r1, float c1, + int count, + fz_mesh_processor *painter) +{ + fz_vertex t0, t1, t2, t3, b0, b1, b2, b3; + float theta, step, a, b; + int i; + + theta = atan2f(p1.y - p0.y, p1.x - p0.x); + step = FZ_PI / count; + + a = 0; + for (i = 1; i <= count; i++) + { + b = i * step; + + t0.p = fz_transform_point(fz_point_on_circle(p0, r0, theta + a), ctm); + t1.p = fz_transform_point(fz_point_on_circle(p0, r0, theta + b), ctm); + t2.p = fz_transform_point(fz_point_on_circle(p1, r1, theta + a), ctm); + t3.p = fz_transform_point(fz_point_on_circle(p1, r1, theta + b), ctm); + b0.p = fz_transform_point(fz_point_on_circle(p0, r0, theta - a), ctm); + b1.p = fz_transform_point(fz_point_on_circle(p0, r0, theta - b), ctm); + b2.p = fz_transform_point(fz_point_on_circle(p1, r1, theta - a), ctm); + b3.p = fz_transform_point(fz_point_on_circle(p1, r1, theta - b), ctm); + + fz_prepare_color(ctx, painter, &t0, &c0); + fz_prepare_color(ctx, painter, &t1, &c0); + fz_prepare_color(ctx, painter, &t2, &c1); + fz_prepare_color(ctx, painter, &t3, &c1); + fz_prepare_color(ctx, painter, &b0, &c0); + fz_prepare_color(ctx, painter, &b1, &c0); + fz_prepare_color(ctx, painter, &b2, &c1); + fz_prepare_color(ctx, painter, &b3, &c1); + + paint_quad(ctx, painter, &t0, &t2, &t3, &t1); + paint_quad(ctx, painter, &b0, &b2, &b3, &b1); + + a = b; + } +} + +static void +fz_process_shade_type3(fz_context *ctx, fz_shade *shade, fz_matrix ctm, fz_mesh_processor *painter) +{ + fz_point p0, p1; + float r0, r1; + fz_point e; + float er, rs; + int count; + + p0.x = shade->u.l_or_r.coords[0][0]; + p0.y = shade->u.l_or_r.coords[0][1]; + r0 = shade->u.l_or_r.coords[0][2]; + + p1.x = shade->u.l_or_r.coords[1][0]; + p1.y = shade->u.l_or_r.coords[1][1]; + r1 = shade->u.l_or_r.coords[1][2]; + + /* number of segments for a half-circle */ + count = 4 * sqrtf(fz_matrix_expansion(ctm) * fz_max(r0, r1)); + if (count < 3) + count = 3; + if (count > 1024) + count = 1024; + + if (shade->u.l_or_r.extend[0]) + { + if (r0 < r1) + rs = r0 / (r0 - r1); + else + rs = -HUGENUM; + + e.x = p0.x + (p1.x - p0.x) * rs; + e.y = p0.y + (p1.y - p0.y) * rs; + er = r0 + (r1 - r0) * rs; + + fz_paint_annulus(ctx, ctm, e, er, 0, p0, r0, 0, count, painter); + } + + fz_paint_annulus(ctx, ctm, p0, r0, 0, p1, r1, 1, count, painter); + + if (shade->u.l_or_r.extend[1]) + { + if (r0 > r1) + rs = r1 / (r1 - r0); + else + rs = -HUGENUM; + + e.x = p1.x + (p0.x - p1.x) * rs; + e.y = p1.y + (p0.y - p1.y) * rs; + er = r1 + (r0 - r1) * rs; + + fz_paint_annulus(ctx, ctm, p1, r1, 1, e, er, 1, count, painter); + } +} + +static inline float read_sample(fz_context *ctx, fz_stream *stream, int bits, float min, float max) +{ + /* we use pow(2,x) because (1<<x) would overflow the math on 32-bit samples */ + float bitscale = 1 / (powf(2, bits) - 1); + return min + fz_read_bits(ctx, stream, bits) * (max - min) * bitscale; +} + +static void +fz_process_shade_type4(fz_context *ctx, fz_shade *shade, fz_matrix ctm, fz_mesh_processor *painter) +{ + fz_stream *stream = fz_open_compressed_buffer(ctx, shade->buffer); + fz_vertex v[4]; + fz_vertex *va = &v[0]; + fz_vertex *vb = &v[1]; + fz_vertex *vc = &v[2]; + fz_vertex *vd = &v[3]; + int flag, i, ncomp = painter->ncomp; + int bpflag = shade->u.m.bpflag; + int bpcoord = shade->u.m.bpcoord; + int bpcomp = shade->u.m.bpcomp; + float x0 = shade->u.m.x0; + float x1 = shade->u.m.x1; + float y0 = shade->u.m.y0; + float y1 = shade->u.m.y1; + const float *c0 = shade->u.m.c0; + const float *c1 = shade->u.m.c1; + float x, y, c[FZ_MAX_COLORS]; + int first_triangle = 1; + + fz_try(ctx) + { + while (!fz_is_eof_bits(ctx, stream)) + { + flag = fz_read_bits(ctx, stream, bpflag); + x = read_sample(ctx, stream, bpcoord, x0, x1); + y = read_sample(ctx, stream, bpcoord, y0, y1); + for (i = 0; i < ncomp; i++) + c[i] = read_sample(ctx, stream, bpcomp, c0[i], c1[i]); + fz_prepare_vertex(ctx, painter, vd, ctm, x, y, c); + + if (first_triangle) + { + if (flag != 0) + { + fz_warn(ctx, "ignoring non-zero edge flags for first vertex in mesh"); + flag = 0; + } + first_triangle = 0; + } + + switch (flag) + { + default: + fz_warn(ctx, "ignoring out of range edge flag in mesh"); + /* fallthrough */ + + case 0: /* start new triangle */ + SWAP(va, vd); + + fz_read_bits(ctx, stream, bpflag); + x = read_sample(ctx, stream, bpcoord, x0, x1); + y = read_sample(ctx, stream, bpcoord, y0, y1); + for (i = 0; i < ncomp; i++) + c[i] = read_sample(ctx, stream, bpcomp, c0[i], c1[i]); + fz_prepare_vertex(ctx, painter, vb, ctm, x, y, c); + + fz_read_bits(ctx, stream, bpflag); + x = read_sample(ctx, stream, bpcoord, x0, x1); + y = read_sample(ctx, stream, bpcoord, y0, y1); + for (i = 0; i < ncomp; i++) + c[i] = read_sample(ctx, stream, bpcomp, c0[i], c1[i]); + fz_prepare_vertex(ctx, painter, vc, ctm, x, y, c); + + paint_tri(ctx, painter, va, vb, vc); + break; + + case 1: /* Vb, Vc, Vd */ + SWAP(va, vb); + SWAP(vb, vc); + SWAP(vc, vd); + paint_tri(ctx, painter, va, vb, vc); + break; + + case 2: /* Va, Vc, Vd */ + SWAP(vb, vc); + SWAP(vc, vd); + paint_tri(ctx, painter, va, vb, vc); + break; + } + } + } + fz_always(ctx) + { + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +static void +fz_process_shade_type5(fz_context *ctx, fz_shade *shade, fz_matrix ctm, fz_mesh_processor *painter) +{ + fz_stream *stream = fz_open_compressed_buffer(ctx, shade->buffer); + fz_vertex *buf = NULL; + fz_vertex *ref = NULL; + int first; + int ncomp = painter->ncomp; + int i, k; + int vprow = shade->u.m.vprow; + int bpcoord = shade->u.m.bpcoord; + int bpcomp = shade->u.m.bpcomp; + float x0 = shade->u.m.x0; + float x1 = shade->u.m.x1; + float y0 = shade->u.m.y0; + float y1 = shade->u.m.y1; + const float *c0 = shade->u.m.c0; + const float *c1 = shade->u.m.c1; + float x, y, c[FZ_MAX_COLORS]; + + fz_var(buf); + fz_var(ref); + + fz_try(ctx) + { + ref = fz_malloc_array(ctx, vprow, fz_vertex); + buf = fz_malloc_array(ctx, vprow, fz_vertex); + first = 1; + + while (!fz_is_eof_bits(ctx, stream)) + { + for (i = 0; i < vprow; i++) + { + x = read_sample(ctx, stream, bpcoord, x0, x1); + y = read_sample(ctx, stream, bpcoord, y0, y1); + for (k = 0; k < ncomp; k++) + c[k] = read_sample(ctx, stream, bpcomp, c0[k], c1[k]); + fz_prepare_vertex(ctx, painter, &buf[i], ctm, x, y, c); + } + + if (!first) + for (i = 0; i < vprow - 1; i++) + paint_quad(ctx, painter, &ref[i], &ref[i+1], &buf[i+1], &buf[i]); + + SWAP(ref,buf); + first = 0; + } + } + fz_always(ctx) + { + fz_free(ctx, ref); + fz_free(ctx, buf); + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +/* Subdivide and tessellate tensor-patches */ + +typedef struct +{ + fz_point pole[4][4]; + float color[4][FZ_MAX_COLORS]; +} tensor_patch; + +static void +triangulate_patch(fz_context *ctx, fz_mesh_processor *painter, tensor_patch *p) +{ + fz_vertex v0, v1, v2, v3; + + v0.p = p->pole[0][0]; + v1.p = p->pole[0][3]; + v2.p = p->pole[3][3]; + v3.p = p->pole[3][0]; + + fz_prepare_color(ctx, painter, &v0, p->color[0]); + fz_prepare_color(ctx, painter, &v1, p->color[1]); + fz_prepare_color(ctx, painter, &v2, p->color[2]); + fz_prepare_color(ctx, painter, &v3, p->color[3]); + + paint_quad(ctx, painter, &v0, &v1, &v2, &v3); +} + +static inline void midcolor(float *c, float *c1, float *c2, int n) +{ + int i; + for (i = 0; i < n; i++) + c[i] = (c1[i] + c2[i]) * 0.5f; +} + +static void +split_curve(fz_point *pole, fz_point *q0, fz_point *q1, int polestep) +{ + /* + split bezier curve given by control points pole[0]..pole[3] + using de casteljau algo at midpoint and build two new + bezier curves q0[0]..q0[3] and q1[0]..q1[3]. all indices + should be multiplies by polestep == 1 for vertical bezier + curves in patch and == 4 for horizontal bezier curves due + to C's multi-dimensional matrix memory layout. + */ + + float x12 = (pole[1 * polestep].x + pole[2 * polestep].x) * 0.5f; + float y12 = (pole[1 * polestep].y + pole[2 * polestep].y) * 0.5f; + + q0[1 * polestep].x = (pole[0 * polestep].x + pole[1 * polestep].x) * 0.5f; + q0[1 * polestep].y = (pole[0 * polestep].y + pole[1 * polestep].y) * 0.5f; + q1[2 * polestep].x = (pole[2 * polestep].x + pole[3 * polestep].x) * 0.5f; + q1[2 * polestep].y = (pole[2 * polestep].y + pole[3 * polestep].y) * 0.5f; + + q0[2 * polestep].x = (q0[1 * polestep].x + x12) * 0.5f; + q0[2 * polestep].y = (q0[1 * polestep].y + y12) * 0.5f; + q1[1 * polestep].x = (x12 + q1[2 * polestep].x) * 0.5f; + q1[1 * polestep].y = (y12 + q1[2 * polestep].y) * 0.5f; + + q0[3 * polestep].x = (q0[2 * polestep].x + q1[1 * polestep].x) * 0.5f; + q0[3 * polestep].y = (q0[2 * polestep].y + q1[1 * polestep].y) * 0.5f; + q1[0 * polestep].x = (q0[2 * polestep].x + q1[1 * polestep].x) * 0.5f; + q1[0 * polestep].y = (q0[2 * polestep].y + q1[1 * polestep].y) * 0.5f; + + q0[0 * polestep].x = pole[0 * polestep].x; + q0[0 * polestep].y = pole[0 * polestep].y; + q1[3 * polestep].x = pole[3 * polestep].x; + q1[3 * polestep].y = pole[3 * polestep].y; +} + +static void +split_stripe(tensor_patch *p, tensor_patch *s0, tensor_patch *s1, int n) +{ + /* + split all horizontal bezier curves in patch, + creating two new patches with half the width. + */ + split_curve(&p->pole[0][0], &s0->pole[0][0], &s1->pole[0][0], 4); + split_curve(&p->pole[0][1], &s0->pole[0][1], &s1->pole[0][1], 4); + split_curve(&p->pole[0][2], &s0->pole[0][2], &s1->pole[0][2], 4); + split_curve(&p->pole[0][3], &s0->pole[0][3], &s1->pole[0][3], 4); + + /* interpolate the colors for the two new patches. */ + memcpy(s0->color[0], p->color[0], n * sizeof(s0->color[0][0])); + memcpy(s0->color[1], p->color[1], n * sizeof(s0->color[1][0])); + midcolor(s0->color[2], p->color[1], p->color[2], n); + midcolor(s0->color[3], p->color[0], p->color[3], n); + + memcpy(s1->color[0], s0->color[3], n * sizeof(s1->color[0][0])); + memcpy(s1->color[1], s0->color[2], n * sizeof(s1->color[1][0])); + memcpy(s1->color[2], p->color[2], n * sizeof(s1->color[2][0])); + memcpy(s1->color[3], p->color[3], n * sizeof(s1->color[3][0])); +} + +static void +draw_stripe(fz_context *ctx, fz_mesh_processor *painter, tensor_patch *p, int depth) +{ + tensor_patch s0, s1; + + /* split patch into two half-height patches */ + split_stripe(p, &s0, &s1, painter->ncomp); + + depth--; + if (depth == 0) + { + /* if no more subdividing, draw two new patches... */ + triangulate_patch(ctx, painter, &s1); + triangulate_patch(ctx, painter, &s0); + } + else + { + /* ...otherwise, continue subdividing. */ + draw_stripe(ctx, painter, &s1, depth); + draw_stripe(ctx, painter, &s0, depth); + } +} + +static void +split_patch(tensor_patch *p, tensor_patch *s0, tensor_patch *s1, int n) +{ + /* + split all vertical bezier curves in patch, + creating two new patches with half the height. + */ + split_curve(p->pole[0], s0->pole[0], s1->pole[0], 1); + split_curve(p->pole[1], s0->pole[1], s1->pole[1], 1); + split_curve(p->pole[2], s0->pole[2], s1->pole[2], 1); + split_curve(p->pole[3], s0->pole[3], s1->pole[3], 1); + + /* interpolate the colors for the two new patches. */ + memcpy(s0->color[0], p->color[0], n * sizeof(s0->color[0][0])); + midcolor(s0->color[1], p->color[0], p->color[1], n); + midcolor(s0->color[2], p->color[2], p->color[3], n); + memcpy(s0->color[3], p->color[3], n * sizeof(s0->color[3][0])); + + memcpy(s1->color[0], s0->color[1], n * sizeof(s1->color[0][0])); + memcpy(s1->color[1], p->color[1], n * sizeof(s1->color[1][0])); + memcpy(s1->color[2], p->color[2], n * sizeof(s1->color[2][0])); + memcpy(s1->color[3], s0->color[2], n * sizeof(s1->color[3][0])); +} + +static void +draw_patch(fz_context *ctx, fz_mesh_processor *painter, tensor_patch *p, int depth, int origdepth) +{ + tensor_patch s0, s1; + + /* split patch into two half-width patches */ + split_patch(p, &s0, &s1, painter->ncomp); + + depth--; + if (depth == 0) + { + /* if no more subdividing, draw two new patches... */ + draw_stripe(ctx, painter, &s0, origdepth); + draw_stripe(ctx, painter, &s1, origdepth); + } + else + { + /* ...otherwise, continue subdividing. */ + draw_patch(ctx, painter, &s0, depth, origdepth); + draw_patch(ctx, painter, &s1, depth, origdepth); + } +} + +static fz_point +compute_tensor_interior( + fz_point a, fz_point b, fz_point c, fz_point d, + fz_point e, fz_point f, fz_point g, fz_point h) +{ + fz_point pt; + + /* see equations at page 330 in pdf 1.7 */ + + pt.x = -4 * a.x; + pt.x += 6 * (b.x + c.x); + pt.x += -2 * (d.x + e.x); + pt.x += 3 * (f.x + g.x); + pt.x += -1 * h.x; + pt.x /= 9; + + pt.y = -4 * a.y; + pt.y += 6 * (b.y + c.y); + pt.y += -2 * (d.y + e.y); + pt.y += 3 * (f.y + g.y); + pt.y += -1 * h.y; + pt.y /= 9; + + return pt; +} + +static void +make_tensor_patch(tensor_patch *p, int type, fz_point *pt) +{ + if (type == 6) + { + /* see control point stream order at page 325 in pdf 1.7 */ + + p->pole[0][0] = pt[0]; + p->pole[0][1] = pt[1]; + p->pole[0][2] = pt[2]; + p->pole[0][3] = pt[3]; + p->pole[1][3] = pt[4]; + p->pole[2][3] = pt[5]; + p->pole[3][3] = pt[6]; + p->pole[3][2] = pt[7]; + p->pole[3][1] = pt[8]; + p->pole[3][0] = pt[9]; + p->pole[2][0] = pt[10]; + p->pole[1][0] = pt[11]; + + /* see equations at page 330 in pdf 1.7 */ + + p->pole[1][1] = compute_tensor_interior( + p->pole[0][0], p->pole[0][1], p->pole[1][0], p->pole[0][3], + p->pole[3][0], p->pole[3][1], p->pole[1][3], p->pole[3][3]); + + p->pole[1][2] = compute_tensor_interior( + p->pole[0][3], p->pole[0][2], p->pole[1][3], p->pole[0][0], + p->pole[3][3], p->pole[3][2], p->pole[1][0], p->pole[3][0]); + + p->pole[2][1] = compute_tensor_interior( + p->pole[3][0], p->pole[3][1], p->pole[2][0], p->pole[3][3], + p->pole[0][0], p->pole[0][1], p->pole[2][3], p->pole[0][3]); + + p->pole[2][2] = compute_tensor_interior( + p->pole[3][3], p->pole[3][2], p->pole[2][3], p->pole[3][0], + p->pole[0][3], p->pole[0][2], p->pole[2][0], p->pole[0][0]); + } + else if (type == 7) + { + /* see control point stream order at page 330 in pdf 1.7 */ + + p->pole[0][0] = pt[0]; + p->pole[0][1] = pt[1]; + p->pole[0][2] = pt[2]; + p->pole[0][3] = pt[3]; + p->pole[1][3] = pt[4]; + p->pole[2][3] = pt[5]; + p->pole[3][3] = pt[6]; + p->pole[3][2] = pt[7]; + p->pole[3][1] = pt[8]; + p->pole[3][0] = pt[9]; + p->pole[2][0] = pt[10]; + p->pole[1][0] = pt[11]; + p->pole[1][1] = pt[12]; + p->pole[1][2] = pt[13]; + p->pole[2][2] = pt[14]; + p->pole[2][1] = pt[15]; + } +} + +/* FIXME: Nasty */ +#define SUBDIV 3 /* how many levels to subdivide patches */ + +static void +fz_process_shade_type6(fz_context *ctx, fz_shade *shade, fz_matrix ctm, fz_mesh_processor *painter) +{ + fz_stream *stream = fz_open_compressed_buffer(ctx, shade->buffer); + float color_storage[2][4][FZ_MAX_COLORS]; + fz_point point_storage[2][12]; + int store = 0; + int ncomp = painter->ncomp; + int i, k; + int bpflag = shade->u.m.bpflag; + int bpcoord = shade->u.m.bpcoord; + int bpcomp = shade->u.m.bpcomp; + float x0 = shade->u.m.x0; + float x1 = shade->u.m.x1; + float y0 = shade->u.m.y0; + float y1 = shade->u.m.y1; + const float *c0 = shade->u.m.c0; + const float *c1 = shade->u.m.c1; + + fz_try(ctx) + { + float (*prevc)[FZ_MAX_COLORS] = NULL; + fz_point *prevp = NULL; + while (!fz_is_eof_bits(ctx, stream)) + { + float (*c)[FZ_MAX_COLORS] = color_storage[store]; + fz_point *v = point_storage[store]; + int startcolor; + int startpt; + int flag; + tensor_patch patch; + + flag = fz_read_bits(ctx, stream, bpflag); + + if (flag == 0) + { + startpt = 0; + startcolor = 0; + } + else + { + startpt = 4; + startcolor = 2; + } + + for (i = startpt; i < 12; i++) + { + v[i].x = read_sample(ctx, stream, bpcoord, x0, x1); + v[i].y = read_sample(ctx, stream, bpcoord, y0, y1); + v[i] = fz_transform_point(v[i], ctm); + } + + for (i = startcolor; i < 4; i++) + { + for (k = 0; k < ncomp; k++) + c[i][k] = read_sample(ctx, stream, bpcomp, c0[k], c1[k]); + } + + if (flag == 0) + { + /* No patch data to copy forwards */ + } + else if (flag == 1 && prevc) + { + v[0] = prevp[3]; + v[1] = prevp[4]; + v[2] = prevp[5]; + v[3] = prevp[6]; + memcpy(c[0], prevc[1], ncomp * sizeof(float)); + memcpy(c[1], prevc[2], ncomp * sizeof(float)); + } + else if (flag == 2 && prevc) + { + v[0] = prevp[6]; + v[1] = prevp[7]; + v[2] = prevp[8]; + v[3] = prevp[9]; + memcpy(c[0], prevc[2], ncomp * sizeof(float)); + memcpy(c[1], prevc[3], ncomp * sizeof(float)); + } + else if (flag == 3 && prevc) + { + v[0] = prevp[ 9]; + v[1] = prevp[10]; + v[2] = prevp[11]; + v[3] = prevp[ 0]; + memcpy(c[0], prevc[3], ncomp * sizeof(float)); + memcpy(c[1], prevc[0], ncomp * sizeof(float)); + } + else + continue; + + make_tensor_patch(&patch, 6, v); + + for (i = 0; i < 4; i++) + memcpy(patch.color[i], c[i], ncomp * sizeof(float)); + + draw_patch(ctx, painter, &patch, SUBDIV, SUBDIV); + + prevp = v; + prevc = c; + store ^= 1; + } + } + fz_always(ctx) + { + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +static void +fz_process_shade_type7(fz_context *ctx, fz_shade *shade, fz_matrix ctm, fz_mesh_processor *painter) +{ + fz_stream *stream = fz_open_compressed_buffer(ctx, shade->buffer); + int bpflag = shade->u.m.bpflag; + int bpcoord = shade->u.m.bpcoord; + int bpcomp = shade->u.m.bpcomp; + float x0 = shade->u.m.x0; + float x1 = shade->u.m.x1; + float y0 = shade->u.m.y0; + float y1 = shade->u.m.y1; + const float *c0 = shade->u.m.c0; + const float *c1 = shade->u.m.c1; + float color_storage[2][4][FZ_MAX_COLORS]; + fz_point point_storage[2][16]; + int store = 0; + int ncomp = painter->ncomp; + int i, k; + float (*prevc)[FZ_MAX_COLORS] = NULL; + fz_point (*prevp) = NULL; + + fz_try(ctx) + { + while (!fz_is_eof_bits(ctx, stream)) + { + float (*c)[FZ_MAX_COLORS] = color_storage[store]; + fz_point *v = point_storage[store]; + int startcolor; + int startpt; + int flag; + tensor_patch patch; + + flag = fz_read_bits(ctx, stream, bpflag); + + if (flag == 0) + { + startpt = 0; + startcolor = 0; + } + else + { + startpt = 4; + startcolor = 2; + } + + for (i = startpt; i < 16; i++) + { + v[i].x = read_sample(ctx, stream, bpcoord, x0, x1); + v[i].y = read_sample(ctx, stream, bpcoord, y0, y1); + v[i] = fz_transform_point(v[i], ctm); + } + + for (i = startcolor; i < 4; i++) + { + for (k = 0; k < ncomp; k++) + c[i][k] = read_sample(ctx, stream, bpcomp, c0[k], c1[k]); + } + + if (flag == 0) + { + /* No patch data to copy forward */ + } + else if (flag == 1 && prevc) + { + v[0] = prevp[3]; + v[1] = prevp[4]; + v[2] = prevp[5]; + v[3] = prevp[6]; + memcpy(c[0], prevc[1], ncomp * sizeof(float)); + memcpy(c[1], prevc[2], ncomp * sizeof(float)); + } + else if (flag == 2 && prevc) + { + v[0] = prevp[6]; + v[1] = prevp[7]; + v[2] = prevp[8]; + v[3] = prevp[9]; + memcpy(c[0], prevc[2], ncomp * sizeof(float)); + memcpy(c[1], prevc[3], ncomp * sizeof(float)); + } + else if (flag == 3 && prevc) + { + v[0] = prevp[ 9]; + v[1] = prevp[10]; + v[2] = prevp[11]; + v[3] = prevp[ 0]; + memcpy(c[0], prevc[3], ncomp * sizeof(float)); + memcpy(c[1], prevc[0], ncomp * sizeof(float)); + } + else + continue; /* We have no patch! */ + + make_tensor_patch(&patch, 7, v); + + for (i = 0; i < 4; i++) + memcpy(patch.color[i], c[i], ncomp * sizeof(float)); + + draw_patch(ctx, painter, &patch, SUBDIV, SUBDIV); + + prevp = v; + prevc = c; + store ^= 1; + } + } + fz_always(ctx) + { + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +void +fz_process_shade(fz_context *ctx, fz_shade *shade, fz_matrix ctm, fz_rect scissor, + fz_shade_prepare_fn *prepare, fz_shade_process_fn *process, void *process_arg) +{ + fz_mesh_processor painter; + + painter.shade = shade; + painter.prepare = prepare; + painter.process = process; + painter.process_arg = process_arg; + painter.ncomp = (shade->function_stride > 0 ? 1 : fz_colorspace_n(ctx, shade->colorspace)); + + if (shade->type == FZ_FUNCTION_BASED) + fz_process_shade_type1(ctx, shade, ctm, &painter); + else if (shade->type == FZ_LINEAR) + fz_process_shade_type2(ctx, shade, ctm, &painter, scissor); + else if (shade->type == FZ_RADIAL) + fz_process_shade_type3(ctx, shade, ctm, &painter); + else if (shade->type == FZ_MESH_TYPE4) + fz_process_shade_type4(ctx, shade, ctm, &painter); + else if (shade->type == FZ_MESH_TYPE5) + fz_process_shade_type5(ctx, shade, ctm, &painter); + else if (shade->type == FZ_MESH_TYPE6) + fz_process_shade_type6(ctx, shade, ctm, &painter); + else if (shade->type == FZ_MESH_TYPE7) + fz_process_shade_type7(ctx, shade, ctm, &painter); + else + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Unexpected mesh type %d\n", shade->type); +} + +static fz_rect +fz_bound_mesh_type1(fz_context *ctx, fz_shade *shade) +{ + fz_rect bbox; + bbox.x0 = shade->u.f.domain[0][0]; + bbox.y0 = shade->u.f.domain[0][1]; + bbox.x1 = shade->u.f.domain[1][0]; + bbox.y1 = shade->u.f.domain[1][1]; + return fz_transform_rect(bbox, shade->u.f.matrix); +} + +static fz_rect +fz_bound_mesh_type2(fz_context *ctx, fz_shade *shade) +{ + /* FIXME: If axis aligned and not extended, the bbox may only be + * infinite in one direction */ + return fz_infinite_rect; +} + +static fz_rect +fz_bound_mesh_type3(fz_context *ctx, fz_shade *shade) +{ + fz_rect bbox; + fz_point p0, p1; + float r0, r1; + + r0 = shade->u.l_or_r.coords[0][2]; + r1 = shade->u.l_or_r.coords[1][2]; + + if (shade->u.l_or_r.extend[0]) + { + if (r0 >= r1) + return fz_infinite_rect; + } + + if (shade->u.l_or_r.extend[1]) + { + if (r0 <= r1) + return fz_infinite_rect; + } + + p0.x = shade->u.l_or_r.coords[0][0]; + p0.y = shade->u.l_or_r.coords[0][1]; + p1.x = shade->u.l_or_r.coords[1][0]; + p1.y = shade->u.l_or_r.coords[1][1]; + + bbox.x0 = p0.x - r0; bbox.y0 = p0.y - r0; + bbox.x1 = p0.x + r0; bbox.y1 = p0.x + r0; + if (bbox.x0 > p1.x - r1) + bbox.x0 = p1.x - r1; + if (bbox.x1 < p1.x + r1) + bbox.x1 = p1.x + r1; + if (bbox.y0 > p1.y - r1) + bbox.y0 = p1.y - r1; + if (bbox.y1 < p1.y + r1) + bbox.y1 = p1.y + r1; + return bbox; +} + +static fz_rect +fz_bound_mesh_type4567(fz_context *ctx, fz_shade *shade) +{ + fz_rect bbox; + bbox.x0 = fz_min(shade->u.m.x0, shade->u.m.x1); + bbox.y0 = fz_min(shade->u.m.y0, shade->u.m.y1); + bbox.x1 = fz_max(shade->u.m.x0, shade->u.m.x1); + bbox.y1 = fz_max(shade->u.m.y0, shade->u.m.y1); + return bbox; +} + +static fz_rect +fz_bound_mesh(fz_context *ctx, fz_shade *shade) +{ + if (shade->type == FZ_FUNCTION_BASED) + return fz_bound_mesh_type1(ctx, shade); + else if (shade->type == FZ_LINEAR) + return fz_bound_mesh_type2(ctx, shade); + else if (shade->type == FZ_RADIAL) + return fz_bound_mesh_type3(ctx, shade); + else if (shade->type == FZ_MESH_TYPE4 || + shade->type == FZ_MESH_TYPE5 || + shade->type == FZ_MESH_TYPE6 || + shade->type == FZ_MESH_TYPE7) + return fz_bound_mesh_type4567(ctx, shade); + else + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Unexpected mesh type %d\n", shade->type); +} + +fz_shade * +fz_keep_shade(fz_context *ctx, fz_shade *shade) +{ + return fz_keep_storable(ctx, &shade->storable); +} + +void +fz_drop_shade_imp(fz_context *ctx, fz_storable *shade_) +{ + fz_shade *shade = (fz_shade *)shade_; + + fz_drop_colorspace(ctx, shade->colorspace); + if (shade->type == FZ_FUNCTION_BASED) + fz_free(ctx, shade->u.f.fn_vals); + fz_drop_compressed_buffer(ctx, shade->buffer); + fz_free(ctx, shade->function); + fz_free(ctx, shade); +} + +void +fz_drop_shade(fz_context *ctx, fz_shade *shade) +{ + fz_drop_storable(ctx, &shade->storable); +} + +fz_rect +fz_bound_shade(fz_context *ctx, fz_shade *shade, fz_matrix ctm) +{ + ctm = fz_concat(shade->matrix, ctm); + if (shade->type != FZ_LINEAR && shade->type != FZ_RADIAL) + { + fz_rect rect = fz_bound_mesh(ctx, shade); + rect = fz_intersect_rect(rect, shade->bbox); + return fz_transform_rect(rect, ctm); + } + return fz_transform_rect(shade->bbox, ctm); +}
