Example #1
0
fz_rect
fz_boundmasknode(fz_masknode *node, fz_matrix ctm)
{
	fz_node *shape;
	fz_node *color;
	fz_rect one, two;

	shape = node->super.first;
	color = shape->next;

	one = fz_boundnode(shape, ctm);
	two = fz_boundnode(color, ctm);
	return fz_intersectrects(one, two);
}
Example #2
0
fz_rect
fz_boundtree(fz_tree *tree, fz_matrix ctm)
{
	if (tree->root)
		return fz_boundnode(tree->root, ctm);
	return fz_emptyrect;
}
Example #3
0
fz_rect
fz_boundtransformnode(fz_transformnode *node, fz_matrix ctm)
{
	if (!node->super.first)
		return fz_emptyrect;
	return fz_boundnode(node->super.first, fz_concat(node->m, ctm));
}
Example #4
0
static fz_error *
rendertext(fz_renderer *gc, fz_textnode *text, fz_matrix ctm)
{
	fz_error *error;
	fz_irect tbox;
	fz_irect clip;
	fz_matrix tm, trm;
	fz_glyph glyph;
	int i, x, y, cid;

	tbox = fz_roundrect(fz_boundnode((fz_node*)text, ctm));
	clip = fz_intersectirects(gc->clip, tbox);

DEBUG("text %s n=%d [%g %g %g %g];\n",
text->font->name, text->len,
text->trm.a, text->trm.b, text->trm.c, text->trm.d);

	if (fz_isemptyrect(clip))
		return fz_okay;

	if (!(gc->flag & FOVER))
	{
		error = fz_newpixmapwithrect(&gc->dest, clip, 1);
		if (error)
			return error;
		fz_clearpixmap(gc->dest);
	}

	tm = text->trm;

	for (i = 0; i < text->len; i++)
	{
		cid = text->els[i].cid;
		tm.e = text->els[i].x;
		tm.f = text->els[i].y;
		trm = fz_concat(tm, ctm);
		x = fz_floor(trm.e);
		y = fz_floor(trm.f);
		trm.e = QUANT(trm.e - fz_floor(trm.e), HSUBPIX);
		trm.f = QUANT(trm.f - fz_floor(trm.f), VSUBPIX);

		error = fz_renderglyph(gc->cache, &glyph, text->font, cid, trm);
		if (error)
			return error;

		if (!(gc->flag & FOVER))
			drawglyph(gc, gc->dest, &glyph, x, y);
		else
			drawglyph(gc, gc->over, &glyph, x, y);
	}

	return fz_okay;
}
Example #5
0
static int fitsinside(fz_node *node, fz_rect clip)
{
    fz_rect bbox;
    bbox = fz_boundnode(node, fz_identity());
    if (fz_isinfiniterect(bbox)) return 0;
    if (fz_isemptyrect(bbox)) return 1;
    if (bbox.x0 < clip.x0) return 0;
    if (bbox.x1 > clip.x1) return 0;
    if (bbox.y0 < clip.y0) return 0;
    if (bbox.y1 > clip.y1) return 0;
    return 1;
}
Example #6
0
/*
 * Over
 */

fz_error
fz_newovernode(fz_node **nodep)
{
	fz_node *node;

	node = *nodep = fz_malloc(sizeof (fz_overnode));
	if (!node)
		return fz_rethrow(-1, "out of memory");

	fz_initnode(node, FZ_NOVER);

	return fz_okay;
}

