/* Copyright © 2007 Bart Massey * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * Except as contained in this notice, the names of the authors or their * institutions shall not be used in advertising or otherwise to promote the * sale, use or other dealings in this Software without prior written * authorization from the authors. */ #include #include #include #include #include #include #include "xcb_bitops.h" #include "xcb_image.h" #define BUILD #include "xcb_pixel.h" static xcb_format_t * find_format_by_depth (const xcb_setup_t *setup, uint8_t depth) { xcb_format_t *fmt = xcb_setup_pixmap_formats(setup); xcb_format_t *fmtend = fmt + xcb_setup_pixmap_formats_length(setup); for(; fmt != fmtend; ++fmt) if(fmt->depth == depth) return fmt; return 0; } static xcb_image_format_t effective_format(xcb_image_format_t format, uint8_t bpp) { if (format == XCB_IMAGE_FORMAT_Z_PIXMAP && bpp != 1) return format; return XCB_IMAGE_FORMAT_XY_PIXMAP; } static int format_valid (uint8_t depth, uint8_t bpp, uint8_t unit, xcb_image_format_t format, uint8_t xpad) { xcb_image_format_t ef = effective_format(format, bpp); if (depth > bpp) return 0; switch(ef) { case XCB_IMAGE_FORMAT_XY_PIXMAP: switch(unit) { case 8: case 16: case 32: break; default: return 0; } if (xpad < bpp) return 0; switch (xpad) { case 8: case 16: case 32: break; default: return 0; } break; case XCB_IMAGE_FORMAT_Z_PIXMAP: switch (bpp) { case 4: if (unit != 8) return 0; break; case 8: case 16: case 24: case 32: if (unit != bpp) return 0; break; default: return 0; } break; default: return 0; } return 1; } static int image_format_valid (xcb_image_t *image) { return format_valid(image->depth, image->bpp, image->unit, image->format, image->scanline_pad); } void xcb_image_annotate (xcb_image_t *image) { xcb_image_format_t ef = effective_format(image->format, image->bpp); switch (ef) { case XCB_IMAGE_FORMAT_XY_PIXMAP: image->stride = xcb_roundup(image->width, image->scanline_pad) >> 3; image->size = image->height * image->stride * image->depth; break; case XCB_IMAGE_FORMAT_Z_PIXMAP: image->stride = xcb_roundup((uint32_t)image->width * (uint32_t)image->bpp, image->scanline_pad) >> 3; image->size = image->height * image->stride; break; default: assert(0); } } xcb_image_t * xcb_image_create_native (xcb_connection_t * c, uint16_t width, uint16_t height, xcb_image_format_t format, uint8_t depth, void * base, uint32_t bytes, uint8_t * data) { const xcb_setup_t * setup = xcb_get_setup(c); xcb_format_t * fmt; xcb_image_format_t ef = format; if (ef == XCB_IMAGE_FORMAT_Z_PIXMAP && depth == 1) ef = XCB_IMAGE_FORMAT_XY_PIXMAP; switch (ef) { case XCB_IMAGE_FORMAT_XY_BITMAP: if (depth != 1) return 0; /* fall through */ case XCB_IMAGE_FORMAT_XY_PIXMAP: if (depth > 1) { fmt = find_format_by_depth(setup, depth); if (!fmt) return 0; } return xcb_image_create(width, height, format, setup->bitmap_format_scanline_pad, depth, depth, setup->bitmap_format_scanline_unit, setup->image_byte_order, setup->bitmap_format_bit_order, base, bytes, data); case XCB_IMAGE_FORMAT_Z_PIXMAP: fmt = find_format_by_depth(setup, depth); if (!fmt) return 0; return xcb_image_create(width, height, format, fmt->scanline_pad, fmt->depth, fmt->bits_per_pixel, 0, setup->image_byte_order, XCB_IMAGE_ORDER_MSB_FIRST, base, bytes, data); default: assert(0); } assert(0); } xcb_image_t * xcb_image_create (uint16_t width, uint16_t height, xcb_image_format_t format, uint8_t xpad, uint8_t depth, uint8_t bpp, uint8_t unit, xcb_image_order_t byte_order, xcb_image_order_t bit_order, void * base, uint32_t bytes, uint8_t * data) { xcb_image_t * image; if (unit == 0) { switch (format) { case XCB_IMAGE_FORMAT_XY_BITMAP: case XCB_IMAGE_FORMAT_XY_PIXMAP: unit = 32; break; case XCB_IMAGE_FORMAT_Z_PIXMAP: if (bpp == 1) { unit = 32; break; } if (bpp < 8) { unit = 8; break; } unit = bpp; break; } } if (!format_valid(depth, bpp, unit, format, xpad)) return 0; image = malloc(sizeof(*image)); if (image == 0) return 0; image->width = width; image->height = height; image->format = format; image->scanline_pad = xpad; image->depth = depth; image->bpp = bpp; image->unit = unit; image->plane_mask = xcb_mask(depth); image->byte_order = byte_order; image->bit_order = bit_order; xcb_image_annotate(image); /* * Ways this function can be called: * * with data: we fail if bytes isn't * large enough, else leave well enough alone. * * with base and !data: if bytes is zero, we * default; otherwise we fail if bytes isn't * large enough, else fill in data * * with !base and !data: we malloc storage * for the data, save that address as the base, * and fail if malloc does. * * When successful, we establish the invariant that data * points at sufficient storage that may have been * supplied, and base is set iff it should be * auto-freed when the image is destroyed. * * Except as a special case when base = 0 && data == 0 && * bytes == ~0 we just return the image structure and let * the caller deal with getting the allocation right. */ if (!base && !data && bytes == ~0) { image->base = 0; image->data = 0; return image; } if (!base && data && bytes == 0) bytes = image->size; image->base = base; image->data = data; if (!image->data) { if (image->base) { image->data = image->base; } else { bytes = image->size; image->base = malloc(bytes); image->data = image->base; } } if (!image->data || bytes < image->size) { free(image); return 0; } return image; } void xcb_image_destroy (xcb_image_t *image) { if (image->base) free (image->base); free (image); } xcb_image_t * xcb_image_get (xcb_connection_t * conn, xcb_drawable_t draw, int16_t x, int16_t y, uint16_t width, uint16_t height, uint32_t plane_mask, xcb_image_format_t format) { xcb_get_image_cookie_t image_cookie; xcb_get_image_reply_t * imrep; xcb_image_t * image = 0; uint32_t bytes; uint8_t * data; image_cookie = xcb_get_image(conn, format, draw, x, y, width, height, plane_mask); imrep = xcb_get_image_reply(conn, image_cookie, 0); if (!imrep) return 0; bytes = xcb_get_image_data_length(imrep); data = xcb_get_image_data(imrep); switch (format) { case XCB_IMAGE_FORMAT_XY_PIXMAP: plane_mask &= xcb_mask(imrep->depth); if (plane_mask != xcb_mask(imrep->depth)) { xcb_image_t * tmp_image = xcb_image_create_native(conn, width, height, format, imrep->depth, 0, 0, 0); if (!tmp_image) { free(imrep); return 0; } int i; uint32_t rpm = plane_mask; uint8_t * src_plane = image->data; uint8_t * dst_plane = tmp_image->data; uint32_t size = image->height * image->stride; if (tmp_image->bit_order == XCB_IMAGE_ORDER_MSB_FIRST) rpm = xcb_bit_reverse(plane_mask, imrep->depth); for (i = 0; i < imrep->depth; i++) { if (rpm & 1) { memcpy(dst_plane, src_plane, size); src_plane += size; } else { memset(dst_plane, 0, size); } dst_plane += size; } tmp_image->plane_mask = plane_mask; image = tmp_image; free(imrep); break; } /* fall through */ case XCB_IMAGE_FORMAT_Z_PIXMAP: image = xcb_image_create_native(conn, width, height, format, imrep->depth, imrep, bytes, data); if (!image) { free(imrep); return 0; } break; default: assert(0); } assert(bytes == image->size); return image; } xcb_image_t * xcb_image_native (xcb_connection_t * c, xcb_image_t * image, int convert) { xcb_image_t * tmp_image = 0; const xcb_setup_t * setup = xcb_get_setup(c); xcb_format_t * fmt = 0; xcb_image_format_t ef = effective_format(image->format, image->bpp); uint8_t bpp = 1; if (image->depth > 1 || ef == XCB_IMAGE_FORMAT_Z_PIXMAP) { fmt = find_format_by_depth(setup, image->depth); /* XXX For now, we don't do depth conversions, even for xy-pixmaps */ if (!fmt) return 0; bpp = fmt->bits_per_pixel; } switch (ef) { case XCB_IMAGE_FORMAT_XY_PIXMAP: if (setup->bitmap_format_scanline_unit != image->unit || setup->bitmap_format_scanline_pad != image->scanline_pad || setup->image_byte_order != image->byte_order || setup->bitmap_format_bit_order != image->bit_order || bpp != image->bpp) { if (!convert) return 0; tmp_image = xcb_image_create(image->width, image->height, image->format, setup->bitmap_format_scanline_pad, image->depth, bpp, setup->bitmap_format_scanline_unit, setup->image_byte_order, setup->bitmap_format_bit_order, 0, 0, 0); if (!tmp_image) return 0; } break; case XCB_IMAGE_FORMAT_Z_PIXMAP: if (fmt->scanline_pad != image->scanline_pad || setup->image_byte_order != image->byte_order || bpp != image->bpp) { if (!convert) return 0; tmp_image = xcb_image_create(image->width, image->height, image->format, fmt->scanline_pad, image->depth, bpp, 0, setup->image_byte_order, XCB_IMAGE_ORDER_MSB_FIRST, 0, 0, 0); if (!tmp_image) return 0; } break; default: assert(0); } if (tmp_image) { if (!xcb_image_convert(image, tmp_image)) { xcb_image_destroy(tmp_image); return 0; } image = tmp_image; } return image; } xcb_void_cookie_t xcb_image_put (xcb_connection_t * conn, xcb_drawable_t draw, xcb_gcontext_t gc, xcb_image_t * image, int16_t x, int16_t y, uint8_t left_pad) { return xcb_put_image(conn, image->format, draw, gc, image->width, image->height, x, y, left_pad, image->depth, image->size, image->data); } /* * Shm stuff */ xcb_image_t * xcb_image_shm_put (xcb_connection_t * conn, xcb_drawable_t draw, xcb_gcontext_t gc, xcb_image_t * image, xcb_shm_segment_info_t shminfo, int16_t src_x, int16_t src_y, int16_t dest_x, int16_t dest_y, uint16_t src_width, uint16_t src_height, uint8_t send_event) { if (!xcb_image_native(conn, image, 0)) return 0; if (!shminfo.shmaddr) return 0; xcb_shm_put_image(conn, draw, gc, image->width, image->height, src_x, src_y, src_width, src_height, dest_x, dest_y, image->depth, image->format, send_event, shminfo.shmseg, image->data - shminfo.shmaddr); return image; } int xcb_image_shm_get (xcb_connection_t * conn, xcb_drawable_t draw, xcb_image_t * image, xcb_shm_segment_info_t shminfo, int16_t x, int16_t y, uint32_t plane_mask) { xcb_shm_get_image_reply_t * setup; xcb_shm_get_image_cookie_t cookie; xcb_generic_error_t * err = 0; if (!shminfo.shmaddr) return 0; cookie = xcb_shm_get_image(conn, draw, x, y, image->width, image->height, plane_mask, image->format, shminfo.shmseg, image->data - shminfo.shmaddr); setup = xcb_shm_get_image_reply(conn, cookie, &err); if (err) { fprintf(stderr, "ShmGetImageReply error %d\n", (int)err->error_code); free(err); return 0; } else { free (setup); return 1; } } static uint32_t xy_image_byte (xcb_image_t *image, uint32_t x) { x >>= 3; if (image->byte_order == image->bit_order) return x; switch (image->unit) { default: case 8: return x; case 16: return x ^ 1; case 32: return x ^ 3; } } static uint32_t xy_image_bit (xcb_image_t *image, uint32_t x) { x &= 7; if (image->bit_order == XCB_IMAGE_ORDER_MSB_FIRST) x = 7 - x; return x; } /* GetPixel/PutPixel */ /* XXX this is the most hideously done cut-and-paste to below. Any bugs fixed there should be fixed here and vice versa. */ void xcb_image_put_pixel (xcb_image_t *image, uint32_t x, uint32_t y, uint32_t pixel) { uint8_t *row; if (x > image->width || y > image->height) return; row = image->data + (y * image->stride); switch (effective_format(image->format, image->bpp)) { case XCB_IMAGE_FORMAT_XY_BITMAP: case XCB_IMAGE_FORMAT_XY_PIXMAP: /* block */ { int p; uint32_t plane_mask = image->plane_mask; uint8_t * plane = row; uint32_t byte = xy_image_byte(image, x); uint32_t bit = xy_image_bit(image,x); uint8_t mask = 1 << bit; for (p = image->bpp - 1; p >= 0; p--) { if ((plane_mask >> p) & 1) { uint8_t * bp = plane + byte; uint8_t this_bit = ((pixel >> p) & 1) << bit; *bp = (*bp & ~mask) | this_bit; } plane += image->stride * image->height; } } break; case XCB_IMAGE_FORMAT_Z_PIXMAP: switch (image->bpp) { uint32_t mask; case 4: mask = 0xf; pixel &= 0xf; if ((x & 1) == (image->byte_order == XCB_IMAGE_ORDER_MSB_FIRST)) { pixel <<= 4; mask <<= 4; } row[x >> 1] = (row[x >> 1] & ~mask) | pixel; break; case 8: row[x] = pixel; break; case 16: switch (image->byte_order) { case XCB_IMAGE_ORDER_LSB_FIRST: row[x << 1] = pixel; row[(x << 1) + 1] = pixel >> 8; break; case XCB_IMAGE_ORDER_MSB_FIRST: row[x << 1] = pixel >> 8; row[(x << 1) + 1] = pixel; break; } break; case 24: switch (image->byte_order) { case XCB_IMAGE_ORDER_LSB_FIRST: row[x * 3] = pixel; row[x * 3 + 1] = pixel >> 8; row[x * 3 + 2] = pixel >> 16; break; case XCB_IMAGE_ORDER_MSB_FIRST: row[x * 3] = pixel >> 16; row[x * 3 + 1] = pixel >> 8; row[x * 3 + 2] = pixel; break; } break; case 32: switch (image->byte_order) { case XCB_IMAGE_ORDER_LSB_FIRST: row[x << 2] = pixel; row[(x << 2) + 1] = pixel >> 8; row[(x << 2) + 2] = pixel >> 16; row[(x << 2) + 3] = pixel >> 24; break; case XCB_IMAGE_ORDER_MSB_FIRST: row[x << 2] = pixel >> 24; row[(x << 2) + 1] = pixel >> 16; row[(x << 2) + 2] = pixel >> 8; row[(x << 2) + 3] = pixel; break; } break; default: assert(0); } break; default: assert(0); } } /* XXX this is the most hideously done cut-and-paste from above. Any bugs fixed there should be fixed here and vice versa. */ uint32_t xcb_image_get_pixel (xcb_image_t *image, uint32_t x, uint32_t y) { uint32_t pixel = 0; uint8_t *row; assert(x < image->width && y < image->height); row = image->data + (y * image->stride); switch (effective_format(image->format, image->bpp)) { case XCB_IMAGE_FORMAT_XY_BITMAP: case XCB_IMAGE_FORMAT_XY_PIXMAP: /* block */ { int p; uint32_t plane_mask = image->plane_mask; uint8_t * plane = row; uint32_t byte = xy_image_byte(image, x); uint32_t bit = xy_image_bit(image,x); for (p = image->bpp - 1; p >= 0; p--) { pixel <<= 1; if ((plane_mask >> p) & 1) { uint8_t * bp = plane + byte; pixel |= (*bp >> bit) & 1; } plane += image->stride * image->height; } } return pixel; case XCB_IMAGE_FORMAT_Z_PIXMAP: switch (image->bpp) { case 4: if ((x & 1) == (image->byte_order == XCB_IMAGE_ORDER_MSB_FIRST)) return row[x >> 1] >> 4; return row[x >> 1] & 0xf; case 8: return row[x]; case 16: switch (image->byte_order) { case XCB_IMAGE_ORDER_LSB_FIRST: pixel = row[x << 1]; pixel |= row[(x << 1) + 1] << 8; break; case XCB_IMAGE_ORDER_MSB_FIRST: pixel = row[x << 1] << 8; pixel |= row[(x << 1) + 1]; break; } break; case 24: switch (image->byte_order) { case XCB_IMAGE_ORDER_LSB_FIRST: pixel = row[x * 3]; pixel |= row[x * 3 + 1] << 8; pixel |= row[x * 3 + 2] << 16; break; case XCB_IMAGE_ORDER_MSB_FIRST: pixel = row[x * 3] << 16; pixel |= row[x * 3 + 1] << 8; pixel |= row[x * 3 + 2]; break; } break; case 32: switch (image->byte_order) { case XCB_IMAGE_ORDER_LSB_FIRST: pixel = row[x << 2]; pixel |= row[(x << 2) + 1] << 8; pixel |= row[(x << 2) + 2] << 16; pixel |= row[(x << 2) + 3] << 24; break; case XCB_IMAGE_ORDER_MSB_FIRST: pixel = row[x << 2] << 24; pixel |= row[(x << 2) + 1] << 16; pixel |= row[(x << 2) + 2] << 8; pixel |= row[(x << 2) + 3]; break; } break; default: assert(0); } return pixel; default: assert(0); } } xcb_image_t * xcb_image_create_from_bitmap_data (uint8_t * data, uint32_t width, uint32_t height) { return xcb_image_create(width, height, XCB_IMAGE_FORMAT_XY_PIXMAP, 8, 1, 1, 8, XCB_IMAGE_ORDER_LSB_FIRST, XCB_IMAGE_ORDER_LSB_FIRST, 0, 0, data); } /* * (Adapted from libX11.) * * xcb_create_pixmap_from_bitmap_data: Routine to make a pixmap of * given depth from user supplied bitmap data. * D is any drawable on the same screen that the pixmap will be used in. * Data is a pointer to the bit data, and * width & height give the size in bits of the pixmap. * * The following format is assumed for data: * * format=XY (will use XYPixmap for depth 1 and XYBitmap for larger) * bit_order=LSBFirst * padding=8 * bitmap_unit=8 */ xcb_pixmap_t xcb_create_pixmap_from_bitmap_data (xcb_connection_t * display, xcb_drawable_t d, uint8_t * data, uint32_t width, uint32_t height, uint32_t depth, uint32_t fg, uint32_t bg, xcb_gcontext_t * gcp) { xcb_pixmap_t pix; xcb_image_t * image; xcb_image_t * final_image; xcb_gcontext_t gc; uint32_t mask = 0; xcb_params_gc_t gcv; image = xcb_image_create_from_bitmap_data(data, width, height); if (!image) return 0; if (depth > 1) image->format = XCB_IMAGE_FORMAT_XY_BITMAP; final_image = xcb_image_native(display, image, 1); if (!final_image) { xcb_image_destroy(image); return 0; } pix = xcb_generate_id(display); xcb_create_pixmap(display, depth, pix, d, width, height); gc = xcb_generate_id(display); XCB_AUX_ADD_PARAM(&mask, &gcv, foreground, fg); XCB_AUX_ADD_PARAM(&mask, &gcv, background, bg); xcb_aux_create_gc(display, gc, pix, mask, &gcv); xcb_image_put(display, pix, gc, final_image, 0, 0, 0); if (final_image != image) xcb_image_destroy(final_image); xcb_image_destroy(image); if (gcp) *gcp = gc; else xcb_free_gc(display, gc); return pix; } /* Thanks to Keith Packard for this code */ static void swap_image(uint8_t * src, uint32_t src_stride, uint8_t * dst, uint32_t dst_stride, uint32_t height, uint32_t byteswap, int bitswap, int nibbleswap) { while (height--) { uint32_t s; for (s = 0; s < src_stride; s++) { uint8_t b; uint32_t d = s ^ byteswap; if (d > dst_stride) continue; b = src[s]; if (bitswap) b = xcb_bit_reverse(b, 8); if (nibbleswap) b = (b << 4) | (b >> 4); dst[d] = b; } src += src_stride; dst += dst_stride; } } /* Which order are bytes in (low two bits), given * code which accesses an image one byte at a time */ static uint32_t byte_order(xcb_image_t *i) { uint32_t flip = i->byte_order == XCB_IMAGE_ORDER_MSB_FIRST; switch (i->bpp) { default: case 8: return 0; case 16: return flip; case 32: return flip | (flip << 1); } } static uint32_t bit_order(xcb_image_t *i) { uint32_t flip = i->byte_order != i->bit_order; switch (i->unit) { default: case 8: return 0; case 16: return flip; case 32: return flip | (flip << 1); } } /* Convert from one byte order to another by flipping the * low two bits of the byte index along a scanline */ static uint32_t conversion_byte_swap(xcb_image_t *src, xcb_image_t *dst) { xcb_image_format_t ef = effective_format(src->format, src->bpp); /* src_ef == dst_ef in all callers of this function */ if (ef == XCB_IMAGE_FORMAT_XY_PIXMAP) { return bit_order(src) ^ bit_order(dst); } else { /* src_bpp == dst_bpp in all callers of this function */ return byte_order(src) ^ byte_order(dst); } } xcb_image_t * xcb_image_convert (xcb_image_t * src, xcb_image_t * dst) { xcb_image_format_t ef = effective_format(src->format, src->bpp); /* Things will go horribly wrong here if a bad image is passed in, so we check some things up front just to be nice. */ assert(image_format_valid(src)); assert(image_format_valid(dst)); /* images must be the same size * (yes, we could copy a sub-set) */ if (src->width != dst->width || src->height != dst->height) return 0; if (ef == effective_format(dst->format, dst->bpp) && src->bpp == dst->bpp) { if (src->unit == dst->unit && src->scanline_pad == dst->scanline_pad && src->byte_order == dst->byte_order && (ef == XCB_IMAGE_FORMAT_Z_PIXMAP || src->bit_order == dst->bit_order)) { memcpy(dst->data, src->data, src->size); } else { int bitswap = 0; int nibbleswap = 0; uint32_t byteswap = conversion_byte_swap(src, dst); uint32_t height = src->height;; if (ef == XCB_IMAGE_FORMAT_Z_PIXMAP) { if (src->bpp == 4 && src->byte_order != dst->byte_order) nibbleswap = 1; } else { if (src->bit_order != dst->bit_order) bitswap = 1; height *= src->depth; } swap_image (src->data, src->stride, dst->data, dst->stride, height, byteswap, bitswap, nibbleswap); } } else { uint32_t x; uint32_t y; /* General case: Slow pixel copy. Should we optimize Z24<->Z32 copies of either endianness? */ for (y = 0; y < src->height; y++) { for (x = 0; x < src->width; x++) { uint32_t pixel = xcb_image_get_pixel(src, x, y); xcb_image_put_pixel(dst, x, y, pixel); } } } return dst; } xcb_image_t * xcb_image_subimage(xcb_image_t * image, uint32_t x, uint32_t y, uint32_t width, uint32_t height, void * base, uint32_t bytes, uint8_t * data) { int i, j; xcb_image_t * result; if (x + width > image->width) return 0; if (y + height > image->height) return 0; result = xcb_image_create(width, height, image->format, image->scanline_pad, image->depth, image->bpp, image->unit, image->byte_order, image->bit_order, base, bytes, data); if (!result) return 0; /* XXX FIXME For now, lose on performance. Sorry. */ for (j = 0; j < height; j++) { for (i = 0; i < width; i++) { uint32_t pixel = xcb_image_get_pixel(image, x + i, y + j); xcb_image_put_pixel(result, i, j, pixel); } } return result; }