/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
   Copyright (C) 2009 Red Hat, Inc.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/

#include <config.h>

#include <stdarg.h>
#include <stdlib.h>
#include <setjmp.h>
#include <stdio.h>
#include <math.h>

#ifdef USE_LZ4
#include <lz4.h>
#endif
#include <spice/macros.h>
#include "log.h"
#include "quic.h"
#include "lz.h"
#include "canvas_base.h"
#include "pixman_utils.h"
#include "canvas_utils.h"
#include "rect.h"
#include "lines.h"
#include "rop3.h"
#include "mem.h"
#include "macros.h"

#include <spice/start-packed.h>
typedef struct SPICE_ATTR_PACKED {
    uint32_t v;
} uint32_unaligned_t;
#include <spice/end-packed.h>

#define READ_UINT32_BE(ptr) GUINT32_FROM_BE(((uint32_unaligned_t *)(ptr))->v)

#define ROUND(_x) ((int)floor((_x) + 0.5))

 static inline int fix_to_int(SPICE_FIXED28_4 fixed)
{
    int val, rem;

    rem = fixed & 0x0f;
    val = fixed >> 4;
    if (rem > 8) {
        val++;
    }
    return val;
}

 static inline SPICE_FIXED28_4  int_to_fix(int v)
{
    return v << 4;
}

static inline double fix_to_double(SPICE_FIXED28_4 fixed)
{
    return (double)(fixed & 0x0f) / 0x0f + (fixed >> 4);
}

static inline uint16_t rgb_32_to_16_555(uint32_t color)
{
    return
        (((color) >> 3) & 0x001f) |
        (((color) >> 6) & 0x03e0) |
        (((color) >> 9) & 0x7c00);
}
static inline uint16_t rgb_32_to_16_565(uint32_t color)
{
    return
        (((color) >> 3) & 0x001f) |
        (((color) >> 5) & 0x07e0) |
        (((color) >> 8) & 0xf800);
}

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;
}

typedef struct LzData {
    LzUsrContext usr;
    LzContext *lz;
    LzDecodeUsrData decode_data;
    jmp_buf jmp_env;
    char message_buf[512];
} LzData;

typedef struct GlzData {
    SpiceGlzDecoder *decoder;
    LzDecodeUsrData decode_data;
} GlzData;

typedef struct QuicData {
    QuicUsrContext usr;
    QuicContext *quic;
    jmp_buf jmp_env;
    char message_buf[512];
    SpiceChunks *chunks;
    uint32_t current_chunk;
} QuicData;

typedef struct CanvasBase {
    SpiceCanvas parent;
    uint32_t color_shift;
    uint32_t color_mask;
    QuicData quic_data;

    uint32_t format;
    int width;
    int height;
    pixman_region32_t canvas_region;

    SpiceImageCache *bits_cache;
#ifdef SW_CANVAS_CACHE
    SpicePaletteCache *palette_cache;
#endif

    SpiceImageSurfaces *surfaces;

    LzData lz_data;
    GlzData glz_data;
    SpiceJpegDecoder* jpeg;
    SpiceZlibDecoder* zlib;
} CanvasBase;

typedef enum {
    ROP_INPUT_SRC,
    ROP_INPUT_BRUSH,
    ROP_INPUT_DEST
} ROPInput;

static 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;
}

//#define DEBUG_DUMP_COMPRESS
#ifdef DEBUG_DUMP_COMPRESS
static void dump_surface(pixman_image_t *surface, int cache);
#endif


static pixman_format_code_t canvas_get_target_format(CanvasBase *canvas,
                                                     int source_has_alpha)
{
    pixman_format_code_t format;

    /* Convert to target surface format */
    format = spice_surface_format_to_pixman (canvas->format);

    if (source_has_alpha) {
        /* Even though the destination has no alpha, we make the source
         * remember there are alpha bits instead of throwing away this
         * information. The results are the same if alpha is not
         * interpreted, and if need to interpret alpha, don't use
         * conversion to target format.
         * This is needed for instance when doing the final
         * canvas_get_target_format() in canvas_get_image_internal
         * as otherwise we wouldn't know if the bitmap source
         * really had alpha.
         */
        if (format == PIXMAN_x8r8g8b8) {
            format = PIXMAN_a8r8g8b8;
        }
    } else { /* !source_has_alpha */
        /* If the source doesn't have alpha, but the destination has,
           don't convert to alpha, since that would just do an unnecessary
           copy to fill the alpha bytes with 0xff which is not expected if
           we just use the raw bits, (and handled implicitly by pixman if
           we're interpreting data) */
        if (format == PIXMAN_a8r8g8b8) {
            format = PIXMAN_x8r8g8b8;
        }
    }

    return format;
}

static pixman_image_t *canvas_get_quic(CanvasBase *canvas, SpiceImage *image,
                                       int want_original)
{
    pixman_image_t *surface = NULL;
    QuicData *quic_data = &canvas->quic_data;
    QuicImageType type, as_type;
    pixman_format_code_t pixman_format;
    uint8_t *dest;
    int stride;
    int width;
    int height;

    if (setjmp(quic_data->jmp_env)) {
        if (surface != NULL) {
            pixman_image_unref(surface);
        }
        g_warning("%s", quic_data->message_buf);
        return NULL;
    }

    quic_data->chunks = image->u.quic.data;
    quic_data->current_chunk = 0;

    if (quic_decode_begin(quic_data->quic,
                          SPICE_UNALIGNED_CAST(uint32_t *,image->u.quic.data->chunk[0].data),
                          image->u.quic.data->chunk[0].len >> 2,
                          &type, &width, &height) == QUIC_ERROR) {
        g_warning("quic decode begin failed");
        return NULL;
    }

    switch (type) {
    case QUIC_IMAGE_TYPE_RGBA:
        as_type = QUIC_IMAGE_TYPE_RGBA;
        pixman_format = PIXMAN_LE_a8r8g8b8;
        break;
    case QUIC_IMAGE_TYPE_RGB32:
    case QUIC_IMAGE_TYPE_RGB24:
        as_type = QUIC_IMAGE_TYPE_RGB32;
        pixman_format = PIXMAN_LE_x8r8g8b8;
        break;
    case QUIC_IMAGE_TYPE_RGB16:
        if (!want_original &&
            (canvas->format == SPICE_SURFACE_FMT_32_xRGB ||
             canvas->format == SPICE_SURFACE_FMT_32_ARGB)) {
            as_type = QUIC_IMAGE_TYPE_RGB32;
            pixman_format = PIXMAN_LE_x8r8g8b8;
        } else {
            as_type = QUIC_IMAGE_TYPE_RGB16;
            pixman_format = PIXMAN_x1r5g5b5;
        }
        break;
    case QUIC_IMAGE_TYPE_INVALID:
    case QUIC_IMAGE_TYPE_GRAY:
    default:
        spice_warn_if_reached();
        return NULL;
    }

    spice_return_val_if_fail((uint32_t)width == image->descriptor.width, NULL);
    spice_return_val_if_fail((uint32_t)height == image->descriptor.height, NULL);

    surface = surface_create(pixman_format,
                             width, height, FALSE);

    spice_return_val_if_fail(surface != NULL, NULL);

    dest = (uint8_t *)pixman_image_get_data(surface);
    stride = pixman_image_get_stride(surface);
    if (quic_decode(quic_data->quic, as_type,
                    dest, stride) == QUIC_ERROR) {
        pixman_image_unref(surface);
        g_warning("quic decode failed");
        return NULL;
    }

#ifdef DEBUG_DUMP_COMPRESS
    dump_surface(surface, 0);
#endif
    return surface;
}


//#define DUMP_JPEG
static void
dump_jpeg(SPICE_GNUC_UNUSED SpiceChunks* data, SPICE_GNUC_UNUSED uint32_t data_size)
{
#ifdef DUMP_JPEG
    static uint32_t jpeg_id = 0;
    char file_str[200];
    uint32_t id = ++jpeg_id;

#ifdef WIN32
    sprintf(file_str, "c:\\tmp\\spice_dump\\%u.jpg", id);
#else
    sprintf(file_str, "/tmp/spice_dump/%u.jpg", id);
#endif

    FILE *f = fopen(file_str, "wb");
    if (!f) {
        return;
    }

    for (uint32_t n = 0; n < data->num_chunks; ++n) {
        fwrite(data->chunk[n].data, 1, data->chunk[n].len, f);
    }
    fclose(f);
#endif
}

static pixman_image_t *canvas_get_jpeg(CanvasBase *canvas, SpiceImage *image)
{
    pixman_image_t *surface = NULL;
    int stride;
    int width;
    int height;
    uint8_t *dest;

    spice_return_val_if_fail(image->u.jpeg.data->num_chunks == 1, NULL);
    canvas->jpeg->ops->begin_decode(canvas->jpeg, image->u.jpeg.data->chunk[0].data, image->u.jpeg.data->chunk[0].len,
                                    &width, &height);
    spice_return_val_if_fail((uint32_t)width == image->descriptor.width, NULL);
    spice_return_val_if_fail((uint32_t)height == image->descriptor.height, NULL);

    surface = surface_create(PIXMAN_LE_x8r8g8b8,
                             width, height, FALSE);
    if (surface == NULL) {
        g_warning("create surface failed");
        return NULL;
    }

    dest = (uint8_t *)pixman_image_get_data(surface);
    stride = pixman_image_get_stride(surface);

    canvas->jpeg->ops->decode(canvas->jpeg, dest, stride, SPICE_BITMAP_FMT_32BIT);

    dump_jpeg(image->u.jpeg.data, image->u.jpeg.data_size);
    return surface;
}