#if 0 /* cf. http://code.google.com/p/sumatrapdf/issues/detail?id=533 */
fz_rect
fz_boundovernode(fz_overnode *node, fz_matrix ctm)
{
	fz_node *child;
	fz_rect bbox;
	fz_rect temp;

	child = node->super.first;
	if (!child)
		return fz_emptyrect;

	bbox = fz_boundnode(child, ctm);

	child = child->next;
	while (child)
	{
		temp = fz_boundnode(child, ctm);
		bbox = fz_mergerects(temp, bbox);
		child = child->next;
	}

	return bbox;
}
Example #7
0
static fz_error *
rendershade(fz_renderer *gc, fz_shadenode *node, fz_matrix ctm)
{
	fz_error *error;
	fz_irect bbox;

	assert(!gc->maskonly);

	DEBUG("shade;\n");

	bbox = fz_roundrect(fz_boundnode((fz_node*)node, ctm));
	bbox = fz_intersectirects(gc->clip, bbox);

	error = fz_newpixmapwithrect(&gc->dest, bbox, gc->model->n + 1);
	if (error)
		return error;

	return fz_rendershade(node->shade, ctm, gc->model, gc->dest);
}
Example #8
0
fz_error*
processPage(
    soPdfFile* inFile,
    int pageNo,
    fz_rect *bbRect,
    int rectCount
    )
{
    fz_error    *error;
    fz_obj      *pageRef;
    pdf_page    *pdfPage;
    fz_rect     contentBox;
    fz_rect     mediaBox;


    // Initialize 
    for (int ctr = 0; ctr < rectCount; ctr++)
        bbRect[ctr] = fz_emptyrect;

    // Get the page reference and load the page contents
    pageRef = pdf_getpageobject(inFile->pageTree, pageNo);
    error = pdf_loadpage(&pdfPage, inFile->xref, pageRef);
    if (error != NULL)
    {
        // Ideally pdf_loadpage should render all the pages
        // and this should never happen
        return processErrorPage(inFile, pageRef, pageNo, bbRect, error);
    }

    // Get the bounding box for the page
    mediaBox = pdfPage->mediabox;
    float mbHeight = mediaBox.y1 - mediaBox.y0;
    
    // calculate the bounding box for all the elements in the page
    contentBox = fz_boundnode(pdfPage->tree->root, fz_identity());
    float cbHeight = contentBox.y1 - contentBox.y0;

    // If there is nothing on the page we return nothing.
    // should we return an empty page instead ???
    if (fz_isemptyrect(contentBox))
        goto Cleanup;

    // if contentBox is bigger than mediaBox then there are some
    // elements that should not be display and hence we reset the
    // content box to media box
    if ((cbHeight > mbHeight) || ((contentBox.x1 - contentBox.x0) > (mediaBox.x1 - mediaBox.x0)))
    {
        // Calculate the new content box based on the content that is 
        // inside the the media box and recalculate cbHeight
        contentBox = getContainingRect(pdfPage->tree->root, mediaBox);
        cbHeight = contentBox.y1 - contentBox.y0;
    }


#ifdef _blahblah
    printf("-->Page %d\n", pageNo);
    bbdump(pdfPage->tree->root, 1);
#endif

    // The rotation takes place when we insert the page into destination
    // If only the mupdf renderer could give accurate values of the 
    // bounding box of all the elements in a page, the splitting would
    // be so much more easier without overlapping, cutting text, etc
    switch(p_mode)
    {
    case FitHeight:
    case FitWidth:
        bbRect[0] = contentBox;
        goto Cleanup;

    case Fit2xHeight:
    case Fit2xWidth:
        // Let the processing happen.
        break;

    case SmartFitHeight:
    case SmartFitWidth:
    default:
        return fz_throw("Mode(%d) not yet implemented.", p_mode);
        break;
    }

    // If the contentBox is 60% of mediaBox then do not split 
    if (((cbHeight / mbHeight) * 100) <= 55)
    {
        bbRect[0] = contentBox;
        goto Cleanup;
    }

    // Get the first split contents from top. The box we specify is 
    // top 55% (bottom + 45) of the contents
    bbRect[0] = contentBox;
    bbRect[0].y0 = bbRect[0].y0 + (float)(0.45 * cbHeight);
    bbRect[0] = getContainingRect(pdfPage->tree->root, bbRect[0]);

    // Check if the contents we got in first split is more than 40%
    // of the total contents
    float bbRect0Height = bbRect[0].y1 - bbRect[0].y0;
    if (((bbRect0Height / cbHeight) * 100) >= 40)
    {
        // The content is more than 40%. Put the rest of the 
        // content in the second split and exit
        bbRect[1] = contentBox;
        bbRect[1].y1 = bbRect[1].y1 - bbRect0Height;

        // Adjust the split box height by X points to make sure
        // we get everything and dont have annoying tail cuts
        bbRect[0].y0 -= 2;
        goto Cleanup;
    }

    // Since the contents we got in first split is less than 40%
    // of the total contents, split the content in half with overlap
    float overlap = (cbHeight * (float)(p_overlap / 100)) / 2;
    bbRect[0] = contentBox;
    bbRect[0].y0 = bbRect[0].y0 + (float)(0.5 * cbHeight) - overlap;
    bbRect[1] = contentBox;
    bbRect[1].y1 = bbRect[1].y1 - (float)(0.5 * cbHeight) + overlap;


Cleanup:

    // This function can overflow the stack when the pdf page
    // to be rendered is very complex. I had to increase the 
    // stack size reserved for exe using compiler option
    pdf_droppage(pdfPage);

    return error;
}
Example #9
0
static fz_error *
rendermask(fz_renderer *gc, fz_masknode *mask, fz_matrix ctm)
{
	fz_error *error;
	int oldmaskonly;
	fz_pixmap *oldover;
	fz_irect oldclip;
	fz_irect bbox;
	fz_irect clip;
	fz_pixmap *shapepix = nil;
	fz_pixmap *colorpix = nil;
	fz_node *shape;
	fz_node *color;
	float rgb[3];

	shape = mask->super.first;
	color = shape->next;

	/* special case black voodo */
	if (gc->flag & FOVER)
	{
		if (fz_issolidnode(color))
		{
			fz_solidnode *solid = (fz_solidnode*)color;

			fz_convertcolor(solid->cs, solid->samples, gc->model, rgb);
			gc->argb[0] = solid->a * 255;
			gc->argb[1] = rgb[0] * solid->a * 255;
			gc->argb[2] = rgb[1] * solid->a * 255;
			gc->argb[3] = rgb[2] * solid->a * 255;
			gc->argb[4] = rgb[0] * 255;
			gc->argb[5] = rgb[1] * 255;
			gc->argb[6] = rgb[2] * 255;
			gc->flag |= FRGB;

			/* we know these can handle the FRGB shortcut */
			if (fz_ispathnode(shape))
				return renderpath(gc, (fz_pathnode*)shape, ctm);
			if (fz_istextnode(shape))
				return rendertext(gc, (fz_textnode*)shape, ctm);
			if (fz_isimagenode(shape))
				return renderimage(gc, (fz_imagenode*)shape, ctm);
		}
	}

	oldclip = gc->clip;
	oldover = gc->over;

	bbox = fz_roundrect(fz_boundnode(shape, ctm));
	clip = fz_intersectirects(bbox, gc->clip);
	bbox = fz_roundrect(fz_boundnode(color, ctm));
	clip = fz_intersectirects(bbox, clip);

	if (fz_isemptyrect(clip))
		return fz_okay;

DEBUG("mask [%d %d %d %d]\n{\n", clip.x0, clip.y0, clip.x1, clip.y1);

{
fz_irect sbox = fz_roundrect(fz_boundnode(shape, ctm));
fz_irect cbox = fz_roundrect(fz_boundnode(color, ctm));
if (cbox.x0 >= sbox.x0 && cbox.x1 <= sbox.x1)
if (cbox.y0 >= sbox.y0 && cbox.y1 <= sbox.y1)
DEBUG("potentially useless mask\n");
}

	gc->clip = clip;
	gc->over = nil;

	oldmaskonly = gc->maskonly;
	gc->maskonly = 1;

	error = rendernode(gc, shape, ctm);
	if (error)
		goto cleanup;
	shapepix = gc->dest;
	gc->dest = nil;

	gc->maskonly = oldmaskonly;

	error = rendernode(gc, color, ctm);
	if (error)
		goto cleanup;
	colorpix = gc->dest;
	gc->dest = nil;

	gc->clip = oldclip;
	gc->over = oldover;

	if (shapepix && colorpix)
	{
		if (gc->over)
		{
			blendmask(gc, colorpix, shapepix, gc->over, 1);
		}
		else
		{
			clip.x0 = MAX(colorpix->x, shapepix->x);
			clip.y0 = MAX(colorpix->y, shapepix->y);
			clip.x1 = MIN(colorpix->x+colorpix->w, shapepix->x+shapepix->w);
			clip.y1 = MIN(colorpix->y+colorpix->h, shapepix->y+shapepix->h);
			error = fz_newpixmapwithrect(&gc->dest, clip, colorpix->n);
			if (error)
				goto cleanup;
			blendmask(gc, colorpix, shapepix, gc->dest, 0);
		}
	}

DEBUG("}\n");

	if (shapepix) fz_droppixmap(shapepix);
	if (colorpix) fz_droppixmap(colorpix);
	return fz_okay;

cleanup:
	if (shapepix) fz_droppixmap(shapepix);
	if (colorpix) fz_droppixmap(colorpix);
	return error;
}
Example #10
0
static fz_error *
renderimage(fz_renderer *gc, fz_imagenode *node, fz_matrix ctm)
{
	fz_error *error;
	fz_image *image = node->image;
	fz_irect bbox;
	fz_irect clip;
	int dx, dy;
	fz_pixmap *tile;
	fz_pixmap *temp;
	fz_matrix imgmat;
	fz_matrix invmat;
	int fa, fb, fc, fd;
	int u0, v0;
	int x0, y0;
	int w, h;
	int tileheight;

DEBUG("image %dx%d %d+%d %s\n{\n", image->w, image->h, image->n, image->a, image->cs?image->cs->name:"(nil)");

	bbox = fz_roundrect(fz_boundnode((fz_node*)node, ctm));
	clip = fz_intersectirects(gc->clip, bbox);

	if (fz_isemptyrect(clip))
		return fz_okay;
        if (image->w == 0 || image->h == 0)
                return fz_okay;

	if (image->n + image->a == 0)
		return fz_okay;

	calcimagescale(ctm, image->w, image->h, &dx, &dy);

	/* try to fit tile into a typical L2 cachce */
	tileheight = 512 * 1024 / (image->w * (image->n + image->a));
	/* tileheight must be an even multiple of dy, except for last band */
	tileheight = (tileheight + dy - 1) / dy * dy;

	if ((dx != 1 || dy != 1) && image->h > tileheight) {
		int y = 0;

		DEBUG("  load image tile size = %dx%d\n", image->w, tileheight);
		error = fz_newpixmap(&tile, 0, 0, image->w,
				     tileheight, image->n + 1);
		if (error)
			return error;

		error = fz_newscaledpixmap(&temp, image->w, image->h, image->n + 1, dx, dy);
		if (error)
			goto cleanup;

		do {
			if (y + tileheight > image->h)
				tileheight = image->h - y;
			tile->y = y;
			tile->h = tileheight;
			DEBUG("  tile xywh=%d %d %d %d sxsy=1/%d 1/%d\n",
			      0, y, image->w, tileheight, dx, dy);
			error = image->loadtile(image, tile);
			if (error)
				goto cleanup1;

			error = fz_scalepixmaptile(temp, 0, y, tile, dx, dy);
			if (error)
				goto cleanup1;

			y += tileheight;
		} while (y < image->h);

		fz_droppixmap(tile);
		tile = temp;
	}
	else {


DEBUG("  load image\n");
		error = fz_newpixmap(&tile, 0, 0, image->w, image->h, image->n + 1);
		if (error)
			return error;

		error = image->loadtile(image, tile);
		if (error)
			goto cleanup;

		if (dx != 1 || dy != 1)
		{
DEBUG("  scale image 1/%d 1/%d\n", dx, dy);
			error = fz_scalepixmap(&temp, tile, dx, dy);
			if (error)
				goto cleanup;
			fz_droppixmap(tile);
			tile = temp;
		}
	}

	if (image->cs && image->cs != gc->model)
	{
DEBUG("  convert from %s to %s\n", image->cs->name, gc->model->name);
		error = fz_newpixmap(&temp, tile->x, tile->y, tile->w, tile->h, gc->model->n + 1);
		if (error)
			goto cleanup;
		fz_convertpixmap(image->cs, tile, gc->model, temp);
		fz_droppixmap(tile);
		tile = temp;
	}

	imgmat.a = 1.0 / tile->w;
	imgmat.b = 0.0;
	imgmat.c = 0.0;
	imgmat.d = -1.0 / tile->h;
	imgmat.e = 0.0;
	imgmat.f = 1.0;
	invmat = fz_invertmatrix(fz_concat(imgmat, ctm));

	w = clip.x1 - clip.x0;
	h = clip.y1 - clip.y0;
	x0 = clip.x0;
	y0 = clip.y0;
	u0 = (invmat.a * (x0+0.5) + invmat.c * (y0+0.5) + invmat.e) * 65536;
	v0 = (invmat.b * (x0+0.5) + invmat.d * (y0+0.5) + invmat.f) * 65536;
	fa = invmat.a * 65536;
	fb = invmat.b * 65536;
	fc = invmat.c * 65536;
	fd = invmat.d * 65536;

#define PSRC tile->samples, tile->w, tile->h
#define PDST(p) p->samples + ((y0-p->y) * p->w + (x0-p->x)) * p->n, p->w * p->n
#define PCTM u0, v0, fa, fb, fc, fd, w, h

	switch (gc->flag)
	{
	case FNONE:
		{
DEBUG("  fnone %d x %d\n", w, h);
			if (image->cs)
				error = fz_newpixmapwithrect(&gc->dest, clip, gc->model->n + 1);
			else
				error = fz_newpixmapwithrect(&gc->dest, clip, 1);
			if (error)
				goto cleanup;

			if (image->cs)
				fz_img_4c4(PSRC, PDST(gc->dest), PCTM);
			else
				fz_img_1c1(PSRC, PDST(gc->dest), PCTM);
		}
		break;

	case FOVER:
		{
DEBUG("  fover %d x %d\n", w, h);
			if (image->cs)
				fz_img_4o4(PSRC, PDST(gc->over), PCTM);
			else
				fz_img_1o1(PSRC, PDST(gc->over), PCTM);
		}
		break;

	case FOVER | FRGB:
DEBUG("  fover+rgb %d x %d\n", w, h);
		fz_img_w4i1o4(gc->argb, PSRC, PDST(gc->over), PCTM);
		break;

	default:
		assert(!"impossible flag in image span function");
	}

DEBUG("}\n");

	fz_droppixmap(tile);
	return fz_okay;

cleanup1:
	fz_droppixmap(temp);
cleanup:
	fz_droppixmap(tile);
	return error;
}
Example #11
0
fz_rect
fz_boundovernode(fz_overnode *node, fz_matrix ctm)
{
	struct {
		int cap, ix;
		struct {
			fz_node *node;
			fz_rect bbox;
		} *items;
	} stack;

	fz_node *child = node->super.first;
	fz_rect bbox = fz_emptyrect;
	fz_rect temp;

	if (!child)
		return fz_emptyrect;

	stack.cap = 8;
	stack.ix = 0;
	stack.items = nil;
	do
	{
		if (child->kind != FZ_NOVER)
		{
			temp = fz_boundnode(child, ctm);
			bbox = fz_mergerects(temp, bbox);
			child = child->next;
		}
		else if (child->first)
		{
			if (!stack.items)
				stack.items = fz_malloc(stack.cap * sizeof(*stack.items));
			else if (stack.ix >= stack.cap)
			{
				stack.cap *= 2;
				stack.items = fz_realloc(stack.items, stack.cap * sizeof(*stack.items));
				if (!stack.items)
					return fz_emptyrect;
			}

			stack.items[stack.ix].node = child;
			stack.items[stack.ix].bbox = bbox;
			stack.ix++;

			bbox = fz_emptyrect;
			child = child->first;
		}
		else
		{
			child = child->next;
		}
		while (!child && stack.ix > 0)
		{
			stack.ix--;
			temp = stack.items[stack.ix].bbox;
			bbox = fz_mergerects(temp, bbox);
			child = stack.items[stack.ix].node->next;
		}
	} while (child);
	fz_free(stack.items);

	return bbox;
}
Example #12
0
static fz_error *
addpatternshape(pdf_gstate *gs, fz_node *shape,
		pdf_pattern *pat, fz_colorspace *cs, float *v)
{
	fz_error *error;
	fz_node *xform;
	fz_node *over;
	fz_node *mask;
	fz_node *link;
	fz_matrix ctm;
	fz_matrix inv;
	fz_matrix ptm;
	fz_rect bbox;
	int x, y, x0, y0, x1, y1;

	/* patterns are painted in user space */
	ctm = getmatrix(gs->head);
	inv = fz_invertmatrix(ctm);

	error = fz_newmasknode(&mask);
	if (error)
		return fz_rethrow(error, "cannot create mask node");

	ptm = fz_concat(pat->matrix, fz_invertmatrix(ctm));
	error = fz_newtransformnode(&xform, ptm);
	if (error)
	{
		fz_dropnode(mask);
		return fz_rethrow(error, "cannot create transform node");
	}

	error = pdf_newovernode(&over, gs);
	if (error)
	{
		fz_dropnode(xform);
		fz_dropnode(mask);
		return fz_rethrow(error, "cannot create over node");
	}

	fz_insertnodelast(mask, shape);
	fz_insertnodelast(mask, xform);
	fz_insertnodelast(xform, over);
	xform = nil;

	/* over, xform, mask are now owned by the tree */

	/* get bbox of shape in pattern space for stamping */
	ptm = fz_concat(ctm, fz_invertmatrix(pat->matrix));
	bbox = fz_boundnode(shape, ptm);

	/* expand bbox by pattern bbox */
	bbox.x0 += pat->bbox.x0;
	bbox.y0 += pat->bbox.y0;
	bbox.x1 += pat->bbox.x1;
	bbox.y1 += pat->bbox.y1;

	x0 = fz_floor(bbox.x0 / pat->xstep);
	y0 = fz_floor(bbox.y0 / pat->ystep);
	x1 = fz_ceil(bbox.x1 / pat->xstep);
	y1 = fz_ceil(bbox.y1 / pat->ystep);

	for (y = y0; y <= y1; y++)
	{
		for (x = x0; x <= x1; x++)
		{
			ptm = fz_translate(x * pat->xstep, y * pat->ystep);
			error = fz_newtransformnode(&xform, ptm);
			if (error)
				return fz_rethrow(error, "cannot create transform node for stamp");
			error = fz_newlinknode(&link, pat->tree);
			if (error)
			{
				fz_dropnode(xform);
				return fz_rethrow(error, "cannot create link node for stamp");
			}
			fz_insertnodelast(xform, link);
			fz_insertnodelast(over, xform);
		}
	}

	if (pat->ismask)
	{
		error = addcolorshape(gs, mask, 1.0, cs, v);
		if (error)
			return fz_rethrow(error, "cannot add colored shape");
		return fz_okay;
	}

	fz_insertnodelast(gs->head, mask);
	return fz_okay;
}
Example #13
0
static fz_error *
renderimage(fz_renderer *gc, fz_imagenode *node, fz_matrix ctm)
{
	fz_error *error;
	fz_image *image = node->image;
	fz_irect bbox;
	fz_irect clip;
	int dx, dy;
	fz_pixmap *tile;
	fz_pixmap *temp;
	fz_matrix imgmat;
	fz_matrix invmat;
	int fa, fb, fc, fd;
	int u0, v0;
	int x0, y0;
	int w, h;

DEBUG("image %dx%d %d+%d %s\n{\n", image->w, image->h, image->n, image->a, image->cs?image->cs->name:"(nil)");

	bbox = fz_roundrect(fz_boundnode((fz_node*)node, ctm));
	clip = fz_intersectirects(gc->clip, bbox);

	if (fz_isemptyrect(clip))
		return nil;

	calcimagescale(ctm, image->w, image->h, &dx, &dy);

DEBUG("  load image\n");
	error = fz_newpixmap(&tile, 0, 0, image->w, image->h, image->n + 1);
	if (error)
		return error;

	error = image->loadtile(image, tile);
	if (error)
		goto cleanup;

	if (dx != 1 || dy != 1)
	{
DEBUG("  scale image 1/%d 1/%d\n", dx, dy);
		error = fz_scalepixmap(&temp, tile, dx, dy);
		if (error)
			goto cleanup;
		fz_droppixmap(tile);
		tile = temp;
	}

	if (image->cs && image->cs != gc->model)
	{
DEBUG("  convert from %s to %s\n", image->cs->name, gc->model->name);
		error = fz_newpixmap(&temp, tile->x, tile->y, tile->w, tile->h, gc->model->n + 1);
		if (error)
			goto cleanup;
		fz_convertpixmap(image->cs, tile, gc->model, temp);
		fz_droppixmap(tile);
		tile = temp;
	}

	imgmat.a = 1.0 / tile->w;
	imgmat.b = 0.0;
	imgmat.c = 0.0;
	imgmat.d = -1.0 / tile->h;
	imgmat.e = 0.0;
	imgmat.f = 1.0;
	invmat = fz_invertmatrix(fz_concat(imgmat, ctm));

	w = clip.x1 - clip.x0;
	h = clip.y1 - clip.y0;
	x0 = clip.x0;
	y0 = clip.y0;
	u0 = (invmat.a * (x0+0.5) + invmat.c * (y0+0.5) + invmat.e) * 65536;
	v0 = (invmat.b * (x0+0.5) + invmat.d * (y0+0.5) + invmat.f) * 65536;
	fa = invmat.a * 65536;
	fb = invmat.b * 65536;
	fc = invmat.c * 65536;
	fd = invmat.d * 65536;

#define PSRC tile->samples, tile->w, tile->h
#define PDST(p) p->samples + ((y0-p->y) * p->w + (x0-p->x)) * p->n, p->w * p->n
#define PCTM u0, v0, fa, fb, fc, fd, w, h

	switch (gc->flag)
	{
	case FNONE:
		{
DEBUG("  fnone %d x %d\n", w, h);
			if (image->cs)
				error = fz_newpixmapwithrect(&gc->dest, clip, gc->model->n + 1);
			else
				error = fz_newpixmapwithrect(&gc->dest, clip, 1);
			if (error)
				goto cleanup;

			if (image->cs)
				fz_img_4c4(PSRC, PDST(gc->dest), PCTM);
			else
				fz_img_1c1(PSRC, PDST(gc->dest), PCTM);
		}
		break;

	case FOVER:
		{
DEBUG("  fover %d x %d\n", w, h);
			if (image->cs)
				fz_img_4o4(PSRC, PDST(gc->over), PCTM);
			else
				fz_img_1o1(PSRC, PDST(gc->over), PCTM);
		}
		break;

	case FOVER | FRGB:
DEBUG("  fover+rgb %d x %d\n", w, h);
		fz_img_w3i1o4(gc->rgb, PSRC, PDST(gc->over), PCTM);
		break;

	default:
		assert(!"impossible flag in image span function");
	}

DEBUG("}\n");

	fz_droppixmap(tile);
	return nil;

cleanup:
	fz_droppixmap(tile);
	return error;
}