diff options
author | culler <culler> | 2020-08-24 21:44:49 (GMT) |
---|---|---|
committer | culler <culler> | 2020-08-24 21:44:49 (GMT) |
commit | 8207bbae557d4d7711c3b5a383dfb791f8156345 (patch) | |
tree | 7fc9416d11870363552b99fc06622ab028957ec6 /macosx/tkMacOSXImage.c | |
parent | 0a55a8cb5333b7fdbb29273ca364102c7a7f743e (diff) | |
parent | 2bc6fae00d9ae77272e63ae6c01bf2a127088d3f (diff) | |
download | tk-8207bbae557d4d7711c3b5a383dfb791f8156345.zip tk-8207bbae557d4d7711c3b5a383dfb791f8156345.tar.gz tk-8207bbae557d4d7711c3b5a383dfb791f8156345.tar.bz2 |
Move XGetImage and XCopyArea to tkMacOSXImage.c and completely remove XImage.pixelpower.
Diffstat (limited to 'macosx/tkMacOSXImage.c')
-rw-r--r-- | macosx/tkMacOSXImage.c | 628 |
1 files changed, 463 insertions, 165 deletions
diff --git a/macosx/tkMacOSXImage.c b/macosx/tkMacOSXImage.c index 4aea52c..9257829 100644 --- a/macosx/tkMacOSXImage.c +++ b/macosx/tkMacOSXImage.c @@ -6,7 +6,7 @@ * Copyright (c) 1995-1997 Sun Microsystems, Inc. * Copyright 2001-2009, Apple Inc. * Copyright (c) 2005-2009 Daniel A. Steffen <das@users.sourceforge.net> - * Copyright 2017-2018 Marc Culler. + * Copyright (c) 2017-2020 Marc Culler. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -15,14 +15,16 @@ #include "tkMacOSXPrivate.h" #include "xbytes.h" +static CGImageRef CreateCGImageFromPixmap(Drawable pixmap); +static CGImageRef CreateCGImageFromDrawableRect( Drawable drawable, + int x, int y, unsigned int width, unsigned int height); + #pragma mark XImage handling int _XInitImageFuncPtrs( - XImage *image) + TCL_UNUSED(XImage *)) /* image */ { - (void)image; - return 0; } @@ -43,10 +45,11 @@ _XInitImageFuncPtrs( *---------------------------------------------------------------------- */ -static void ReleaseData(void *info, const void *data, size_t size) { - (void)data; - (void)size; - +static void ReleaseData( + void *info, + TCL_UNUSED(const void *), /* data */ + TCL_UNUSED(size_t)) /* size */ +{ ckfree(info); } @@ -77,7 +80,7 @@ TkMacOSXCreateCGImageWithXImage( if (image->bitmap_bit_order != MSBFirst) { char *srcPtr = image->data + image->xoffset; char *endPtr = srcPtr + len; - char *destPtr = (data = (char *)ckalloc(len)); + char *destPtr = (data = ckalloc(len)); while (srcPtr < endPtr) { *destPtr++ = xBitReverseTable[(unsigned char)(*(srcPtr++))]; @@ -136,142 +139,6 @@ TkMacOSXCreateCGImageWithXImage( /* *---------------------------------------------------------------------- * - * XGetImage -- - * - * This function copies data from a pixmap or window into an XImage. It - * is essentially never used. At one time it was called by - * pTkImgPhotoDisplay, but that is no longer the case. Currently it is - * called two places, one of which is requesting an XY image which we do - * not support. It probably does not work correctly -- see the comments - * for TkMacOSXBitmapRepFromDrawableRect. - * - * Results: - * Returns a newly allocated XImage containing the data from the given - * rectangle of the given drawable, or NULL if the XImage could not be - * constructed. NOTE: If we are copying from a window on a Retina - * display, the dimensions of the XImage will be 2*width x 2*height. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ -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( - Display *display, - Drawable drawable, - int x, - int y, - unsigned int width, - unsigned int height, - unsigned long plane_mask, - int format) -{ - NSBitmapImageRep* bitmap_rep = NULL; - 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; - unsigned int scalefactor=1, scaled_height=height, scaled_width=width; - NSWindow *win = TkMacOSXDrawableWindow(drawable); - static enum {unknown, no, yes} has_retina = unknown; - (void)plane_mask; - - if (win && has_retina == unknown) { -#ifdef __clang__ - has_retina = [win respondsToSelector:@selector(backingScaleFactor)] ? - yes : no; -#else - has_retina = no; -#endif - } - - if (has_retina == yes) { - /* - * We only allow scale factors 1 or 2, as Apple currently does. - */ - -#ifdef __clang__ - scalefactor = [win backingScaleFactor] == 2.0 ? 2 : 1; -#endif - scaled_height *= scalefactor; - scaled_width *= scalefactor; - } - - if (format == ZPixmap) { - if (width == 0 || height == 0) { - return NULL; - } - - bitmap_rep = TkMacOSXBitmapRepFromDrawableRect(drawable, - x, y, width, height); - if (!bitmap_rep) { - TkMacOSXDbgMsg("XGetImage: Failed to construct NSBitmapRep"); - return NULL; - } - bitmap_fmt = [bitmap_rep bitmapFormat]; - size = [bitmap_rep bytesPerPlane]; - bytes_per_row = [bitmap_rep bytesPerRow]; - bitmap = (char *)ckalloc(size); - if (!bitmap - || (bitmap_fmt != 0 && bitmap_fmt != 1) - || [bitmap_rep samplesPerPixel] != 4 - || [bitmap_rep isPlanar] != 0 - || bytes_per_row < 4 * scaled_width - || size != bytes_per_row * scaled_height) { - TkMacOSXDbgMsg("XGetImage: Unrecognized bitmap format"); - CFRelease(bitmap_rep); - return NULL; - } - memcpy(bitmap, (char *)[bitmap_rep bitmapData], size); - CFRelease(bitmap_rep); - - /* - * 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; - - for (row = 0, n = 0; row < scaled_height; row++, n += bytes_per_row) { - for (m = n; m < n + 4*scaled_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; - } - } - imagePtr = XCreateImage(display, NULL, depth, format, offset, - (char*) bitmap, scaled_width, scaled_height, - bitmap_pad, bytes_per_row); - if (scalefactor == 2) { - imagePtr->pixelpower = 1; - } - } 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; -} - -/* - *---------------------------------------------------------------------- - * * DestroyImage -- * * Destroys storage associated with an image. @@ -456,7 +323,7 @@ ImagePutPixel( XImage * XCreateImage( Display* display, - Visual* visual, + TCL_UNUSED(Visual*), /* visual */ unsigned int depth, int format, int offset, @@ -467,10 +334,9 @@ XCreateImage( int bytes_per_line) { XImage *ximage; - (void)visual; display->request++; - ximage = (XImage *)ckalloc(sizeof(XImage)); + ximage = ckalloc(sizeof(XImage)); ximage->height = height; ximage->width = width; @@ -480,13 +346,6 @@ XCreateImage( ximage->data = data; ximage->obdata = NULL; - /* - * The default pixelpower is 0. This must be explicitly set to 1 in the - * case of an XImage extracted from a Retina display. - */ - - ximage->pixelpower = 0; - if (format == ZPixmap) { ximage->bits_per_pixel = 32; ximage->bitmap_unit = 32; @@ -581,17 +440,8 @@ XPutImage( } if (img) { - /* - * If the XImage has big pixels, the source is rescaled to reflect - * the actual pixel dimensions. This is not currently used, but - * could arise if the image were copied from a retina monitor and - * redrawn on an ordinary monitor. - */ - - int pp = image->pixelpower; - bounds = CGRectMake(0, 0, image->width, image->height); - srcRect = CGRectMake(src_x<<pp, src_y<<pp, width<<pp, height<<pp); + srcRect = CGRectMake(src_x, src_y, width, height); dstRect = CGRectMake(dest_x, dest_y, width, height); TkMacOSXDrawCGImage(drawable, gc, dc.context, img, gc->foreground, gc->background, @@ -608,6 +458,454 @@ XPutImage( } /* + *---------------------------------------------------------------------- + * + * CreateCGImageFromDrawableRect + * + * Extract image data from a MacOSX drawable as a CGImage. + * + * 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. + * + * This implementation does not work correctly. Originally it 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. + * + * Results: + * Returns an NSBitmapRep representing the image of the given rectangle of + * the given drawable. This object is retained. The caller is responsible + * for releasing it. + * + * NOTE: The x,y coordinates should be relative to a coordinate system + * with origin at the top left, as used by XImage and CGImage, not bottom + * left as used by NSView. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static CGImageRef +CreateCGImageFromDrawableRect( + Drawable drawable, + int x, + int y, + unsigned int width, + unsigned int height) +{ + MacDrawable *mac_drawable = (MacDrawable *) drawable; + CGContextRef cg_context = NULL; + CGImageRef cg_image = NULL, result = NULL; + NSBitmapImageRep *bitmapRep = NULL; + NSView *view = 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 (TkMacOSXDrawableView(mac_drawable) != NULL) { + + /* + * 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; + } + } else { + TkMacOSXDbgMsg("Invalid source drawable"); + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * CreateCGImageFromPixmap -- + * + * Create a CGImage from an X Pixmap. + * + * Results: + * CGImage, release after use. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static CGImageRef +CreateCGImageFromPixmap( + Drawable pixmap) +{ + CGImageRef img = NULL; + CGContextRef context = TkMacOSXGetCGContextForDrawable(pixmap); + + if (context) { + img = CGBitmapContextCreateImage(context); + } + return img; +} + +/* + *---------------------------------------------------------------------- + * + * XGetImage -- + * + * This function copies data from a pixmap or window into an XImage. It + * is essentially never used. At one time it was called by + * pTkImgPhotoDisplay, but that is no longer the case. Currently it is + * called two places, one of which is requesting an XY image which we do + * not support. It probably does not work correctly -- see the comments + * for CGImageFromDrawableRect. + * + * Results: + * Returns a newly allocated XImage containing the data from the given + * rectangle of the given drawable, or NULL if the XImage could not be + * constructed. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +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( + Display *display, + Drawable drawable, + int x, + int y, + unsigned int width, + unsigned int height, + TCL_UNUSED(unsigned long), /* plane_mask */ + int format) +{ + NSBitmapImageRep* bitmapRep = NULL; + 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; + + if (format == ZPixmap) { + CGImageRef cgImage; + if (width == 0 || height == 0) { + return NULL; + } + + cgImage = CreateCGImageFromDrawableRect(drawable, x, y, width, height); + if (cgImage) { + bitmapRep = [NSBitmapImageRep alloc]; + [bitmapRep initWithCGImage:cgImage]; + CFRelease(cgImage); + } else { + TkMacOSXDbgMsg("XGetImage: Failed to construct CGImage"); + return NULL; + } + bitmap_fmt = [bitmapRep bitmapFormat]; + size = [bitmapRep bytesPerPlane]; + bytes_per_row = [bitmapRep bytesPerRow]; + bitmap = 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) { + TkMacOSXDbgMsg("XGetImage: Unrecognized bitmap format"); + CFRelease(bitmapRep); + 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; + + 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; + } + } + 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; +} + +/* + *---------------------------------------------------------------------- + * + * XCopyArea -- + * + * Copies image data from one drawable to another. + * + * Results: + * None. + * + * Side effects: + * Image data is moved from a window or bitmap to a second window or bitmap. + * + *---------------------------------------------------------------------- + */ + +int +XCopyArea( + Display *display, /* Display. */ + Drawable src, /* Source drawable. */ + Drawable dst, /* Destination drawable. */ + GC gc, /* GC to use. */ + int src_x, /* X & Y, width & height */ + int src_y, /* define the source rectangle */ + unsigned int width, /* that will be copied. */ + unsigned int height, + int dest_x, /* Dest X & Y on dest rect. */ + int dest_y) +{ + TkMacOSXDrawingContext dc; + MacDrawable *srcDraw = (MacDrawable *) src; + CGImageRef img = NULL; + CGRect bounds, srcRect, dstRect; + + display->request++; + if (!width || !height) { + return BadDrawable; + } + + if (!TkMacOSXSetupDrawingContext(dst, gc, &dc)) { + TkMacOSXDbgMsg("Failed to setup drawing context."); + return BadDrawable; + } + + if (!dc.context) { + TkMacOSXDbgMsg("Invalid destination drawable - no context."); + return BadDrawable; + } + + if (srcDraw->flags & TK_IS_PIXMAP) { + img = CreateCGImageFromPixmap(src); + } else if (TkMacOSXDrawableWindow(src)) { + img = CreateCGImageFromDrawableRect(src, src_x, src_y, width, height); + } else { + TkMacOSXDbgMsg("Invalid source drawable - neither window nor pixmap."); + } + + if (img) { + bounds = CGRectMake(0, 0, srcDraw->size.width, srcDraw->size.height); + srcRect = CGRectMake(src_x, src_y, width, height); + dstRect = CGRectMake(dest_x, dest_y, width, height); + TkMacOSXDrawCGImage(dst, gc, dc.context, img, + gc->foreground, gc->background, bounds, srcRect, dstRect); + CFRelease(img); + } else { + TkMacOSXDbgMsg("Failed to construct CGImage."); + } + + TkMacOSXRestoreDrawingContext(&dc); + return Success; +} + +/* + *---------------------------------------------------------------------- + * + * XCopyPlane -- + * + * Copies a bitmap from a source drawable to a destination drawable. The + * plane argument specifies which bit plane of the source contains the + * bitmap. Note that this implementation ignores the gc->function. + * + * Results: + * None. + * + * Side effects: + * Changes the destination drawable. + * + *---------------------------------------------------------------------- + */ + +int +XCopyPlane( + Display *display, /* Display. */ + Drawable src, /* Source drawable. */ + Drawable dst, /* Destination drawable. */ + GC gc, /* GC to use. */ + int src_x, /* X & Y, width & height */ + int src_y, /* define the source rectangle */ + unsigned int width, /* that will be copied. */ + unsigned int height, + int dest_x, /* Dest X & Y on dest rect. */ + int dest_y, + unsigned long plane) /* Which plane to copy. */ +{ + TkMacOSXDrawingContext dc; + MacDrawable *srcDraw = (MacDrawable *) src; + MacDrawable *dstDraw = (MacDrawable *) dst; + CGRect bounds, srcRect, dstRect; + display->request++; + if (!width || !height) { + /* TkMacOSXDbgMsg("Drawing of empty area requested"); */ + return BadDrawable; + } + if (plane != 1) { + Tcl_Panic("Unexpected plane specified for XCopyPlane"); + } + if (srcDraw->flags & TK_IS_PIXMAP) { + if (!TkMacOSXSetupDrawingContext(dst, gc, &dc)) { + return BadDrawable; + } + + CGContextRef context = dc.context; + + if (context) { + CGImageRef img = CreateCGImageFromPixmap(src); + + if (img) { + TkpClipMask *clipPtr = (TkpClipMask *) gc->clip_mask; + unsigned long imageBackground = gc->background; + + if (clipPtr && clipPtr->type == TKP_CLIP_PIXMAP) { + srcRect = CGRectMake(src_x, src_y, width, height); + CGImageRef mask = CreateCGImageFromPixmap( + clipPtr->value.pixmap); + CGImageRef submask = CGImageCreateWithImageInRect( + img, srcRect); + CGRect rect = CGRectMake(dest_x, dest_y, width, height); + + rect = CGRectOffset(rect, dstDraw->xOff, dstDraw->yOff); + CGContextSaveGState(context); + + /* + * Move the origin of the destination to top left. + */ + + CGContextTranslateCTM(context, + 0, rect.origin.y + CGRectGetMaxY(rect)); + CGContextScaleCTM(context, 1, -1); + + /* + * Fill with the background color, clipping to the mask. + */ + + CGContextClipToMask(context, rect, submask); + TkMacOSXSetColorInContext(gc, gc->background, dc.context); + CGContextFillRect(context, rect); + + /* + * Fill with the foreground color, clipping to the + * intersection of img and mask. + */ + + CGImageRef subimage = CGImageCreateWithImageInRect( + img, srcRect); + CGContextClipToMask(context, rect, subimage); + TkMacOSXSetColorInContext(gc, gc->foreground, context); + CGContextFillRect(context, rect); + CGContextRestoreGState(context); + CGImageRelease(img); + CGImageRelease(mask); + CGImageRelease(submask); + CGImageRelease(subimage); + } else { + bounds = CGRectMake(0, 0, + srcDraw->size.width, srcDraw->size.height); + srcRect = CGRectMake(src_x, src_y, width, height); + dstRect = CGRectMake(dest_x, dest_y, width, height); + TkMacOSXDrawCGImage(dst, gc, dc.context, img, + gc->foreground, imageBackground, bounds, + srcRect, dstRect); + CGImageRelease(img); + } + } else { + /* no image */ + TkMacOSXDbgMsg("Invalid source drawable"); + } + } else { + TkMacOSXDbgMsg("Invalid destination drawable - " + "could not get a bitmap context."); + } + TkMacOSXRestoreDrawingContext(&dc); + return Success; + } else { + /* + * Source drawable is a Window, not a Pixmap. + */ + + return XCopyArea(display, src, dst, gc, src_x, src_y, width, height, + dest_x, dest_y); + } +} + +/* * Local Variables: * mode: objc * c-basic-offset: 4 |