#if defined(USE_LZ4) || defined(SW_CANVAS_CACHE)
static void canvas_fix_alignment(uint8_t *bits,
                                 int stride_encoded, int stride_pixman,
                                 int height)
{
    if (stride_pixman > stride_encoded) {
        // Fix the row alignment
        int row;
        for (row = height - 1; row > 0; --row) {
            uint8_t *aligned, *misaligned;
            aligned = bits + stride_pixman*row;
            misaligned = bits + stride_encoded*row;
            memmove(aligned, misaligned, stride_encoded);
        }
    }
}
#endif

#ifdef USE_LZ4
static pixman_image_t *canvas_get_lz4(CanvasBase *canvas, SpiceImage *image)
{
    pixman_image_t *surface = NULL;
    int dec_size, enc_size, available;
    int stride, stride_abs, stride_encoded;
    uint8_t *dest, *data, *data_end, *bits;
    int width, height, top_down;
    LZ4_streamDecode_t *stream;
    uint8_t spice_format;
    pixman_format_code_t format;

    spice_chunks_linearize(image->u.lz4.data);
    data = image->u.lz4.data->chunk[0].data;
    data_end = data + image->u.lz4.data->chunk[0].len;
    width = image->descriptor.width;
    stride_encoded = width;
    height = image->descriptor.height;
    if (data + 2 > data_end) {
        g_warning("missing header in LZ4 data");
        return NULL;
    }
    top_down = !!*(data++);
    spice_format = *(data++);
    switch (spice_format) {
        case SPICE_BITMAP_FMT_16BIT:
            format = PIXMAN_x1r5g5b5;
            stride_encoded *= 2;
            break;
        case SPICE_BITMAP_FMT_24BIT:
            format = PIXMAN_LE_r8g8b8;
            stride_encoded *= 3;
            break;
        case SPICE_BITMAP_FMT_32BIT:
            format = PIXMAN_LE_x8r8g8b8;
            stride_encoded *= 4;
            break;
        case SPICE_BITMAP_FMT_RGBA:
            format = PIXMAN_LE_a8r8g8b8;
            stride_encoded *= 4;
            break;
        default:
            g_warning("unsupported bitmap format %d with LZ4", spice_format);
            return NULL;
    }

    surface = surface_create(format,
                             width, height, top_down);
    if (surface == NULL) {
        g_warning("create surface failed");
        return NULL;
    }

    stream = LZ4_createStreamDecode();
    dest = (uint8_t *)pixman_image_get_data(surface);
    stride = pixman_image_get_stride(surface);
    stride_abs = abs(stride);
    available = height * stride_abs;
    if (!top_down) {
        dest -= (stride_abs * (height - 1));
    }
    bits = dest;

    do {
        if (data + 4 > data_end) {
            goto format_error;
        }
        // Read next compressed block
        enc_size = READ_UINT32_BE(data);
        data += 4;
        /* check overflow. This check is a bit different to avoid
         * possible overflows. From previous check data_end - data cannot overflow.
         * Computing data + enc_size on 32 bit could cause overflows. */
        if (enc_size < 0 || data_end - data < (unsigned int) enc_size) {
            goto format_error;
        }
        dec_size = LZ4_decompress_safe_continue(stream, (const char *) data,
                                                (char *) dest, enc_size, available);
        if (dec_size <= 0) {
            goto format_error;
        }
        dest += dec_size;
        available -= dec_size;
        data += enc_size;
    } while (data < data_end);

    canvas_fix_alignment(bits, stride_encoded, stride_abs, height);

    LZ4_freeStreamDecode(stream);
    return surface;

format_error:
    g_warning("error decoding LZ4 block");
    LZ4_freeStreamDecode(stream);
    pixman_image_unref(surface);
    return NULL;
}
#endif

static pixman_image_t *canvas_get_jpeg_alpha(CanvasBase *canvas, SpiceImage *image)
{
    pixman_image_t *surface = NULL;
    int stride;
    int width;
    int height;
    uint8_t *dest;
    int alpha_top_down = FALSE;
    LzData *lz_data = &canvas->lz_data;
    LzImageType lz_alpha_type;
    uint8_t *comp_alpha_buf = NULL;
    uint8_t *decomp_alpha_buf = NULL;
    int alpha_size;
    int lz_alpha_width, lz_alpha_height, n_comp_pixels, lz_alpha_top_down;

    spice_return_val_if_fail(image->u.jpeg_alpha.data->num_chunks == 1, NULL);
    canvas->jpeg->ops->begin_decode(canvas->jpeg,
                                    image->u.jpeg_alpha.data->chunk[0].data,
                                    image->u.jpeg_alpha.jpeg_size,
                                    &width, &height);
    spice_return_val_if_fail((uint32_t)width == image->descriptor.width, NULL);
    spice_return_val_if_fail((uint32_t)height == image->descriptor.height, NULL);

    if (image->u.jpeg_alpha.flags & SPICE_JPEG_ALPHA_FLAGS_TOP_DOWN) {
        alpha_top_down = TRUE;
    }

    surface = alloc_lz_image_surface(&lz_data->decode_data, PIXMAN_LE_a8r8g8b8,
                                     width, height, width*height, alpha_top_down);

    if (surface == NULL) {
        g_warning("create surface failed");
        return NULL;
    }

    dest = (uint8_t *)pixman_image_get_data(surface);
    stride = pixman_image_get_stride(surface);

    canvas->jpeg->ops->decode(canvas->jpeg, dest, stride, SPICE_BITMAP_FMT_32BIT);

    comp_alpha_buf = image->u.jpeg_alpha.data->chunk[0].data + image->u.jpeg_alpha.jpeg_size;
    alpha_size = image->u.jpeg_alpha.data_size - image->u.jpeg_alpha.jpeg_size;

    lz_decode_begin(lz_data->lz, comp_alpha_buf, alpha_size, &lz_alpha_type,
                    &lz_alpha_width, &lz_alpha_height, &n_comp_pixels,
                    &lz_alpha_top_down, NULL);
    spice_return_val_if_fail(lz_alpha_type == LZ_IMAGE_TYPE_XXXA, NULL);
    spice_return_val_if_fail(!!lz_alpha_top_down == !!alpha_top_down, NULL);
    spice_return_val_if_fail(lz_alpha_width == width, NULL);
    spice_return_val_if_fail(lz_alpha_height == height, NULL);
    spice_return_val_if_fail(n_comp_pixels == width * height, NULL);

    if (!alpha_top_down) {
        decomp_alpha_buf = dest + stride * (height - 1);
    } else {
        decomp_alpha_buf = dest;
    }
    lz_decode(lz_data->lz, LZ_IMAGE_TYPE_XXXA, decomp_alpha_buf);

    dump_jpeg(image->u.jpeg_alpha.data, image->u.jpeg_alpha.jpeg_size);
    return surface;
}

static pixman_image_t *canvas_bitmap_to_surface(CanvasBase *canvas, SpiceBitmap* bitmap,
                                                SpicePalette *palette, int want_original)
{
    uint8_t* src;
    pixman_image_t *image;
    pixman_format_code_t format;

    spice_chunks_linearize(bitmap->data);

    src = bitmap->data->chunk[0].data;

    if (want_original) {
        format = spice_bitmap_format_to_pixman(bitmap->format, canvas->format);
    } else {
        format = canvas_get_target_format(canvas,
                                          bitmap->format == SPICE_BITMAP_FMT_RGBA);
    }

    image = surface_create(format,
                           bitmap->x, bitmap->y, FALSE);
    if (image == NULL) {
        g_warning("create surface failed");
        return NULL;
    }

    spice_bitmap_convert_to_pixman(format, image,
                                   bitmap->format,
                                   bitmap->flags,
                                   bitmap->x, bitmap->y,
                                   src, bitmap->stride,
                                   canvas->format, palette);
    return image;
}


#ifdef SW_CANVAS_CACHE

static inline SpicePalette *canvas_get_palette(CanvasBase *canvas, SpicePalette *base_palette, uint64_t palette_id, uint8_t flags)
{
    SpicePalette *palette;

    if (flags & SPICE_BITMAP_FLAGS_PAL_FROM_CACHE) {
        palette = canvas->palette_cache->ops->get(canvas->palette_cache, palette_id);
    } else {
        palette = base_palette;
        if (palette != NULL && flags & SPICE_BITMAP_FLAGS_PAL_CACHE_ME) {
            canvas->palette_cache->ops->put(canvas->palette_cache, palette);
        }
    }
    return palette;
}

static inline SpicePalette *canvas_get_localized_palette(CanvasBase *canvas, SpicePalette *base_palette, uint64_t palette_id, uint8_t flags, int *free_palette)
{
    SpicePalette *palette = canvas_get_palette(canvas, base_palette, palette_id, flags);
    SpicePalette *copy;
    uint32_t *now, *end;

    if (canvas->format == SPICE_SURFACE_FMT_32_xRGB ||
        canvas->format == SPICE_SURFACE_FMT_32_ARGB) {
        return palette;
    }

    copy = spice_memdup(palette, sizeof(SpicePalette) + palette->num_ents * 4);

    switch (canvas->format) {
    case SPICE_SURFACE_FMT_32_xRGB:
    case SPICE_SURFACE_FMT_32_ARGB:
        /* Won't happen */
        break;
    case SPICE_SURFACE_FMT_16_555:
        now = copy->ents;
        end = now + copy->num_ents;
        for (; now < end; now++) {
            *now = canvas_16bpp_to_32bpp(*now);
        }
        break;
    case SPICE_SURFACE_FMT_16_565:
    default:
        spice_warn_if_reached();
        free(copy);
        return NULL;
    }
    *free_palette = TRUE;
    return copy;
}

