diff options
Diffstat (limited to 'macosx/tkMacOSXImage.c')
-rw-r--r-- | macosx/tkMacOSXImage.c | 398 |
1 files changed, 253 insertions, 145 deletions
diff --git a/macosx/tkMacOSXImage.c b/macosx/tkMacOSXImage.c index 69967af..f256d7a 100644 --- a/macosx/tkMacOSXImage.c +++ b/macosx/tkMacOSXImage.c @@ -4,26 +4,92 @@ * The code in this file provides an interface for XImages, * * Copyright (c) 1995-1997 Sun Microsystems, Inc. - * Copyright 2001-2009, Apple Inc. + * Copyright (c) 2001-2009, Apple Inc. * Copyright (c) 2005-2009 Daniel A. Steffen <das@users.sourceforge.net> - * Copyright (c) 2017-2020 Marc Culler. + * Copyright (c) 2017-2021 Marc Culler. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tkMacOSXPrivate.h" +#include "tkMacOSXConstants.h" #include "xbytes.h" static CGImageRef CreateCGImageFromPixmap(Drawable pixmap); static CGImageRef CreateCGImageFromDrawableRect( Drawable drawable, int x, int y, unsigned int width, unsigned int height); +/* Pixel formats + * + * Tk uses the XImage structure defined in Xlib.h for storing images. The + * image data in an XImage is a 32-bit aligned array of bytes. Interpretation + * of that data is not specified, but the structure includes parameters which + * provide interpretation hints so that an application can use a family of + * different data structures. + * + * The possible values for the XImage format field are XYBitmap, XYPixmap and + * ZPixmap. The macOS port does not support the XYPixmap format. This means + * that bitmap images are stored as a single bit plane (XYBitmap) and that + * color images are stored as a sequence of pixel values (ZPixmap). + * + * For a ZPixmap, the number of bits allocated to each pixel is specified by + * the bits_per_pixel field of the XImage structure. The functions in this + * module which convert between XImage and native CGImage or NSImage structures + * only support XImages with 32 bits per pixel. The ImageGetPixel and PutPixel + * implementations in this file allow 1, 4, 8, 16 or 32 bits per pixel, however. + * + * In tkImgPhInstance.c the layout used for pixels is determined by the values + * of the red_mask, blue_mask and green_mask fields in the XImage structure. + * The Aqua port always sets red_mask = 0xFF0000, green_mask = 0xFF00, and + * blue_mask = 0xFF. This means that a 32bpp ZPixmap XImage uses ARGB32 pixels, + * with small-endian byte order BGRA. The data array for such an XImage can be + * passed directly to construct a CGBitmapImageRep if one specifies the + * bitmapInfo as kCGBitmapByteOrder32Big | kCGImageAlphaLast. + * + * The structures below describe the bitfields in two common 32 bpp pixel + * layouts. Note that bit field layouts are compiler dependent. The layouts + * shown in the comments are those produced by clang and gcc. Also note + * that kCGBitmapByteOrder32Big is consistently set when creating CGImages or + * CGImageBitmapReps. + */ + +/* RGBA32 0xRRGGBBAA (Byte order is RGBA on big-endian systems.) + * This is used by NSBitmapImageRep when the bitmapFormat property is 0, + * the default value. + */ + +typedef struct RGBA32pixel_t { + unsigned red: 8; + unsigned green: 8; + unsigned blue: 8; + unsigned alpha: 8; +} RGBA32pixel; + +/* + * ARGB32 0xAARRGGBB (Byte order is ARGB on big-endian systems.) + * This is used by Aqua Tk for XImages and by NSBitmapImageReps whose + * bitmapFormat property is NSAlphaFirstBitmapFormat. + */ + +typedef struct ARGB32pixel_t { + unsigned blue: 8; + unsigned green: 8; + unsigned red: 8; + unsigned alpha: 8; +} ARGB32pixel; + +typedef union pixel32_t { + unsigned int uint; + RGBA32pixel rgba; + ARGB32pixel argb; +} pixel32; + #pragma mark XImage handling int _XInitImageFuncPtrs( - XImage *image) + TCL_UNUSED(XImage *)) /* image */ { return 0; } @@ -45,13 +111,18 @@ _XInitImageFuncPtrs( *---------------------------------------------------------------------- */ -static void ReleaseData(void *info, const void *data, size_t size) { +static void ReleaseData( + void *info, + TCL_UNUSED(const void *), /* data */ + TCL_UNUSED(size_t)) /* size */ +{ ckfree(info); } CGImageRef TkMacOSXCreateCGImageWithXImage( - XImage *image) + XImage *image, + uint32_t alphaInfo) { CGImageRef img = NULL; size_t bitsPerComponent, bitsPerPixel; @@ -76,7 +147,7 @@ TkMacOSXCreateCGImageWithXImage( if (image->bitmap_bit_order != MSBFirst) { char *srcPtr = image->data + image->xoffset; char *endPtr = srcPtr + len; - char *destPtr = (data = ckalloc(len)); + char *destPtr = (data = (char *)ckalloc(len)); while (srcPtr < endPtr) { *destPtr++ = xBitReverseTable[(unsigned char)(*(srcPtr++))]; @@ -94,6 +165,7 @@ TkMacOSXCreateCGImageWithXImage( provider, decode, 0); } } else if ((image->format == ZPixmap) && (image->bits_per_pixel == 32)) { + /* * Color image */ @@ -101,6 +173,7 @@ TkMacOSXCreateCGImageWithXImage( CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); if (image->width == 0 && image->height == 0) { + /* * CGCreateImage complains on early macOS releases. */ @@ -109,9 +182,7 @@ TkMacOSXCreateCGImageWithXImage( } bitsPerComponent = 8; bitsPerPixel = 32; - bitmapInfo = (image->byte_order == MSBFirst ? - kCGBitmapByteOrder32Little : kCGBitmapByteOrder32Big); - bitmapInfo |= kCGImageAlphaLast; + bitmapInfo = kCGBitmapByteOrder32Big | alphaInfo; data = (char *)memcpy(ckalloc(len), image->data + image->xoffset, len); if (data) { provider = CGDataProviderCreateWithData(data, data, len, @@ -201,14 +272,12 @@ ImageGetPixel( switch (image->bits_per_pixel) { case 32: /* 8 bits per channel */ - r = (*((unsigned int*) srcPtr) >> 16) & 0xff; - g = (*((unsigned int*) srcPtr) >> 8) & 0xff; - b = (*((unsigned int*) srcPtr) ) & 0xff; - /*if (image->byte_order == LSBFirst) { - r = srcPtr[2]; g = srcPtr[1]; b = srcPtr[0]; - } else { - r = srcPtr[1]; g = srcPtr[2]; b = srcPtr[3]; - }*/ + { + ARGB32pixel *pixel = (ARGB32pixel *)srcPtr; + r = pixel->red; + g = pixel->green; + b = pixel->blue; + } break; case 16: /* 5 bits per channel */ r = (*((unsigned short*) srcPtr) >> 7) & 0xf8; @@ -245,7 +314,10 @@ ImageGetPixel( * * ImagePutPixel -- * - * Set a single pixel in an image. + * Set a single pixel in an image. The pixel is provided as an unsigned + * 32-bit integer. The value of that integer is interpreted by assuming + * that its low-order N bits have the format specified by the XImage, + * where N is equal to the bits_per_pixel field of the XImage. * * Results: * None. @@ -271,27 +343,20 @@ ImagePutPixel( if (image->bits_per_pixel == 32) { *((unsigned int*) dstPtr) = pixel; } else { - unsigned char r = ((pixel & image->red_mask) >> 16) & 0xff; - unsigned char g = ((pixel & image->green_mask) >> 8) & 0xff; - unsigned char b = ((pixel & image->blue_mask) ) & 0xff; switch (image->bits_per_pixel) { case 16: - *((unsigned short*) dstPtr) = ((r & 0xf8) << 7) | - ((g & 0xf8) << 2) | ((b & 0xf8) >> 3); + *((unsigned short*) dstPtr) = pixel & 0xffff; break; case 8: - *dstPtr = ((r & 0xc0) >> 2) | ((g & 0xc0) >> 4) | - ((b & 0xc0) >> 6); + *dstPtr = pixel & 0xff; break; case 4: { - unsigned char c = ((r & 0x80) >> 5) | ((g & 0x80) >> 6) | - ((b & 0x80) >> 7); - *dstPtr = (x % 2) ? ((*dstPtr & 0xf0) | (c & 0x0f)) : - ((*dstPtr & 0x0f) | ((c << 4) & 0xf0)); + *dstPtr = (x % 2) ? ((*dstPtr & 0xf0) | (pixel & 0x0f)) : + ((*dstPtr & 0x0f) | ((pixel << 4) & 0xf0)); break; } case 1: - *dstPtr = ((r|g|b) & 0x80) ? (*dstPtr | (0x80 >> (x % 8))) : + *dstPtr = pixel ? (*dstPtr | (0x80 >> (x % 8))) : (*dstPtr & ~(0x80 >> (x % 8))); break; } @@ -319,7 +384,7 @@ ImagePutPixel( XImage * XCreateImage( Display* display, - Visual* visual, + TCL_UNUSED(Visual*), /* visual */ unsigned int depth, int format, int offset, @@ -388,14 +453,25 @@ XCreateImage( /* *---------------------------------------------------------------------- * - * TkPutImage, XPutImage -- + * TkPutImage, XPutImage, TkpPutRGBAImage -- + * + * These functions, which all have the same signature, copy a rectangular + * subimage of an XImage into a drawable. The first two are identical on + * macOS. They assume that the XImage data has the structure of a 32bpp + * ZPixmap in which the image data is an array of 32bit integers packed + * with 8 bit values for the Red Green and Blue channels. They ignore the + * fourth byte. The function TkpPutRGBAImage assumes that the XImage data + * has been extended by using the fourth byte to store an 8-bit Alpha + * value. (The Alpha data is assumed not to pre-multiplied). The image + * is then drawn into the drawable using standard Porter-Duff Source Atop + * Composition (kCGBlendModeSourceAtop in Apple's Core Graphics). * - * Copies a rectangular subimage of an XImage into a drawable. Currently - * this is only called by TkImgPhotoDisplay, using a Window as the - * drawable. + * The TkpPutRGBAImage function is used by TkImgPhotoDisplay to render photo + * images if the compile-time variable TK_CAN_RENDER_RGBA is defined in + * a platform's tkXXXXPort.h header, as is the case for the macOS Aqua port. * * Results: - * None. + * These functions return either BadDrawable or Success. * * Side effects: * Draws the image on the specified drawable. @@ -403,8 +479,12 @@ XCreateImage( *---------------------------------------------------------------------- */ -int -XPutImage( +#define USE_ALPHA kCGImageAlphaLast +#define IGNORE_ALPHA kCGImageAlphaNoneSkipLast + +static int +TkMacOSXPutImage( + uint32_t pixelFormat, Display* display, /* Display. */ Drawable drawable, /* Drawable to place image on. */ GC gc, /* GC to use. */ @@ -418,14 +498,14 @@ XPutImage( { TkMacOSXDrawingContext dc; MacDrawable *macDraw = (MacDrawable *)drawable; - + int result = Success; display->request++; if (!TkMacOSXSetupDrawingContext(drawable, gc, &dc)) { return BadDrawable; } if (dc.context) { CGRect bounds, srcRect, dstRect; - CGImageRef img = TkMacOSXCreateCGImageWithXImage(image); + CGImageRef img = TkMacOSXCreateCGImageWithXImage(image, pixelFormat); /* * The CGContext for a pixmap is RGB only, with A = 0. @@ -435,7 +515,6 @@ XPutImage( CGContextSetBlendMode(dc.context, kCGBlendModeSourceAtop); } if (img) { - bounds = CGRectMake(0, 0, image->width, image->height); srcRect = CGRectMake(src_x, src_y, width, height); dstRect = CGRectMake(dest_x, dest_y, width, height); @@ -445,63 +524,103 @@ XPutImage( CFRelease(img); } else { TkMacOSXDbgMsg("Invalid source drawable"); + result = BadDrawable; } } else { TkMacOSXDbgMsg("Invalid destination drawable"); + result = BadDrawable; } TkMacOSXRestoreDrawingContext(&dc); - return Success; + return result; } -int -TkPutImage( - unsigned long *colors, /* Array of pixel values used by this image. - * May be NULL. */ - int ncolors, /* Number of colors used, or 0. */ - Display *display, - Drawable d, /* Destination drawable. */ +int XPutImage( + Display* display, + Drawable drawable, GC gc, - XImage *image, /* Source image. */ - int src_x, int src_y, /* Offset of subimage. */ - int dest_x, int dest_y, /* Position of subimage origin in drawable. */ - unsigned int width, unsigned int height) - /* Dimensions of subimage. */ -{ - return XPutImage(display, d, gc, image, src_x, src_y, dest_x, dest_y, width, height); + XImage* image, + int src_x, + int src_y, + int dest_x, + int dest_y, + unsigned int width, + unsigned int height) { + return TkMacOSXPutImage(IGNORE_ALPHA, display, drawable, gc, image, + src_x, src_y, dest_x, dest_y, width, height); +} + +int TkPutImage( + TCL_UNUSED(unsigned long *), + TCL_UNUSED(int), + Display* display, + Drawable drawable, + GC gc, + XImage* image, + int src_x, + int src_y, + int dest_x, + int dest_y, + unsigned int width, + unsigned int height) { + return TkMacOSXPutImage(IGNORE_ALPHA, display, drawable, gc, image, + src_x, src_y, dest_x, dest_y, width, height); +} + +int TkpPutRGBAImage( + Display* display, + Drawable drawable, + GC gc, + XImage* image, + int src_x, + int src_y, + int dest_x, + int dest_y, + unsigned int width, + unsigned int height) { + return TkMacOSXPutImage(USE_ALPHA, display, drawable, gc, image, + src_x, src_y, dest_x, dest_y, width, height); } + /* *---------------------------------------------------------------------- * * CreateCGImageFromDrawableRect * - * Extract image data from a MacOSX drawable as a CGImage. + * Extract image data from a MacOSX drawable as a CGImage. The drawable + * may be either a pixmap or a window, but there issues in the case of + * a window. * - * This is only called by XGetImage and XCopyArea. The Tk core uses - * these functions on some platforms, but on macOS the core does not - * call them with a source drawable which is a window. Such calls are - * used only for double-buffered drawing. Since macOS defines the - * macro TK_NO_DOUBLE_BUFFERING, the generic code never calls XGetImage - * or XCopyArea on macOS. Nonetheless, these function are in the stubs - * table and therefore could be used by extensions. + * CreateCGImageFromDrawableRect is called by XGetImage and XCopyArea. + * The Tk core uses these two functions on some platforms in order to + * implement explicit double-buffered drawing -- a pixmap is copied from a + * window, modified using CPU-based graphics composition, and then copied + * back to the window. Platforms, such as macOS, on which the system + * provides double-buffered drawing and GPU-based composition operations + * can avoid calls to XGetImage and XCopyArea from the core by defining + * the compile-time variable TK_NO_DOUBLE_BUFFERING. Nonetheless, these + * two functions are in the stubs table and therefore could be used by + * extensions. * - * This implementation does not work correctly. Originally it relied on + * The implementation here does not always work correctly when the source + * is a window. The original version of this function relied on * [NSBitmapImageRep initWithFocusedViewRect:view_rect] which was * deprecated by Apple in OSX 10.14 and also required the use of other * deprecated functions such as [NSView lockFocus]. Apple's suggested * replacement is [NSView cacheDisplayInRect: toBitmapImageRep:] and that - * is what is being used here. However, that method only works when the - * view has a valid CGContext, and a view is only guaranteed to have a - * valid context during a call to [NSView drawRect]. To further complicate - * matters, cacheDisplayInRect calls [NSView drawRect]. Essentially it is - * asking the view to draw a subrectangle of itself using a special - * graphics context which is linked to the BitmapImageRep. But our - * implementation of [NSView drawRect] does not allow recursive calls. If - * called recursively it returns immediately without doing any drawing. - * So the bottom line is that this function either returns a NULL pointer - * or a black image. To make it useful would require a significant amount - * of rewriting of the drawRect method. Perhaps the next release of OSX - * will include some more helpful ways of doing this. + * is being used here. However, cacheDisplayInRect works by calling + * [NSView drawRect] after setting the current graphics context to be one + * which draws to a bitmap. There are situations in which this can be + * used, e.g. when taking a screenshot of a window. But it cannot be used + * as part of a normal display procedure, using the copy-modify-paste + * paradigm that is the basis of the explicit double-buffering. Since the + * copy operation will call the same display procedure that is calling + * this function via XGetImage or XCopyArea, this would create an infinite + * recursion. + * + * An alternative to the copy-modify-paste paradigm is to use GPU-based + * graphics composition, clipping to the specified rectangle. That is + * the approach that must be followed by display procedures on macOS. * * Results: * Returns an NSBitmapRep representing the image of the given rectangle of @@ -528,51 +647,43 @@ CreateCGImageFromDrawableRect( { MacDrawable *mac_drawable = (MacDrawable *)drawable; CGContextRef cg_context = NULL; + CGRect image_rect = CGRectMake(x, y, width, height); CGImageRef cg_image = NULL, result = NULL; - NSBitmapImageRep *bitmapRep = nil; - NSView *view = nil; + unsigned char *imageData = NULL; if (mac_drawable->flags & TK_IS_PIXMAP) { - /* - * This MacDrawable is a bitmap, so its view is NULL. - */ - - CGRect image_rect = CGRectMake(x, y, width, height); - cg_context = TkMacOSXGetCGContextForDrawable(drawable); - cg_image = CGBitmapContextCreateImage((CGContextRef) cg_context); - if (cg_image) { - result = CGImageCreateWithImageInRect(cg_image, image_rect); - CGImageRelease(cg_image); - } - } else if (TkMacOSXGetNSViewForDrawable(mac_drawable) != nil) { - - /* - * Convert Tk top-left to NSView bottom-left coordinates. - */ - - int view_height = [view bounds].size.height; - NSRect view_rect = NSMakeRect(x + mac_drawable->xOff, - view_height - height - y - mac_drawable->yOff, - width, height); - - /* - * Attempt to copy from the view to a bitmapImageRep. If the view does - * not have a valid CGContext, doing this will silently corrupt memory - * and make a big mess. So, in that case, we just return NULL. - */ - - if (view == [NSView focusView]) { - bitmapRep = [view bitmapImageRepForCachingDisplayInRect: view_rect]; - [view cacheDisplayInRect:view_rect toBitmapImageRep:bitmapRep]; - result = [bitmapRep CGImage]; - CFRelease(bitmapRep); - } else { - TkMacOSXDbgMsg("No CGContext - cannot copy from screen to bitmap."); - result = NULL; + if (cg_context) { + cg_image = CGBitmapContextCreateImage((CGContextRef) cg_context); } } else { - TkMacOSXDbgMsg("Invalid source drawable"); + NSView *view = TkMacOSXGetNSViewForDrawable(mac_drawable); + if (view == nil) { + TkMacOSXDbgMsg("Invalid source drawable"); + return NULL; + } + NSSize size = view.frame.size; + NSUInteger view_width = size.width, view_height = size.height; + NSUInteger bytesPerPixel = 4, + bytesPerRow = bytesPerPixel * view_width, + bitsPerComponent = 8; + imageData = ckalloc(view_height * bytesPerRow); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + cg_context = CGBitmapContextCreate(imageData, view_width, view_height, + bitsPerComponent, bytesPerRow, colorSpace, + kCGImageAlphaPremultipliedLast | + kCGBitmapByteOrder32Big); + CFRelease(colorSpace); + [view.layer renderInContext:cg_context]; + } + if (cg_context) { + cg_image = CGBitmapContextCreateImage(cg_context); + CGContextRelease(cg_context); + } + if (cg_image) { + result = CGImageCreateWithImageInRect(cg_image, image_rect); + CGImageRelease(cg_image); } + ckfree(imageData); return result; } @@ -627,9 +738,6 @@ CreateCGImageFromPixmap( * *---------------------------------------------------------------------- */ -struct pixel_fmt {int r; int g; int b; int a;}; -static struct pixel_fmt bgra = {2, 1, 0, 3}; -static struct pixel_fmt abgr = {3, 2, 1, 0}; XImage * XGetImage( @@ -639,14 +747,13 @@ XGetImage( int y, unsigned int width, unsigned int height, - unsigned long plane_mask, + TCL_UNUSED(unsigned long), /* plane_mask */ int format) { NSBitmapImageRep* bitmapRep = nil; NSUInteger bitmap_fmt = 0; XImage* imagePtr = NULL; char *bitmap = NULL; - char R, G, B, A; int depth = 32, offset = 0, bitmap_pad = 0; unsigned int bytes_per_row, size, row, n, m; @@ -669,48 +776,49 @@ XGetImage( size = [bitmapRep bytesPerPlane]; bytes_per_row = [bitmapRep bytesPerRow]; bitmap = (char *)ckalloc(size); - if (!bitmap - || (bitmap_fmt != 0 && bitmap_fmt != 1) - || [bitmapRep samplesPerPixel] != 4 - || [bitmapRep isPlanar] != 0 - || bytes_per_row < 4 * width - || size != bytes_per_row * height) { + if ((bitmap_fmt != 0 && bitmap_fmt != NSAlphaFirstBitmapFormat) + || [bitmapRep samplesPerPixel] != 4 + || [bitmapRep isPlanar] != 0 + || bytes_per_row < 4 * width + || size != bytes_per_row * height) { TkMacOSXDbgMsg("XGetImage: Unrecognized bitmap format"); - CFRelease(bitmapRep); + [bitmapRep release]; return NULL; } memcpy(bitmap, (char *)[bitmapRep bitmapData], size); - CFRelease(bitmapRep); - - /* - * When Apple extracts a bitmap from an NSView, it may be in either - * BGRA or ABGR format. For an XImage we need RGBA. - */ - - struct pixel_fmt pixel = bitmap_fmt == 0 ? bgra : abgr; + [bitmapRep release]; for (row = 0, n = 0; row < height; row++, n += bytes_per_row) { for (m = n; m < n + 4*width; m += 4) { - R = *(bitmap + m + pixel.r); - G = *(bitmap + m + pixel.g); - B = *(bitmap + m + pixel.b); - A = *(bitmap + m + pixel.a); - - *(bitmap + m) = R; - *(bitmap + m + 1) = G; - *(bitmap + m + 2) = B; - *(bitmap + m + 3) = A; + pixel32 pixel = *((pixel32 *)(bitmap + m)); + if (bitmap_fmt == 0) { // default format + + /* + * This pixel is in ARGB32 format. We need RGBA32. + */ + + pixel32 flipped; + flipped.rgba.red = pixel.argb.red; + flipped.rgba.green = pixel.argb.green; + flipped.rgba.blue = pixel.argb.blue; + flipped.rgba.alpha = pixel.argb.alpha; + *((pixel32 *)(bitmap + m)) = flipped; + } else { // bitmap_fmt = NSAlphaFirstBitmapFormat + *((pixel32 *)(bitmap + m)) = pixel; + } } } imagePtr = XCreateImage(display, NULL, depth, format, offset, (char*) bitmap, width, height, bitmap_pad, bytes_per_row); } else { + /* * There are some calls to XGetImage in the generic Tk code which pass * an XYPixmap rather than a ZPixmap. XYPixmaps should be handled * here. */ + TkMacOSXDbgMsg("XGetImage does not handle XYPixmaps at the moment."); } return imagePtr; |