cairo/pixman/src/icimage.c
Carl Worth 9a5dfe1415 Fix implementation of radial gradients for inner radius != 0
The previous implementation fell apart quite badly when neither radius
value was equal to 0.0. I derived the math from scratch, (much thanks to
Vincent Torri <vtorri@univ-evry.fr> for guiding me to a simpler derivation
than I was doing originally), and it's working much better now without
being any slower, (in fact, cairo-perf shows speedup of 1.05x to 1.58x on
my laptop here).

This work also provides groundwork for defining the behavior of radial
gradients where neither circle is wholly contained within the other, (though
we haven't done that definition yet---it will require a new test case and a
very little bit of work on the implementation).

This is a fix for the following bug report:

	Radial Gradients with nonzero inner radius misplace stops
	https://bugs.freedesktop.org/show_bug.cgi?id=7685
2007-03-01 14:53:48 -08:00

937 lines
21 KiB
C

/*
* Copyright © 2000 SuSE, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of SuSE not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. SuSE makes no representations about the
* suitability of this software for any purpose. It is provided "as is"
* without express or implied warranty.
*
* SuSE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL SuSE
* BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Author: Keith Packard, SuSE, Inc.
*/
#include "pixman-xserver-compat.h"
pixman_image_t *
pixman_image_create (pixman_format_t *format,
int width,
int height)
{
pixman_image_t *image;
FbPixels *pixels;
pixels = FbPixelsCreate (width, height, format->depth);
if (pixels == NULL)
return NULL;
image = pixman_image_createForPixels (pixels, format);
if (image == NULL) {
FbPixelsDestroy (pixels);
return NULL;
}
image->owns_pixels = 1;
return image;
}
pixman_image_t *
pixman_image_create_for_data (FbBits *data, pixman_format_t *format, int width, int height, int bpp, int stride)
{
pixman_image_t *image;
FbPixels *pixels;
pixels = FbPixelsCreateForData (data, width, height, format->depth, bpp, stride);
if (pixels == NULL)
return NULL;
image = pixman_image_createForPixels (pixels, format);
if (image == NULL) {
FbPixelsDestroy (pixels);
return NULL;
}
image->owns_pixels = 1;
return image;
}
pixman_image_t *
pixman_image_createForPixels (FbPixels *pixels,
pixman_format_t *format)
{
pixman_image_t *image;
image = malloc (sizeof (pixman_image_t));
if (!image)
{
return NULL;
}
image->pixels = pixels;
image->image_format = *format;
image->format_code = format->format_code;
/* XXX: What's all this about?
if (pDrawable->type == DRAWABLE_PIXMAP)
{
++((PixmapPtr)pDrawable)->refcnt;
image->pNext = 0;
}
else
{
image->pNext = GetPictureWindow(((WindowPtr) pDrawable));
SetPictureWindow(((WindowPtr) pDrawable), image);
}
*/
pixman_image_init (image);
return image;
}
static CARD32 xRenderColorToCard32(pixman_color_t c)
{
return
(c.alpha >> 8 << 24) |
(c.red >> 8 << 16) |
(c.green & 0xff00) |
(c.blue >> 8);
}
static uint32_t premultiply(uint32_t x)
{
uint32_t a = x >> 24;
uint32_t t = (x & 0xff00ff) * a + 0x800080;
t = (t + ((t >> 8) & 0xff00ff)) >> 8;
t &= 0xff00ff;
x = ((x >> 8) & 0xff) * a + 0x80;
x = (x + ((x >> 8) & 0xff));
x &= 0xff00;
x |= t | (a << 24);
return x;
}
static uint32_t INTERPOLATE_PIXEL_256(uint32_t x, uint32_t a, uint32_t y, uint32_t b)
{
CARD32 t = (x & 0xff00ff) * a + (y & 0xff00ff) * b;
t >>= 8;
t &= 0xff00ff;
x = ((x >> 8) & 0xff00ff) * a + ((y >> 8) & 0xff00ff) * b;
x &= 0xff00ff00;
x |= t;
return x;
}
uint32_t
pixman_gradient_color (pixman_gradient_stop_t *stop1,
pixman_gradient_stop_t *stop2,
uint32_t x)
{
uint32_t current_color, next_color;
int dist, idist;
current_color = xRenderColorToCard32 (stop1->color);
next_color = xRenderColorToCard32 (stop2->color);
dist = (int) (256 * (x - stop1->x) / (stop2->x - stop1->x));
idist = 256 - dist;
return premultiply (INTERPOLATE_PIXEL_256 (current_color, idist,
next_color, dist));
}
static int
_pixman_init_gradient (pixman_gradient_image_t *gradient,
const pixman_gradient_stop_t *stops,
int n_stops)
{
pixman_fixed16_16_t dpos;
int i;
if (n_stops <= 0)
return 1;
dpos = -1;
for (i = 0; i < n_stops; i++)
{
if (stops[i].x < dpos || stops[i].x > (1 << 16))
return 1;
dpos = stops[i].x;
}
gradient->class = SourcePictClassUnknown;
gradient->stopRange = 0xffff;
gradient->colorTable = NULL;
gradient->colorTableSize = 0;
return 0;
}
static pixman_image_t *
_pixman_create_source_image (void)
{
pixman_image_t *image;
image = (pixman_image_t *) malloc (sizeof (pixman_image_t));
image->pDrawable = NULL;
image->pixels = NULL;
image->format_code = PICT_a8r8g8b8;
pixman_image_init (image);
return image;
}
pixman_image_t *
pixman_image_create_linear_gradient (const pixman_linear_gradient_t *gradient,
const pixman_gradient_stop_t *stops,
int n_stops)
{
pixman_linear_gradient_image_t *linear;
pixman_image_t *image;
if (n_stops < 2)
return NULL;
image = _pixman_create_source_image ();
if (!image)
return NULL;
linear = malloc (sizeof (pixman_linear_gradient_image_t) +
sizeof (pixman_gradient_stop_t) * n_stops);
if (!linear)
{
free (image);
return NULL;
}
linear->stops = (pixman_gradient_stop_t *) (linear + 1);
linear->nstops = n_stops;
memcpy (linear->stops, stops, sizeof (pixman_gradient_stop_t) * n_stops);
linear->type = SourcePictTypeLinear;
linear->p1 = gradient->p1;
linear->p2 = gradient->p2;
image->pSourcePict = (pixman_source_image_t *) linear;
if (_pixman_init_gradient (&image->pSourcePict->gradient, stops, n_stops))
{
free (linear);
free (image);
return NULL;
}
return image;
}
pixman_image_t *
pixman_image_create_radial_gradient (const pixman_radial_gradient_t *gradient,
const pixman_gradient_stop_t *stops,
int n_stops)
{
pixman_radial_gradient_image_t *radial;
pixman_image_t *image;
if (n_stops < 2)
return NULL;
image = _pixman_create_source_image ();
if (!image)
return NULL;
radial = malloc (sizeof (pixman_radial_gradient_image_t) +
sizeof (pixman_gradient_stop_t) * n_stops);
if (!radial)
{
free (image);
return NULL;
}
radial->stops = (pixman_gradient_stop_t *) (radial + 1);
radial->nstops = n_stops;
memcpy (radial->stops, stops, sizeof (pixman_gradient_stop_t) * n_stops);
radial->type = SourcePictTypeRadial;
radial->c1 = gradient->c1;
radial->c2 = gradient->c2;
radial->cdx = xFixedToDouble (gradient->c2.x - gradient->c1.x);
radial->cdy = xFixedToDouble (gradient->c2.y - gradient->c1.y);
radial->dr = xFixedToDouble (gradient->c2.radius - gradient->c1.radius);
radial->A = ( radial->cdx * radial->cdx
+ radial->cdy * radial->cdy
- radial->dr * radial->dr);
image->pSourcePict = (pixman_source_image_t *) radial;
if (_pixman_init_gradient (&image->pSourcePict->gradient, stops, n_stops))
{
free (radial);
free (image);
return NULL;
}
return image;
}
void
pixman_image_init (pixman_image_t *image)
{
image->refcnt = 1;
image->repeat = PIXMAN_REPEAT_NONE;
image->graphicsExposures = 0;
image->subWindowMode = ClipByChildren;
image->polyEdge = PolyEdgeSharp;
image->polyMode = PolyModePrecise;
/*
* In the server this was 0 because the composite clip list
* can be referenced from a window (and often is)
*/
image->freeCompClip = 0;
image->freeSourceClip = 0;
image->clientClipType = CT_NONE;
image->componentAlpha = 0;
image->compositeClipSource = 0;
image->alphaMap = NULL;
image->alphaOrigin.x = 0;
image->alphaOrigin.y = 0;
image->clipOrigin.x = 0;
image->clipOrigin.y = 0;
image->clientClip = NULL;
image->dither = 0L;
image->stateChanges = (1 << (CPLastBit+1)) - 1;
/* XXX: What to lodge here?
image->serialNumber = GC_CHANGE_SERIAL_BIT;
*/
if (image->pixels)
{
image->pCompositeClip = pixman_region_create();
pixman_region_union_rect (image->pCompositeClip, image->pCompositeClip,
0, 0, image->pixels->width,
image->pixels->height);
image->freeCompClip = 1;
image->pSourceClip = pixman_region_create ();
pixman_region_union_rect (image->pSourceClip, image->pSourceClip,
0, 0, image->pixels->width,
image->pixels->height);
image->freeSourceClip = 1;
}
else
{
image->pCompositeClip = NULL;
image->pSourceClip = NULL;
}
image->transform = NULL;
image->filter = PIXMAN_FILTER_NEAREST;
image->filter_params = NULL;
image->filter_nparams = 0;
image->owns_pixels = 0;
image->pSourcePict = NULL;
}
void
pixman_image_set_component_alpha (pixman_image_t *image,
int component_alpha)
{
if (image)
image->componentAlpha = component_alpha;
}
int
pixman_image_set_transform (pixman_image_t *image,
pixman_transform_t *transform)
{
static const pixman_transform_t identity = { {
{ xFixed1, 0x00000, 0x00000 },
{ 0x00000, xFixed1, 0x00000 },
{ 0x00000, 0x00000, xFixed1 },
} };
if (transform && memcmp (transform, &identity, sizeof (pixman_transform_t)) == 0)
transform = NULL;
if (transform)
{
if (!image->transform)
{
image->transform = malloc (sizeof (pixman_transform_t));
if (!image->transform)
return 1;
}
*image->transform = *transform;
}
else
{
if (image->transform)
{
free (image->transform);
image->transform = NULL;
}
}
return 0;
}
void
pixman_image_set_repeat (pixman_image_t *image,
pixman_repeat_t repeat)
{
if (image)
image->repeat = repeat;
}
void
pixman_image_set_filter (pixman_image_t *image,
pixman_filter_t filter)
{
if (image)
image->filter = filter;
}
int
pixman_image_get_width (pixman_image_t *image)
{
if (image->pixels)
return image->pixels->width;
return 0;
}
int
pixman_image_get_height (pixman_image_t *image)
{
if (image->pixels)
return image->pixels->height;
return 0;
}
int
pixman_image_get_depth (pixman_image_t *image)
{
if (image->pixels)
return image->pixels->depth;
return 0;
}
int
pixman_image_get_stride (pixman_image_t *image)
{
if (image->pixels)
return image->pixels->stride;
return 0;
}
pixman_format_t *
pixman_image_get_format (pixman_image_t *image)
{
return &image->image_format;
}
FbBits *
pixman_image_get_data (pixman_image_t *image)
{
if (image->pixels)
return image->pixels->data;
return NULL;
}
void
pixman_image_destroy (pixman_image_t *image)
{
pixman_image_destroyClip (image);
if (image->freeCompClip) {
pixman_region_destroy (image->pCompositeClip);
image->pCompositeClip = NULL;
}
if (image->freeSourceClip) {
pixman_region_destroy (image->pSourceClip);
image->pSourceClip = NULL;
}
if (image->owns_pixels) {
FbPixelsDestroy (image->pixels);
image->pixels = NULL;
}
if (image->transform) {
free (image->transform);
image->transform = NULL;
}
if (image->pSourcePict) {
free (image->pSourcePict);
image->pSourcePict = NULL;
}
free (image);
}
void
pixman_image_destroyClip (pixman_image_t *image)
{
switch (image->clientClipType) {
case CT_NONE:
return;
case CT_PIXMAP:
pixman_image_destroy (image->clientClip);
break;
default:
pixman_region_destroy (image->clientClip);
break;
}
image->clientClip = NULL;
image->clientClipType = CT_NONE;
}
int
pixman_image_set_clip_region (pixman_image_t *image,
pixman_region16_t *region)
{
pixman_image_destroyClip (image);
if (region) {
image->clientClip = pixman_region_create ();
pixman_region_copy (image->clientClip, region);
image->clientClipType = CT_REGION;
}
image->stateChanges |= CPClipMask;
if (image->pSourcePict)
return 0;
if (image->freeCompClip)
pixman_region_destroy (image->pCompositeClip);
image->pCompositeClip = pixman_region_create();
pixman_region_union_rect (image->pCompositeClip, image->pCompositeClip,
0, 0, image->pixels->width, image->pixels->height);
image->freeCompClip = 1;
if (region) {
pixman_region_translate (image->pCompositeClip,
- image->clipOrigin.x,
- image->clipOrigin.y);
pixman_region_intersect (image->pCompositeClip,
image->pCompositeClip,
region);
pixman_region_translate (image->pCompositeClip,
image->clipOrigin.x,
image->clipOrigin.y);
}
return 0;
}
#define BOUND(v) (int16_t) ((v) < MINSHORT ? MINSHORT : (v) > MAXSHORT ? MAXSHORT : (v))
static __inline int
FbClipImageReg (pixman_region16_t *region,
pixman_region16_t *clip,
int dx,
int dy)
{
if (pixman_region_num_rects (region) == 1 &&
pixman_region_num_rects (clip) == 1)
{
pixman_box16_t *pRbox = pixman_region_rects (region);
pixman_box16_t *pCbox = pixman_region_rects (clip);
int v;
if (pRbox->x1 < (v = pCbox->x1 + dx))
pRbox->x1 = BOUND(v);
if (pRbox->x2 > (v = pCbox->x2 + dx))
pRbox->x2 = BOUND(v);
if (pRbox->y1 < (v = pCbox->y1 + dy))
pRbox->y1 = BOUND(v);
if (pRbox->y2 > (v = pCbox->y2 + dy))
pRbox->y2 = BOUND(v);
if (pRbox->x1 >= pRbox->x2 ||
pRbox->y1 >= pRbox->y2)
{
pixman_region_empty (region);
}
}
else
{
pixman_region_translate (region, dx, dy);
pixman_region_intersect (region, clip, region);
pixman_region_translate (region, -dx, -dy);
}
return 1;
}
static __inline int
FbClipImageSrc (pixman_region16_t *region,
pixman_image_t *image,
int dx,
int dy)
{
/* XXX what to do with clipping from transformed pictures? */
if (image->transform)
return 1;
/* XXX davidr hates this, wants to never use source-based clipping */
if (image->repeat != PIXMAN_REPEAT_NONE || image->pSourcePict)
{
/* XXX no source clipping */
if (image->compositeClipSource &&
image->clientClipType != CT_NONE)
{
pixman_region_translate (region,
dx - image->clipOrigin.x,
dy - image->clipOrigin.y);
pixman_region_intersect (region, image->clientClip, region);
pixman_region_translate (region,
- (dx - image->clipOrigin.x),
- (dy - image->clipOrigin.y));
}
return 1;
}
else
{
pixman_region16_t *clip;
if (image->compositeClipSource)
clip = image->pCompositeClip;
else
clip = image->pSourceClip;
return FbClipImageReg (region,
clip,
dx,
dy);
}
return 1;
}
/* XXX: Need to decide what to do with this
#define NEXT_VAL(_type) (vlist ? (_type) *vlist++ : (_type) ulist++->val)
#define NEXT_PTR(_type) ((_type) ulist++->ptr)
int
pixman_image_change (pixman_image_t *image,
Mask vmask,
unsigned int *vlist,
DevUnion *ulist,
int *error_value)
{
BITS32 index2;
int error = 0;
BITS32 maskQ;
maskQ = vmask;
while (vmask && !error)
{
index2 = (BITS32) lowbit (vmask);
vmask &= ~index2;
image->stateChanges |= index2;
switch (index2)
{
case CPRepeat:
{
unsigned int newr;
newr = NEXT_VAL(unsigned int);
if (newr <= xTrue)
image->repeat = newr;
else
{
*error_value = newr;
error = BadValue;
}
}
break;
case CPAlphaMap:
{
pixman_image_t *iAlpha;
iAlpha = NEXT_PTR(pixman_image_t *);
if (iAlpha)
iAlpha->refcnt++;
if (image->alphaMap)
pixman_image_destroy ((void *) image->alphaMap);
image->alphaMap = iAlpha;
}
break;
case CPAlphaXOrigin:
image->alphaOrigin.x = NEXT_VAL(int16_t);
break;
case CPAlphaYOrigin:
image->alphaOrigin.y = NEXT_VAL(int16_t);
break;
case CPClipXOrigin:
image->clipOrigin.x = NEXT_VAL(int16_t);
break;
case CPClipYOrigin:
image->clipOrigin.y = NEXT_VAL(int16_t);
break;
case CPClipMask:
{
pixman_image_t *mask;
int clipType;
mask = NEXT_PTR(pixman_image_t *);
if (mask) {
clipType = CT_PIXMAP;
mask->refcnt++;
} else {
clipType = CT_NONE;
}
error = pixman_image_change_clip (image, clipType,
(void *)mask, 0);
break;
}
case CPGraphicsExposure:
{
unsigned int newe;
newe = NEXT_VAL(unsigned int);
if (newe <= xTrue)
image->graphicsExposures = newe;
else
{
*error_value = newe;
error = BadValue;
}
}
break;
case CPSubwindowMode:
{
unsigned int news;
news = NEXT_VAL(unsigned int);
if (news == ClipByChildren || news == IncludeInferiors)
image->subWindowMode = news;
else
{
*error_value = news;
error = BadValue;
}
}
break;
case CPPolyEdge:
{
unsigned int newe;
newe = NEXT_VAL(unsigned int);
if (newe == PolyEdgeSharp || newe == PolyEdgeSmooth)
image->polyEdge = newe;
else
{
*error_value = newe;
error = BadValue;
}
}
break;
case CPPolyMode:
{
unsigned int newm;
newm = NEXT_VAL(unsigned int);
if (newm == PolyModePrecise || newm == PolyModeImprecise)
image->polyMode = newm;
else
{
*error_value = newm;
error = BadValue;
}
}
break;
case CPDither:
image->dither = NEXT_VAL(unsigned long);
break;
case CPComponentAlpha:
{
unsigned int newca;
newca = NEXT_VAL (unsigned int);
if (newca <= xTrue)
image->componentAlpha = newca;
else
{
*error_value = newca;
error = BadValue;
}
}
break;
default:
*error_value = maskQ;
error = BadValue;
break;
}
}
return error;
}
*/
/* XXX: Do we need this?
int
SetPictureClipRects (PicturePtr pPicture,
int xOrigin,
int yOrigin,
int nRect,
xRectangle *rects)
{
ScreenPtr pScreen = pPicture->pDrawable->pScreen;
PictureScreenPtr ps = GetPictureScreen(pScreen);
pixman_region16_t *clientClip;
int result;
clientClip = RECTS_TO_REGION(pScreen,
nRect, rects, CT_UNSORTED);
if (!clientClip)
return 1;
result =(*ps->ChangePictureClip) (pPicture, CT_REGION,
(void *) clientClip, 0);
if (result == 0)
{
pPicture->clipOrigin.x = xOrigin;
pPicture->clipOrigin.y = yOrigin;
pPicture->stateChanges |= CPClipXOrigin|CPClipYOrigin|CPClipMask;
pPicture->serialNumber |= GC_CHANGE_SERIAL_BIT;
}
return result;
}
*/
int
FbComputeCompositeRegion (pixman_region16_t *region,
pixman_image_t *iSrc,
pixman_image_t *iMask,
pixman_image_t *iDst,
int16_t xSrc,
int16_t ySrc,
int16_t xMask,
int16_t yMask,
int16_t xDst,
int16_t yDst,
uint16_t width,
uint16_t height)
{
int v;
int x1, y1, x2, y2;
/* XXX: This code previously directly set the extents of the
region here. I need to decide whether removing that has broken
this. Also, it might be necessary to just make the pixman_region16_t
data structure transparent anyway in which case I can just put
the code back. */
x1 = xDst;
v = xDst + width;
x2 = BOUND(v);
y1 = yDst;
v = yDst + height;
y2 = BOUND(v);
/* Check for empty operation */
if (x1 >= x2 ||
y1 >= y2)
{
pixman_region_empty (region);
return 1;
}
/* clip against src */
if (!FbClipImageSrc (region, iSrc, xDst - xSrc, yDst - ySrc))
{
pixman_region_destroy (region);
return 0;
}
if (iSrc->alphaMap)
{
if (!FbClipImageSrc (region, iSrc->alphaMap,
xDst - (xSrc + iSrc->alphaOrigin.x),
yDst - (ySrc + iSrc->alphaOrigin.y)))
{
pixman_region_destroy (region);
return 0;
}
}
/* clip against mask */
if (iMask)
{
if (!FbClipImageSrc (region, iMask, xDst - xMask, yDst - yMask))
{
pixman_region_destroy (region);
return 0;
}
if (iMask->alphaMap)
{
if (!FbClipImageSrc (region, iMask->alphaMap,
xDst - (xMask + iMask->alphaOrigin.x),
yDst - (yMask + iMask->alphaOrigin.y)))
{
pixman_region_destroy (region);
return 0;
}
}
}
if (!FbClipImageReg (region, iDst->pCompositeClip, 0, 0))
{
pixman_region_destroy (region);
return 0;
}
if (iDst->alphaMap)
{
if (!FbClipImageReg (region, iDst->alphaMap->pCompositeClip,
-iDst->alphaOrigin.x,
-iDst->alphaOrigin.y))
{
pixman_region_destroy (region);
return 0;
}
}
return 1;
}
int
miIsSolidAlpha (pixman_image_t *src)
{
char line[1];
/* Alpha-only */
if (PICT_FORMAT_TYPE (src->format_code) != PICT_TYPE_A)
return 0;
/* repeat */
if (!src->repeat)
return 0;
/* 1x1 */
if (src->pixels->width != 1 || src->pixels->height != 1)
return 0;
line[0] = 1;
/* XXX: For the next line, fb has:
(*pScreen->GetImage) (src->pixels, 0, 0, 1, 1, ZPixmap, ~0L, line);
Is the following simple assignment sufficient?
*/
line[0] = src->pixels->data[0];
switch (src->pixels->bpp) {
case 1:
return (uint8_t) line[0] == 1 || (uint8_t) line[0] == 0x80;
case 4:
return (uint8_t) line[0] == 0xf || (uint8_t) line[0] == 0xf0;
case 8:
return (uint8_t) line[0] == 0xff;
default:
return 0;
}
}