static pixman_image_t *canvas_get_lz(CanvasBase *canvas, SpiceImage *image,
                                     int want_original)
{
    LzData *lz_data = &canvas->lz_data;
    uint8_t *comp_buf = NULL;
    int comp_size;
    uint8_t    *decomp_buf = NULL;
    pixman_format_code_t pixman_format;
    LzImageType type, as_type;
    SpicePalette *palette = NULL;
    int n_comp_pixels;
    int width;
    int height;
    int top_down;
    int stride_encoded;
    int stride;
    volatile int free_palette = FALSE;

    if (setjmp(lz_data->jmp_env)) {
        if (free_palette)  {
            free(palette);
        }
        free(decomp_buf);
        g_warning("%s", lz_data->message_buf);
        return NULL;
    }

    if (image->descriptor.type == SPICE_IMAGE_TYPE_LZ_RGB) {
        spice_return_val_if_fail(image->u.lz_rgb.data->num_chunks == 1, NULL); /* TODO: Handle chunks */
        comp_buf = image->u.lz_rgb.data->chunk[0].data;
        comp_size = image->u.lz_rgb.data->chunk[0].len;
        palette = NULL;
    } else if (image->descriptor.type == SPICE_IMAGE_TYPE_LZ_PLT) {
        spice_return_val_if_fail(image->u.lz_plt.data->num_chunks == 1, NULL); /* TODO: Handle chunks */
        comp_buf = image->u.lz_plt.data->chunk[0].data;
        comp_size = image->u.lz_plt.data->chunk[0].len;
        palette = canvas_get_localized_palette(canvas, image->u.lz_plt.palette,
                                               image->u.lz_plt.palette_id, image->u.lz_plt.flags,
                                               (int*) &free_palette);
    } else {
        spice_warn_if_reached();
        return NULL;
    }

    lz_decode_begin(lz_data->lz, comp_buf, comp_size, &type,
                    &width, &height, &n_comp_pixels, &top_down, palette);

    stride_encoded = n_comp_pixels / height;
    switch (type) {
    case LZ_IMAGE_TYPE_RGBA:
        as_type = LZ_IMAGE_TYPE_RGBA;
        pixman_format = PIXMAN_LE_a8r8g8b8;
        stride_encoded *= 4;
        break;
    case LZ_IMAGE_TYPE_RGB32:
    case LZ_IMAGE_TYPE_RGB24:
    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:
        as_type = LZ_IMAGE_TYPE_RGB32;
        pixman_format = PIXMAN_LE_x8r8g8b8;
        stride_encoded *= 4;
        break;
    case LZ_IMAGE_TYPE_A8:
        as_type = LZ_IMAGE_TYPE_A8;
        pixman_format = PIXMAN_a8;
        break;
    case LZ_IMAGE_TYPE_RGB16:
        if (!want_original &&
            (canvas->format == SPICE_SURFACE_FMT_32_xRGB ||
             canvas->format == SPICE_SURFACE_FMT_32_ARGB)) {
            as_type = LZ_IMAGE_TYPE_RGB32;
            pixman_format = PIXMAN_LE_x8r8g8b8;
            stride_encoded *= 4;
        } else {
            as_type = LZ_IMAGE_TYPE_RGB16;
            pixman_format = PIXMAN_x1r5g5b5;
            stride_encoded *= 2;
        }
        break;
    default:
        spice_warn_if_reached();
        return NULL;
    }

    spice_return_val_if_fail((unsigned)width == image->descriptor.width, NULL);
    spice_return_val_if_fail((unsigned)height == image->descriptor.height, NULL);

    spice_return_val_if_fail((image->descriptor.type == SPICE_IMAGE_TYPE_LZ_PLT) || (n_comp_pixels == width * height), NULL);

    alloc_lz_image_surface(&lz_data->decode_data, pixman_format,
                           width, height, n_comp_pixels, top_down);

    stride = pixman_image_get_stride(lz_data->decode_data.out_surface);
    stride = abs(stride);

    decomp_buf = (uint8_t *)pixman_image_get_data(lz_data->decode_data.out_surface);
    if (!top_down) {
        decomp_buf -= stride * (height - 1);
    }

    lz_decode(lz_data->lz, as_type, decomp_buf);

    canvas_fix_alignment(decomp_buf, stride_encoded, stride, height);

    if (free_palette)  {
        free(palette);
    }

    return lz_data->decode_data.out_surface;
}

static pixman_image_t *canvas_get_glz_rgb_common(CanvasBase *canvas, uint8_t *data,
                                                 int want_original)
{
    spice_return_val_if_fail(canvas->glz_data.decoder != NULL, NULL);

    canvas->glz_data.decoder->ops->decode(canvas->glz_data.decoder,
                                          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);
}

// don't handle plts since bitmaps with plt can be decoded globally 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, SpiceImage *image,
                                      int want_original)
{
    spice_return_val_if_fail(image->descriptor.type == SPICE_IMAGE_TYPE_GLZ_RGB, NULL);

    spice_return_val_if_fail(image->u.lz_rgb.data->num_chunks == 1, NULL); /* TODO: Handle chunks */
    return canvas_get_glz_rgb_common(canvas, image->u.lz_rgb.data->chunk[0].data, want_original);
}

static pixman_image_t *canvas_get_zlib_glz_rgb(CanvasBase *canvas, SpiceImage *image,
                                               int want_original)
{
    uint8_t *glz_data;
    pixman_image_t *surface;

    spice_return_val_if_fail(canvas->zlib != NULL, NULL);

    spice_return_val_if_fail(image->u.zlib_glz.data->num_chunks == 1, NULL); /* TODO: Handle chunks */
    glz_data = (uint8_t*)spice_malloc(image->u.zlib_glz.glz_data_size);
    canvas->zlib->ops->decode(canvas->zlib, image->u.zlib_glz.data->chunk[0].data,
                              image->u.zlib_glz.data->chunk[0].len,
                              glz_data, image->u.zlib_glz.glz_data_size);
    surface = canvas_get_glz_rgb_common(canvas, glz_data, want_original);
    free(glz_data);
    return 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,
                                       int want_original)
{
    pixman_image_t* surface;
    SpicePalette *palette;

    palette = canvas_get_palette(canvas, bitmap->palette, bitmap->palette_id, bitmap->flags);
#ifdef DEBUG_DUMP_BITMAP
    if (palette) {
        dump_bitmap(bitmap, palette);
    }
#endif

    surface = canvas_bitmap_to_surface(canvas, bitmap, palette, want_original);

    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,
                                       int want_original)
{
    SpicePalette *palette;

    if (!bitmap->palette) {
        return canvas_bitmap_to_surface(canvas, bitmap, NULL, want_original);
    }
    palette = (SpicePalette *)SPICE_GET_ADDRESS(bitmap->palette);
    return canvas_bitmap_to_surface(canvas, bitmap, palette, want_original);
}

#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_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

static SpiceCanvas *canvas_get_surface_internal(CanvasBase *canvas, SpiceImage *image)
{
    if (image->descriptor.type == SPICE_IMAGE_TYPE_SURFACE) {
        SpiceSurface *surface = &image->u.surface;
        return canvas->surfaces->ops->get(canvas->surfaces, surface->surface_id);
    }
    return NULL;
}

static SpiceCanvas *canvas_get_surface_mask_internal(CanvasBase *canvas, SpiceImage *image)
{
    if (image->descriptor.type == SPICE_IMAGE_TYPE_SURFACE) {
        SpiceSurface *surface = &image->u.surface;
        return canvas->surfaces->ops->get(canvas->surfaces, surface->surface_id);
    }
    return NULL;
}


#if defined(SW_CANVAS_CACHE)
static int image_has_palette_to_cache(SpiceImage *image)
{
    SpiceImageDescriptor *descriptor = &image->descriptor;

    if (descriptor->type == SPICE_IMAGE_TYPE_BITMAP) {
        return image->u.bitmap.palette &&
               (image->u.bitmap.flags & SPICE_BITMAP_FLAGS_PAL_CACHE_ME);
    } else if (descriptor->type == SPICE_IMAGE_TYPE_LZ_PLT) {
        return image->u.lz_plt.palette &&
               (image->u.lz_plt.flags & SPICE_BITMAP_FLAGS_PAL_CACHE_ME);
    }
    return FALSE;
}
#endif

//#define DEBUG_LZ

