/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (C) 2009 Red Hat, Inc.
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, see .
*/
#include
#include "cairo_canvas.h"
#define CANVAS_USE_PIXMAN
#define CANVAS_SINGLE_INSTANCE
#include "canvas_base.c"
#include "rop3.h"
#include "rect.h"
#include "region.h"
#include "lines.h"
#include "pixman_utils.h"
typedef struct CairoCanvas CairoCanvas;
struct CairoCanvas {
CanvasBase base;
uint32_t *private_data;
int private_data_size;
pixman_image_t *image;
pixman_region32_t canvas_region;
};
typedef enum {
ROP_INPUT_SRC,
ROP_INPUT_BRUSH,
ROP_INPUT_DEST
} ROPInput;
SpiceROP ropd_descriptor_to_rop(int desc,
ROPInput src_input,
ROPInput dest_input)
{
int old;
int invert_masks[] = {
SPICE_ROPD_INVERS_SRC,
SPICE_ROPD_INVERS_BRUSH,
SPICE_ROPD_INVERS_DEST
};
old = desc;
desc &= ~(SPICE_ROPD_INVERS_SRC | SPICE_ROPD_INVERS_DEST);
if (old & invert_masks[src_input]) {
desc |= SPICE_ROPD_INVERS_SRC;
}
if (old & invert_masks[dest_input]) {
desc |= SPICE_ROPD_INVERS_DEST;
}
if (desc & SPICE_ROPD_OP_PUT) {
if (desc & SPICE_ROPD_INVERS_SRC) {
if (desc & SPICE_ROPD_INVERS_RES) {
return SPICE_ROP_COPY;
}
return SPICE_ROP_COPY_INVERTED;
} else {
if (desc & SPICE_ROPD_INVERS_RES) {
return SPICE_ROP_COPY_INVERTED;
}
return SPICE_ROP_COPY;
}
} else if (desc & SPICE_ROPD_OP_OR) {
if (desc & SPICE_ROPD_INVERS_RES) {
if (desc & SPICE_ROPD_INVERS_SRC) {
if (desc & SPICE_ROPD_INVERS_DEST) {
/* !(!src or !dest) == src and dest*/
return SPICE_ROP_AND;
} else {
/* ! (!src or dest) = src and !dest*/
return SPICE_ROP_AND_REVERSE;
}
} else {
if (desc & SPICE_ROPD_INVERS_DEST) {
/* !(src or !dest) == !src and dest */
return SPICE_ROP_AND_INVERTED;
} else {
/* !(src or dest) */
return SPICE_ROP_NOR;
}
}
} else {
if (desc & SPICE_ROPD_INVERS_SRC) {
if (desc & SPICE_ROPD_INVERS_DEST) {
/* !src or !dest == !(src and dest)*/
return SPICE_ROP_NAND;
} else {
/* !src or dest */
return SPICE_ROP_OR_INVERTED;
}
} else {
if (desc & SPICE_ROPD_INVERS_DEST) {
/* src or !dest */
return SPICE_ROP_OR_REVERSE;
} else {
/* src or dest */
return SPICE_ROP_OR;
}
}
}
} else if (desc & SPICE_ROPD_OP_AND) {
if (desc & SPICE_ROPD_INVERS_RES) {
if (desc & SPICE_ROPD_INVERS_SRC) {
if (desc & SPICE_ROPD_INVERS_DEST) {
/* !(!src and !dest) == src or dest*/
return SPICE_ROP_OR;
} else {
/* ! (!src and dest) = src or !dest*/
return SPICE_ROP_OR_REVERSE;
}
} else {
if (desc & SPICE_ROPD_INVERS_DEST) {
/* !(src and !dest) == !src or dest */
return SPICE_ROP_OR_INVERTED;
} else {
/* !(src and dest) */
return SPICE_ROP_NAND;
}
}
} else {
if (desc & SPICE_ROPD_INVERS_SRC) {
if (desc & SPICE_ROPD_INVERS_DEST) {
/* !src and !dest == !(src or dest)*/
return SPICE_ROP_NOR;
} else {
/* !src and dest */
return SPICE_ROP_AND_INVERTED;
}
} else {
if (desc & SPICE_ROPD_INVERS_DEST) {
/* src and !dest */
return SPICE_ROP_AND_REVERSE;
} else {
/* src and dest */
return SPICE_ROP_AND;
}
}
}
} else if (desc & SPICE_ROPD_OP_XOR) {
if (desc & SPICE_ROPD_INVERS_RES) {
if (desc & SPICE_ROPD_INVERS_SRC) {
if (desc & SPICE_ROPD_INVERS_DEST) {
/* !(!src xor !dest) == !src xor dest */
return SPICE_ROP_EQUIV;
} else {
/* ! (!src xor dest) = src xor dest*/
return SPICE_ROP_XOR;
}
} else {
if (desc & SPICE_ROPD_INVERS_DEST) {
/* !(src xor !dest) == src xor dest */
return SPICE_ROP_XOR;
} else {
/* !(src xor dest) */
return SPICE_ROP_EQUIV;
}
}
} else {
if (desc & SPICE_ROPD_INVERS_SRC) {
if (desc & SPICE_ROPD_INVERS_DEST) {
/* !src xor !dest == src xor dest */
return SPICE_ROP_XOR;
} else {
/* !src xor dest */
return SPICE_ROP_EQUIV;
}
} else {
if (desc & SPICE_ROPD_INVERS_DEST) {
/* src xor !dest */
return SPICE_ROP_EQUIV;
} else {
/* src xor dest */
return SPICE_ROP_XOR;
}
}
}
} else if (desc & SPICE_ROPD_OP_BLACKNESS) {
return SPICE_ROP_CLEAR;
} else if (desc & SPICE_ROPD_OP_WHITENESS) {
return SPICE_ROP_SET;
} else if (desc & SPICE_ROPD_OP_INVERS) {
return SPICE_ROP_INVERT;
}
return SPICE_ROP_COPY;
}
static void canvas_clip_pixman(CairoCanvas *canvas,
pixman_region32_t *dest_region,
SpiceClip *clip)
{
pixman_region32_intersect(dest_region, dest_region, &canvas->canvas_region);
switch (clip->type) {
case SPICE_CLIP_TYPE_NONE:
break;
case SPICE_CLIP_TYPE_RECTS: {
uint32_t *n = (uint32_t *)SPICE_GET_ADDRESS(clip->data);
access_test(&canvas->base, n, sizeof(uint32_t));
SpiceRect *now = (SpiceRect *)(n + 1);
access_test(&canvas->base, now, (unsigned long)(now + *n) - (unsigned long)now);
pixman_region32_t clip;
if (spice_pixman_region32_init_rects(&clip, now, *n)) {
pixman_region32_intersect(dest_region, dest_region, &clip);
pixman_region32_fini(&clip);
}
break;
}
case SPICE_CLIP_TYPE_PATH:
CANVAS_ERROR("clip paths not supported anymore");
break;
default:
CANVAS_ERROR("invalid clip type");
}
}
static void canvas_mask_pixman(CairoCanvas *canvas,
pixman_region32_t *dest_region,
SpiceQMask *mask, int x, int y)
{
pixman_image_t *image, *subimage;
int needs_invert;
pixman_region32_t mask_region;
uint32_t *mask_data;
int mask_x, mask_y;
int mask_width, mask_height, mask_stride;
pixman_box32_t extents;
needs_invert = FALSE;
image = canvas_get_mask(&canvas->base,
mask,
&needs_invert);
if (image == NULL) {
return; /* no mask */
}
mask_data = pixman_image_get_data(image);
mask_width = pixman_image_get_width(image);
mask_height = pixman_image_get_height(image);
mask_stride = pixman_image_get_stride(image);
mask_x = mask->pos.x;
mask_y = mask->pos.y;
/* We need to subset the area of the mask that we turn into a region,
because a cached mask may be much larger than what is used for
the clip operation. */
extents = *pixman_region32_extents(dest_region);
/* convert from destination pixels to mask pixels */
extents.x1 -= x - mask_x;
extents.y1 -= y - mask_y;
extents.x2 -= x - mask_x;
extents.y2 -= y - mask_y;
/* clip to mask size */
if (extents.x1 < 0) {
extents.x1 = 0;
}
if (extents.x2 >= mask_width) {
extents.x2 = mask_width;
}
if (extents.x2 < extents.x1) {
extents.x2 = extents.x1;
}
if (extents.y1 < 0) {
extents.y1 = 0;
}
if (extents.y2 >= mask_height) {
extents.y2 = mask_height;
}
if (extents.y2 < extents.y1) {
extents.y2 = extents.y1;
}
/* round down X to even 32 pixels (i.e. uint32_t) */
extents.x1 = extents.x1 & ~(0x1f);
mask_data = (uint32_t *)((uint8_t *)mask_data + mask_stride * extents.y1 + extents.x1 / 32);
mask_x -= extents.x1;
mask_y -= extents.y1;
mask_width = extents.x2 - extents.x1;
mask_height = extents.y2 - extents.y1;
subimage = pixman_image_create_bits(PIXMAN_a1, mask_width, mask_height,
mask_data, mask_stride);
pixman_region32_init_from_image(&mask_region,
subimage);
pixman_image_unref(subimage);
if (needs_invert) {
pixman_box32_t rect;
rect.x1 = rect.y1 = 0;
rect.x2 = mask_width;
rect.y2 = mask_height;
pixman_region32_inverse(&mask_region, &mask_region, &rect);
}
pixman_region32_translate(&mask_region,
-mask_x + x, -mask_y + y);
pixman_region32_intersect(dest_region, dest_region, &mask_region);
pixman_region32_fini(&mask_region);
pixman_image_unref(image);
}
static pixman_image_t* canvas_surface_from_self(CairoCanvas *canvas,
int x, int y,
int32_t width, int32_t heigth)
{
pixman_image_t *surface;
pixman_image_t *src_surface;
uint8_t *dest;
int dest_stride;
uint8_t *src;
int src_stride;
int i;
surface = pixman_image_create_bits(PIXMAN_x8r8g8b8, width, heigth, NULL, 0);
if (surface == NULL) {
CANVAS_ERROR("create surface failed");
}
dest = (uint8_t *)pixman_image_get_data(surface);
dest_stride = pixman_image_get_stride(surface);
src_surface = canvas->image;
src = (uint8_t *)pixman_image_get_data(src_surface);
src_stride = pixman_image_get_stride(src_surface);
src += y * src_stride + (x << 2);
for (i = 0; i < heigth; i++, dest += dest_stride, src += src_stride) {
memcpy(dest, src, width << 2);
}
return surface;
}
static pixman_image_t *canvas_get_pixman_brush(CairoCanvas *canvas,
SpiceBrush *brush)
{
switch (brush->type) {
case SPICE_BRUSH_TYPE_SOLID: {
uint32_t color = brush->u.color;
pixman_color_t c;
c.blue = ((color & canvas->base.color_mask) * 0xffff) / canvas->base.color_mask;
color >>= canvas->base.color_shift;
c.green = ((color & canvas->base.color_mask) * 0xffff) / canvas->base.color_mask;
color >>= canvas->base.color_shift;
c.red = ((color & canvas->base.color_mask) * 0xffff) / canvas->base.color_mask;
c.alpha = 0xffff;
return pixman_image_create_solid_fill(&c);
}
case SPICE_BRUSH_TYPE_PATTERN: {
pixman_image_t* surface;
pixman_transform_t t;
surface = canvas_get_image(&canvas->base, brush->u.pattern.pat);
pixman_transform_init_translate(&t,
pixman_int_to_fixed(-brush->u.pattern.pos.x),
pixman_int_to_fixed(-brush->u.pattern.pos.y));
pixman_image_set_transform(surface, &t);
pixman_image_set_repeat(surface, PIXMAN_REPEAT_NORMAL);
return surface;
}
case SPICE_BRUSH_TYPE_NONE:
return NULL;
default:
CANVAS_ERROR("invalid brush type");
}
}
static void copy_region(CairoCanvas *canvas,
pixman_region32_t *dest_region,
int dx, int dy)
{
pixman_box32_t *dest_rects;
int n_rects;
int i, j, end_line;
dest_rects = pixman_region32_rectangles(dest_region, &n_rects);
if (dy > 0) {
if (dx >= 0) {
/* south-east: copy x and y in reverse order */
for (i = n_rects - 1; i >= 0; i--) {
spice_pixman_copy_rect(canvas->image,
dest_rects[i].x1 - dx, dest_rects[i].y1 - dy,
dest_rects[i].x2 - dest_rects[i].x1,
dest_rects[i].y2 - dest_rects[i].y1,
dest_rects[i].x1, dest_rects[i].y1);
}
} else {
/* south-west: Copy y in reverse order, but x in forward order */
i = n_rects - 1;
while (i >= 0) {
/* Copy all rects with same y in forward order */
for (end_line = i - 1; end_line >= 0 && dest_rects[end_line].y1 == dest_rects[i].y1; end_line--) {
}
for (j = end_line + 1; j <= i; j++) {
spice_pixman_copy_rect(canvas->image,
dest_rects[j].x1 - dx, dest_rects[j].y1 - dy,
dest_rects[j].x2 - dest_rects[j].x1,
dest_rects[j].y2 - dest_rects[j].y1,
dest_rects[j].x1, dest_rects[j].y1);
}
i = end_line;
}
}
} else {
if (dx > 0) {
/* north-east: copy y in forward order, but x in reverse order */
i = 0;
while (i < n_rects) {
/* Copy all rects with same y in reverse order */
for (end_line = i; end_line < n_rects && dest_rects[end_line].y1 == dest_rects[i].y1; end_line++) {
}
for (j = end_line - 1; j >= i; j--) {
spice_pixman_copy_rect(canvas->image,
dest_rects[j].x1 - dx, dest_rects[j].y1 - dy,
dest_rects[j].x2 - dest_rects[j].x1,
dest_rects[j].y2 - dest_rects[j].y1,
dest_rects[j].x1, dest_rects[j].y1);
}
i = end_line;
}
} else {
/* north-west: Copy x and y in forward order */
for (i = 0; i < n_rects; i++) {
spice_pixman_copy_rect(canvas->image,
dest_rects[i].x1 - dx, dest_rects[i].y1 - dy,
dest_rects[i].x2 - dest_rects[i].x1,
dest_rects[i].y2 - dest_rects[i].y1,
dest_rects[i].x1, dest_rects[i].y1);
}
}
}
}
static void fill_solid_rects(CairoCanvas *canvas,
pixman_region32_t *region,
uint32_t color)
{
pixman_box32_t *rects;
int n_rects;
int i;
rects = pixman_region32_rectangles(region, &n_rects);
for (i = 0; i < n_rects; i++) {
spice_pixman_fill_rect(canvas->image,
rects[i].x1, rects[i].y1,
rects[i].x2 - rects[i].x1,
rects[i].y2 - rects[i].y1,
color);
}
}
static void fill_solid_rects_rop(CairoCanvas *canvas,
pixman_region32_t *region,
uint32_t color,
SpiceROP rop)
{
pixman_box32_t *rects;
int n_rects;
int i;
rects = pixman_region32_rectangles(region, &n_rects);
for (i = 0; i < n_rects; i++) {
spice_pixman_fill_rect_rop(canvas->image,
rects[i].x1, rects[i].y1,
rects[i].x2 - rects[i].x1,
rects[i].y2 - rects[i].y1,
color, rop);
}
}
static void fill_tiled_rects(CairoCanvas *canvas,
pixman_region32_t *region,
SpicePattern *pattern)
{
pixman_image_t *tile;
int offset_x, offset_y;
pixman_box32_t *rects;
int n_rects;
int i;
rects = pixman_region32_rectangles(region, &n_rects);
tile = canvas_get_image(&canvas->base, pattern->pat);
offset_x = pattern->pos.x;
offset_y = pattern->pos.y;
for (i = 0; i < n_rects; i++) {
spice_pixman_tile_rect(canvas->image,
rects[i].x1, rects[i].y1,
rects[i].x2 - rects[i].x1,
rects[i].y2 - rects[i].y1,
tile, offset_x, offset_y);
}
pixman_image_unref(tile);
}
static void fill_tiled_rects_rop(CairoCanvas *canvas,
pixman_region32_t *region,
SpicePattern *pattern,
SpiceROP rop)
{
pixman_image_t *tile;
int offset_x, offset_y;
pixman_box32_t *rects;
int n_rects;
int i;
rects = pixman_region32_rectangles(region, &n_rects);
tile = canvas_get_image(&canvas->base, pattern->pat);
offset_x = pattern->pos.x;
offset_y = pattern->pos.y;
for (i = 0; i < n_rects; i++) {
spice_pixman_tile_rect_rop(canvas->image,
rects[i].x1, rects[i].y1,
rects[i].x2 - rects[i].x1,
rects[i].y2 - rects[i].y1,
tile, offset_x, offset_y,
rop);
}
pixman_image_unref(tile);
}
static void blit_with_region(CairoCanvas *canvas,
pixman_region32_t *region,
pixman_image_t *src_image,
int offset_x, int offset_y)
{
pixman_box32_t *rects;
int n_rects, i;
rects = pixman_region32_rectangles(region, &n_rects);
for (i = 0; i < n_rects; i++) {
int src_x, src_y, dest_x, dest_y, width, height;
dest_x = rects[i].x1;
dest_y = rects[i].y1;
width = rects[i].x2 - rects[i].x1;
height = rects[i].y2 - rects[i].y1;
src_x = rects[i].x1 - offset_x;
src_y = rects[i].y1 - offset_y;
spice_pixman_blit(canvas->image,
src_image,
src_x, src_y,
dest_x, dest_y,
width, height);
}
}
static void blit_image(CairoCanvas *canvas,
pixman_region32_t *region,
SPICE_ADDRESS src_bitmap,
int offset_x, int offset_y)
{
pixman_image_t *src_image;
src_image = canvas_get_image(&canvas->base, src_bitmap);
blit_with_region(canvas, region, src_image,
offset_x, offset_y);
pixman_image_unref(src_image);
}
static void blit_image_rop(CairoCanvas *canvas,
pixman_region32_t *region,
SPICE_ADDRESS src_bitmap,
int offset_x, int offset_y,
SpiceROP rop)
{
pixman_image_t *src_image;
pixman_box32_t *rects;
int n_rects, i;
rects = pixman_region32_rectangles(region, &n_rects);
src_image = canvas_get_image(&canvas->base, src_bitmap);
for (i = 0; i < n_rects; i++) {
int src_x, src_y, dest_x, dest_y, width, height;
dest_x = rects[i].x1;
dest_y = rects[i].y1;
width = rects[i].x2 - rects[i].x1;
height = rects[i].y2 - rects[i].y1;
src_x = rects[i].x1 - offset_x;
src_y = rects[i].y1 - offset_y;
spice_pixman_blit_rop(canvas->image,
src_image,
src_x, src_y,
dest_x, dest_y,
width, height, rop);
}
pixman_image_unref(src_image);
}
static void scale_image(CairoCanvas *canvas,
pixman_region32_t *region,
SPICE_ADDRESS src_bitmap,
int src_x, int src_y,
int src_width, int src_height,
int dest_x, int dest_y,
int dest_width, int dest_height,
int scale_mode)
{
pixman_transform_t transform;
pixman_image_t *src;
double sx, sy;
sx = (double)(src_width) / (dest_width);
sy = (double)(src_height) / (dest_height);
src = canvas_get_image(&canvas->base, src_bitmap);
pixman_image_set_clip_region32(canvas->image, region);
pixman_transform_init_scale(&transform,
pixman_double_to_fixed(sx),
pixman_double_to_fixed(sy));
pixman_image_set_transform(src, &transform);
pixman_image_set_repeat(src, PIXMAN_REPEAT_NONE);
ASSERT(scale_mode == SPICE_IMAGE_SCALE_MODE_INTERPOLATE ||
scale_mode == SPICE_IMAGE_SCALE_MODE_NEAREST);
pixman_image_set_filter(src,
(scale_mode == SPICE_IMAGE_SCALE_MODE_NEAREST) ?
PIXMAN_FILTER_NEAREST : PIXMAN_FILTER_GOOD,
NULL, 0);
pixman_image_composite32(PIXMAN_OP_SRC,
src, NULL, canvas->image,
ROUND(src_x / sx), ROUND(src_y / sy), /* src */
0, 0, /* mask */
dest_x, dest_y, /* dst */
dest_width, dest_height);
pixman_transform_init_identity(&transform);
pixman_image_set_transform(src, &transform);
pixman_image_set_clip_region32(canvas->image, NULL);
pixman_image_unref(src);
}
static void scale_image_rop(CairoCanvas *canvas,
pixman_region32_t *region,
SPICE_ADDRESS src_bitmap,
int src_x, int src_y,
int src_width, int src_height,
int dest_x, int dest_y,
int dest_width, int dest_height,
int scale_mode, SpiceROP rop)
{
pixman_transform_t transform;
pixman_image_t *src;
pixman_image_t *scaled;
pixman_box32_t *rects;
int n_rects, i;
double sx, sy;
sx = (double)(src_width) / (dest_width);
sy = (double)(src_height) / (dest_height);
src = canvas_get_image(&canvas->base, src_bitmap);
scaled = pixman_image_create_bits(PIXMAN_x8r8g8b8,
dest_width,
dest_height,
NULL, 0);
pixman_region32_translate(region, -dest_x, -dest_y);
pixman_image_set_clip_region32(scaled, region);
pixman_transform_init_scale(&transform,
pixman_double_to_fixed(sx),
pixman_double_to_fixed(sy));
pixman_image_set_transform(src, &transform);
pixman_image_set_repeat(src, PIXMAN_REPEAT_NONE);
ASSERT(scale_mode == SPICE_IMAGE_SCALE_MODE_INTERPOLATE ||
scale_mode == SPICE_IMAGE_SCALE_MODE_NEAREST);
pixman_image_set_filter(src,
(scale_mode == SPICE_IMAGE_SCALE_MODE_NEAREST) ?
PIXMAN_FILTER_NEAREST : PIXMAN_FILTER_GOOD,
NULL, 0);
pixman_image_composite32(PIXMAN_OP_SRC,
src, NULL, scaled,
ROUND(src_x / sx), ROUND(src_y / sy), /* src */
0, 0, /* mask */
0, 0, /* dst */
dest_width,
dest_height);
pixman_transform_init_identity(&transform);
pixman_image_set_transform(src, &transform);
/* Translate back */
pixman_region32_translate(region, dest_x, dest_y);
rects = pixman_region32_rectangles(region, &n_rects);
for (i = 0; i < n_rects; i++) {
spice_pixman_blit_rop(canvas->image,
scaled,
rects[i].x1 - dest_x,
rects[i].y1 - dest_y,
rects[i].x1, rects[i].y1,
rects[i].x2 - rects[i].x1,
rects[i].y2 - rects[i].y1,
rop);
}
pixman_image_unref(scaled);
pixman_image_unref(src);
}
static void blend_image(CairoCanvas *canvas,
pixman_region32_t *region,
SPICE_ADDRESS src_bitmap,
int src_x, int src_y,
int dest_x, int dest_y,
int width, int height,
int overall_alpha)
{
pixman_image_t *src, *mask;
src = canvas_get_image(&canvas->base, src_bitmap);
pixman_image_set_clip_region32(canvas->image, region);
mask = NULL;
if (overall_alpha != 0xff) {
pixman_color_t color = { 0 };
color.alpha = overall_alpha * 0x101;
mask = pixman_image_create_solid_fill(&color);
}
pixman_image_set_repeat(src, PIXMAN_REPEAT_NONE);
pixman_image_composite32(PIXMAN_OP_OVER,
src, mask, canvas->image,
src_x, src_y, /* src */
0, 0, /* mask */
dest_x, dest_y, /* dst */
width,
height);
if (mask) {
pixman_image_unref(mask);
}
pixman_image_unref(src);
pixman_image_set_clip_region32(canvas->image, NULL);
}
static void blend_scale_image(CairoCanvas *canvas,
pixman_region32_t *region,
SPICE_ADDRESS src_bitmap,
int src_x, int src_y,
int src_width, int src_height,
int dest_x, int dest_y,
int dest_width, int dest_height,
int scale_mode,
int overall_alpha)
{
pixman_transform_t transform;
pixman_image_t *src, *mask;
double sx, sy;
sx = (double)(src_width) / (dest_width);
sy = (double)(src_height) / (dest_height);
src = canvas_get_image(&canvas->base, src_bitmap);
pixman_image_set_clip_region32(canvas->image, region);
pixman_transform_init_scale(&transform,
pixman_double_to_fixed(sx),
pixman_double_to_fixed(sy));
mask = NULL;
if (overall_alpha != 0xff) {
pixman_color_t color = { 0 };
color.alpha = overall_alpha * 0x101;
mask = pixman_image_create_solid_fill(&color);
}
pixman_image_set_transform(src, &transform);
pixman_image_set_repeat(src, PIXMAN_REPEAT_NONE);
ASSERT(scale_mode == SPICE_IMAGE_SCALE_MODE_INTERPOLATE ||
scale_mode == SPICE_IMAGE_SCALE_MODE_NEAREST);
pixman_image_set_filter(src,
(scale_mode == SPICE_IMAGE_SCALE_MODE_NEAREST) ?
PIXMAN_FILTER_NEAREST : PIXMAN_FILTER_GOOD,
NULL, 0);
pixman_image_composite32(PIXMAN_OP_OVER,
src, mask, canvas->image,
ROUND(src_x / sx), ROUND(src_y / sy), /* src */
0, 0, /* mask */
dest_x, dest_y, /* dst */
dest_width, dest_height);
pixman_transform_init_identity(&transform);
pixman_image_set_transform(src, &transform);
if (mask) {
pixman_image_unref(mask);
}
pixman_image_unref(src);
pixman_image_set_clip_region32(canvas->image, NULL);
}
static void colorkey_image(CairoCanvas *canvas,
pixman_region32_t *region,
SPICE_ADDRESS src_bitmap,
int offset_x, int offset_y,
uint32_t transparent_color)
{
pixman_image_t *src_image;
pixman_box32_t *rects;
int n_rects, i;
rects = pixman_region32_rectangles(region, &n_rects);
src_image = canvas_get_image(&canvas->base, src_bitmap);
for (i = 0; i < n_rects; i++) {
int src_x, src_y, dest_x, dest_y, width, height;
dest_x = rects[i].x1;
dest_y = rects[i].y1;
width = rects[i].x2 - rects[i].x1;
height = rects[i].y2 - rects[i].y1;
src_x = rects[i].x1 - offset_x;
src_y = rects[i].y1 - offset_y;
spice_pixman_blit_colorkey(canvas->image,
src_image,
src_x, src_y,
dest_x, dest_y,
width, height,
transparent_color);
}
pixman_image_unref(src_image);
}
static void colorkey_scale_image(CairoCanvas *canvas,
pixman_region32_t *region,
SPICE_ADDRESS src_bitmap,
int src_x, int src_y,
int src_width, int src_height,
int dest_x, int dest_y,
int dest_width, int dest_height,
uint32_t transparent_color)
{
pixman_transform_t transform;
pixman_image_t *src;
pixman_image_t *scaled;
pixman_box32_t *rects;
int n_rects, i;
double sx, sy;
sx = (double)(src_width) / (dest_width);
sy = (double)(src_height) / (dest_height);
src = canvas_get_image(&canvas->base, src_bitmap);
scaled = pixman_image_create_bits(PIXMAN_x8r8g8b8,
dest_width,
dest_height,
NULL, 0);
pixman_region32_translate(region, -dest_x, -dest_y);
pixman_image_set_clip_region32(scaled, region);
pixman_transform_init_scale(&transform,
pixman_double_to_fixed(sx),
pixman_double_to_fixed(sy));
pixman_image_set_transform(src, &transform);
pixman_image_set_repeat(src, PIXMAN_REPEAT_NONE);
pixman_image_set_filter(src,
PIXMAN_FILTER_NEAREST,
NULL, 0);
pixman_image_composite32(PIXMAN_OP_SRC,
src, NULL, scaled,
ROUND(src_x / sx), ROUND(src_y / sy), /* src */
0, 0, /* mask */
0, 0, /* dst */
dest_width,
dest_height);
pixman_transform_init_identity(&transform);
pixman_image_set_transform(src, &transform);
/* Translate back */
pixman_region32_translate(region, dest_x, dest_y);
rects = pixman_region32_rectangles(region, &n_rects);
for (i = 0; i < n_rects; i++) {
spice_pixman_blit_colorkey(canvas->image,
scaled,
rects[i].x1 - dest_x,
rects[i].y1 - dest_y,
rects[i].x1, rects[i].y1,
rects[i].x2 - rects[i].x1,
rects[i].y2 - rects[i].y1,
transparent_color);
}
pixman_image_unref(scaled);
pixman_image_unref(src);
}
static void draw_brush(CairoCanvas *canvas,
pixman_region32_t *region,
SpiceBrush *brush,
SpiceROP rop)
{
uint32_t color;
SpicePattern *pattern;
switch (brush->type) {
case SPICE_BRUSH_TYPE_SOLID:
color = brush->u.color;
if (rop == SPICE_ROP_COPY) {
fill_solid_rects(canvas, region, color);
} else {
fill_solid_rects_rop(canvas, region, color, rop);
}
break;
case SPICE_BRUSH_TYPE_PATTERN:
pattern = &brush->u.pattern;
if (rop == SPICE_ROP_COPY) {
fill_tiled_rects(canvas, region, pattern);
} else {
fill_tiled_rects_rop(canvas, region, pattern, rop);
}
break;
case SPICE_BRUSH_TYPE_NONE:
/* Still need to do *something* here, because rop could be e.g invert dest */
fill_solid_rects_rop(canvas, region, 0, rop);
break;
default:
CANVAS_ERROR("invalid brush type");
}
}
/* If we're exiting early we may still have to load an image in case
it has to be cached or something */
static void touch_brush(CairoCanvas *canvas, SpiceBrush *brush)
{
SpicePattern *pattern;
if (brush->type == SPICE_BRUSH_TYPE_PATTERN) {
pattern = &brush->u.pattern;
canvas_touch_image(&canvas->base, pattern->pat);
}
}
static void canvas_draw_fill(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceFill *fill)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_region32_t dest_region;
SpiceROP rop;
pixman_region32_init_rect(&dest_region,
bbox->left, bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top);
canvas_clip_pixman(canvas, &dest_region, clip);
canvas_mask_pixman(canvas, &dest_region, &fill->mask,
bbox->left, bbox->top);
rop = ropd_descriptor_to_rop(fill->rop_decriptor,
ROP_INPUT_BRUSH,
ROP_INPUT_DEST);
if (rop == SPICE_ROP_NOOP || pixman_region32_n_rects(&dest_region) == 0) {
touch_brush(canvas, &fill->brush);
pixman_region32_fini(&dest_region);
return;
}
draw_brush(canvas, &dest_region, &fill->brush, rop);
pixman_region32_fini(&dest_region);
}
static void canvas_draw_copy(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceCopy *copy)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_region32_t dest_region;
SpiceROP rop;
pixman_region32_init_rect(&dest_region,
bbox->left, bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top);
canvas_clip_pixman(canvas, &dest_region, clip);
canvas_mask_pixman(canvas, &dest_region, ©->mask,
bbox->left, bbox->top);
rop = ropd_descriptor_to_rop(copy->rop_decriptor,
ROP_INPUT_SRC,
ROP_INPUT_DEST);
if (rop == SPICE_ROP_NOOP || pixman_region32_n_rects(&dest_region) == 0) {
canvas_touch_image(&canvas->base, copy->src_bitmap);
pixman_region32_fini(&dest_region);
return;
}
if (rect_is_same_size(bbox, ©->src_area)) {
if (rop == SPICE_ROP_COPY) {
blit_image(canvas, &dest_region,
copy->src_bitmap,
bbox->left - copy->src_area.left,
bbox->top - copy->src_area.top);
} else {
blit_image_rop(canvas, &dest_region,
copy->src_bitmap,
bbox->left - copy->src_area.left,
bbox->top - copy->src_area.top,
rop);
}
} else {
if (rop == SPICE_ROP_COPY) {
scale_image(canvas, &dest_region,
copy->src_bitmap,
copy->src_area.left,
copy->src_area.top,
copy->src_area.right - copy->src_area.left,
copy->src_area.bottom - copy->src_area.top,
bbox->left,
bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top,
copy->scale_mode);
} else {
scale_image_rop(canvas, &dest_region,
copy->src_bitmap,
copy->src_area.left,
copy->src_area.top,
copy->src_area.right - copy->src_area.left,
copy->src_area.bottom - copy->src_area.top,
bbox->left,
bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top,
copy->scale_mode,
rop);
}
}
pixman_region32_fini(&dest_region);
}
static void canvas_put_image(SpiceCanvas *spice_canvas,
#ifdef WIN32
HDC dc,
#endif
const SpiceRect *dest, const uint8_t *src_data,
uint32_t src_width, uint32_t src_height, int src_stride,
const QRegion *clip)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_image_t *src;
int dest_width;
int dest_height;
double sx, sy;
pixman_transform_t transform;
src = pixman_image_create_bits(PIXMAN_x8r8g8b8,
src_width,
src_height,
(uint32_t*)src_data,
src_stride);
if (clip) {
pixman_image_set_clip_region32 (canvas->image, (pixman_region32_t *)clip);
}
dest_width = dest->right - dest->left;
dest_height = dest->bottom - dest->top;
if (dest_width != src_width || dest_height != src_height) {
sx = (double)(src_width) / (dest_width);
sy = (double)(src_height) / (dest_height);
pixman_transform_init_scale(&transform,
pixman_double_to_fixed(sx),
pixman_double_to_fixed(sy));
pixman_image_set_transform(src, &transform);
pixman_image_set_filter(src,
PIXMAN_FILTER_NEAREST,
NULL, 0);
}
pixman_image_set_repeat(src, PIXMAN_REPEAT_NONE);
pixman_image_composite32(PIXMAN_OP_SRC,
src, NULL, canvas->image,
0, 0, /* src */
0, 0, /* mask */
dest->left, dest->top, /* dst */
dest_width, dest_height);
if (clip) {
pixman_image_set_clip_region32(canvas->image, NULL);
}
pixman_image_unref(src);
}
static void canvas_draw_transparent(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceTransparent* transparent)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_region32_t dest_region;
pixman_region32_init_rect(&dest_region,
bbox->left, bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top);
canvas_clip_pixman(canvas, &dest_region, clip);
if (pixman_region32_n_rects (&dest_region) == 0) {
canvas_touch_image(&canvas->base, transparent->src_bitmap);
pixman_region32_fini(&dest_region);
return;
}
if (rect_is_same_size(bbox, &transparent->src_area)) {
colorkey_image(canvas, &dest_region,
transparent->src_bitmap,
bbox->left - transparent->src_area.left,
bbox->top - transparent->src_area.top,
transparent->true_color);
} else {
colorkey_scale_image(canvas, &dest_region,
transparent->src_bitmap,
transparent->src_area.left,
transparent->src_area.top,
transparent->src_area.right - transparent->src_area.left,
transparent->src_area.bottom - transparent->src_area.top,
bbox->left,
bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top,
transparent->true_color);
}
pixman_region32_fini(&dest_region);
}
static void canvas_draw_alpha_blend(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceAlphaBlnd* alpha_blend)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_region32_t dest_region;
pixman_region32_init_rect(&dest_region,
bbox->left, bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top);
canvas_clip_pixman(canvas, &dest_region, clip);
if (alpha_blend->alpha == 0 ||
pixman_region32_n_rects(&dest_region) == 0) {
canvas_touch_image(&canvas->base, alpha_blend->src_bitmap);
pixman_region32_fini(&dest_region);
return;
}
if (rect_is_same_size(bbox, &alpha_blend->src_area)) {
blend_image(canvas, &dest_region,
alpha_blend->src_bitmap,
alpha_blend->src_area.left,
alpha_blend->src_area.top,
bbox->left,
bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top,
alpha_blend->alpha);
} else {
blend_scale_image(canvas, &dest_region,
alpha_blend->src_bitmap,
alpha_blend->src_area.left,
alpha_blend->src_area.top,
alpha_blend->src_area.right - alpha_blend->src_area.left,
alpha_blend->src_area.bottom - alpha_blend->src_area.top,
bbox->left,
bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top,
SPICE_IMAGE_SCALE_MODE_INTERPOLATE,
alpha_blend->alpha);
}
pixman_region32_fini(&dest_region);
}
static void canvas_draw_opaque(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceOpaque *opaque)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_region32_t dest_region;
SpiceROP rop;
pixman_region32_init_rect(&dest_region,
bbox->left, bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top);
canvas_clip_pixman(canvas, &dest_region, clip);
canvas_mask_pixman(canvas, &dest_region, &opaque->mask,
bbox->left, bbox->top);
rop = ropd_descriptor_to_rop(opaque->rop_decriptor,
ROP_INPUT_BRUSH,
ROP_INPUT_SRC);
if (rop == SPICE_ROP_NOOP || pixman_region32_n_rects(&dest_region) == 0) {
canvas_touch_image(&canvas->base, opaque->src_bitmap);
touch_brush(canvas, &opaque->brush);
pixman_region32_fini(&dest_region);
return;
}
if (rect_is_same_size(bbox, &opaque->src_area)) {
blit_image(canvas, &dest_region,
opaque->src_bitmap,
bbox->left - opaque->src_area.left,
bbox->top - opaque->src_area.top);
} else {
scale_image(canvas, &dest_region,
opaque->src_bitmap,
opaque->src_area.left,
opaque->src_area.top,
opaque->src_area.right - opaque->src_area.left,
opaque->src_area.bottom - opaque->src_area.top,
bbox->left,
bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top,
opaque->scale_mode);
}
draw_brush(canvas, &dest_region, &opaque->brush, rop);
pixman_region32_fini(&dest_region);
}
static void canvas_draw_blend(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceBlend *blend)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_region32_t dest_region;
SpiceROP rop;
pixman_region32_init_rect(&dest_region,
bbox->left, bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top);
canvas_clip_pixman(canvas, &dest_region, clip);
canvas_mask_pixman(canvas, &dest_region, &blend->mask,
bbox->left, bbox->top);
rop = ropd_descriptor_to_rop(blend->rop_decriptor,
ROP_INPUT_SRC,
ROP_INPUT_DEST);
if (rop == SPICE_ROP_NOOP || pixman_region32_n_rects(&dest_region) == 0) {
canvas_touch_image(&canvas->base, blend->src_bitmap);
pixman_region32_fini(&dest_region);
return;
}
if (rect_is_same_size(bbox, &blend->src_area)) {
if (rop == SPICE_ROP_COPY)
blit_image(canvas, &dest_region,
blend->src_bitmap,
bbox->left - blend->src_area.left,
bbox->top - blend->src_area.top);
else
blit_image_rop(canvas, &dest_region,
blend->src_bitmap,
bbox->left - blend->src_area.left,
bbox->top - blend->src_area.top,
rop);
} else {
double sx, sy;
sx = (double)(blend->src_area.right - blend->src_area.left) / (bbox->right - bbox->left);
sy = (double)(blend->src_area.bottom - blend->src_area.top) / (bbox->bottom - bbox->top);
if (rop == SPICE_ROP_COPY) {
scale_image(canvas, &dest_region,
blend->src_bitmap,
blend->src_area.left,
blend->src_area.top,
blend->src_area.right - blend->src_area.left,
blend->src_area.bottom - blend->src_area.top,
bbox->left,
bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top,
blend->scale_mode);
} else {
scale_image_rop(canvas, &dest_region,
blend->src_bitmap,
blend->src_area.left,
blend->src_area.top,
blend->src_area.right - blend->src_area.left,
blend->src_area.bottom - blend->src_area.top,
bbox->left,
bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top,
blend->scale_mode, rop);
}
}
pixman_region32_fini(&dest_region);
}
static void canvas_draw_blackness(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceBlackness *blackness)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_region32_t dest_region;
pixman_region32_init_rect(&dest_region,
bbox->left, bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top);
canvas_clip_pixman(canvas, &dest_region, clip);
canvas_mask_pixman(canvas, &dest_region, &blackness->mask,
bbox->left, bbox->top);
if (pixman_region32_n_rects(&dest_region) == 0) {
pixman_region32_fini (&dest_region);
return;
}
fill_solid_rects(canvas, &dest_region, 0x000000);
pixman_region32_fini(&dest_region);
}
static void canvas_draw_whiteness(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceWhiteness *whiteness)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_region32_t dest_region;
pixman_region32_init_rect(&dest_region,
bbox->left, bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top);
canvas_clip_pixman(canvas, &dest_region, clip);
canvas_mask_pixman(canvas, &dest_region, &whiteness->mask,
bbox->left, bbox->top);
if (pixman_region32_n_rects(&dest_region) == 0) {
pixman_region32_fini(&dest_region);
return;
}
fill_solid_rects(canvas, &dest_region, 0xffffffff);
pixman_region32_fini(&dest_region);
}
static void canvas_draw_invers(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceInvers *invers)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_region32_t dest_region;
pixman_region32_init_rect(&dest_region,
bbox->left, bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top);
canvas_clip_pixman(canvas, &dest_region, clip);
canvas_mask_pixman(canvas, &dest_region, &invers->mask,
bbox->left, bbox->top);
if (pixman_region32_n_rects(&dest_region) == 0) {
pixman_region32_fini(&dest_region);
return;
}
fill_solid_rects_rop(canvas, &dest_region, 0x00000000,
SPICE_ROP_INVERT);
pixman_region32_fini(&dest_region);
}
static void canvas_draw_rop3(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceRop3 *rop3)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_region32_t dest_region;
pixman_image_t *d;
pixman_image_t *s;
SpicePoint src_pos;
int width;
int heigth;
pixman_region32_init_rect(&dest_region,
bbox->left, bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top);
canvas_clip_pixman(canvas, &dest_region, clip);
canvas_mask_pixman(canvas, &dest_region, &rop3->mask,
bbox->left, bbox->top);
width = bbox->right - bbox->left;
heigth = bbox->bottom - bbox->top;
d = canvas_surface_from_self(canvas, bbox->left, bbox->top, width, heigth);
s = canvas_get_image(&canvas->base, rop3->src_bitmap);
if (!rect_is_same_size(bbox, &rop3->src_area)) {
pixman_image_t *scaled_s = canvas_scale_surface(s, &rop3->src_area, width, heigth,
rop3->scale_mode);
pixman_image_unref(s);
s = scaled_s;
src_pos.x = 0;
src_pos.y = 0;
} else {
src_pos.x = rop3->src_area.left;
src_pos.y = rop3->src_area.top;
}
if (pixman_image_get_width(s) - src_pos.x < width ||
pixman_image_get_height(s) - src_pos.y < heigth) {
CANVAS_ERROR("bad src bitmap size");
}
if (rop3->brush.type == SPICE_BRUSH_TYPE_PATTERN) {
pixman_image_t *p = canvas_get_image(&canvas->base, rop3->brush.u.pattern.pat);
SpicePoint pat_pos;
pat_pos.x = (bbox->left - rop3->brush.u.pattern.pos.x) % pixman_image_get_width(p);
pat_pos.y = (bbox->top - rop3->brush.u.pattern.pos.y) % pixman_image_get_height(p);
do_rop3_with_pattern(rop3->rop3, d, s, &src_pos, p, &pat_pos);
pixman_image_unref(p);
} else {
uint32_t color = (canvas->base.color_shift) == 8 ? rop3->brush.u.color :
canvas_16bpp_to_32bpp(rop3->brush.u.color);
do_rop3_with_color(rop3->rop3, d, s, &src_pos, color);
}
pixman_image_unref(s);
blit_with_region(canvas, &dest_region, d,
bbox->left,
bbox->top);
pixman_image_unref(d);
pixman_region32_fini(&dest_region);
}
static void canvas_copy_bits(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpicePoint *src_pos)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_region32_t dest_region;
int dx, dy;
pixman_region32_init_rect(&dest_region,
bbox->left, bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top);
canvas_clip_pixman(canvas, &dest_region, clip);
dx = bbox->left - src_pos->x;
dy = bbox->top - src_pos->y;
if (dx != 0 || dy != 0) {
pixman_region32_t src_region;
/* Clip so we don't read outside canvas */
pixman_region32_init_rect(&src_region,
dx, dy,
pixman_image_get_width (canvas->image),
pixman_image_get_height (canvas->image));
pixman_region32_intersect(&dest_region, &dest_region, &src_region);
pixman_region32_fini(&src_region);
copy_region(canvas, &dest_region, dx, dy);
}
pixman_region32_fini(&dest_region);
}
static void canvas_draw_text(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceText *text)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_region32_t dest_region;
pixman_image_t *str_mask, *brush;
SpiceString *str;
SpicePoint pos;
int depth;
pixman_region32_init_rect(&dest_region,
bbox->left, bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top);
canvas_clip_pixman(canvas, &dest_region, clip);
if (pixman_region32_n_rects(&dest_region) == 0) {
touch_brush(canvas, &text->fore_brush);
touch_brush(canvas, &text->back_brush);
pixman_region32_fini(&dest_region);
return;
}
if (!rect_is_empty(&text->back_area)) {
pixman_region32_t back_region;
/* Nothing else makes sense for text and we should deprecate it
* and actually it means OVER really */
ASSERT(text->fore_mode == SPICE_ROPD_OP_PUT);
pixman_region32_init_rect(&back_region,
text->back_area.left,
text->back_area.top,
text->back_area.right - text->back_area.left,
text->back_area.bottom - text->back_area.top);
pixman_region32_intersect(&back_region, &back_region, &dest_region);
if (pixman_region32_not_empty(&back_region)) {
draw_brush(canvas, &back_region, &text->back_brush, SPICE_ROP_COPY);
}
pixman_region32_fini(&back_region);
}
str = (SpiceString *)SPICE_GET_ADDRESS(text->str);
if (str->flags & SPICE_STRING_FLAGS_RASTER_A1) {
depth = 1;
} else if (str->flags & SPICE_STRING_FLAGS_RASTER_A4) {
depth = 4;
} else if (str->flags & SPICE_STRING_FLAGS_RASTER_A8) {
WARN("untested path A8 glyphs");
depth = 8;
} else {
WARN("unsupported path vector glyphs");
pixman_region32_fini (&dest_region);
return;
}
brush = canvas_get_pixman_brush(canvas, &text->fore_brush);
str_mask = canvas_get_str_mask(&canvas->base, str, depth, &pos);
if (brush) {
pixman_image_set_clip_region32(canvas->image, &dest_region);
pixman_image_composite32(PIXMAN_OP_OVER,
brush,
str_mask,
canvas->image,
0, 0,
0, 0,
pos.x, pos.y,
pixman_image_get_width(str_mask),
pixman_image_get_height(str_mask));
pixman_image_unref(brush);
pixman_image_set_clip_region32(canvas->image, NULL);
}
pixman_image_unref(str_mask);
pixman_region32_fini(&dest_region);
}
typedef struct {
lineGC base;
pixman_image_t *dest;
pixman_region32_t dest_region;
SpiceROP fore_rop;
SpiceROP back_rop;
int solid;
uint32_t color;
pixman_image_t *tile;
int tile_offset_x;
int tile_offset_y;
} StrokeGC;
static void stroke_fill_spans(lineGC * pGC,
int num_spans,
SpicePoint *points,
int *widths,
int sorted,
int foreground)
{
StrokeGC *strokeGC;
int i;
pixman_image_t *dest;
SpiceROP rop;
strokeGC = (StrokeGC *)pGC;
dest = strokeGC->dest;
num_spans = spice_canvas_clip_spans(&strokeGC->dest_region,
points, widths, num_spans,
points, widths, sorted);
if (foreground) {
rop = strokeGC->fore_rop;
} else {
rop = strokeGC->back_rop;
}
for (i = 0; i < num_spans; i++) {
if (strokeGC->solid) {
if (rop == SPICE_ROP_COPY) {
spice_pixman_fill_rect(dest, points[i].x, points[i].y, widths[i], 1,
strokeGC->color);
} else {
spice_pixman_fill_rect_rop(dest, points[i].x, points[i].y, widths[i], 1,
strokeGC->color, rop);
}
} else {
if (rop == SPICE_ROP_COPY) {
spice_pixman_tile_rect(dest,
points[i].x, points[i].y,
widths[i], 1,
strokeGC->tile,
strokeGC->tile_offset_x,
strokeGC->tile_offset_y);
} else {
spice_pixman_tile_rect_rop(dest,
points[i].x, points[i].y,
widths[i], 1,
strokeGC->tile,
strokeGC->tile_offset_x,
strokeGC->tile_offset_y,
rop);
}
}
}
}
static void stroke_fill_rects(lineGC * pGC,
int num_rects,
pixman_rectangle32_t *rects,
int foreground)
{
pixman_region32_t area;
pixman_box32_t *boxes;
StrokeGC *strokeGC;
pixman_image_t *dest;
SpiceROP rop;
int i;
pixman_box32_t *area_rects;
int n_area_rects;
strokeGC = (StrokeGC *)pGC;
dest = strokeGC->dest;
if (foreground) {
rop = strokeGC->fore_rop;
} else {
rop = strokeGC->back_rop;
}
/* TODO: We can optimize this for more common cases where
dest is one rect */
boxes = (pixman_box32_t *)malloc(num_rects * sizeof(pixman_box32_t));
for (i = 0; i < num_rects; i++) {
boxes[i].x1 = rects[i].x;
boxes[i].y1 = rects[i].y;
boxes[i].x2 = rects[i].x + rects[i].width;
boxes[i].y2 = rects[i].y + rects[i].height;
}
pixman_region32_init_rects(&area, boxes, num_rects);
pixman_region32_intersect(&area, &area, &strokeGC->dest_region);
free(boxes);
area_rects = pixman_region32_rectangles(&area, &n_area_rects);
for (i = 0; i < n_area_rects; i++) {
if (strokeGC->solid) {
if (rop == SPICE_ROP_COPY) {
spice_pixman_fill_rect(dest,
area_rects[i].x1,
area_rects[i].y1,
area_rects[i].x2 - area_rects[i].x1,
area_rects[i].y2 - area_rects[i].y1,
strokeGC->color);
} else {
spice_pixman_fill_rect_rop(dest,
area_rects[i].x1,
area_rects[i].y1,
area_rects[i].x2 - area_rects[i].x1,
area_rects[i].y2 - area_rects[i].y1,
strokeGC->color, rop);
}
} else {
if (rop == SPICE_ROP_COPY) {
spice_pixman_tile_rect(dest,
area_rects[i].x1,
area_rects[i].y1,
area_rects[i].x2 - area_rects[i].x1,
area_rects[i].y2 - area_rects[i].y1,
strokeGC->tile,
strokeGC->tile_offset_x,
strokeGC->tile_offset_y);
} else {
spice_pixman_tile_rect_rop(dest,
area_rects[i].x1,
area_rects[i].y1,
area_rects[i].x2 - area_rects[i].x1,
area_rects[i].y2 - area_rects[i].y1,
strokeGC->tile,
strokeGC->tile_offset_x,
strokeGC->tile_offset_y,
rop);
}
}
}
pixman_region32_fini(&area);
}
typedef struct {
SpicePoint *points;
int num_points;
int size;
} StrokeLines;
static void stroke_lines_init(StrokeLines *lines)
{
lines->points = (SpicePoint *)malloc(10*sizeof(SpicePoint));
lines->size = 10;
lines->num_points = 0;
}
static void stroke_lines_free(StrokeLines *lines)
{
free(lines->points);
}
static void stroke_lines_append(StrokeLines *lines,
int x, int y)
{
if (lines->num_points == lines->size) {
lines->size *= 2;
lines->points = (SpicePoint *)realloc(lines->points,
lines->size * sizeof(SpicePoint));
}
lines->points[lines->num_points].x = x;
lines->points[lines->num_points].y = y;
lines->num_points++;
}
static void stroke_lines_append_fix(StrokeLines *lines,
SpicePointFix *point)
{
stroke_lines_append(lines,
fix_to_int(point->x),
fix_to_int(point->y));
}
static inline int64_t dot(SPICE_FIXED28_4 x1,
SPICE_FIXED28_4 y1,
SPICE_FIXED28_4 x2,
SPICE_FIXED28_4 y2)
{
return (((int64_t)x1) *((int64_t)x2) +
((int64_t)y1) *((int64_t)y2)) >> 4;
}
static inline int64_t dot2(SPICE_FIXED28_4 x,
SPICE_FIXED28_4 y)
{
return (((int64_t)x) *((int64_t)x) +
((int64_t)y) *((int64_t)y)) >> 4;
}
static void subdivide_bezier(StrokeLines *lines,
SpicePointFix point0,
SpicePointFix point1,
SpicePointFix point2,
SpicePointFix point3)
{
int64_t A2, B2, C2, AB, CB, h1, h2;
A2 = dot2(point1.x - point0.x,
point1.y - point0.y);
B2 = dot2(point3.x - point0.x,
point3.y - point0.y);
C2 = dot2(point2.x - point3.x,
point2.y - point3.y);
AB = dot(point1.x - point0.x,
point1.y - point0.y,
point3.x - point0.x,
point3.y - point0.y);
CB = dot(point2.x - point3.x,
point2.y - point3.y,
point0.x - point3.x,
point0.y - point3.y);
h1 = (A2*B2 - AB*AB) >> 3;
h2 = (C2*B2 - CB*CB) >> 3;
if (h1 < B2 && h2 < B2) {
/* deviation squared less than half a pixel, use straight line */
stroke_lines_append_fix(lines, &point3);
} else {
SpicePointFix point01, point23, point12, point012, point123, point0123;
point01.x = (point0.x + point1.x) / 2;
point01.y = (point0.y + point1.y) / 2;
point12.x = (point1.x + point2.x) / 2;
point12.y = (point1.y + point2.y) / 2;
point23.x = (point2.x + point3.x) / 2;
point23.y = (point2.y + point3.y) / 2;
point012.x = (point01.x + point12.x) / 2;
point012.y = (point01.y + point12.y) / 2;
point123.x = (point12.x + point23.x) / 2;
point123.y = (point12.y + point23.y) / 2;
point0123.x = (point012.x + point123.x) / 2;
point0123.y = (point012.y + point123.y) / 2;
subdivide_bezier(lines, point0, point01, point012, point0123);
subdivide_bezier(lines, point0123, point123, point23, point3);
}
}
static void stroke_lines_append_bezier(StrokeLines *lines,
SpicePointFix *point1,
SpicePointFix *point2,
SpicePointFix *point3)
{
SpicePointFix point0;
point0.x = int_to_fix(lines->points[lines->num_points-1].x);
point0.y = int_to_fix(lines->points[lines->num_points-1].y);
subdivide_bezier(lines, point0, *point1, *point2, *point3);
}
static void stroke_lines_draw(StrokeLines *lines,
lineGC *gc,
int dashed)
{
if (lines->num_points != 0) {
if (dashed) {
spice_canvas_zero_dash_line(gc, CoordModeOrigin,
lines->num_points, lines->points);
} else {
spice_canvas_zero_line(gc, CoordModeOrigin,
lines->num_points, lines->points);
}
lines->num_points = 0;
}
}
static void canvas_draw_stroke(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceStroke *stroke)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
StrokeGC gc = { {0} };
lineGCOps ops = {
stroke_fill_spans,
stroke_fill_rects
};
uint32_t *data_size;
uint32_t more;
SpicePathSeg *seg;
StrokeLines lines;
int i;
int dashed;
pixman_region32_init_rect(&gc.dest_region,
bbox->left, bbox->top,
bbox->right - bbox->left,
bbox->bottom - bbox->top);
canvas_clip_pixman(canvas, &gc.dest_region, clip);
if (pixman_region32_n_rects(&gc.dest_region) == 0) {
touch_brush(canvas, &stroke->brush);
pixman_region32_fini(&gc.dest_region);
return;
}
gc.fore_rop = ropd_descriptor_to_rop(stroke->fore_mode,
ROP_INPUT_BRUSH,
ROP_INPUT_DEST);
gc.back_rop = ropd_descriptor_to_rop(stroke->back_mode,
ROP_INPUT_BRUSH,
ROP_INPUT_DEST);
gc.dest = canvas->image;
gc.base.width = pixman_image_get_width(gc.dest);
gc.base.height = pixman_image_get_height(gc.dest);
gc.base.alu = gc.fore_rop;
gc.base.lineWidth = 0;
/* dash */
gc.base.dashOffset = 0;
gc.base.dash = NULL;
gc.base.numInDashList = 0;
gc.base.lineStyle = LineSolid;
/* win32 cosmetic lines are endpoint-exclusive, so use CapNotLast */
gc.base.capStyle = CapNotLast;
gc.base.joinStyle = JoinMiter;
gc.base.ops = &ops;
dashed = 0;
if (stroke->attr.flags & SPICE_LINE_FLAGS_STYLED) {
SPICE_FIXED28_4 *style = (SPICE_FIXED28_4*)SPICE_GET_ADDRESS(stroke->attr.style);
int nseg;
dashed = 1;
nseg = stroke->attr.style_nseg;
/* To truly handle back_mode we should use LineDoubleDash here
and treat !foreground as back_rop using the same brush.
However, using the same brush for that seems wrong.
The old cairo backend was stroking the non-dashed line with
rop_mode before enabling dashes for the foreground which is
not right either. The gl an gdi backend don't use back_mode
at all */
gc.base.lineStyle = LineOnOffDash;
gc.base.dash = (unsigned char *)malloc(nseg);
gc.base.numInDashList = nseg;
access_test(&canvas->base, style, nseg * sizeof(*style));
if (stroke->attr.flags & SPICE_LINE_FLAGS_START_WITH_GAP) {
gc.base.dash[stroke->attr.style_nseg - 1] = fix_to_int(style[0]);
for (i = 0; i < stroke->attr.style_nseg - 1; i++) {
gc.base.dash[i] = fix_to_int(style[i+1]);
}
gc.base.dashOffset = gc.base.dash[0];
} else {
for (i = 0; i < stroke->attr.style_nseg; i++) {
gc.base.dash[i] = fix_to_int(style[i]);
}
}
}
switch (stroke->brush.type) {
case SPICE_BRUSH_TYPE_NONE:
gc.solid = TRUE;
gc.color = 0;
break;
case SPICE_BRUSH_TYPE_SOLID:
gc.solid = TRUE;
gc.color = stroke->brush.u.color;
break;
case SPICE_BRUSH_TYPE_PATTERN:
gc.solid = FALSE;
gc.tile = canvas_get_image(&canvas->base,
stroke->brush.u.pattern.pat);
gc.tile_offset_x = stroke->brush.u.pattern.pos.x;
gc.tile_offset_y = stroke->brush.u.pattern.pos.y;
break;
default:
CANVAS_ERROR("invalid brush type");
}
data_size = (uint32_t*)SPICE_GET_ADDRESS(stroke->path);
access_test(&canvas->base, data_size, sizeof(uint32_t));
more = *data_size;
seg = (SpicePathSeg*)(data_size + 1);
stroke_lines_init(&lines);
do {
access_test(&canvas->base, seg, sizeof(SpicePathSeg));
uint32_t flags = seg->flags;
SpicePointFix* point = (SpicePointFix*)seg->data;
SpicePointFix* end_point = point + seg->count;
access_test(&canvas->base, point, (unsigned long)end_point - (unsigned long)point);
ASSERT(point < end_point);
more -= ((unsigned long)end_point - (unsigned long)seg);
seg = (SpicePathSeg*)end_point;
if (flags & SPICE_PATH_BEGIN) {
stroke_lines_draw(&lines, (lineGC *)&gc, dashed);
stroke_lines_append_fix(&lines, point);
point++;
}
if (flags & SPICE_PATH_BEZIER) {
ASSERT((point - end_point) % 3 == 0);
for (; point + 2 < end_point; point += 3) {
stroke_lines_append_bezier(&lines,
&point[0],
&point[1],
&point[2]);
}
} else
{
for (; point < end_point; point++) {
stroke_lines_append_fix(&lines, point);
}
}
if (flags & SPICE_PATH_END) {
if (flags & SPICE_PATH_CLOSE) {
stroke_lines_append(&lines,
lines.points[0].x, lines.points[0].y);
}
stroke_lines_draw(&lines, (lineGC *)&gc, dashed);
}
} while (more);
stroke_lines_draw(&lines, (lineGC *)&gc, dashed);
if (gc.base.dash) {
free(gc.base.dash);
}
stroke_lines_free(&lines);
if (!gc.solid && gc.tile) {
pixman_image_unref(gc.tile);
}
pixman_region32_fini(&gc.dest_region);
}
static void canvas_read_bits(SpiceCanvas *spice_canvas, uint8_t *dest, int dest_stride, const SpiceRect *area)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_image_t* surface;
uint8_t *src;
int src_stride;
uint8_t *dest_end;
ASSERT(canvas && area);
surface = canvas->image;
src_stride = pixman_image_get_stride(surface);
src = (uint8_t *)pixman_image_get_data(surface) +
area->top * src_stride + area->left * sizeof(uint32_t);
dest_end = dest + (area->bottom - area->top) * dest_stride;
for (; dest != dest_end; dest += dest_stride, src += src_stride) {
memcpy(dest, src, dest_stride);
}
}
static void canvas_group_start(SpiceCanvas *spice_canvas, QRegion *region)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_region32_fini(&canvas->canvas_region);
/* Make sure we always clip to canvas size */
pixman_region32_init_rect(&canvas->canvas_region,
0, 0,
pixman_image_get_width (canvas->image),
pixman_image_get_height (canvas->image));
pixman_region32_intersect(&canvas->canvas_region, &canvas->canvas_region, region);
}
static void canvas_group_end(SpiceCanvas *spice_canvas)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
pixman_region32_fini(&canvas->canvas_region);
pixman_region32_init_rect(&canvas->canvas_region,
0, 0,
pixman_image_get_width(canvas->image),
pixman_image_get_height(canvas->image));
}
static void canvas_clear(SpiceCanvas *spice_canvas)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
spice_pixman_fill_rect(canvas->image,
0, 0,
pixman_image_get_width(canvas->image),
pixman_image_get_height(canvas->image),
0);
}
static void canvas_set_access_params(SpiceCanvas *spice_canvas, unsigned long base, unsigned long max)
{
#ifdef CAIRO_CANVAS_ACCESS_TEST
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
__canvas_set_access_params(&canvas->base, base, max);
#endif
}
static void canvas_destroy(SpiceCanvas *spice_canvas)
{
CairoCanvas *canvas = (CairoCanvas *)spice_canvas;
if (!canvas) {
return;
}
pixman_image_unref(canvas->image);
canvas_base_destroy(&canvas->base);
if (canvas->private_data) {
free(canvas->private_data);
}
free(canvas);
}
static int need_init = 1;
static SpiceCanvasOps cairo_canvas_ops;
SpiceCanvas *canvas_create(pixman_image_t *image, int bits
#ifdef CAIRO_CANVAS_CACHE
, SpiceImageCache *bits_cache
, SpicePaletteCache *palette_cache
#elif defined(CAIRO_CANVAS_IMAGE_CACHE)
, SpiceImageCache *bits_cache
#endif
, SpiceGlzDecoder *glz_decoder
#ifndef CAIRO_CANVAS_NO_CHUNKS
, SpiceVirtMapping *virt_mapping
#endif
)
{
CairoCanvas *canvas;
int init_ok;
if (need_init || !(canvas = (CairoCanvas *)malloc(sizeof(CairoCanvas)))) {
return NULL;
}
memset(canvas, 0, sizeof(CairoCanvas));
init_ok = canvas_base_init(&canvas->base, &cairo_canvas_ops, bits
#ifdef CAIRO_CANVAS_CACHE
, bits_cache
, palette_cache
#elif defined(CAIRO_CANVAS_IMAGE_CACHE)
, bits_cache
#endif
, glz_decoder
#ifndef CAIRO_CANVAS_NO_CHUNKS
, virt_mapping
#endif
);
canvas->private_data = NULL;
canvas->private_data_size = 0;
canvas->image = pixman_image_ref(image);
pixman_region32_init_rect(&canvas->canvas_region,
0, 0,
pixman_image_get_width (canvas->image),
pixman_image_get_height (canvas->image));
return (SpiceCanvas *)canvas;
}
void cairo_canvas_init() //unsafe global function
{
if (!need_init) {
return;
}
need_init = 0;
canvas_base_init_ops(&cairo_canvas_ops);
cairo_canvas_ops.draw_fill = canvas_draw_fill;
cairo_canvas_ops.draw_copy = canvas_draw_copy;
cairo_canvas_ops.draw_opaque = canvas_draw_opaque;
cairo_canvas_ops.copy_bits = canvas_copy_bits;
cairo_canvas_ops.draw_text = canvas_draw_text;
cairo_canvas_ops.draw_stroke = canvas_draw_stroke;
cairo_canvas_ops.draw_rop3 = canvas_draw_rop3;
cairo_canvas_ops.draw_blend = canvas_draw_blend;
cairo_canvas_ops.draw_blackness = canvas_draw_blackness;
cairo_canvas_ops.draw_whiteness = canvas_draw_whiteness;
cairo_canvas_ops.draw_invers = canvas_draw_invers;
cairo_canvas_ops.draw_transparent = canvas_draw_transparent;
cairo_canvas_ops.draw_alpha_blend = canvas_draw_alpha_blend;
cairo_canvas_ops.put_image = canvas_put_image;
cairo_canvas_ops.clear = canvas_clear;
cairo_canvas_ops.read_bits = canvas_read_bits;
cairo_canvas_ops.group_start = canvas_group_start;
cairo_canvas_ops.group_end = canvas_group_end;
cairo_canvas_ops.set_access_params = canvas_set_access_params;
cairo_canvas_ops.destroy = canvas_destroy;
rop3_init();
}