/* 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 . */ #include "common.h" #include "red_drawable.h" #include "pixels_source_p.h" #include "debug.h" #include "x_platform.h" #include "utils.h" #ifdef USE_OGL #include "gl_utils.h" #include #include #include static inline void copy_to_gldrawable_from_gltexture(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y) { float text_x1, text_x2; float text_y1, text_y2; int vertex_x1, vertex_x2; int vertex_y1, vertex_y2; GLXPbuffer pbuffer; GLXContext context; RenderType rendertype; Window win; text_x1 = (float)src_x / source->gl.width_powed; text_x2 = text_x1 + (float)(area.right - area.left) / source->gl.width_powed; text_y1 = ((float)source->gl.height - (area.bottom - area.top) - src_y) / source->gl.height_powed; text_y2 = text_y1 + (float)(area.bottom - area.top) / source->gl.height_powed; vertex_x1 = area.left + offset.x; vertex_y1 = dest->source.x_drawable.height - (area.top + offset.y) - (area.bottom - area.top); vertex_x2 = vertex_x1 + (area.right - area.left); vertex_y2 = vertex_y1 + (area.bottom - area.top); glEnable(GL_TEXTURE_2D); rendertype = source->gl.rendertype; if (rendertype == RENDER_TYPE_FBO) { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } else { win = source->gl.win; context = source->gl.context; glXMakeCurrent(XPlatform::get_display(), win, context); } glBindTexture(GL_TEXTURE_2D, source->gl.tex); glBegin(GL_QUADS); glTexCoord2f(text_x1, text_y1); glVertex2i(vertex_x1, vertex_y1); glTexCoord2f(text_x1, text_y2); glVertex2i(vertex_x1, vertex_y2); glTexCoord2f(text_x2, text_y2); glVertex2i(vertex_x2, vertex_y2); glTexCoord2f(text_x2, text_y1); glVertex2i(vertex_x2, vertex_y1); glEnd(); if (rendertype == RENDER_TYPE_FBO) { GLuint fbo; fbo = source->gl.fbo; glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); } else { pbuffer = source->gl.pbuff; glXMakeCurrent(XPlatform::get_display(), pbuffer, context); } } static inline void copy_to_gldrawable_from_pixmap(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y) { uint8_t *addr; GLXContext context = NULL; GLXPbuffer pbuffer; RenderType rendertype; Window win; rendertype = dest->source.x_drawable.rendertype; if (rendertype == RENDER_TYPE_FBO) { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glBindTexture(GL_TEXTURE_2D, 0); } else { context = dest->source.x_drawable.context; win = dest->source.x_drawable.drawable; glXMakeCurrent(XPlatform::get_display(), win, context); glDisable(GL_TEXTURE_2D); } glPixelStorei(GL_UNPACK_ROW_LENGTH, pixman_image_get_stride(source->pixmap.pixman_image) / 4); glPixelZoom(1, -1); addr = (uint8_t *)pixman_image_get_data(source->pixmap.pixman_image); addr += (src_x * 4 + src_y * pixman_image_get_stride(source->pixmap.pixman_image)); glWindowPos2i(area.left + offset.x, dest->source.x_drawable.height - (area.top + offset.y)); //+ (area.bottom - area.top))); glDrawPixels(area.right - area.left, area.bottom - area.top, GL_BGRA, GL_UNSIGNED_BYTE, addr); if (rendertype == RENDER_TYPE_FBO) { GLuint fbo; fbo = dest->source.x_drawable.fbo; glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); } else { pbuffer = dest->source.x_drawable.pbuff; glXMakeCurrent(XPlatform::get_display(), pbuffer, context); } } #endif // USE_OGL static inline void copy_to_drawable_from_drawable(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y) { XGCValues gc_vals; gc_vals.function = GXcopy; XChangeGC(XPlatform::get_display(), dest->source.x_drawable.gc, GCFunction, &gc_vals); XCopyArea(XPlatform::get_display(), source->x_drawable.drawable, dest->source.x_drawable.drawable, dest->source.x_drawable.gc, src_x, src_y, area.right - area.left, area.bottom - area.top, area.left + offset.x, area.top + offset.y); } static XImage *create_temp_image(int screen, int width, int height, pixman_image_t **pixman_image_out, XShmSegmentInfo **shminfo_out) { XImage *image; XShmSegmentInfo *shminfo; RedDrawable::Format format; pixman_image_t *pixman_image; XVisualInfo *vinfo; image = NULL; shminfo = NULL; vinfo = XPlatform::get_vinfo()[screen]; format = XPlatform::get_screen_format(screen); image = XPlatform::create_x_image(format, width, height, vinfo->depth, vinfo->visual, &shminfo); pixman_image = pixman_image_create_bits(RedDrawable::format_to_pixman(format), width, height, (uint32_t *)image->data, image->bytes_per_line); if (pixman_image == NULL) { THROW("surf create failed"); } *pixman_image_out = pixman_image; *shminfo_out = shminfo; return image; } static void free_temp_image(XImage *image, XShmSegmentInfo *shminfo, pixman_image_t *pixman_image) { XPlatform::free_x_image(image, shminfo); pixman_image_unref(pixman_image); } static inline void copy_to_drawable_from_pixmap(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y) { pixman_image_t *src_surface = source->pixmap.pixman_image; XGCValues gc_vals; gc_vals.function = GXcopy; RedDrawable::Format screen_format; XImage *image; XShmSegmentInfo *shminfo; pixman_image_t *pixman_image; int screen; screen = dest->source.x_drawable.screen; screen_format = XPlatform::get_screen_format(screen); XChangeGC(XPlatform::get_display(), dest->source.x_drawable.gc, GCFunction, &gc_vals); if (source->pixmap.x_image != NULL && RedDrawable::format_copy_compatible(source->pixmap.format, screen_format)) { if (source->pixmap.shminfo) { XShmPutImage(XPlatform::get_display(), dest->source.x_drawable.drawable, dest->source.x_drawable.gc, source->pixmap.x_image, src_x, src_y, area.left + offset.x, area.top + offset.y, area.right - area.left, area.bottom - area.top, false); } else { XPutImage(XPlatform::get_display(), dest->source.x_drawable.drawable, dest->source.x_drawable.gc, source->pixmap.x_image, src_x, src_y, area.left + offset.x, area.top + offset.y, area.right - area.left, area.bottom - area.top); } } else { image = create_temp_image(screen, area.right - area.left, area.bottom - area.top, &pixman_image, &shminfo); pixman_image_composite32(PIXMAN_OP_SRC, src_surface, NULL, pixman_image, src_x + offset.x, src_y + offset.y, 0, 0, 0, 0, area.right - area.left, area.bottom - area.top); if (shminfo) { XShmPutImage(XPlatform::get_display(), dest->source.x_drawable.drawable, dest->source.x_drawable.gc, image, 0, 0, area.left + offset.x, area.top + offset.y, area.right - area.left, area.bottom - area.top, false); } else { XPutImage(XPlatform::get_display(), dest->source.x_drawable.drawable, dest->source.x_drawable.gc, image, 0, 0, area.left + offset.x, area.top + offset.y, area.right - area.left, area.bottom - area.top); } free_temp_image(image, shminfo, pixman_image); } XFlush(XPlatform::get_display()); } static inline void copy_to_x_drawable(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y) { switch (source->type) { case PIXELS_SOURCE_TYPE_X_DRAWABLE: copy_to_drawable_from_drawable(dest, area, offset, source, src_x, src_y); break; case PIXELS_SOURCE_TYPE_PIXMAP: copy_to_drawable_from_pixmap(dest, area, offset, source, src_x, src_y); break; default: THROW("invalid source type %d", source->type); } } #ifdef USE_OGL static inline void copy_to_gl_drawable(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y) { switch (source->type) { case PIXELS_SOURCE_TYPE_GL_TEXTURE: copy_to_gldrawable_from_gltexture(dest, area, offset, source, src_x, src_y); break; case PIXELS_SOURCE_TYPE_PIXMAP: copy_to_gldrawable_from_pixmap(dest, area, offset, source, src_x, src_y); break; default: THROW("invalid source type %d", source->type); } } #endif // USE_OGL static inline void copy_to_pixmap_from_drawable(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y) { LOG_WARN("not implemented"); } static inline void copy_to_pixmap_from_pixmap(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y) { pixman_image_t *dest_surface = dest->source.pixmap.pixman_image; pixman_image_t *src_surface = source->pixmap.pixman_image; pixman_image_composite32(PIXMAN_OP_SRC, src_surface, NULL, dest_surface, src_x + offset.x, src_y + offset.y, 0, 0, area.left + offset.x, area.top + offset.y, area.right - area.left, area.bottom - area.top); } #ifdef USE_OGL static inline void copy_to_pixmap_from_gltexture(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y) { int y, height; GLXContext context = NULL; GLXPbuffer pbuffer; Window win; RenderType rendertype; y = source->gl.height - src_y; height = area.bottom - area.top; rendertype = source->gl.rendertype; if (rendertype == RENDER_TYPE_FBO) { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, source->gl.fbo); glBindTexture(GL_TEXTURE_2D, 0); } else { context = source->gl.context; pbuffer = source->gl.pbuff; glXMakeCurrent(XPlatform::get_display(), pbuffer, context); glDisable(GL_TEXTURE_2D); } glReadBuffer(GL_COLOR_ATTACHMENT0_EXT); glPixelStorei(GL_PACK_ROW_LENGTH, pixman_image_get_stride(dest->source.pixmap.pixman_image) / 4); while (height > 0) { glReadPixels(src_x, y - height, area.right - area.left, 1, GL_BGRA, GL_UNSIGNED_BYTE, (uint8_t *)pixman_image_get_data(dest->source.pixmap.pixman_image) + (area.left + offset.x) * 4 + (area.top + offset.y + height - 1) * pixman_image_get_stride(dest->source.pixmap.pixman_image)); height--; } if (rendertype != RENDER_TYPE_FBO) { win = source->gl.win; glXMakeCurrent(XPlatform::get_display(), win, context); } } #endif // USE_OGL static inline void copy_to_pixmap(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y) { switch (source->type) { #ifdef USE_OGL case PIXELS_SOURCE_TYPE_GL_TEXTURE: copy_to_pixmap_from_gltexture(dest, area, offset, source, src_x, src_y); break; #endif // USE_OGL case PIXELS_SOURCE_TYPE_X_DRAWABLE: copy_to_pixmap_from_drawable(dest, area, offset, source, src_x, src_y); break; case PIXELS_SOURCE_TYPE_PIXMAP: copy_to_pixmap_from_pixmap(dest, area, offset, source, src_x, src_y); break; default: THROW("invalid source type %d", source->type); } } void RedDrawable::copy_pixels(const PixelsSource& src, int src_x, int src_y, const SpiceRect& area) { PixelsSource_p* source = (PixelsSource_p*)src.get_opaque(); RedDrawable_p* dest = (RedDrawable_p*)get_opaque(); switch (dest->source.type) { #ifdef USE_OGL case PIXELS_SOURCE_TYPE_GL_DRAWABLE: copy_to_gl_drawable(dest, area, _origin, source, src_x + src._origin.x, src_y + src._origin.y); break; #endif // USE_OGL case PIXELS_SOURCE_TYPE_X_DRAWABLE: copy_to_x_drawable(dest, area, _origin, source, src_x + src._origin.x, src_y + src._origin.y); break; case PIXELS_SOURCE_TYPE_PIXMAP: copy_to_pixmap(dest, area, _origin, source, src_x + src._origin.x, src_y + src._origin.y); break; default: THROW("invalid dest type %d", dest->source.type); } } static inline void blend_to_drawable(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y) { LOG_WARN("not implemented"); } static inline void blend_to_pixmap_from_drawable(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y) { LOG_WARN("not implemented"); } static inline void blend_to_pixmap_from_pixmap(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y) { pixman_image_t *dest_surface = dest->source.pixmap.pixman_image; pixman_image_t *src_surface = source->pixmap.pixman_image; pixman_image_composite32 (PIXMAN_OP_ATOP, src_surface, NULL, dest_surface, src_x + offset.x, src_y + offset.y, 0, 0, area.left + offset.x, area.top + offset.y, area.right - area.left, area.bottom - area.top); } static inline void blend_to_pixmap(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y) { switch (source->type) { case PIXELS_SOURCE_TYPE_X_DRAWABLE: blend_to_pixmap_from_drawable(dest, area, offset, source, src_x, src_y); break; case PIXELS_SOURCE_TYPE_PIXMAP: blend_to_pixmap_from_pixmap(dest, area, offset, source, src_x, src_y); break; default: THROW("invalid source type %d", source->type); } } void RedDrawable::blend_pixels(const PixelsSource& src, int src_x, int src_y, const SpiceRect& area) { PixelsSource_p* source = (PixelsSource_p*)src.get_opaque(); RedDrawable_p* dest = (RedDrawable_p*)get_opaque(); switch (dest->source.type) { case PIXELS_SOURCE_TYPE_X_DRAWABLE: blend_to_drawable(dest, area, _origin, source, src_x + src._origin.x, src_y + src._origin.y); break; case PIXELS_SOURCE_TYPE_PIXMAP: blend_to_pixmap(dest, area, _origin, source, src_x + src._origin.x, src_y + src._origin.y); break; default: THROW("invalid dest type %d", dest->source.type); } } static inline void combine_to_drawable(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y, RedDrawable::CombineOP op) { LOG_WARN("not implemented"); } static inline void combine_to_pixmap_from_drawable(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y, RedDrawable::CombineOP op) { LOG_WARN("not implemented"); } static inline void combine_to_pixmap_from_pixmap(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y, RedDrawable::CombineOP op) { pixman_image_t *dest_surface = dest->source.pixmap.pixman_image; pixman_image_t *src_surface = source->pixmap.pixman_image; SpiceROP rop; switch (op) { case RedDrawable::OP_COPY: rop = SPICE_ROP_COPY; break; case RedDrawable::OP_AND: rop = SPICE_ROP_AND; break; case RedDrawable::OP_XOR: rop = SPICE_ROP_XOR; break; default: THROW("invalid op %d", op); } if (pixman_image_get_depth (src_surface) == 1) { pixman_color_t white = { 0xffff, 0xffff, 0xffff, 0xffff }; pixman_image_t *solid; pixman_image_t *temp; /* Create a temporary rgb32 image that is black where mask is 0 and white where mask is 1 */ temp = pixman_image_create_bits(pixman_image_get_depth(dest_surface) == 24 ? PIXMAN_x8r8g8b8 : PIXMAN_a8r8g8b8, area.right - area.left, area.bottom - area.top, NULL, 0); solid = pixman_image_create_solid_fill(&white); pixman_image_composite32(PIXMAN_OP_SRC, solid, src_surface, temp, 0, 0, src_x + offset.x, src_y + offset.y, 0, 0, area.right - area.left, area.bottom - area.top); pixman_image_unref(solid); /* ROP the temp image on the destination */ spice_pixman_blit_rop(dest_surface, temp, 0, 0, area.left + offset.x, area.top + offset.y, area.right - area.left, area.bottom - area.top, rop); pixman_image_unref(temp); } else { spice_pixman_blit_rop(dest_surface, src_surface, src_x + offset.x, src_y + offset.y, area.left + offset.x, area.top + offset.y, area.right - area.left, area.bottom - area.top, rop); } } static inline void combine_to_pixmap(const RedDrawable_p* dest, const SpiceRect& area, const SpicePoint& offset, const PixelsSource_p* source, int src_x, int src_y, RedDrawable::CombineOP op) { switch (source->type) { case PIXELS_SOURCE_TYPE_X_DRAWABLE: combine_to_pixmap_from_drawable(dest, area, offset, source, src_x, src_y, op); break; case PIXELS_SOURCE_TYPE_PIXMAP: combine_to_pixmap_from_pixmap(dest, area, offset, source, src_x, src_y, op); break; default: THROW("invalid source type %d", source->type); } } void RedDrawable::combine_pixels(const PixelsSource& src, int src_x, int src_y, const SpiceRect& area, CombineOP op) { PixelsSource_p* source = (PixelsSource_p*)src.get_opaque(); RedDrawable_p* dest = (RedDrawable_p*)get_opaque(); switch (dest->source.type) { case PIXELS_SOURCE_TYPE_X_DRAWABLE: combine_to_drawable(dest, area, _origin, source, src_x + src._origin.x, src_y + src._origin.y, op); break; case PIXELS_SOURCE_TYPE_PIXMAP: combine_to_pixmap(dest, area, _origin, source, src_x + src._origin.x, src_y + src._origin.y, op); break; default: THROW("invalid dest type %d", dest->source.type); } } void RedDrawable::erase_rect(const SpiceRect& area, rgb32_t color) { LOG_WARN("not implemented"); } static inline void fill_drawable(RedDrawable_p* dest, const SpiceRect& area, rgb32_t color, const SpicePoint& offset) { Drawable drawable = dest->source.x_drawable.drawable; GC gc = dest->source.x_drawable.gc; XLockDisplay(XPlatform::get_display()); Colormap color_map = DefaultColormap(XPlatform::get_display(), DefaultScreen(XPlatform::get_display())); XColor x_color; x_color.red = (uint16_t)rgb32_get_red(color) << 8; x_color.green = (uint16_t)rgb32_get_green(color) << 8; x_color.blue = (uint16_t)rgb32_get_blue(color) << 8; x_color.flags = DoRed | DoGreen | DoBlue; //todo: optimize color map if (!XAllocColor(XPlatform::get_display(), color_map, &x_color)) { LOG_WARN("color map failed"); } XUnlockDisplay(XPlatform::get_display()); XGCValues gc_vals; gc_vals.foreground = x_color.pixel; gc_vals.function = GXcopy; gc_vals.fill_style = FillSolid; XChangeGC(XPlatform::get_display(), gc, GCFunction | GCForeground | GCFillStyle, &gc_vals); XFillRectangle(XPlatform::get_display(), drawable, gc, area.left + offset.x, area.top + offset.y, area.right - area.left, area.bottom - area.top); } #ifdef USE_OGL static inline void fill_gl_drawable(RedDrawable_p* dest, const SpiceRect& area, rgb32_t color, const SpicePoint& offset) { int vertex_x1, vertex_x2; int vertex_y1, vertex_y2; GLXContext context; context = glXGetCurrentContext(); if (!context) { return; } vertex_x1 = area.left + offset.x; vertex_y1 = dest->source.x_drawable.height - (area.top + offset.y) - (area.bottom - area.top); vertex_x2 = vertex_x1 + (area.right - area.left); vertex_y2 = vertex_y1 + (area.bottom - area.top); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glColor3f(rgb32_get_red(color), rgb32_get_green(color), rgb32_get_blue(color)); glBegin(GL_QUADS); glVertex2i(vertex_x1, vertex_y1); glVertex2i(vertex_x1, vertex_y2); glVertex2i(vertex_x2, vertex_y2); glVertex2i(vertex_x2, vertex_y1); glEnd(); glFlush(); glColor3f(1, 1, 1); } #endif // USE_OGL static inline void fill_pixmap(RedDrawable_p* dest, const SpiceRect& area, rgb32_t color, const SpicePoint& offset) { pixman_image_t *dest_surface = dest->source.pixmap.pixman_image; spice_pixman_fill_rect(dest_surface, area.left + offset.x, area.top + offset.y, area.right - area.left, area.bottom - area.top, color); } void RedDrawable::fill_rect(const SpiceRect& area, rgb32_t color) { RedDrawable_p* dest = (RedDrawable_p*)get_opaque(); switch (dest->source.type) { #ifdef USE_OGL case PIXELS_SOURCE_TYPE_GL_DRAWABLE: fill_gl_drawable(dest, area, color, _origin); break; #endif // USE_OGL case PIXELS_SOURCE_TYPE_X_DRAWABLE: fill_drawable(dest, area, color, _origin); break; case PIXELS_SOURCE_TYPE_PIXMAP: fill_pixmap(dest, area, color, _origin); break; default: THROW("invalid dest type %d", dest->source.type); } } static inline void frame_drawable(RedDrawable_p* dest, const SpiceRect& area, rgb32_t color, const SpicePoint& offset) { Drawable drawable = dest->source.x_drawable.drawable; GC gc = dest->source.x_drawable.gc; XLockDisplay(XPlatform::get_display()); Colormap color_map = DefaultColormap(XPlatform::get_display(), DefaultScreen(XPlatform::get_display())); XColor x_color; x_color.red = (uint16_t)rgb32_get_red(color) << 8; x_color.green = (uint16_t)rgb32_get_green(color) << 8; x_color.blue = (uint16_t)rgb32_get_blue(color) << 8; x_color.flags = DoRed | DoGreen | DoBlue; //todo: optimize color map if (!XAllocColor(XPlatform::get_display(), color_map, &x_color)) { LOG_WARN("color map failed"); } XUnlockDisplay(XPlatform::get_display()); XGCValues gc_vals; gc_vals.foreground = x_color.pixel; gc_vals.function = GXcopy; gc_vals.fill_style = FillSolid; XChangeGC(XPlatform::get_display(), gc, GCFunction | GCForeground | GCFillStyle, &gc_vals); XFillRectangle(XPlatform::get_display(), drawable, gc, area.left + offset.x, area.top + offset.y, area.right - area.left, area.bottom - area.top); } static inline void frame_pixmap(RedDrawable_p* dest, const SpiceRect& area, rgb32_t color, const SpicePoint& offset) { pixman_image_t *dest_surface = dest->source.pixmap.pixman_image; spice_pixman_fill_rect(dest_surface, area.left + offset.x, area.top + offset.y, area.right - area.left, 1, color); spice_pixman_fill_rect(dest_surface, area.left + offset.x, area.bottom + offset.y, area.right - area.left, 1, color); spice_pixman_fill_rect(dest_surface, area.left + offset.x, area.top + offset.y, 1, area.bottom - area.top, color); spice_pixman_fill_rect(dest_surface, area.right + offset.x, area.top + offset.y, 1, area.bottom - area.top, color); } void RedDrawable::frame_rect(const SpiceRect& area, rgb32_t color) { RedDrawable_p* dest = (RedDrawable_p*)get_opaque(); switch (dest->source.type) { case PIXELS_SOURCE_TYPE_X_DRAWABLE: frame_drawable(dest, area, color, _origin); break; case PIXELS_SOURCE_TYPE_PIXMAP: frame_pixmap(dest, area, color, _origin); break; default: THROW("invalid dest type %d", dest->source.type); } }