static pixman_image_t *get_surface_from_canvas(CanvasBase *canvas,
                                               SpiceImage *image,
                                               int want_original)
{
    switch (image->descriptor.type) {
    case SPICE_IMAGE_TYPE_QUIC:
        return canvas_get_quic(canvas, image, want_original);

#if defined(SW_CANVAS_CACHE)
    case SPICE_IMAGE_TYPE_LZ_PLT:
    case SPICE_IMAGE_TYPE_LZ_RGB:
        return canvas_get_lz(canvas, image, want_original);

    case SPICE_IMAGE_TYPE_GLZ_RGB:
        return canvas_get_glz(canvas, image, want_original);

    case SPICE_IMAGE_TYPE_ZLIB_GLZ_RGB:
        return canvas_get_zlib_glz_rgb(canvas, image, want_original);

    case SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS:
        return canvas->bits_cache->ops->get_lossless(canvas->bits_cache,
                                                     image->descriptor.id);
#endif

    case SPICE_IMAGE_TYPE_JPEG:
        return canvas_get_jpeg(canvas, image);

    case SPICE_IMAGE_TYPE_JPEG_ALPHA:
        return canvas_get_jpeg_alpha(canvas, image);

    case SPICE_IMAGE_TYPE_LZ4:
#ifdef USE_LZ4
        return canvas_get_lz4(canvas, image);
#else
        g_warning("LZ4 compression algorithm not supported");
        return NULL;
#endif

    case SPICE_IMAGE_TYPE_FROM_CACHE:
        return canvas->bits_cache->ops->get(canvas->bits_cache,
                                            image->descriptor.id);

    case SPICE_IMAGE_TYPE_BITMAP:
        return canvas_get_bits(canvas, &image->u.bitmap, want_original);

    default:
        spice_warn_if_reached();
        return NULL;
    }
}

/* If real get is FALSE, then only do whatever is needed but don't return an image. For instance,
 *  if we need to read it to cache it we do.
 *
 * This generally converts the image to the right type for the canvas.
 * However, if want_original is set the real source format is returned, and
 * you have to be able to handle any image format. This is useful to avoid
 * e.g. losing alpha when blending a argb32 image on a rgb16 surface.
 */
static pixman_image_t *canvas_get_image_internal(CanvasBase *canvas, SpiceImage *image,
                                                 int want_original, int real_get)
{
    SpiceImageDescriptor *descriptor = &image->descriptor;
    pixman_image_t *surface, *converted;
    pixman_format_code_t wanted_format, surface_format;
    int saved_want_original;

    /* When touching, only really allocate if we need to cache, or
     * if we're loading a GLZ stream (since those need inter-thread communication
     * to happen which breaks if we don't. */
    if (!real_get &&
        !(descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_ME) &&
#ifdef SW_CANVAS_CACHE
        !(descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME) &&
        !image_has_palette_to_cache(image) &&
#endif
        (descriptor->type != SPICE_IMAGE_TYPE_GLZ_RGB) &&
        (descriptor->type != SPICE_IMAGE_TYPE_ZLIB_GLZ_RGB)) {
        return NULL;
    }

    saved_want_original = want_original;
    if (descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_ME
#ifdef SW_CANVAS_CACHE
        || descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME
#endif
       ) {
        want_original = TRUE;
    }

    surface = get_surface_from_canvas(canvas, image, want_original);

    spice_return_val_if_fail(surface != NULL, NULL);
    spice_return_val_if_fail(spice_pixman_image_get_format(surface, &surface_format), NULL);

    if (descriptor->flags & SPICE_IMAGE_FLAGS_HIGH_BITS_SET &&
        descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE &&
#ifdef SW_CANVAS_CACHE
        descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS &&
#endif
        surface_format == PIXMAN_x8r8g8b8) {
        spice_pixman_fill_rect_rop(surface,
                                   0, 0,
                                   pixman_image_get_width(surface),
                                   pixman_image_get_height(surface),
                                   0xff000000U, SPICE_ROP_OR);
    }

    if (descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_ME &&
#ifdef SW_CANVAS_CACHE
        descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS &&
#endif
        descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE ) {
#ifdef SW_CANVAS_CACHE
        if (!spice_image_descriptor_is_lossy(descriptor)) {
            canvas->bits_cache->ops->put(canvas->bits_cache, descriptor->id, surface);
        } else {
            canvas->bits_cache->ops->put_lossy(canvas->bits_cache, descriptor->id, surface);
        }
#else
        canvas->bits_cache->ops->put(canvas->bits_cache, descriptor->id, surface);
#endif
#ifdef DEBUG_DUMP_SURFACE
        dump_surface(surface, 1);
#endif
#ifdef SW_CANVAS_CACHE
    } else if (descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME) {
        if (spice_image_descriptor_is_lossy(descriptor)) {
            g_warning("invalid cache replace request: the image is lossy");
            return NULL;
        }
        canvas->bits_cache->ops->replace_lossy(canvas->bits_cache, descriptor->id, surface);
#ifdef DEBUG_DUMP_SURFACE
        dump_surface(surface, 1);
#endif
#endif
#ifdef DEBUG_DUMP_SURFACE
    } else if (descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE
#ifdef SW_CANVAS_CACHE
               && descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS
#endif
    ) {

        dump_surface(surface, 0);
#endif
    }

    if (!real_get) {
        pixman_image_unref(surface);
        return NULL;
    }

    if (!saved_want_original) {
        /* Conversion to canvas format was requested, but maybe it didn't
           happen above (due to save/load to cache for instance, or
           maybe the reader didn't support conversion).
           If so we convert here. */

        wanted_format = canvas_get_target_format(canvas,
#ifdef WORDS_BIGENDIAN
                                                 surface_format == PIXMAN_b8g8r8a8 ||
#endif
                                                 surface_format == PIXMAN_a8r8g8b8);

        if (surface_format != wanted_format) {
            converted = surface_create(wanted_format,
                                       pixman_image_get_width(surface),
                                       pixman_image_get_height(surface),
                                       TRUE);
            pixman_image_composite32 (PIXMAN_OP_SRC,
                                      surface, NULL, converted,
                                      0, 0,
                                      0, 0,
                                      0, 0,
                                      pixman_image_get_width(surface),
                                      pixman_image_get_height(surface));
            pixman_image_unref (surface);
            surface = converted;
        }
    }

    return surface;
}

static SpiceCanvas *canvas_get_surface_mask(CanvasBase *canvas, SpiceImage *image)
{
    return canvas_get_surface_mask_internal(canvas, image);
}

static SpiceCanvas *canvas_get_surface(CanvasBase *canvas, SpiceImage *image)
{
    return canvas_get_surface_internal(canvas, image);
}

static pixman_image_t *canvas_get_image(CanvasBase *canvas, SpiceImage *image,
                                        int want_original)
{
    return canvas_get_image_internal(canvas, image, want_original, TRUE);
}

static void canvas_touch_image(CanvasBase *canvas, SpiceImage *image)
{
    canvas_get_image_internal(canvas, image, TRUE, FALSE);
}

static pixman_image_t* canvas_get_image_from_self(SpiceCanvas *canvas,
                                                  int x, int y,
                                                  int32_t width, int32_t height,
                                                  int force_opaque)
{
    CanvasBase *canvas_base = (CanvasBase *)canvas;
    pixman_image_t *surface;
    uint8_t *dest;
    int dest_stride;
    SpiceRect area;
    pixman_format_code_t format;

    format = spice_surface_format_to_pixman (canvas_base->format);
    if (force_opaque)
    {
        /* Set alpha bits of the format to 0 */
        format = (pixman_format_code_t)(((uint32_t)format) & ~(0xf << 12));

        spice_return_val_if_fail (
            pixman_format_supported_destination (format), NULL);
    }

    surface = pixman_image_create_bits(spice_surface_format_to_pixman (canvas_base->format),
                                       width, height, NULL, 0);
    spice_return_val_if_fail(surface != NULL, NULL);

    dest = (uint8_t *)pixman_image_get_data(surface);
    dest_stride = pixman_image_get_stride(surface);

    area.left = x;
    area.top = y;
    area.right = x + width;
    area.bottom = y + height;

    canvas->ops->read_bits(canvas, dest, dest_stride, &area);

    return surface;
}


