mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice-common
synced 2026-01-08 21:14:20 +00:00
This allows us to use the simpler dependency of pixman outside of the cairo backend, and it later lets us move the cairo backend to using pixman only.
1647 lines
47 KiB
C
1647 lines
47 KiB
C
/* -*- 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
#include <cairo.h>
|
|
#include <stdlib.h>
|
|
#include <setjmp.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
|
|
#include <spice/draw.h>
|
|
#include "quic.h"
|
|
#include "lz.h"
|
|
#include "canvas_base.h"
|
|
#include "pixman_utils.h"
|
|
#include "canvas_utils.h"
|
|
#include "rect.h"
|
|
|
|
#include "mutex.h"
|
|
|
|
#ifndef CANVAS_ERROR
|
|
#define CANVAS_ERROR(format, ...) { \
|
|
printf("%s: " format "\n", __FUNCTION__, ## __VA_ARGS__); \
|
|
abort(); \
|
|
}
|
|
#endif
|
|
|
|
#ifdef CAIRO_CANVAS_ACCESS_TEST
|
|
#define access_test(cancas, ptr, size) \
|
|
if ((unsigned long)(ptr) < (cancas)->base || \
|
|
(unsigned long)(ptr) + (size) > (cancas)->max) { \
|
|
CANVAS_ERROR("access violation 0x%lx %lu", (unsigned long)ptr, (unsigned long)(size)); \
|
|
}
|
|
#else
|
|
#define access_test(cancas, base, size)
|
|
#endif
|
|
|
|
#ifndef ASSERT
|
|
#define ASSERT(x) if (!(x)) { \
|
|
printf("%s: ASSERT %s failed\n", __FUNCTION__, #x); \
|
|
abort(); \
|
|
}
|
|
#endif
|
|
|
|
#ifndef WARN
|
|
#define WARN(x) printf("warning: %s\n", x)
|
|
#endif
|
|
|
|
#ifndef DBG
|
|
#define DBG(level, format, ...) printf("%s: debug: " format "\n", __FUNCTION__, ## __VA_ARGS__);
|
|
#endif
|
|
|
|
#ifndef ALIGN
|
|
#define ALIGN(a, b) (((a) + ((b) - 1)) & ~((b) - 1))
|
|
#endif
|
|
|
|
#ifndef MIN
|
|
#define MIN(x, y) (((x) <= (y)) ? (x) : (y))
|
|
#endif
|
|
|
|
#ifndef MAX
|
|
#define MAX(x, y) (((x) >= (y)) ? (x) : (y))
|
|
#endif
|
|
|
|
#define ROUND(_x) floor((_x) + 0.5)
|
|
|
|
#ifdef WIN32
|
|
typedef struct __declspec (align(1)) LZImage {
|
|
#else
|
|
typedef struct __attribute__ ((__packed__)) LZImage {
|
|
#endif
|
|
SpiceImageDescriptor descriptor;
|
|
union {
|
|
SpiceLZRGBData lz_rgb;
|
|
SpiceLZPLTData lz_plt;
|
|
};
|
|
} LZImage;
|
|
|
|
static const cairo_user_data_key_t pixman_data_type = {0};
|
|
|
|
static inline double fix_to_double(SPICE_FIXED28_4 fixed)
|
|
{
|
|
return (double)(fixed & 0x0f) / 0x0f + (fixed >> 4);
|
|
}
|
|
|
|
static inline uint32_t canvas_16bpp_to_32bpp(uint32_t color)
|
|
{
|
|
uint32_t ret;
|
|
|
|
ret = ((color & 0x001f) << 3) | ((color & 0x001c) >> 2);
|
|
ret |= ((color & 0x03e0) << 6) | ((color & 0x0380) << 1);
|
|
ret |= ((color & 0x7c00) << 9) | ((color & 0x7000) << 4);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline int test_bit(void* addr, int bit)
|
|
{
|
|
return !!(((uint32_t*)addr)[bit >> 5] & (1 << (bit & 0x1f)));
|
|
}
|
|
|
|
static inline int test_bit_be(void* addr, int bit)
|
|
{
|
|
return !!(((uint8_t*)addr)[bit >> 3] & (0x80 >> (bit & 0x07)));
|
|
}
|
|
|
|
#ifdef WIN32
|
|
static HDC create_compatible_dc()
|
|
{
|
|
HDC dc = CreateCompatibleDC(NULL);
|
|
if (!dc) {
|
|
CANVAS_ERROR("create compatible DC failed");
|
|
}
|
|
return dc;
|
|
}
|
|
|
|
#endif
|
|
|
|
typedef struct LzData {
|
|
LzUsrContext usr;
|
|
LzContext *lz;
|
|
LzDecodeUsrData decode_data;
|
|
jmp_buf jmp_env;
|
|
char message_buf[512];
|
|
} LzData;
|
|
|
|
typedef struct GlzData {
|
|
void *decoder_opaque;
|
|
glz_decode_fn_t decode;
|
|
LzDecodeUsrData decode_data;
|
|
} GlzData;
|
|
|
|
typedef struct QuicData {
|
|
QuicUsrContext usr;
|
|
QuicContext *quic;
|
|
jmp_buf jmp_env;
|
|
#ifndef CAIRO_CANVAS_NO_CHUNKS
|
|
SPICE_ADDRESS next;
|
|
get_virt_fn_t get_virt;
|
|
void *get_virt_opaque;
|
|
validate_virt_fn_t validate_virt;
|
|
void *validate_virt_opaque;
|
|
#endif
|
|
char message_buf[512];
|
|
} QuicData;
|
|
|
|
typedef struct CanvasBase {
|
|
uint32_t color_shift;
|
|
uint32_t color_mask;
|
|
QuicData quic_data;
|
|
#ifdef CAIRO_CANVAS_ACCESS_TEST
|
|
unsigned long base;
|
|
unsigned long max;
|
|
#endif
|
|
|
|
#if defined(CAIRO_CANVAS_CACHE) || defined(CAIRO_CANVAS_IMAGE_CACHE)
|
|
SpiceImageCache *bits_cache;
|
|
#endif
|
|
#ifdef CAIRO_CANVAS_CACHE
|
|
SpicePaletteCache *palette_cache;
|
|
#endif
|
|
#ifdef WIN32
|
|
HDC dc;
|
|
#endif
|
|
|
|
LzData lz_data;
|
|
GlzData glz_data;
|
|
} CanvasBase;
|
|
|
|
|
|
#ifndef CAIRO_CANVAS_NO_CHUNKS
|
|
|
|
#ifdef __GNUC__
|
|
#define ATTR_PACKED __attribute__ ((__packed__))
|
|
#else
|
|
#pragma pack(push)
|
|
#pragma pack(1)
|
|
#define ATTR_PACKED
|
|
#endif
|
|
|
|
typedef struct ATTR_PACKED DataChunk {
|
|
uint32_t size;
|
|
SPICE_ADDRESS prev;
|
|
SPICE_ADDRESS next;
|
|
uint8_t data[0];
|
|
} DataChunk;
|
|
|
|
#undef ATTR_PACKED
|
|
|
|
#ifndef __GNUC__
|
|
#pragma pack(pop)
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#ifdef CANVAS_USE_PIXMAN
|
|
|
|
static pixman_format_code_t
|
|
pixman_format_from_cairo_format (cairo_format_t format)
|
|
{
|
|
switch (format) {
|
|
case CAIRO_FORMAT_A1:
|
|
return PIXMAN_a1;
|
|
case CAIRO_FORMAT_A8:
|
|
return PIXMAN_a8;
|
|
case CAIRO_FORMAT_RGB24:
|
|
return PIXMAN_x8r8g8b8;
|
|
case CAIRO_FORMAT_ARGB32:
|
|
default:
|
|
return PIXMAN_a8r8g8b8;
|
|
}
|
|
}
|
|
|
|
static pixman_image_t *
|
|
pixman_image_from_surface (cairo_surface_t *surface)
|
|
{
|
|
pixman_image_t *image;
|
|
cairo_format_t format;
|
|
|
|
format = cairo_image_surface_get_format (surface);
|
|
|
|
image = (pixman_image_t *)cairo_surface_get_user_data(surface, &pixman_data_type);
|
|
|
|
if (image)
|
|
return pixman_image_ref (image);
|
|
|
|
image = pixman_image_create_bits (pixman_format_from_cairo_format (format),
|
|
cairo_image_surface_get_width (surface),
|
|
cairo_image_surface_get_height (surface),
|
|
(uint32_t *)cairo_image_surface_get_data (surface),
|
|
cairo_image_surface_get_stride (surface));
|
|
|
|
return image;
|
|
}
|
|
|
|
static cairo_format_t
|
|
cairo_format_from_depth (int depth)
|
|
{
|
|
switch (depth) {
|
|
case 1:
|
|
return CAIRO_FORMAT_A1;
|
|
case 8:
|
|
return CAIRO_FORMAT_A8;
|
|
case 24:
|
|
return CAIRO_FORMAT_RGB24;
|
|
case 32:
|
|
default:
|
|
return CAIRO_FORMAT_ARGB32;
|
|
}
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
surface_from_pixman_image (pixman_image_t *image)
|
|
{
|
|
cairo_surface_t *surface;
|
|
int depth;
|
|
|
|
depth = pixman_image_get_depth (image);
|
|
|
|
surface = cairo_image_surface_create_for_data ((uint8_t *)pixman_image_get_data (image),
|
|
cairo_format_from_depth (depth),
|
|
pixman_image_get_width (image),
|
|
pixman_image_get_height (image),
|
|
pixman_image_get_stride (image));
|
|
|
|
|
|
if (cairo_surface_set_user_data (surface, &pixman_data_type,
|
|
image, (cairo_destroy_func_t) pixman_image_unref) == CAIRO_STATUS_SUCCESS)
|
|
pixman_image_ref (image);
|
|
|
|
return surface;
|
|
}
|
|
|
|
#endif
|
|
|
|
static inline void canvas_localize_palette(CanvasBase *canvas, SpicePalette *palette)
|
|
{
|
|
if (canvas->color_shift == 5) {
|
|
uint32_t *now = palette->ents;
|
|
uint32_t *end = now + palette->num_ents;
|
|
for (; now < end; now++) {
|
|
*now = canvas_16bpp_to_32bpp(*now);
|
|
}
|
|
}
|
|
}
|
|
|
|
//#define DEBUG_DUMP_COMPRESS
|
|
#ifdef DEBUG_DUMP_COMPRESS
|
|
static void dump_surface(pixman_image_t *surface, int cache);
|
|
#endif
|
|
static pixman_image_t *canvas_get_quic(CanvasBase *canvas, SpiceQUICImage *image, int invers)
|
|
{
|
|
pixman_image_t *surface = NULL;
|
|
QuicData *quic_data = &canvas->quic_data;
|
|
QuicImageType type;
|
|
uint8_t *dest;
|
|
int stride;
|
|
int width;
|
|
int height;
|
|
int alpha;
|
|
#ifndef CAIRO_CANVAS_NO_CHUNKS
|
|
DataChunk **tmp;
|
|
DataChunk *chunk;
|
|
#endif
|
|
|
|
if (setjmp(quic_data->jmp_env)) {
|
|
pixman_image_unref(surface);
|
|
CANVAS_ERROR("quic error, %s", quic_data->message_buf);
|
|
}
|
|
|
|
#ifdef CAIRO_CANVAS_NO_CHUNKS
|
|
if (quic_decode_begin(quic_data->quic, (uint32_t *)image->quic.data,
|
|
image->quic.data_size >> 2, &type, &width, &height) == QUIC_ERROR) {
|
|
CANVAS_ERROR("quic decode begin failed");
|
|
}
|
|
#else
|
|
tmp = (DataChunk **)image->quic.data;
|
|
chunk = *tmp;
|
|
quic_data->next = chunk->next;
|
|
if (quic_decode_begin(quic_data->quic, (uint32_t *)chunk->data, chunk->size >> 2,
|
|
&type, &width, &height) == QUIC_ERROR) {
|
|
CANVAS_ERROR("quic decode begin failed");
|
|
}
|
|
#endif
|
|
|
|
switch (type) {
|
|
case QUIC_IMAGE_TYPE_RGBA:
|
|
alpha = 1;
|
|
break;
|
|
case QUIC_IMAGE_TYPE_RGB32:
|
|
case QUIC_IMAGE_TYPE_RGB24:
|
|
case QUIC_IMAGE_TYPE_RGB16:
|
|
alpha = 0;
|
|
break;
|
|
case QUIC_IMAGE_TYPE_INVALID:
|
|
case QUIC_IMAGE_TYPE_GRAY:
|
|
default:
|
|
CANVAS_ERROR("unexpected image type");
|
|
}
|
|
|
|
ASSERT((uint32_t)width == image->descriptor.width);
|
|
ASSERT((uint32_t)height == image->descriptor.height);
|
|
|
|
surface = surface_create(
|
|
#ifdef WIN32
|
|
canvas->dc,
|
|
#endif
|
|
alpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
|
|
width, height, FALSE);
|
|
|
|
if (surface == NULL) {
|
|
CANVAS_ERROR("create surface failed");
|
|
}
|
|
|
|
dest = (uint8_t *)pixman_image_get_data(surface);
|
|
stride = pixman_image_get_stride(surface);
|
|
if (quic_decode(quic_data->quic, alpha ? QUIC_IMAGE_TYPE_RGBA : QUIC_IMAGE_TYPE_RGB32,
|
|
dest, stride) == QUIC_ERROR) {
|
|
pixman_image_unref(surface);
|
|
CANVAS_ERROR("quic decode failed");
|
|
}
|
|
|
|
if (invers) {
|
|
uint8_t *end = dest + height * stride;
|
|
for (; dest != end; dest += stride) {
|
|
uint32_t *pix;
|
|
uint32_t *end_pix;
|
|
|
|
pix = (uint32_t *)dest;
|
|
end_pix = pix + width;
|
|
for (; pix < end_pix; pix++) {
|
|
*pix ^= 0x00ffffff;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_DUMP_COMPRESS
|
|
dump_surface(surface, 0);
|
|
#endif
|
|
return surface;
|
|
}
|
|
|
|
static inline void canvas_copy_32bpp(uint8_t* dest, int dest_stride, uint8_t* src, int src_stride,
|
|
int width, uint8_t* end)
|
|
{
|
|
for (; src != end; src += src_stride, dest += dest_stride) {
|
|
memcpy(dest, src, width << 2);
|
|
}
|
|
}
|
|
|
|
static inline void canvas_copy_24bpp(uint8_t* dest, int dest_stride, uint8_t* src, int src_stride,
|
|
int width, uint8_t* end)
|
|
{
|
|
for (; src != end; src += src_stride, dest += dest_stride) {
|
|
uint8_t* src_line = src;
|
|
uint8_t* src_line_end = src_line + width * 3;
|
|
uint8_t* dest_line = dest;
|
|
|
|
for (; src_line < src_line_end; ++dest_line) {
|
|
*(dest_line++) = *(src_line++);
|
|
*(dest_line++) = *(src_line++);
|
|
*(dest_line++) = *(src_line++);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void canvas_copy_16bpp(uint8_t* dest, int dest_stride, uint8_t* src, int src_stride,
|
|
int width, uint8_t* end)
|
|
{
|
|
for (; src != end; src += src_stride, dest += dest_stride) {
|
|
uint16_t* src_line = (uint16_t*)src;
|
|
uint16_t* src_line_end = src_line + width;
|
|
uint32_t* dest_line = (uint32_t*)dest;
|
|
|
|
for (; src_line < src_line_end; ++dest_line, src_line++) {
|
|
*dest_line = canvas_16bpp_to_32bpp(*src_line);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void canvas_copy_8bpp(uint8_t *dest, int dest_stride, uint8_t *src, int src_stride,
|
|
int width, uint8_t *end, SpicePalette *palette)
|
|
{
|
|
if (!palette) {
|
|
CANVAS_ERROR("no palette");
|
|
}
|
|
|
|
for (; src != end; src += src_stride, dest += dest_stride) {
|
|
uint32_t *dest_line = (uint32_t*)dest;
|
|
uint8_t *src_line = src;
|
|
uint8_t *src_line_end = src_line + width;
|
|
|
|
while (src_line < src_line_end) {
|
|
ASSERT(*src_line < palette->num_ents);
|
|
*(dest_line++) = palette->ents[*(src_line++)];
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void canvas_copy_4bpp_be(uint8_t* dest, int dest_stride, uint8_t* src, int src_stride,
|
|
int width, uint8_t* end, SpicePalette *palette)
|
|
{
|
|
if (!palette) {
|
|
CANVAS_ERROR("no palette");
|
|
}
|
|
|
|
for (; src != end; src += src_stride, dest += dest_stride) {
|
|
uint32_t *dest_line = (uint32_t *)dest;
|
|
uint8_t *now = src;
|
|
int i;
|
|
|
|
for (i = 0; i < (width >> 1); i++) {
|
|
ASSERT((*now & 0x0f) < palette->num_ents);
|
|
ASSERT(((*now >> 4) & 0x0f) < palette->num_ents);
|
|
*(dest_line++) = palette->ents[(*now >> 4) & 0x0f];
|
|
*(dest_line++) = palette->ents[*(now++) & 0x0f];
|
|
}
|
|
if (width & 1) {
|
|
*(dest_line) = palette->ents[(*src >> 4) & 0x0f];
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void canvas_copy_1bpp_be(uint8_t* dest, int dest_stride, uint8_t* src, int src_stride,
|
|
int width, uint8_t* end, SpicePalette *palette)
|
|
{
|
|
uint32_t fore_color;
|
|
uint32_t back_color;
|
|
|
|
if (!palette) {
|
|
CANVAS_ERROR("no palette");
|
|
}
|
|
|
|
fore_color = palette->ents[1];
|
|
back_color = palette->ents[0];
|
|
|
|
for (; src != end; src += src_stride, dest += dest_stride) {
|
|
uint32_t* dest_line = (uint32_t*)dest;
|
|
int i;
|
|
|
|
for (i = 0; i < width; i++) {
|
|
if (test_bit_be(src, i)) {
|
|
*(dest_line++) = fore_color;
|
|
} else {
|
|
*(dest_line++) = back_color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static pixman_image_t *canvas_bitmap_to_surface(CanvasBase *canvas, SpiceBitmap* bitmap,
|
|
SpicePalette *palette)
|
|
{
|
|
uint8_t* src = (uint8_t *)SPICE_GET_ADDRESS(bitmap->data);
|
|
int src_stride;
|
|
uint8_t* end;
|
|
uint8_t* dest;
|
|
int dest_stride;
|
|
pixman_image_t* image;
|
|
|
|
src_stride = bitmap->stride;
|
|
end = src + (bitmap->y * src_stride);
|
|
access_test(canvas, src, bitmap->y * src_stride);
|
|
|
|
image = surface_create(
|
|
#ifdef WIN32
|
|
canvas->dc,
|
|
#endif
|
|
(bitmap->format == SPICE_BITMAP_FMT_RGBA) ? PIXMAN_a8r8g8b8 :
|
|
PIXMAN_x8r8g8b8,
|
|
bitmap->x, bitmap->y, FALSE);
|
|
if (image == NULL) {
|
|
CANVAS_ERROR("create surface failed");
|
|
}
|
|
dest = (uint8_t *)pixman_image_get_data(image);
|
|
dest_stride = pixman_image_get_stride(image);
|
|
if (!(bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) {
|
|
ASSERT(bitmap->y > 0);
|
|
dest += dest_stride * ((int)bitmap->y - 1);
|
|
dest_stride = -dest_stride;
|
|
}
|
|
|
|
switch (bitmap->format) {
|
|
case SPICE_BITMAP_FMT_32BIT:
|
|
case SPICE_BITMAP_FMT_RGBA:
|
|
canvas_copy_32bpp(dest, dest_stride, src, src_stride, bitmap->x, end);
|
|
break;
|
|
case SPICE_BITMAP_FMT_24BIT:
|
|
canvas_copy_24bpp(dest, dest_stride, src, src_stride, bitmap->x, end);
|
|
break;
|
|
case SPICE_BITMAP_FMT_16BIT:
|
|
canvas_copy_16bpp(dest, dest_stride, src, src_stride, bitmap->x, end);
|
|
break;
|
|
case SPICE_BITMAP_FMT_8BIT:
|
|
canvas_copy_8bpp(dest, dest_stride, src, src_stride, bitmap->x, end, palette);
|
|
break;
|
|
case SPICE_BITMAP_FMT_4BIT_BE:
|
|
canvas_copy_4bpp_be(dest, dest_stride, src, src_stride, bitmap->x, end, palette);
|
|
break;
|
|
case SPICE_BITMAP_FMT_1BIT_BE:
|
|
canvas_copy_1bpp_be(dest, dest_stride, src, src_stride, bitmap->x, end, palette);
|
|
break;
|
|
}
|
|
return image;
|
|
}
|
|
|
|
#ifdef CAIRO_CANVAS_CACHE
|
|
|
|
static inline SpicePalette *canvas_get_palett(CanvasBase *canvas, SPICE_ADDRESS base_palette, uint8_t flags)
|
|
{
|
|
SpicePalette *palette;
|
|
if (!base_palette) {
|
|
return NULL;
|
|
}
|
|
|
|
if (flags & SPICE_BITMAP_FLAGS_PAL_FROM_CACHE) {
|
|
palette = canvas->palette_cache->ops->get(canvas->palette_cache, base_palette);
|
|
} else if (flags & SPICE_BITMAP_FLAGS_PAL_CACHE_ME) {
|
|
palette = (SpicePalette *)SPICE_GET_ADDRESS(base_palette);
|
|
access_test(canvas, palette, sizeof(SpicePalette));
|
|
access_test(canvas, palette, sizeof(SpicePalette) + palette->num_ents * sizeof(uint32_t));
|
|
canvas_localize_palette(canvas, palette);
|
|
canvas->palette_cache->ops->put(canvas->palette_cache, palette);
|
|
} else {
|
|
palette = (SpicePalette *)SPICE_GET_ADDRESS(base_palette);
|
|
canvas_localize_palette(canvas, palette);
|
|
}
|
|
return palette;
|
|
}
|
|
|
|
static pixman_image_t *canvas_get_lz(CanvasBase *canvas, LZImage *image, int invers)
|
|
{
|
|
LzData *lz_data = &canvas->lz_data;
|
|
uint8_t *comp_buf = NULL;
|
|
int comp_size;
|
|
uint8_t *decomp_buf = NULL;
|
|
uint8_t *src;
|
|
LzImageType type;
|
|
SpicePalette *palette;
|
|
int alpha;
|
|
int n_comp_pixels;
|
|
int width;
|
|
int height;
|
|
int top_down;
|
|
int stride;
|
|
|
|
if (setjmp(lz_data->jmp_env)) {
|
|
if (decomp_buf) {
|
|
free(decomp_buf);
|
|
}
|
|
CANVAS_ERROR("lz error, %s", lz_data->message_buf);
|
|
}
|
|
|
|
if (image->descriptor.type == SPICE_IMAGE_TYPE_LZ_RGB) {
|
|
comp_buf = image->lz_rgb.data;
|
|
comp_size = image->lz_rgb.data_size;
|
|
palette = NULL;
|
|
} else if (image->descriptor.type == SPICE_IMAGE_TYPE_LZ_PLT) {
|
|
comp_buf = image->lz_plt.data;
|
|
comp_size = image->lz_plt.data_size;
|
|
palette = canvas_get_palett(canvas, image->lz_plt.palette, image->lz_plt.flags);
|
|
} else {
|
|
CANVAS_ERROR("unexpected image type");
|
|
}
|
|
|
|
lz_decode_begin(lz_data->lz, comp_buf, comp_size, &type,
|
|
&width, &height, &n_comp_pixels, &top_down, palette);
|
|
|
|
switch (type) {
|
|
case LZ_IMAGE_TYPE_RGBA:
|
|
alpha = 1;
|
|
break;
|
|
case LZ_IMAGE_TYPE_RGB32:
|
|
case LZ_IMAGE_TYPE_RGB24:
|
|
case LZ_IMAGE_TYPE_RGB16:
|
|
case LZ_IMAGE_TYPE_PLT1_LE:
|
|
case LZ_IMAGE_TYPE_PLT1_BE:
|
|
case LZ_IMAGE_TYPE_PLT4_LE:
|
|
case LZ_IMAGE_TYPE_PLT4_BE:
|
|
case LZ_IMAGE_TYPE_PLT8:
|
|
alpha = 0;
|
|
break;
|
|
default:
|
|
CANVAS_ERROR("unexpected LZ image type");
|
|
}
|
|
|
|
ASSERT(width == image->descriptor.width);
|
|
ASSERT(height == image->descriptor.height);
|
|
|
|
ASSERT((image->descriptor.type == SPICE_IMAGE_TYPE_LZ_PLT) || (n_comp_pixels == width * height));
|
|
#ifdef WIN32
|
|
lz_data->decode_data.dc = canvas->dc;
|
|
#endif
|
|
|
|
|
|
alloc_lz_image_surface(&lz_data->decode_data, alpha ? LZ_IMAGE_TYPE_RGBA : LZ_IMAGE_TYPE_RGB32,
|
|
width, height, n_comp_pixels, top_down);
|
|
|
|
src = (uint8_t *)pixman_image_get_data(lz_data->decode_data.out_surface);
|
|
|
|
stride = (n_comp_pixels / height) * 4;
|
|
if (!top_down) {
|
|
stride = -stride;
|
|
decomp_buf = src + stride * (height - 1);
|
|
} else {
|
|
decomp_buf = src;
|
|
}
|
|
|
|
lz_decode(lz_data->lz, alpha ? LZ_IMAGE_TYPE_RGBA : LZ_IMAGE_TYPE_RGB32, decomp_buf);
|
|
|
|
if (invers) {
|
|
uint8_t *line = src;
|
|
uint8_t *end = src + height * stride;
|
|
for (; line != end; line += stride) {
|
|
uint32_t *pix;
|
|
uint32_t *end_pix;
|
|
|
|
pix = (uint32_t *)line;
|
|
end_pix = pix + width;
|
|
for (; pix < end_pix; pix++) {
|
|
*pix ^= 0x00ffffff;
|
|
}
|
|
}
|
|
}
|
|
|
|
return lz_data->decode_data.out_surface;
|
|
}
|
|
|
|
// don't handle plts since bitmaps with plt can be decoded globaly to RGB32 (because
|
|
// same byte sequence can be transformed to different RGB pixels by different plts)
|
|
static pixman_image_t *canvas_get_glz(CanvasBase *canvas, LZImage *image)
|
|
{
|
|
ASSERT(image->descriptor.type == SPICE_IMAGE_TYPE_GLZ_RGB);
|
|
#ifdef WIN32
|
|
canvas->glz_data.decode_data.dc = canvas->dc;
|
|
#endif
|
|
canvas->glz_data.decode(canvas->glz_data.decoder_opaque, image->lz_rgb.data, NULL,
|
|
&canvas->glz_data.decode_data);
|
|
/* global_decode calls alloc_lz_image, which sets canvas->glz_data.surface */
|
|
return (canvas->glz_data.decode_data.out_surface);
|
|
}
|
|
|
|
//#define DEBUG_DUMP_BITMAP
|
|
|
|
#ifdef DEBUG_DUMP_BITMAP
|
|
static void dump_bitmap(SpiceBitmap *bitmap, SpicePalette *palette)
|
|
{
|
|
uint8_t* data = (uint8_t *)SPICE_GET_ADDRESS(bitmap->data);
|
|
static uint32_t file_id = 0;
|
|
uint32_t i, j;
|
|
char file_str[200];
|
|
uint32_t id = ++file_id;
|
|
|
|
#ifdef WIN32
|
|
sprintf(file_str, "c:\\tmp\\spice_dump\\%u.%ubpp", id, bitmap->format);
|
|
#else
|
|
sprintf(file_str, "/tmp/spice_dump/%u.%ubpp", id, bitmap->format);
|
|
#endif
|
|
FILE *f = fopen(file_str, "wb");
|
|
if (!f) {
|
|
return;
|
|
}
|
|
|
|
fprintf(f, "%d\n", bitmap->format); // 1_LE,1_BE,....
|
|
fprintf(f, "%d %d\n", bitmap->x, bitmap->y); // width and height
|
|
fprintf(f, "%d\n", palette->num_ents); // #plt entries
|
|
for (i = 0; i < palette->num_ents; i++) {
|
|
fwrite(&(palette->ents[i]), 4, 1, f);
|
|
}
|
|
fprintf(f, "\n");
|
|
|
|
for (i = 0; i < bitmap->y; i++, data += bitmap->stride) {
|
|
uint8_t *now = data;
|
|
for (j = 0; j < bitmap->x; j++) {
|
|
fwrite(now, 1, 1, f);
|
|
now++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
static pixman_image_t *canvas_get_bits(CanvasBase *canvas, SpiceBitmap *bitmap)
|
|
{
|
|
pixman_image_t* surface;
|
|
SpicePalette *palette;
|
|
|
|
palette = canvas_get_palett(canvas, bitmap->palette, bitmap->flags);
|
|
#ifdef DEBUG_DUMP_BITMAP
|
|
if (palette) {
|
|
dump_bitmap(bitmap, palette);
|
|
}
|
|
#endif
|
|
|
|
surface = canvas_bitmap_to_surface(canvas, bitmap, palette);
|
|
|
|
if (palette && (bitmap->flags & SPICE_BITMAP_FLAGS_PAL_FROM_CACHE)) {
|
|
canvas->palette_cache->ops->release(canvas->palette_cache, palette);
|
|
}
|
|
|
|
return surface;
|
|
}
|
|
|
|
#else
|
|
|
|
|
|
static pixman_image_t *canvas_get_bits(CanvasBase *canvas, SpiceBitmap *bitmap)
|
|
{
|
|
SpicePalette *palette;
|
|
|
|
if (!bitmap->palette) {
|
|
return canvas_bitmap_to_surface(canvas, bitmap, NULL);
|
|
}
|
|
palette = (SpicePalette *)SPICE_GET_ADDRESS(bitmap->palette);
|
|
if (canvas->color_shift == 5) {
|
|
int size = sizeof(SpicePalette) + (palette->num_ents << 2);
|
|
SpicePalette *local_palette = malloc(size);
|
|
pixman_image_t* surface;
|
|
|
|
memcpy(local_palette, palette, size);
|
|
canvas_localize_palette(canvas, local_palette);
|
|
surface = canvas_bitmap_to_surface(canvas, bitmap, local_palette);
|
|
free(local_palette);
|
|
return surface;
|
|
} else {
|
|
return canvas_bitmap_to_surface(canvas, bitmap, palette);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// caution: defining DEBUG_DUMP_SURFACE will dump both cached & non-cached
|
|
// images to disk. it will reduce performance dramatically & eat
|
|
// disk space rapidly. use it only for debugging.
|
|
//#define DEBUG_DUMP_SURFACE
|
|
|
|
#if defined(DEBUG_DUMP_SURFACE) || defined(DEBUG_DUMP_COMPRESS)
|
|
|
|
static void dump_surface(pixman_image_t *surface, int cache)
|
|
{
|
|
static uint32_t file_id = 0;
|
|
int i, j;
|
|
char file_str[200];
|
|
int depth = pixman_image_get_depth(surface);
|
|
|
|
if (depth != 24 && depth != 32) {
|
|
return;
|
|
}
|
|
|
|
uint8_t *data = (uint8_t *)pixman_image_get_data(surface);
|
|
int width = pixman_image_get_width(surface);
|
|
int height = pixman_image_get_height(surface);
|
|
int stride = pixman_image_surface_get_stride(surface);
|
|
|
|
uint32_t id = ++file_id;
|
|
#ifdef WIN32
|
|
sprintf(file_str, "c:\\tmp\\spice_dump\\%d\\%u.ppm", cache, id);
|
|
#else
|
|
sprintf(file_str, "/tmp/spice_dump/%u.ppm", id);
|
|
#endif
|
|
FILE *f = fopen(file_str, "wb");
|
|
if (!f) {
|
|
return;
|
|
}
|
|
fprintf(f, "P6\n");
|
|
fprintf(f, "%d %d\n", width, height);
|
|
fprintf(f, "#spicec dump\n");
|
|
fprintf(f, "255\n");
|
|
for (i = 0; i < height; i++, data += stride) {
|
|
uint8_t *now = data;
|
|
for (j = 0; j < width; j++) {
|
|
fwrite(&now[2], 1, 1, f);
|
|
fwrite(&now[1], 1, 1, f);
|
|
fwrite(&now[0], 1, 1, f);
|
|
now += 4;
|
|
}
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined(CAIRO_CANVAS_CACHE) || defined(CAIRO_CANVAS_IMAGE_CACHE)
|
|
|
|
//#define DEBUG_LZ
|
|
|
|
static pixman_image_t *canvas_get_image(CanvasBase *canvas, SPICE_ADDRESS addr)
|
|
{
|
|
SpiceImageDescriptor *descriptor = (SpiceImageDescriptor *)SPICE_GET_ADDRESS(addr);
|
|
pixman_image_t *surface;
|
|
access_test(canvas, descriptor, sizeof(SpiceImageDescriptor));
|
|
#ifdef DEBUG_LZ
|
|
LOG_DEBUG("canvas_get_image image type: " << (int)descriptor->type);
|
|
#endif
|
|
|
|
switch (descriptor->type) {
|
|
case SPICE_IMAGE_TYPE_QUIC: {
|
|
SpiceQUICImage *image = (SpiceQUICImage *)descriptor;
|
|
access_test(canvas, descriptor, sizeof(SpiceQUICImage));
|
|
surface = canvas_get_quic(canvas, image, 0);
|
|
break;
|
|
}
|
|
#ifdef CAIRO_CANVAS_NO_CHUNKS
|
|
case SPICE_IMAGE_TYPE_LZ_PLT: {
|
|
access_test(canvas, descriptor, sizeof(SpiceLZPLTImage));
|
|
LZImage *image = (LZImage *)descriptor;
|
|
surface = canvas_get_lz(canvas, image, 0);
|
|
break;
|
|
}
|
|
case SPICE_IMAGE_TYPE_LZ_RGB: {
|
|
access_test(canvas, descriptor, sizeof(SpiceLZRGBImage));
|
|
LZImage *image = (LZImage *)descriptor;
|
|
surface = canvas_get_lz(canvas, image, 0);
|
|
break;
|
|
}
|
|
#endif
|
|
#ifdef USE_GLZ
|
|
case SPICE_IMAGE_TYPE_GLZ_RGB: {
|
|
access_test(canvas, descriptor, sizeof(SpiceLZRGBImage));
|
|
LZImage *image = (LZImage *)descriptor;
|
|
surface = canvas_get_glz(canvas, image);
|
|
break;
|
|
}
|
|
#endif
|
|
case SPICE_IMAGE_TYPE_FROM_CACHE:
|
|
return canvas->bits_cache->ops->get(canvas->bits_cache, descriptor->id);
|
|
case SPICE_IMAGE_TYPE_BITMAP: {
|
|
SpiceBitmapImage *bitmap = (SpiceBitmapImage *)descriptor;
|
|
access_test(canvas, descriptor, sizeof(SpiceBitmapImage));
|
|
surface = canvas_get_bits(canvas, &bitmap->bitmap);
|
|
break;
|
|
}
|
|
default:
|
|
CANVAS_ERROR("invalid image type");
|
|
}
|
|
|
|
if (descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_ME) {
|
|
canvas->bits_cache->ops->put(canvas->bits_cache, descriptor->id, surface);
|
|
#ifdef DEBUG_DUMP_SURFACE
|
|
dump_surface(surface, 1);
|
|
#endif
|
|
} else if (descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE) {
|
|
#ifdef DEBUG_DUMP_SURFACE
|
|
dump_surface(surface, 0);
|
|
#endif
|
|
}
|
|
return surface;
|
|
}
|
|
|
|
#else
|
|
|
|
static pixman_image_t *canvas_get_image(CairoCanvas *canvas, SPICE_ADDRESS addr)
|
|
{
|
|
SpiceImageDescriptor *descriptor = (SpiceImageDescriptor *)SPICE_GET_ADDRESS(addr);
|
|
|
|
access_test(canvas, descriptor, sizeof(SpiceImageDescriptor));
|
|
|
|
switch (descriptor->type) {
|
|
case SPICE_IMAGE_TYPE_QUIC: {
|
|
SpiceQUICImage *image = (SpiceQUICImage *)descriptor;
|
|
access_test(canvas, descriptor, sizeof(SpiceQUICImage));
|
|
return canvas_get_quic(canvas, image, 0);
|
|
}
|
|
case SPICE_IMAGE_TYPE_BITMAP: {
|
|
SpiceBitmapImage *bitmap = (SpiceBitmapImage *)descriptor;
|
|
access_test(canvas, descriptor, sizeof(SpiceBitmapImage));
|
|
return canvas_get_bits(canvas, &bitmap->bitmap);
|
|
}
|
|
default:
|
|
CANVAS_ERROR("invalid image type");
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
static inline uint8_t revers_bits(uint8_t byte)
|
|
{
|
|
uint8_t ret = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
int shift = 7 - i * 2;
|
|
ret |= (byte & (1 << i)) << shift;
|
|
ret |= (byte & (0x80 >> i)) >> shift;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static pixman_image_t *canvas_get_bitmap_mask(CanvasBase *canvas, SpiceBitmap* bitmap, int invers)
|
|
{
|
|
pixman_image_t *surface;
|
|
uint8_t *src_line;
|
|
uint8_t *end_line;
|
|
uint8_t *dest_line;
|
|
int src_stride;
|
|
int line_size;
|
|
int dest_stride;
|
|
|
|
surface = surface_create(
|
|
#ifdef WIN32
|
|
canvas->dc,
|
|
#endif
|
|
PIXMAN_a1, bitmap->x, bitmap->y, TRUE);
|
|
if (surface == NULL) {
|
|
CANVAS_ERROR("create surface failed");
|
|
}
|
|
|
|
src_line = (uint8_t *)SPICE_GET_ADDRESS(bitmap->data);
|
|
src_stride = bitmap->stride;
|
|
end_line = src_line + (bitmap->y * src_stride);
|
|
access_test(canvas, src_line, end_line - src_line);
|
|
line_size = ALIGN(bitmap->x, 8) >> 3;
|
|
|
|
dest_stride = pixman_image_get_stride(surface);
|
|
dest_line = (uint8_t *)pixman_image_get_data(surface);
|
|
#if defined(GL_CANVAS)
|
|
if ((bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) {
|
|
#else
|
|
if (!(bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) {
|
|
#endif
|
|
ASSERT(bitmap->y > 0);
|
|
dest_line += dest_stride * ((int)bitmap->y - 1);
|
|
dest_stride = -dest_stride;
|
|
}
|
|
|
|
if (invers) {
|
|
switch (bitmap->format) {
|
|
#if defined(GL_CANVAS) || defined(GDI_CANVAS)
|
|
case SPICE_BITMAP_FMT_1BIT_BE:
|
|
#else
|
|
case SPICE_BITMAP_FMT_1BIT_LE:
|
|
#endif
|
|
for (; src_line != end_line; src_line += src_stride, dest_line += dest_stride) {
|
|
uint8_t *dest = dest_line;
|
|
uint8_t *now = src_line;
|
|
uint8_t *end = now + line_size;
|
|
while (now < end) {
|
|
*(dest++) = ~*(now++);
|
|
}
|
|
}
|
|
break;
|
|
#if defined(GL_CANVAS) || defined(GDI_CANVAS)
|
|
case SPICE_BITMAP_FMT_1BIT_LE:
|
|
#else
|
|
case SPICE_BITMAP_FMT_1BIT_BE:
|
|
#endif
|
|
for (; src_line != end_line; src_line += src_stride, dest_line += dest_stride) {
|
|
uint8_t *dest = dest_line;
|
|
uint8_t *now = src_line;
|
|
uint8_t *end = now + line_size;
|
|
|
|
while (now < end) {
|
|
*(dest++) = ~revers_bits(*(now++));
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
pixman_image_unref(surface);
|
|
surface = NULL;
|
|
CANVAS_ERROR("invalid bitmap format");
|
|
}
|
|
} else {
|
|
switch (bitmap->format) {
|
|
#if defined(GL_CANVAS) || defined(GDI_CANVAS)
|
|
case SPICE_BITMAP_FMT_1BIT_BE:
|
|
#else
|
|
case SPICE_BITMAP_FMT_1BIT_LE:
|
|
#endif
|
|
for (; src_line != end_line; src_line += src_stride, dest_line += dest_stride) {
|
|
memcpy(dest_line, src_line, line_size);
|
|
}
|
|
break;
|
|
#if defined(GL_CANVAS) || defined(GDI_CANVAS)
|
|
case SPICE_BITMAP_FMT_1BIT_LE:
|
|
#else
|
|
case SPICE_BITMAP_FMT_1BIT_BE:
|
|
#endif
|
|
for (; src_line != end_line; src_line += src_stride, dest_line += dest_stride) {
|
|
uint8_t *dest = dest_line;
|
|
uint8_t *now = src_line;
|
|
uint8_t *end = now + line_size;
|
|
|
|
while (now < end) {
|
|
*(dest++) = revers_bits(*(now++));
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
pixman_image_unref(surface);
|
|
surface = NULL;
|
|
CANVAS_ERROR("invalid bitmap format");
|
|
}
|
|
}
|
|
return surface;
|
|
}
|
|
|
|
static inline pixman_image_t *canvas_A1_invers(pixman_image_t *src_surf)
|
|
{
|
|
int width = pixman_image_get_width(src_surf);
|
|
int height = pixman_image_get_height(src_surf);
|
|
|
|
pixman_image_t * invers = pixman_image_create_bits(PIXMAN_a1, width, height, NULL, 0);
|
|
if (invers != NULL) {
|
|
uint8_t *src_line = (uint8_t *)pixman_image_get_data(src_surf);
|
|
int src_stride = pixman_image_get_stride(src_surf);
|
|
uint8_t *end_line = src_line + (height * src_stride);
|
|
int line_size = ALIGN(width, 8) >> 3;
|
|
uint8_t *dest_line = (uint8_t *)pixman_image_get_data(invers);
|
|
int dest_stride = pixman_image_get_stride(invers);
|
|
|
|
for (; src_line != end_line; src_line += src_stride, dest_line += dest_stride) {
|
|
uint8_t *dest = dest_line;
|
|
uint8_t *now = src_line;
|
|
uint8_t *end = now + line_size;
|
|
while (now < end) {
|
|
*(dest++) = ~*(now++);
|
|
}
|
|
}
|
|
}
|
|
return invers;
|
|
}
|
|
|
|
static pixman_image_t *canvas_surf_to_invers(pixman_image_t *surf)
|
|
{
|
|
int width = pixman_image_get_width(surf);
|
|
int height = pixman_image_get_height(surf);
|
|
uint8_t *dest_line;
|
|
uint8_t *dest_line_end;
|
|
uint8_t *src_line;
|
|
int dest_stride;
|
|
int src_stride;
|
|
|
|
ASSERT(pixman_image_get_depth(surf) == 24);
|
|
pixman_image_t *invers = pixman_image_create_bits (PIXMAN_x8r8g8b8, width, height, NULL, 0);
|
|
|
|
if (invers == NULL) {
|
|
CANVAS_ERROR("create surface failed");
|
|
}
|
|
|
|
dest_line = (uint8_t *)pixman_image_get_data(invers);
|
|
dest_stride = pixman_image_get_stride(invers);
|
|
dest_line_end = dest_line + dest_stride * height;
|
|
src_line = (uint8_t *)pixman_image_get_data(surf);
|
|
src_stride = pixman_image_get_stride(surf);
|
|
|
|
for (; dest_line != dest_line_end; dest_line += dest_stride, src_line += src_stride) {
|
|
uint32_t *src = (uint32_t *)src_line;
|
|
uint32_t *dest = (uint32_t *)dest_line;
|
|
uint32_t *end = dest + width;
|
|
while (dest < end) {
|
|
*(dest++) = ~*(src++) & 0x00ffffff;
|
|
}
|
|
}
|
|
return invers;
|
|
}
|
|
|
|
/*
|
|
* Return the inversed surface and assigns it to the user data of the given surface.
|
|
* The routine also handles the reference count of the inversed surface. It you don't use
|
|
* the returned reference, you must call cairo_surface_destroy.
|
|
* Thread safe with respect to the user data.
|
|
*/
|
|
static inline pixman_image_t* canvas_handle_inverse_user_data(pixman_image_t* surface)
|
|
{
|
|
pixman_image_t *inv_surf = NULL;
|
|
|
|
if (pixman_image_get_depth(surface) == 1) {
|
|
inv_surf = canvas_A1_invers(surface);
|
|
} else {
|
|
inv_surf = canvas_surf_to_invers(surface);
|
|
}
|
|
|
|
if (inv_surf == NULL) {
|
|
CANVAS_ERROR("create surface failed");
|
|
}
|
|
|
|
return inv_surf;
|
|
}
|
|
|
|
static pixman_image_t *canvas_get_mask(CanvasBase *canvas, SpiceQMask *mask)
|
|
{
|
|
SpiceImageDescriptor *descriptor;
|
|
pixman_image_t *surface;
|
|
int need_invers;
|
|
int is_invers;
|
|
int cache_me;
|
|
|
|
if (!mask->bitmap) {
|
|
return NULL;
|
|
}
|
|
|
|
descriptor = (SpiceImageDescriptor *)SPICE_GET_ADDRESS(mask->bitmap);
|
|
access_test(canvas, descriptor, sizeof(SpiceImageDescriptor));
|
|
need_invers = mask->flags & SPICE_MASK_FLAGS_INVERS;
|
|
|
|
#ifdef CAIRO_CANVAS_CACHE
|
|
cache_me = descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_ME;
|
|
#else
|
|
cache_me = 0;
|
|
#endif
|
|
|
|
switch (descriptor->type) {
|
|
case SPICE_IMAGE_TYPE_BITMAP: {
|
|
SpiceBitmapImage *bitmap = (SpiceBitmapImage *)descriptor;
|
|
access_test(canvas, descriptor, sizeof(SpiceBitmapImage));
|
|
is_invers = need_invers && !cache_me;
|
|
surface = canvas_get_bitmap_mask(canvas, &bitmap->bitmap, is_invers);
|
|
break;
|
|
}
|
|
#if defined(CAIRO_CANVAS_CACHE) || defined(CAIRO_CANVAS_IMAGE_CACHE)
|
|
case SPICE_IMAGE_TYPE_FROM_CACHE:
|
|
surface = canvas->bits_cache->ops->get(canvas->bits_cache, descriptor->id);
|
|
is_invers = 0;
|
|
break;
|
|
#endif
|
|
default:
|
|
CANVAS_ERROR("invalid image type");
|
|
}
|
|
|
|
#if defined(CAIRO_CANVAS_CACHE) || defined(CAIRO_CANVAS_IMAGE_CACHE)
|
|
if (cache_me) {
|
|
canvas->bits_cache->ops->put(canvas->bits_cache, descriptor->id, surface);
|
|
}
|
|
|
|
if (need_invers && !is_invers) { // surface is in cache
|
|
pixman_image_t *inv_surf;
|
|
|
|
inv_surf = canvas_handle_inverse_user_data(surface);
|
|
|
|
pixman_image_unref(surface);
|
|
surface = inv_surf;
|
|
}
|
|
#endif
|
|
return surface;
|
|
}
|
|
|
|
static inline SpiceRasterGlyph *canvas_next_raster_glyph(const SpiceRasterGlyph *glyph, int bpp)
|
|
{
|
|
return (SpiceRasterGlyph *)((uint8_t *)(glyph + 1) +
|
|
(ALIGN(glyph->width * bpp, 8) * glyph->height >> 3));
|
|
}
|
|
|
|
static inline void canvas_raster_glyph_box(const SpiceRasterGlyph *glyph, SpiceRect *r)
|
|
{
|
|
ASSERT(r);
|
|
r->top = glyph->render_pos.y + glyph->glyph_origin.y;
|
|
r->bottom = r->top + glyph->height;
|
|
r->left = glyph->render_pos.x + glyph->glyph_origin.x;
|
|
r->right = r->left + glyph->width;
|
|
}
|
|
|
|
#ifdef GL_CANVAS
|
|
static inline void __canvas_put_bits(uint8_t *dest, int offset, uint8_t val, int n)
|
|
{
|
|
uint8_t mask;
|
|
int now;
|
|
|
|
dest = dest + (offset >> 3);
|
|
offset &= 0x07;
|
|
now = MIN(8 - offset, n);
|
|
|
|
mask = ~((1 << (8 - now)) - 1);
|
|
mask >>= offset;
|
|
*dest = ((val >> offset) & mask) | *dest;
|
|
|
|
if ((n = n - now)) {
|
|
mask = ~((1 << (8 - n)) - 1);
|
|
dest++;
|
|
*dest = ((val << now) & mask) | *dest;
|
|
}
|
|
}
|
|
|
|
#else
|
|
static inline void __canvas_put_bits(uint8_t *dest, int offset, uint8_t val, int n)
|
|
{
|
|
uint8_t mask;
|
|
int now;
|
|
|
|
dest = dest + (offset >> 3);
|
|
offset &= 0x07;
|
|
|
|
now = MIN(8 - offset, n);
|
|
|
|
mask = (1 << now) - 1;
|
|
mask <<= offset;
|
|
val = revers_bits(val);
|
|
*dest = ((val << offset) & mask) | *dest;
|
|
|
|
if ((n = n - now)) {
|
|
mask = (1 << n) - 1;
|
|
dest++;
|
|
*dest = ((val >> now) & mask) | *dest;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
static inline void canvas_put_bits(uint8_t *dest, int dest_offset, uint8_t *src, int n)
|
|
{
|
|
while (n) {
|
|
int now = MIN(n, 8);
|
|
|
|
n -= now;
|
|
__canvas_put_bits(dest, dest_offset, *src, now);
|
|
dest_offset += now;
|
|
src++;
|
|
}
|
|
}
|
|
|
|
static void canvas_put_glyph_bits(SpiceRasterGlyph *glyph, int bpp, uint8_t *dest, int dest_stride,
|
|
SpiceRect *bounds)
|
|
{
|
|
SpiceRect glyph_box;
|
|
uint8_t *src;
|
|
int lines;
|
|
int width;
|
|
|
|
//todo: support SPICE_STRING_FLAGS_RASTER_TOP_DOWN
|
|
canvas_raster_glyph_box(glyph, &glyph_box);
|
|
ASSERT(glyph_box.top >= bounds->top && glyph_box.bottom <= bounds->bottom);
|
|
ASSERT(glyph_box.left >= bounds->left && glyph_box.right <= bounds->right);
|
|
rect_offset(&glyph_box, -bounds->left, -bounds->top);
|
|
|
|
dest += glyph_box.top * dest_stride;
|
|
src = glyph->data;
|
|
lines = glyph_box.bottom - glyph_box.top;
|
|
width = glyph_box.right - glyph_box.left;
|
|
switch (bpp) {
|
|
case 1: {
|
|
int src_stride = ALIGN(width, 8) >> 3;
|
|
int i;
|
|
|
|
src += src_stride * (lines);
|
|
for (i = 0; i < lines; i++) {
|
|
src -= src_stride;
|
|
canvas_put_bits(dest, glyph_box.left, src, width);
|
|
dest += dest_stride;
|
|
}
|
|
break;
|
|
}
|
|
case 4: {
|
|
uint8_t *end;
|
|
int src_stride = ALIGN(width * 4, 8) >> 3;
|
|
|
|
src += src_stride * lines;
|
|
dest += glyph_box.left;
|
|
end = dest + dest_stride * lines;
|
|
for (; dest != end; dest += dest_stride) {
|
|
int i = 0;
|
|
uint8_t *now;
|
|
|
|
src -= src_stride;
|
|
now = src;
|
|
while (i < (width & ~1)) {
|
|
dest[i] = MAX(dest[i], *now & 0xf0);
|
|
dest[i + 1] = MAX(dest[i + 1], *now << 4);
|
|
i += 2;
|
|
now++;
|
|
}
|
|
if (i < width) {
|
|
dest[i] = MAX(dest[i], *now & 0xf0);
|
|
now++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 8: {
|
|
uint8_t *end;
|
|
src += width * lines;
|
|
dest += glyph_box.left;
|
|
end = dest + dest_stride * lines;
|
|
for (; dest != end; dest += dest_stride, src -= width) {
|
|
int i;
|
|
|
|
for (i = 0; i < width; i++) {
|
|
dest[i] = MAX(dest[i], src[i]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
CANVAS_ERROR("invalid bpp");
|
|
}
|
|
}
|
|
|
|
static pixman_image_t *canvas_get_str_mask(CanvasBase *canvas, SpiceString *str, int bpp, SpicePoint *pos)
|
|
{
|
|
SpiceRasterGlyph *glyph = (SpiceRasterGlyph *)str->data;
|
|
SpiceRasterGlyph *next_glyph;
|
|
SpiceRect bounds;
|
|
pixman_image_t *str_mask;
|
|
uint8_t *dest;
|
|
int dest_stride;
|
|
int i;
|
|
|
|
ASSERT(str->length > 0);
|
|
|
|
access_test(canvas, glyph, sizeof(SpiceRasterGlyph));
|
|
next_glyph = canvas_next_raster_glyph(glyph, bpp);
|
|
access_test(canvas, glyph, (uint8_t*)next_glyph - (uint8_t*)glyph);
|
|
canvas_raster_glyph_box(glyph, &bounds);
|
|
|
|
for (i = 1; i < str->length; i++) {
|
|
SpiceRect glyph_box;
|
|
|
|
glyph = next_glyph;
|
|
access_test(canvas, glyph, sizeof(SpiceRasterGlyph));
|
|
next_glyph = canvas_next_raster_glyph(glyph, bpp);
|
|
access_test(canvas, glyph, (uint8_t*)next_glyph - (uint8_t*)glyph);
|
|
canvas_raster_glyph_box(glyph, &glyph_box);
|
|
rect_union(&bounds, &glyph_box);
|
|
}
|
|
|
|
str_mask = pixman_image_create_bits((bpp == 1) ? PIXMAN_a1 : PIXMAN_a8,
|
|
bounds.right - bounds.left,
|
|
bounds.bottom - bounds.top, NULL, 0);
|
|
if (str_mask == NULL) {
|
|
CANVAS_ERROR("create surface failed");
|
|
}
|
|
dest = (uint8_t *)pixman_image_get_data(str_mask);
|
|
dest_stride = pixman_image_get_stride(str_mask);
|
|
glyph = (SpiceRasterGlyph *)str->data;
|
|
for (i = 0; i < str->length; i++) {
|
|
#if defined(GL_CANVAS)
|
|
canvas_put_glyph_bits(glyph, bpp, dest + (bounds.bottom - bounds.top - 1) * dest_stride,
|
|
-dest_stride, &bounds);
|
|
#else
|
|
canvas_put_glyph_bits(glyph, bpp, dest, dest_stride, &bounds);
|
|
#endif
|
|
glyph = canvas_next_raster_glyph(glyph, bpp);
|
|
}
|
|
|
|
pos->x = bounds.left;
|
|
pos->y = bounds.top;
|
|
return str_mask;
|
|
}
|
|
|
|
static inline SpiceVectorGlyph *canvas_next_vector_glyph(const SpiceVectorGlyph *glyph)
|
|
{
|
|
return (SpiceVectorGlyph *)((uint8_t *)(glyph + 1) + glyph->data_size);
|
|
}
|
|
|
|
static pixman_image_t *canvas_scale_surface(pixman_image_t *src, const SpiceRect *src_area, int width,
|
|
int height, int scale_mode)
|
|
{
|
|
pixman_image_t *surface;
|
|
pixman_transform_t transform;
|
|
double sx, sy;
|
|
|
|
surface = pixman_image_create_bits(PIXMAN_x8r8g8b8, width, height, NULL, 0);
|
|
if (surface == NULL) {
|
|
CANVAS_ERROR("create surface failed");
|
|
}
|
|
|
|
sx = (double)(src_area->right - src_area->left) / width;
|
|
sy = (double)(src_area->bottom - src_area->top) / 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_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, surface,
|
|
ROUND(src_area->left / sx), ROUND (src_area->top / sy),
|
|
0, 0, /* mask */
|
|
0, 0, /* dst */
|
|
width, height);
|
|
|
|
pixman_image_set_transform(src, NULL);
|
|
|
|
return surface;
|
|
}
|
|
|
|
static void quic_usr_error(QuicUsrContext *usr, const char *fmt, ...)
|
|
{
|
|
QuicData *usr_data = (QuicData *)usr;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
longjmp(usr_data->jmp_env, 1);
|
|
}
|
|
|
|
static void quic_usr_warn(QuicUsrContext *usr, const char *fmt, ...)
|
|
{
|
|
QuicData *usr_data = (QuicData *)usr;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static void *quic_usr_malloc(QuicUsrContext *usr, int size)
|
|
{
|
|
return malloc(size);
|
|
}
|
|
|
|
static void quic_usr_free(QuicUsrContext *usr, void *ptr)
|
|
{
|
|
free(ptr);
|
|
}
|
|
|
|
#ifdef CAIRO_CANVAS_NO_CHUNKS
|
|
|
|
static int quic_usr_more_space(QuicUsrContext *usr, uint32_t **io_ptr, int rows_completed)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void lz_usr_warn(LzUsrContext *usr, const char *fmt, ...)
|
|
{
|
|
LzData *usr_data = (LzData *)usr;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static void lz_usr_error(LzUsrContext *usr, const char *fmt, ...)
|
|
{
|
|
LzData *usr_data = (LzData *)usr;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
longjmp(usr_data->jmp_env, 1);
|
|
}
|
|
|
|
static void *lz_usr_malloc(LzUsrContext *usr, int size)
|
|
{
|
|
return malloc(size);
|
|
}
|
|
|
|
static void lz_usr_free(LzUsrContext *usr, void *ptr)
|
|
{
|
|
free(ptr);
|
|
}
|
|
|
|
static int lz_usr_more_space(LzUsrContext *usr, uint8_t **io_ptr)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int lz_usr_more_lines(LzUsrContext *usr, uint8_t **lines)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
static int quic_usr_more_space(QuicUsrContext *usr, uint32_t **io_ptr, int rows_completed)
|
|
{
|
|
QuicData *quic_data = (QuicData *)usr;
|
|
DataChunk *chunk;
|
|
uint32_t size;
|
|
|
|
if (!quic_data->next) {
|
|
return 0;
|
|
}
|
|
chunk = (DataChunk *)quic_data->get_virt(quic_data->get_virt_opaque, quic_data->next,
|
|
sizeof(DataChunk));
|
|
size = chunk->size;
|
|
quic_data->validate_virt(quic_data->validate_virt_opaque, (unsigned long)chunk->data,
|
|
quic_data->next, size);
|
|
|
|
quic_data->next = chunk->next;
|
|
*io_ptr = (uint32_t *)chunk->data;
|
|
return size >> 2;
|
|
}
|
|
|
|
#endif
|
|
|
|
static int quic_usr_more_lines(QuicUsrContext *usr, uint8_t **lines)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CAIRO_CANVAS_ACCESS_TEST
|
|
static void __canvas_set_access_params(CanvasBase *canvas, unsigned long base, unsigned long max)
|
|
{
|
|
canvas->base = base;
|
|
canvas->max = max;
|
|
}
|
|
#endif
|
|
|
|
static void canvas_base_destroy(CanvasBase *canvas)
|
|
{
|
|
quic_destroy(canvas->quic_data.quic);
|
|
#ifdef CAIRO_CANVAS_NO_CHUNKS
|
|
lz_destroy(canvas->lz_data.lz);
|
|
#endif
|
|
#ifdef GDI_CANVAS
|
|
DeleteDC(canvas->dc);
|
|
#endif
|
|
}
|
|
|
|
#ifdef CAIRO_CANVAS_CACHE
|
|
static int canvas_base_init(CanvasBase *canvas, int depth,
|
|
SpiceImageCache *bits_cache,
|
|
SpicePaletteCache *palette_cache
|
|
#elif defined(CAIRO_CANVAS_IMAGE_CACHE)
|
|
static int canvas_base_init(CanvasBase *canvas, int depth,
|
|
SpiceImageCache *bits_cache
|
|
#else
|
|
static int canvas_base_init(CanvasBase *canvas, int depth
|
|
#endif
|
|
#ifdef USE_GLZ
|
|
, void *glz_decoder_opaque, glz_decode_fn_t glz_decode
|
|
#endif
|
|
#ifndef CAIRO_CANVAS_NO_CHUNKS
|
|
, void *get_virt_opaque, get_virt_fn_t get_virt,
|
|
void *validate_virt_opaque, validate_virt_fn_t validate_virt
|
|
#endif
|
|
)
|
|
{
|
|
canvas->quic_data.usr.error = quic_usr_error;
|
|
canvas->quic_data.usr.warn = quic_usr_warn;
|
|
canvas->quic_data.usr.info = quic_usr_warn;
|
|
canvas->quic_data.usr.malloc = quic_usr_malloc;
|
|
canvas->quic_data.usr.free = quic_usr_free;
|
|
canvas->quic_data.usr.more_space = quic_usr_more_space;
|
|
canvas->quic_data.usr.more_lines = quic_usr_more_lines;
|
|
#ifndef CAIRO_CANVAS_NO_CHUNKS
|
|
canvas->quic_data.get_virt_opaque = get_virt_opaque;
|
|
canvas->quic_data.get_virt = get_virt;
|
|
canvas->quic_data.validate_virt_opaque = validate_virt_opaque;
|
|
canvas->quic_data.validate_virt = validate_virt;
|
|
#endif
|
|
if (!(canvas->quic_data.quic = quic_create(&canvas->quic_data.usr))) {
|
|
return 0;
|
|
}
|
|
#ifdef CAIRO_CANVAS_NO_CHUNKS
|
|
canvas->lz_data.usr.error = lz_usr_error;
|
|
canvas->lz_data.usr.warn = lz_usr_warn;
|
|
canvas->lz_data.usr.info = lz_usr_warn;
|
|
canvas->lz_data.usr.malloc = lz_usr_malloc;
|
|
canvas->lz_data.usr.free = lz_usr_free;
|
|
canvas->lz_data.usr.more_space = lz_usr_more_space;
|
|
canvas->lz_data.usr.more_lines = lz_usr_more_lines;
|
|
if (!(canvas->lz_data.lz = lz_create(&canvas->lz_data.usr))) {
|
|
return 0;
|
|
}
|
|
#endif
|
|
#ifdef USE_GLZ
|
|
canvas->glz_data.decoder_opaque = glz_decoder_opaque;
|
|
canvas->glz_data.decode = glz_decode;
|
|
#endif
|
|
|
|
if (depth == 16) {
|
|
canvas->color_shift = 5;
|
|
canvas->color_mask = 0x1f;
|
|
} else {
|
|
canvas->color_shift = 8;
|
|
canvas->color_mask = 0xff;
|
|
}
|
|
|
|
|
|
#if defined(CAIRO_CANVAS_CACHE) || defined(CAIRO_CANVAS_IMAGE_CACHE)
|
|
canvas->bits_cache = bits_cache;
|
|
#endif
|
|
#ifdef CAIRO_CANVAS_CACHE
|
|
canvas->palette_cache = palette_cache;
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
canvas->dc = NULL;
|
|
#endif
|
|
|
|
#ifdef GDI_CANVAS
|
|
canvas->dc = create_compatible_dc();
|
|
if (!canvas->dc) {
|
|
lz_destroy(canvas->lz_data.lz);
|
|
return 0;
|
|
}
|
|
#endif
|
|
return 1;
|
|
}
|
|
|