#ifdef REVERS_BITS_SLOW
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;
}
#else
static inline uint8_t revers_bits(uint8_t byte)
{
    static const uint8_t revers[] = {
        0x0, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
        0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
        0x8, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
        0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
        0x4, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
        0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
        0xc, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
        0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
        0x2, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
        0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
        0xa, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
        0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
        0x6, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
        0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
        0xe, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
        0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
        0x1, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
        0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
        0x9, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
        0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
        0x5, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
        0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
        0xd, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
        0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
        0x3, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
        0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
        0xb, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
        0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
        0x7, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
        0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
        0xf, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
        0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
    };

    return revers[byte];
}
#endif

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(PIXMAN_a1, bitmap->x, bitmap->y, TRUE);
    spice_return_val_if_fail(surface != NULL, NULL);

    spice_chunks_linearize(bitmap->data);
    src_line = bitmap->data->chunk[0].data;
    src_stride = bitmap->stride;
    end_line = src_line + (bitmap->y * src_stride);
    line_size = SPICE_ALIGN(bitmap->x, 8) >> 3;

    dest_stride = pixman_image_get_stride(surface);
    dest_line = (uint8_t *)pixman_image_get_data(surface);
    if (!(bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) {
        spice_return_val_if_fail(bitmap->y > 0, NULL);
        dest_line += dest_stride * ((int)bitmap->y - 1);
        dest_stride = -dest_stride;
    }

    if (invers) {
        switch (bitmap->format) {
        case SPICE_BITMAP_FMT_1BIT_LE:
            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;
        case SPICE_BITMAP_FMT_1BIT_BE:
            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;
            spice_warn_if_reached();
            return NULL;
        }
    } else {
        switch (bitmap->format) {
        case SPICE_BITMAP_FMT_1BIT_LE:
            for (; src_line != end_line; src_line += src_stride, dest_line += dest_stride) {
                memcpy(dest_line, src_line, line_size);
            }
            break;
        case SPICE_BITMAP_FMT_1BIT_BE:
            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;
            spice_warn_if_reached();
            return NULL;
        }
    }
    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;
    uint8_t *src_line, *end_line,  *dest_line;
    int src_stride, line_size, dest_stride;

    spice_return_val_if_fail(pixman_image_get_depth(src_surf) == 1, NULL);

    invers = pixman_image_create_bits(PIXMAN_a1, width, height, NULL, 0);
    spice_return_val_if_fail(invers != NULL, NULL);

    src_line = (uint8_t *)pixman_image_get_data(src_surf);
    src_stride = pixman_image_get_stride(src_surf);
    end_line = src_line + (height * src_stride);
    line_size = SPICE_ALIGN(width, 8) >> 3;
    dest_line = (uint8_t *)pixman_image_get_data(invers);
    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_get_mask(CanvasBase *canvas, SpiceQMask *mask, int *needs_invert_out)
{
    SpiceImage *image;
    pixman_image_t *surface;
    int need_invers;
    int is_invers;
    int cache_me;

    if (needs_invert_out) {
        *needs_invert_out = 0;
    }

    image = mask->bitmap;
    need_invers = mask->flags & SPICE_MASK_FLAGS_INVERS;

#ifdef SW_CANVAS_CACHE
    cache_me = image->descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME;
#else
    cache_me = 0;
#endif

    switch (image->descriptor.type) {
    case SPICE_IMAGE_TYPE_BITMAP: {
        is_invers = need_invers && !cache_me;
        surface = canvas_get_bitmap_mask(canvas, &image->u.bitmap, is_invers);
        break;
    }
    case SPICE_IMAGE_TYPE_FROM_CACHE:
        surface = canvas->bits_cache->ops->get(canvas->bits_cache, image->descriptor.id);
        is_invers = 0;
        break;
#ifdef SW_CANVAS_CACHE
    case SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS:
        surface = canvas->bits_cache->ops->get_lossless(canvas->bits_cache, image->descriptor.id);
        is_invers = 0;
        break;
#endif
    default:
        spice_warn_if_reached();
        return NULL;
    }

    if (cache_me) {
        canvas->bits_cache->ops->put(canvas->bits_cache, image->descriptor.id, surface);
    }

    if (need_invers && !is_invers) { // surface is in cache
        if (needs_invert_out != NULL) {
            *needs_invert_out = TRUE;
        } else {
            pixman_image_t *inv_surf;
            inv_surf = canvas_A1_invers(surface);
            pixman_image_unref(surface);
            surface = inv_surf;
        }
    }

    return surface;
}

static inline void canvas_raster_glyph_box(const SpiceRasterGlyph *glyph, SpiceRect *r)
{
    spice_return_if_fail(r != NULL);

    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;
}

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;
    }
}

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);
    spice_return_if_fail(glyph_box.top >= bounds->top && glyph_box.bottom <= bounds->bottom);
    spice_return_if_fail(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 = SPICE_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 = SPICE_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:
        spice_warn_if_reached();
        return;
    }
}

static pixman_image_t *canvas_get_str_mask(CanvasBase *canvas, SpiceString *str, int bpp, SpicePoint *pos)
{
    SpiceRasterGlyph *glyph;
    SpiceRect bounds;
    pixman_image_t *str_mask;
    uint8_t *dest;
    int dest_stride;
    int i;

    spice_return_val_if_fail(str->length > 0, NULL);

    glyph = str->glyphs[0];
    canvas_raster_glyph_box(glyph, &bounds);

    for (i = 1; i < str->length; i++) {
        SpiceRect glyph_box;

        canvas_raster_glyph_box(str->glyphs[i], &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);
    spice_return_val_if_fail(str_mask != NULL, NULL);

    dest = (uint8_t *)pixman_image_get_data(str_mask);
    dest_stride = pixman_image_get_stride(str_mask);
    for (i = 0; i < str->length; i++) {
        glyph = str->glyphs[i];
        canvas_put_glyph_bits(glyph, bpp, dest, dest_stride, &bounds);
    }

    pos->x = bounds.left;
    pos->y = bounds.top;
    return str_mask;
}

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;
    pixman_format_code_t format;
    double sx, sy;

    spice_return_val_if_fail(spice_pixman_image_get_format (src, &format), NULL);

    surface = pixman_image_create_bits(format, width, height, NULL, 0);
    spice_return_val_if_fail(surface != NULL, NULL);

    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);
    spice_return_val_if_fail(scale_mode == SPICE_IMAGE_SCALE_MODE_INTERPOLATE || scale_mode == SPICE_IMAGE_SCALE_MODE_NEAREST, NULL);
    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_transform_init_identity(&transform);
    pixman_image_set_transform(src, &transform);

    return surface;
}

SPICE_GNUC_NORETURN
SPICE_GNUC_PRINTF(2, 3) 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);
}

SPICE_GNUC_PRINTF(2, 3) 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 spice_malloc(size);
}

static void quic_usr_free(QuicUsrContext *usr, void *ptr)
{
    free(ptr);
}

SPICE_GNUC_PRINTF(2, 3) 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);
}

SPICE_GNUC_NORETURN
SPICE_GNUC_PRINTF(2, 3) 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 spice_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;
}

static int quic_usr_more_space(QuicUsrContext *usr, uint32_t **io_ptr, int rows_completed)
{
    QuicData *quic_data = (QuicData *)usr;

    if (quic_data->current_chunk == quic_data->chunks->num_chunks -1) {
        return 0;
    }
    quic_data->current_chunk++;

    *io_ptr = SPICE_ALIGNED_CAST(uint32_t *,
                                 quic_data->chunks->chunk[quic_data->current_chunk].data);
    return quic_data->chunks->chunk[quic_data->current_chunk].len >> 2;
}


static int quic_usr_more_lines(QuicUsrContext *usr, uint8_t **lines)
{
    return 0;
}

static void canvas_base_destroy(CanvasBase *canvas)
{
    quic_destroy(canvas->quic_data.quic);
    lz_destroy(canvas->lz_data.lz);
}

static void canvas_clip_pixman(CanvasBase *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 = clip->rects->num_rects;
        SpiceRect *now = clip->rects->rects;

        pixman_region32_t pixman_clip;

        if (spice_pixman_region32_init_rects(&pixman_clip, now, n)) {
            pixman_region32_intersect(dest_region, dest_region, &pixman_clip);
            pixman_region32_fini(&pixman_clip);
        }

        break;
    }
    default:
        spice_warn_if_reached();
        return;
    }
}

static void canvas_mask_pixman(CanvasBase *canvas,
                               pixman_region32_t *dest_region,
                               SpiceQMask *mask, int x, int y)
{
    SpiceCanvas *surface_canvas;
    pixman_image_t *image, *subimage;
    int needs_invert;
    pixman_region32_t mask_region;
    uint32_t *mask_data;
    uint8_t *mask_data_src;
    int mask_x, mask_y;
    int mask_width, mask_height, mask_stride;
    pixman_box32_t extents;

    if (!mask->bitmap) {
        return;
    }

    surface_canvas = canvas_get_surface_mask(canvas, mask->bitmap);
    if (surface_canvas) {
        needs_invert = mask->flags & SPICE_MASK_FLAGS_INVERS;
        image = surface_canvas->ops->get_image(surface_canvas, FALSE);
    } else {
        needs_invert = FALSE;
        image = canvas_get_mask(canvas,
                                mask,
                                &needs_invert);
    }

    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_src is surely aligned to 4 bytes:
     * - pixman requires mask_data (pixman_image_get_data) to be
     *   aligned to 4 bytes;
     * - pixman requires mask_stride (pixman_image_get_stride) to be
     *   multiple of 4;
     * - extents.x1 is multiple of 32 (see previous line) so
     *   extents.x1 / 8 is multiple of 4.
     */
    mask_data_src = (uint8_t *)mask_data + mask_stride * extents.y1 + extents.x1 / 8;
    mask_data = SPICE_ALIGNED_CAST(uint32_t *, mask_data_src);

    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 void draw_brush(SpiceCanvas *canvas,
                       pixman_region32_t *region,
                       SpiceBrush *brush,
                       SpiceROP rop)
{
    CanvasBase *canvas_base = (CanvasBase *)canvas;
    uint32_t color;
    SpicePattern *pattern;
    pixman_image_t *tile;
    int offset_x, offset_y;
    pixman_box32_t *rects;
    int n_rects;

    rects = pixman_region32_rectangles(region, &n_rects);

    switch (brush->type) {
    case SPICE_BRUSH_TYPE_SOLID:
        color = brush->u.color;
        if (rop == SPICE_ROP_COPY) {
            canvas->ops->fill_solid_rects(canvas, rects, n_rects, color);
        } else {
            canvas->ops->fill_solid_rects_rop(canvas, rects, n_rects, color, rop);
        }
        break;
    case SPICE_BRUSH_TYPE_PATTERN: {
        SpiceCanvas *surface_canvas;

        pattern = &brush->u.pattern;
        offset_x = pattern->pos.x;
        offset_y = pattern->pos.y;

        surface_canvas = canvas_get_surface(canvas_base, pattern->pat);
        if (surface_canvas) {
            if (rop == SPICE_ROP_COPY) {
                canvas->ops->fill_tiled_rects_from_surface(canvas, rects, n_rects, surface_canvas,
                                                           offset_x, offset_y);
            } else {
                canvas->ops->fill_tiled_rects_rop_from_surface(canvas, rects, n_rects,
                                                               surface_canvas, offset_x, offset_y,
                                                               rop);
            }
        } else {
            tile = canvas_get_image(canvas_base, pattern->pat, FALSE);
            spice_return_if_fail(tile != NULL);

            if (rop == SPICE_ROP_COPY) {
                canvas->ops->fill_tiled_rects(canvas, rects, n_rects, tile, offset_x, offset_y);
            } else {
                canvas->ops->fill_tiled_rects_rop(canvas, rects, n_rects,
                                                  tile, offset_x, offset_y, rop);
            }
            pixman_image_unref(tile);
        }
        break;
    }
    case SPICE_BRUSH_TYPE_NONE:
        /* Still need to do *something* here, because rop could be e.g invert dest */
        canvas->ops->fill_solid_rects_rop(canvas, rects, n_rects, 0, rop);
        break;
    default:
        spice_warn_if_reached();
        return;
    }
}

/* 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(CanvasBase *canvas, SpiceBrush *brush)
{
    SpicePattern *pattern;

    if (brush->type == SPICE_BRUSH_TYPE_PATTERN) {
        pattern = &brush->u.pattern;
        canvas_touch_image(canvas, pattern->pat);
    }
}

static void canvas_draw_fill(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceFill *fill)
{
    CanvasBase *canvas = (CanvasBase *)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_descriptor,
                                 ROP_INPUT_BRUSH,
                                 ROP_INPUT_DEST);

    if (rop == SPICE_ROP_NOOP || !pixman_region32_not_empty(&dest_region)) {
        touch_brush(canvas, &fill->brush);
        pixman_region32_fini(&dest_region);
        return;
    }

    draw_brush(spice_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)
{
    CanvasBase *canvas = (CanvasBase *)spice_canvas;
    pixman_region32_t dest_region;
    SpiceCanvas *surface_canvas;
    pixman_image_t *src_image;
    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, &copy->mask,
                       bbox->left, bbox->top);

    rop = ropd_descriptor_to_rop(copy->rop_descriptor,
                                 ROP_INPUT_SRC,
                                 ROP_INPUT_DEST);

    if (rop == SPICE_ROP_NOOP || !pixman_region32_not_empty(&dest_region)) {
        canvas_touch_image(canvas, copy->src_bitmap);
        pixman_region32_fini(&dest_region);
        return;
    }

    surface_canvas = canvas_get_surface(canvas, copy->src_bitmap);
    if (surface_canvas) {
        if (rect_is_same_size(bbox, &copy->src_area)) {
            if (rop == SPICE_ROP_COPY) {
                spice_canvas->ops->blit_image_from_surface(spice_canvas, &dest_region,
                                                           surface_canvas,
                                                           bbox->left - copy->src_area.left,
                                                           bbox->top - copy->src_area.top);
            } else {
                spice_canvas->ops->blit_image_rop_from_surface(spice_canvas, &dest_region,
                                                               surface_canvas,
                                                               bbox->left - copy->src_area.left,
                                                               bbox->top - copy->src_area.top,
                                                               rop);
            }
        } else {
            if (rop == SPICE_ROP_COPY) {
                spice_canvas->ops->scale_image_from_surface(spice_canvas, &dest_region,
                                                            surface_canvas,
                                                            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 {
                spice_canvas->ops->scale_image_rop_from_surface(spice_canvas, &dest_region,
                                                                surface_canvas,
                                                                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);
            }
        }
    } else {
        src_image = canvas_get_image(canvas, copy->src_bitmap, FALSE);
        spice_return_if_fail(src_image != NULL);

        if (rect_is_same_size(bbox, &copy->src_area)) {
            if (rop == SPICE_ROP_COPY) {
                spice_canvas->ops->blit_image(spice_canvas, &dest_region,
                                              src_image,
                                              bbox->left - copy->src_area.left,
                                              bbox->top - copy->src_area.top);
            } else {
                spice_canvas->ops->blit_image_rop(spice_canvas, &dest_region,
                                                  src_image,
                                                  bbox->left - copy->src_area.left,
                                                  bbox->top - copy->src_area.top,
                                                  rop);
            }
        } else {
            if (rop == SPICE_ROP_COPY) {
                spice_canvas->ops->scale_image(spice_canvas, &dest_region,
                                               src_image,
                                               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 {
                spice_canvas->ops->scale_image_rop(spice_canvas, &dest_region,
                                                   src_image,
                                                   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_image_unref(src_image);
    }
    pixman_region32_fini(&dest_region);
}

static void canvas_draw_transparent(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceTransparent* transparent)
{
    CanvasBase *canvas = (CanvasBase *)spice_canvas;
    SpiceCanvas *surface_canvas;
    pixman_image_t *src_image;
    pixman_region32_t dest_region;
    uint32_t transparent_color;

    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, transparent->src_bitmap);
        pixman_region32_fini(&dest_region);
        return;
    }

    switch (canvas->format) {
    case SPICE_SURFACE_FMT_32_xRGB:
    case SPICE_SURFACE_FMT_32_ARGB:
        transparent_color = transparent->true_color;
        break;
    case SPICE_SURFACE_FMT_16_555:
        transparent_color = rgb_32_to_16_555(transparent->true_color);
        break;
    case SPICE_SURFACE_FMT_16_565:
        transparent_color = rgb_32_to_16_565(transparent->true_color);
        break;
    default:
        transparent_color = 0;
    }

    surface_canvas = canvas_get_surface(canvas, transparent->src_bitmap);
    if (surface_canvas) {
        if (rect_is_same_size(bbox, &transparent->src_area)) {
            spice_canvas->ops->colorkey_image_from_surface(spice_canvas, &dest_region,
                                                           surface_canvas,
                                                           bbox->left - transparent->src_area.left,
                                                           bbox->top - transparent->src_area.top,
                                                           transparent_color);
        } else {
            spice_canvas->ops->colorkey_scale_image_from_surface(spice_canvas, &dest_region,
                                                                 surface_canvas,
                                                                 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_color);
        }
    } else {
        src_image = canvas_get_image(canvas, transparent->src_bitmap, FALSE);
        spice_return_if_fail(src_image != NULL);

        if (rect_is_same_size(bbox, &transparent->src_area)) {
            spice_canvas->ops->colorkey_image(spice_canvas, &dest_region,
                                              src_image,
                                              bbox->left - transparent->src_area.left,
                                              bbox->top - transparent->src_area.top,
                                              transparent_color);
        } else {
            spice_canvas->ops->colorkey_scale_image(spice_canvas, &dest_region,
                                                    src_image,
                                                    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_color);
        }
        pixman_image_unref(src_image);
    }
    pixman_region32_fini(&dest_region);
}

static void canvas_draw_alpha_blend(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceAlphaBlend* alpha_blend)
{
    CanvasBase *canvas = (CanvasBase *)spice_canvas;
    pixman_region32_t dest_region;
    SpiceCanvas *surface_canvas;
    pixman_image_t *src_image;

    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_not_empty(&dest_region)) {
        canvas_touch_image(canvas, alpha_blend->src_bitmap);
        pixman_region32_fini(&dest_region);
        return;
    }

    surface_canvas = canvas_get_surface(canvas, alpha_blend->src_bitmap);
    if (surface_canvas) {
        if (rect_is_same_size(bbox, &alpha_blend->src_area)) {
            spice_canvas->ops->blend_image_from_surface(spice_canvas, &dest_region,
                                                        alpha_blend->alpha_flags & SPICE_ALPHA_FLAGS_DEST_HAS_ALPHA,
                                                        surface_canvas,
                                                        alpha_blend->alpha_flags & SPICE_ALPHA_FLAGS_SRC_SURFACE_HAS_ALPHA,
                                                        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 {
            spice_canvas->ops->blend_scale_image_from_surface(spice_canvas, &dest_region,
                                                              alpha_blend->alpha_flags & SPICE_ALPHA_FLAGS_DEST_HAS_ALPHA,
                                                              surface_canvas,
                                                              alpha_blend->alpha_flags & SPICE_ALPHA_FLAGS_SRC_SURFACE_HAS_ALPHA,
                                                              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_NEAREST,
                                                              alpha_blend->alpha);
        }
     } else {
        src_image = canvas_get_image(canvas, alpha_blend->src_bitmap, TRUE);
        spice_return_if_fail(src_image != NULL);

        if (rect_is_same_size(bbox, &alpha_blend->src_area)) {
            spice_canvas->ops->blend_image(spice_canvas, &dest_region,
                                           alpha_blend->alpha_flags & SPICE_ALPHA_FLAGS_DEST_HAS_ALPHA,
                                           src_image,
                                           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 {
            spice_canvas->ops->blend_scale_image(spice_canvas, &dest_region,
                                                 alpha_blend->alpha_flags & SPICE_ALPHA_FLAGS_DEST_HAS_ALPHA,
                                                 src_image,
                                                 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_NEAREST,
                                                 alpha_blend->alpha);
        }

        pixman_image_unref(src_image);
    }

    pixman_region32_fini(&dest_region);
}

static void canvas_draw_opaque(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceOpaque *opaque)
{
    CanvasBase *canvas = (CanvasBase *)spice_canvas;
    pixman_image_t *src_image;
    pixman_region32_t dest_region;
    SpiceCanvas *surface_canvas;
    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_descriptor,
                                 ROP_INPUT_BRUSH,
                                 ROP_INPUT_SRC);

    if (rop == SPICE_ROP_NOOP || !pixman_region32_not_empty(&dest_region)) {
        canvas_touch_image(canvas, opaque->src_bitmap);
        touch_brush(canvas, &opaque->brush);
        pixman_region32_fini(&dest_region);
        return;
    }

    surface_canvas = canvas_get_surface(canvas, opaque->src_bitmap);
    if (surface_canvas) {
        if (rect_is_same_size(bbox, &opaque->src_area)) {
            spice_canvas->ops->blit_image_from_surface(spice_canvas, &dest_region,
                                                       surface_canvas,
                                                       bbox->left - opaque->src_area.left,
                                                       bbox->top - opaque->src_area.top);
        } else {
            spice_canvas->ops->scale_image_from_surface(spice_canvas, &dest_region,
                                                        surface_canvas,
                                                        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);
        }
    } else {
        src_image = canvas_get_image(canvas, opaque->src_bitmap, FALSE);
        spice_return_if_fail(src_image != NULL);

        if (rect_is_same_size(bbox, &opaque->src_area)) {
            spice_canvas->ops->blit_image(spice_canvas, &dest_region,
                                          src_image,
                                          bbox->left - opaque->src_area.left,
                                          bbox->top - opaque->src_area.top);
        } else {
            spice_canvas->ops->scale_image(spice_canvas, &dest_region,
                                           src_image,
                                           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);
        }
        pixman_image_unref(src_image);
    }

    draw_brush(spice_canvas, &dest_region, &opaque->brush, rop);

    pixman_region32_fini(&dest_region);
}

static void canvas_draw_blackness(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceBlackness *blackness)
{
    CanvasBase *canvas = (CanvasBase *)spice_canvas;
    pixman_region32_t dest_region;
    pixman_box32_t *rects;
    int n_rects;

   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_not_empty(&dest_region)) {
        pixman_region32_fini (&dest_region);
        return;
    }

    rects = pixman_region32_rectangles(&dest_region, &n_rects);

    spice_canvas->ops->fill_solid_rects(spice_canvas, rects, n_rects, 0x000000);

    pixman_region32_fini(&dest_region);
}

static void canvas_draw_whiteness(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceWhiteness *whiteness)
{
    CanvasBase *canvas = (CanvasBase *)spice_canvas;
    pixman_region32_t dest_region;
    pixman_box32_t *rects;
    int n_rects;

    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_not_empty(&dest_region)) {
        pixman_region32_fini(&dest_region);
        return;
    }

    rects = pixman_region32_rectangles(&dest_region, &n_rects);
    spice_canvas->ops->fill_solid_rects(spice_canvas, rects, n_rects, 0xffffffff);

    pixman_region32_fini(&dest_region);
}

static void canvas_draw_invers(SpiceCanvas *spice_canvas, SpiceRect *bbox, SpiceClip *clip, SpiceInvers *invers)
{
    CanvasBase *canvas = (CanvasBase *)spice_canvas;
    pixman_region32_t dest_region;
    pixman_box32_t *rects;
    int n_rects;

    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_not_empty(&dest_region)) {
        pixman_region32_fini(&dest_region);
        return;
    }

    rects = pixman_region32_rectangles(&dest_region, &n_rects);
    spice_canvas->ops->fill_solid_rects_rop(spice_canvas, rects, n_rects, 0x00000000,
                                            SPICE_ROP_INVERT);

    pixman_region32_fini(&dest_region);
}

typedef struct {
    lineGC base;
    SpiceCanvas *canvas;
    pixman_region32_t dest_region;
    SpiceROP fore_rop;
    SpiceROP back_rop;
    int solid;
    uint32_t color;
    int use_surface_canvas;
    union {
        SpiceCanvas *surface_canvas;
        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)
{
    SpiceCanvas *canvas;
    StrokeGC *strokeGC;
    int i;
    SpiceROP rop;

    strokeGC = (StrokeGC *)pGC;
    canvas = strokeGC->canvas;

    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;
    }

    if (strokeGC->solid) {
        if (rop == SPICE_ROP_COPY) {
            canvas->ops->fill_solid_spans(canvas, points, widths, num_spans,
                                          strokeGC->color);
        } else {
            for (i = 0; i < num_spans; i++) {
                pixman_box32_t r;
                r.x1 = points[i].x;
                r.y1 = points[i].y;
                r.x2 = points[i].x + widths[i];
                r.y2 = r.y1 + 1;
                canvas->ops->fill_solid_rects_rop(canvas, &r, 1,
                                                  strokeGC->color, rop);
            }
        }
    } else {
        if (rop == SPICE_ROP_COPY) {
            for (i = 0; i < num_spans; i++) {
                pixman_box32_t r;
                r.x1 = points[i].x;
                r.y1 = points[i].y;
                r.x2 = points[i].x + widths[i];
                r.y2 = r.y1 + 1;
                canvas->ops->fill_tiled_rects(canvas, &r, 1,
                                              strokeGC->tile,
                                              strokeGC->tile_offset_x,
                                              strokeGC->tile_offset_y);
            }
        } else {
            for (i = 0; i < num_spans; i++) {
                pixman_box32_t r;
                r.x1 = points[i].x;
                r.y1 = points[i].y;
                r.x2 = points[i].x + widths[i];
                r.y2 = r.y1 + 1;
                canvas->ops->fill_tiled_rects_rop(canvas, &r, 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)
{
    SpiceCanvas *canvas;
    pixman_region32_t area;
    pixman_box32_t *boxes;
    StrokeGC *strokeGC;
    SpiceROP rop;
    int i;
    pixman_box32_t *area_rects;
    int n_area_rects;

    strokeGC = (StrokeGC *)pGC;
    canvas = strokeGC->canvas;

    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 = spice_new(pixman_box32_t, num_rects);
    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);

    if (strokeGC->solid) {
        if (rop == SPICE_ROP_COPY) {
            canvas->ops->fill_solid_rects(canvas, area_rects, n_area_rects,
                                          strokeGC->color);
        } else {
            canvas->ops->fill_solid_rects_rop(canvas, area_rects, n_area_rects,
                                              strokeGC->color, rop);
        }
    } else {
        if (rop == SPICE_ROP_COPY) {
            if (strokeGC->use_surface_canvas) {
                canvas->ops->fill_tiled_rects_from_surface(canvas, area_rects, n_area_rects,
                                                           strokeGC->surface_canvas,
                                                           strokeGC->tile_offset_x,
                                                           strokeGC->tile_offset_y);
            } else {
                canvas->ops->fill_tiled_rects(canvas, area_rects, n_area_rects,
                                              strokeGC->tile,
                                              strokeGC->tile_offset_x,
                                              strokeGC->tile_offset_y);
            }
        } else {
            if (strokeGC->use_surface_canvas) {
                canvas->ops->fill_tiled_rects_rop_from_surface(canvas, area_rects, n_area_rects,
                                                               strokeGC->surface_canvas,
                                                               strokeGC->tile_offset_x,
                                                               strokeGC->tile_offset_y,
                                                               rop);
            } else {
                canvas->ops->fill_tiled_rects_rop(canvas, area_rects, n_area_rects,
                                                  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 = spice_new(SpicePoint, 10);
    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 = spice_renew(SpicePoint, lines->points, lines->size);
    }
    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)
{
    CanvasBase *canvas = (CanvasBase *)spice_canvas;
    SpiceCanvas *surface_canvas = NULL;
    StrokeGC gc;
    lineGCOps ops = {
        stroke_fill_spans,
        stroke_fill_rects
    };
    StrokeLines lines;
    unsigned int i;
    int dashed;

    memset(&gc, 0, sizeof(gc));

    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_not_empty(&gc.dest_region)) {
        touch_brush(canvas, &stroke->brush);
        pixman_region32_fini(&gc.dest_region);
        return;
    }

    gc.canvas = spice_canvas;
    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.base.width = canvas->width;
    gc.base.height = canvas->height;
    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 = 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 *)spice_malloc(nseg);
        gc.base.numInDashList = nseg;

        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 < (unsigned int)(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;
        surface_canvas = canvas_get_surface(canvas,
                                            stroke->brush.u.pattern.pat);
        if (surface_canvas) {
            gc.use_surface_canvas = TRUE;
            gc.surface_canvas = surface_canvas;
        } else {
            gc.use_surface_canvas = FALSE;
            gc.tile = canvas_get_image(canvas,
                                       stroke->brush.u.pattern.pat,
                                       FALSE);
        }
        gc.tile_offset_x = stroke->brush.u.pattern.pos.x;
        gc.tile_offset_y = stroke->brush.u.pattern.pos.y;
        break;
    default:
        spice_warn_if_reached();
        return;
    }

    stroke_lines_init(&lines);

    for (i = 0; i < stroke->path->num_segments; i++) {
        SpicePathSeg *seg = stroke->path->segments[i];
        SpicePointFix* point, *end_point;

        point = seg->points;
        end_point = point + seg->count;

        if (seg->flags & SPICE_PATH_BEGIN) {
            stroke_lines_draw(&lines, (lineGC *)&gc, dashed);
            stroke_lines_append_fix(&lines, point);
            point++;
        }

        if (seg->flags & SPICE_PATH_BEZIER) {
            spice_return_if_fail((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 (seg->flags & SPICE_PATH_END) {
            if (seg->flags & SPICE_PATH_CLOSE) {
                stroke_lines_append(&lines,
                                    lines.points[0].x, lines.points[0].y);
            }
            stroke_lines_draw(&lines, (lineGC *)&gc, dashed);
        }
    }

    stroke_lines_draw(&lines, (lineGC *)&gc, dashed);

    free(gc.base.dash);
    stroke_lines_free(&lines);

    if (!gc.solid && gc.tile && !surface_canvas) {
        pixman_image_unref(gc.tile);
    }

    pixman_region32_fini(&gc.dest_region);
}


//need surfaces handling here !!!
static void canvas_draw_rop3(SpiceCanvas *spice_canvas, SpiceRect *bbox,
                             SpiceClip *clip, SpiceRop3 *rop3)
{
    CanvasBase *canvas = (CanvasBase *)spice_canvas;
    SpiceCanvas *surface_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_get_image_from_self(spice_canvas, bbox->left, bbox->top, width, heigth, FALSE);
    surface_canvas = canvas_get_surface(canvas, rop3->src_bitmap);
    if (surface_canvas) {
        s = surface_canvas->ops->get_image(surface_canvas, FALSE);
    } else {
        s = canvas_get_image(canvas, rop3->src_bitmap, FALSE);
    }

    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) {
        spice_critical("bad src bitmap size");
        return;
    }
    if (rop3->brush.type == SPICE_BRUSH_TYPE_PATTERN) {
        SpiceCanvas *_surface_canvas;
        pixman_image_t *p;

        _surface_canvas = canvas_get_surface(canvas, rop3->brush.u.pattern.pat);
        if (_surface_canvas) {
            p = _surface_canvas->ops->get_image(_surface_canvas, FALSE);
        } else {
            p = canvas_get_image(canvas, rop3->brush.u.pattern.pat, FALSE);
        }
        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 {
        do_rop3_with_color(rop3->rop3, d, s, &src_pos, rop3->brush.u.color);
    }
    pixman_image_unref(s);

    spice_canvas->ops->blit_image(spice_canvas, &dest_region, d,
                                  bbox->left,
                                  bbox->top);

    pixman_image_unref(d);

    pixman_region32_fini(&dest_region);
}

static void transform_to_pixman_transform(SpiceTransform *transform,
                                          pixman_transform_t *p)
{
    p->matrix[0][0] = transform->t00;
    p->matrix[0][1] = transform->t01;
    p->matrix[0][2] = transform->t02;
    p->matrix[1][0] = transform->t10;
    p->matrix[1][1] = transform->t11;
    p->matrix[1][2] = transform->t12;
    p->matrix[2][0] = 0;
    p->matrix[2][1] = 0;
    p->matrix[2][2] = pixman_fixed_1;
}

#define MASK(lo, hi)                                                    \
    (((1U << (hi)) - 1) - (((1U << (lo))) - 1))

#define EXTRACT(v, lo, hi)                                              \
    ((v & MASK(lo, hi)) >> lo)

static void canvas_draw_composite(SpiceCanvas *spice_canvas, SpiceRect *bbox,
                                  SpiceClip *clip, SpiceComposite *composite)
{
    CanvasBase *canvas = (CanvasBase *)spice_canvas;
    SpiceCanvas *surface_canvas;
    pixman_region32_t dest_region;
    pixman_image_t *d;
    pixman_image_t *s;
    pixman_image_t *m;
    pixman_repeat_t src_repeat;
    pixman_filter_t src_filter;
    pixman_op_t op;
    pixman_transform_t transform;
    int width, height;

    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);

    width = bbox->right - bbox->left;
    height = bbox->bottom - bbox->top;

    /* Dest */
    d = canvas_get_image_from_self(spice_canvas, bbox->left, bbox->top, width, height,
                                   (composite->flags & SPICE_COMPOSITE_DEST_OPAQUE));

    /* Src */
    surface_canvas = canvas_get_surface(canvas, composite->src_bitmap);
    if (surface_canvas) {
        s = surface_canvas->ops->get_image(surface_canvas,
                                           (composite->flags & SPICE_COMPOSITE_SOURCE_OPAQUE));
    } else {
        s = canvas_get_image(canvas, composite->src_bitmap, FALSE);
    }
    if (composite->flags & SPICE_COMPOSITE_HAS_SRC_TRANSFORM)
    {
        transform_to_pixman_transform (&composite->src_transform, &transform);
        pixman_image_set_transform (s, &transform);
    }
    src_filter = (pixman_filter_t) EXTRACT (composite->flags, 8, 11);
    src_repeat = (pixman_repeat_t) EXTRACT (composite->flags, 14, 16);
    pixman_image_set_filter (s, src_filter, NULL, 0);
    pixman_image_set_repeat (s, src_repeat);

    /* Mask */
    m = NULL;
    if (composite->flags & SPICE_COMPOSITE_HAS_MASK) {
        pixman_filter_t mask_filter = (pixman_filter_t) EXTRACT (composite->flags, 11, 14);
        pixman_repeat_t mask_repeat = (pixman_repeat_t) EXTRACT (composite->flags, 16, 18);
        pixman_bool_t component_alpha = EXTRACT (composite->flags, 18, 19);

        surface_canvas = canvas_get_surface(canvas, composite->mask_bitmap);
        if (surface_canvas) {
            m = surface_canvas->ops->get_image(surface_canvas, FALSE);
        } else {
            m = canvas_get_image(canvas, composite->mask_bitmap, FALSE);
        }

        if (composite->flags & SPICE_COMPOSITE_HAS_MASK_TRANSFORM) {
            transform_to_pixman_transform (&composite->mask_transform, &transform);
            pixman_image_set_transform (m, &transform);
        }

        pixman_image_set_repeat (m, mask_repeat);
        pixman_image_set_filter (m, mask_filter, NULL, 0);
        pixman_image_set_component_alpha (m, component_alpha);
    }

    op = (pixman_op_t) EXTRACT (composite->flags, 0, 8);

    pixman_image_composite32 (op, s, m, d,
                              composite->src_origin.x, composite->src_origin.y,
                              composite->mask_origin.x, composite->mask_origin.y,
                              0, 0, width, height);

    pixman_image_unref(s);
    if (m)
        pixman_image_unref(m);

    spice_canvas->ops->blit_image(spice_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)
{
    CanvasBase *canvas = (CanvasBase *)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,
                                  canvas->width,
                                  canvas->height);
        pixman_region32_intersect(&dest_region, &dest_region, &src_region);
        pixman_region32_fini(&src_region);

        spice_canvas->ops->copy_region(spice_canvas, &dest_region, dx, dy);
    }

    pixman_region32_fini(&dest_region);
}



static void canvas_base_group_start(SpiceCanvas *spice_canvas, QRegion *region)
{
    CanvasBase *canvas = (CanvasBase *)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,
                              canvas->width,
                              canvas->height);

    pixman_region32_intersect(&canvas->canvas_region, &canvas->canvas_region, region);
}

static void canvas_base_group_end(SpiceCanvas *spice_canvas)
{
    CanvasBase *canvas = (CanvasBase *)spice_canvas;
    pixman_region32_fini(&canvas->canvas_region);
    pixman_region32_init_rect(&canvas->canvas_region,
                              0, 0,
                              canvas->width,
                              canvas->height);
}


static void unimplemented_op(SpiceCanvas *canvas)
{
    spice_critical("unimplemented canvas operation");
}

inline static void canvas_base_init_ops(SpiceCanvasOps *ops)
{
    void **ops_cast;
    unsigned i;

    ops_cast = (void **)ops;
    for (i = 0; i < sizeof(SpiceCanvasOps) / sizeof(void *); i++) {
        ops_cast[i] = (void *) unimplemented_op;
    }

    ops->draw_fill = canvas_draw_fill;
    ops->draw_copy = canvas_draw_copy;
    ops->draw_opaque = canvas_draw_opaque;
    ops->copy_bits = canvas_copy_bits;
    ops->draw_blend = canvas_draw_copy; // copy and blend are the same
    ops->draw_blackness = canvas_draw_blackness;
    ops->draw_whiteness = canvas_draw_whiteness;
    ops->draw_invers = canvas_draw_invers;
    ops->draw_transparent = canvas_draw_transparent;
    ops->draw_alpha_blend = canvas_draw_alpha_blend;
    ops->draw_stroke = canvas_draw_stroke;
    ops->draw_rop3 = canvas_draw_rop3;
    ops->draw_composite = canvas_draw_composite;
    ops->group_start = canvas_base_group_start;
    ops->group_end = canvas_base_group_end;
}

static int canvas_base_init(CanvasBase *canvas, SpiceCanvasOps *ops,
                            int width, int height, uint32_t format
                            , SpiceImageCache *bits_cache
#ifdef SW_CANVAS_CACHE
                            , SpicePaletteCache *palette_cache
#endif
                            , SpiceImageSurfaces *surfaces
                            , SpiceGlzDecoder *glz_decoder
                            , SpiceJpegDecoder *jpeg_decoder
                            , SpiceZlibDecoder *zlib_decoder
                            )
{
    canvas->parent.ops = ops;
    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;
    if (!(canvas->quic_data.quic = quic_create(&canvas->quic_data.usr))) {
            return 0;
    }

    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;
    }

    canvas->surfaces = surfaces;
    canvas->glz_data.decoder = glz_decoder;
    canvas->jpeg = jpeg_decoder;
    canvas->zlib = zlib_decoder;

    canvas->format = format;

    /* TODO: This is all wrong now */
    if (SPICE_SURFACE_FMT_DEPTH(format) == 16) {
        canvas->color_shift = 5;
        canvas->color_mask = 0x1f;
    } else {
        canvas->color_shift = 8;
        canvas->color_mask = 0xff;
    }

    canvas->width = width;
    canvas->height = height;
    pixman_region32_init_rect(&canvas->canvas_region,
                              0, 0,
                              canvas->width,
                              canvas->height);

    canvas->bits_cache = bits_cache;
#ifdef SW_CANVAS_CACHE
    canvas->palette_cache = palette_cache;
#endif

    return 1;
}
