1 /* xscreensaver, Copyright (c) 1991-2017 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
14 But it's a bunch of function definitions that bear some resemblance to
15 Xlib and that do Cocoa-ish things that bear some resemblance to the
16 things that Xlib might have done.
18 This is the original version of jwxyz for MacOS and iOS.
19 The version used by Android is in jwxyz-gl.c and jwxyz-common.c.
20 Both versions depend on jwxyz-cocoa.m.
23 #ifdef JWXYZ_QUARTZ // entire file
30 # import <UIKit/UIKit.h>
31 # import <UIKit/UIScreen.h>
32 # import <QuartzCore/QuartzCore.h>
33 # define NSView UIView
34 # define NSRect CGRect
35 # define NSPoint CGPoint
36 # define NSSize CGSize
37 # define NSColor UIColor
38 # define NSImage UIImage
39 # define NSEvent UIEvent
40 # define NSFont UIFont
41 # define NSGlyph CGGlyph
42 # define NSWindow UIWindow
43 # define NSMakeSize CGSizeMake
44 # define NSBezierPath UIBezierPath
45 # define colorWithDeviceRed colorWithRed
47 # import <Cocoa/Cocoa.h>
50 #import <CoreText/CTFont.h>
51 #import <CoreText/CTLine.h>
54 #import "jwxyz-cocoa.h"
55 #import "jwxyz-timers.h"
61 struct jwxyz_Display {
64 struct jwxyz_sources_data *timers_data;
67 CGDirectDisplayID cgdpy; /* ...of the one and only Window, main_window.
68 This can change if the window is dragged to
69 a different screen. */
72 CGColorSpaceRef colorspace; /* Color space of this screen. We tag all of
73 our images with this to avoid translation
76 unsigned long window_background;
81 CGBitmapInfo bitmap_info;
82 unsigned long black, white;
89 CGImageRef clip_mask; // CGImage copy of the Pixmap in gcv.clip_mask
93 // 24/32bpp -> 32bpp image conversion.
94 // Any of RGBA, BGRA, ABGR, or ARGB can be represented by a rotate of 0/8/16/24
95 // bits and an optional byte order swap.
97 // This type encodes such a conversion.
98 typedef unsigned convert_mode_t;
100 // It's rotate, then swap.
101 // A rotation here shifts bytes forward in memory. On x86/ARM, that's a left
102 // rotate, and on PowerPC, a rightward rotation.
103 static const convert_mode_t CONVERT_MODE_ROTATE_MASK = 0x3;
104 static const convert_mode_t CONVERT_MODE_SWAP = 0x4;
107 // Converts an array of pixels ('src') from one format to another, placing the
108 // result in 'dest', according to the pixel conversion mode 'mode'.
110 convert_row (uint32_t *dest, const void *src, size_t count,
111 convert_mode_t mode, size_t src_bpp)
113 Assert (src_bpp == 24 || src_bpp == 32, "weird bpp");
115 // This works OK iff src == dest or src and dest do not overlap.
119 memcpy (dest, src, count * 4);
123 // This is correct, but not fast.
124 convert_mode_t rot = (mode & CONVERT_MODE_ROTATE_MASK) * 8;
125 convert_mode_t flip = mode & CONVERT_MODE_SWAP;
129 uint32_t *dest_end = dest + count;
130 while (dest != dest_end) {
134 x = *(const uint32_t *)src;
135 else { // src_bpp == 3
136 const uint8_t *src8 = (const uint8_t *)src;
137 // __LITTLE/BIG_ENDIAN__ are defined by the compiler.
138 # if defined __LITTLE_ENDIAN__
139 x = src8[0] | (src8[1] << 8) | (src8[2] << 16) | 0xff000000;
140 # elif defined __BIG_ENDIAN__
141 x = (src8[0] << 24) | (src8[1] << 16) | (src8[2] << 8) | 0xff;
143 # error "Can't determine system endianness."
147 src = (const uint8_t *)src + src_bpp;
149 /* The naive (i.e. ubiquitous) portable implementation of bitwise rotation,
150 for 32-bit integers, is:
152 (x << rot) | (x >> (32 - rot))
154 This works nearly everywhere. Compilers on x86 wil generally recognize
155 the idiom and convert it to a ROL instruction. But there's a problem
156 here: according to the C specification, bit shifts greater than or equal
157 to the length of the integer are undefined. And if rot = 0:
158 1. (x << 0) | (x >> (32 - 0))
159 2. (x << 0) | (x >> 32)
160 3. (x << 0) | (Undefined!)
162 Still, when the compiler converts this to a ROL on x86, everything works
163 as intended. But, there are two additional problems when Clang does
164 compile-time constant expression evaluation with the (x >> 32)
166 1. Instead of evaluating it to something reasonable (either 0, like a
167 human would intuitively expect, or x, like x86 would with SHR), Clang
168 seems to pull a value out of nowhere, like -1, or some other random
170 2. Clang's warning for this, -Wshift-count-overflow, only works when the
171 shift count is a literal constant, as opposed to an arbitrary
172 expression that is optimized down to a constant.
173 Put together, this means that the assertions in jwxyz_make_display with
174 convert_px break with the above naive rotation, but only for a release
177 http://blog.regehr.org/archives/1063
178 http://llvm.org/bugs/show_bug.cgi?id=17332
179 As described in those links, there is a solution here: Masking the
180 undefined shift with '& 31' as below makes the experesion well-defined
181 again. And LLVM is set to pick up on this safe version of the idiom and
182 use a rotation instruction on architectures (like x86) that support it,
183 just like it does with the unsafe version.
185 Too bad LLVM doesn't want to pick up on that particular optimization
186 here. Oh well. At least this code usually isn't critical w.r.t.
190 # if defined __LITTLE_ENDIAN__
191 x = (x << rot) | (x >> ((32 - rot) & 31));
192 # elif defined __BIG_ENDIAN__
193 x = (x >> rot) | (x << ((32 - rot) & 31));
197 x = __builtin_bswap32(x); // LLVM/GCC built-in function.
205 // Converts a single pixel.
207 convert_px (uint32_t px, convert_mode_t mode)
209 convert_row (&px, &px, 1, mode, 32);
214 // This returns the inverse conversion mode, such that:
216 // == convert_px(convert_px(pixel, mode), convert_mode_invert(mode))
217 // == convert_px(convert_px(pixel, convert_mode_invert(mode)), mode)
218 static convert_mode_t
219 convert_mode_invert (convert_mode_t mode)
221 // swap(0); rot(n) == rot(n); swap(0)
222 // swap(1); rot(n) == rot(-n); swap(1)
223 return mode & CONVERT_MODE_SWAP ? mode : CONVERT_MODE_ROTATE_MASK & -mode;
227 // This combines two conversions into one, such that:
228 // convert_px(convert_px(pixel, mode0), mode1)
229 // == convert_px(pixel, convert_mode_merge(mode0, mode1))
230 static convert_mode_t
231 convert_mode_merge (convert_mode_t m0, convert_mode_t m1)
233 // rot(r0); swap(s0); rot(r1); swap(s1)
234 // rot(r0); rot(s0 ? -r1 : r1); swap(s0); swap(s1)
235 // rot(r0 + (s0 ? -r1 : r1)); swap(s0 + s1)
237 ((m0 + (m0 & CONVERT_MODE_SWAP ? -m1 : m1)) & CONVERT_MODE_ROTATE_MASK) |
238 ((m0 ^ m1) & CONVERT_MODE_SWAP);
242 // This returns a conversion mode that converts an arbitrary 32-bit format
243 // specified by bitmap_info to RGBA.
244 static convert_mode_t
245 convert_mode_to_rgba (CGBitmapInfo bitmap_info)
247 // Former default: kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little
250 // green = 0x0000FF00;
251 // blue = 0x000000FF;
253 // RGBA: kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big
255 CGImageAlphaInfo alpha_info =
256 (CGImageAlphaInfo)(bitmap_info & kCGBitmapAlphaInfoMask);
258 Assert (! (bitmap_info & kCGBitmapFloatComponents),
259 "kCGBitmapFloatComponents unsupported");
260 Assert (alpha_info != kCGImageAlphaOnly, "kCGImageAlphaOnly not supported");
262 convert_mode_t rot = alpha_info == kCGImageAlphaFirst ||
263 alpha_info == kCGImageAlphaPremultipliedFirst ||
264 alpha_info == kCGImageAlphaNoneSkipFirst ?
267 CGBitmapInfo byte_order = bitmap_info & kCGBitmapByteOrderMask;
269 Assert (byte_order == kCGBitmapByteOrder32Little ||
270 byte_order == kCGBitmapByteOrder32Big,
271 "byte order not supported");
273 convert_mode_t swap = byte_order == kCGBitmapByteOrder32Little ?
274 CONVERT_MODE_SWAP : 0;
276 rot = CONVERT_MODE_ROTATE_MASK & -rot;
289 jwxyz_alloc_color (Display *dpy,
290 uint16_t r, uint16_t g, uint16_t b, uint16_t a)
292 union color_bytes color;
294 /* Instead of (int)(c / 256.0), another possibility is
295 (int)(c * 255.0 / 65535.0 + 0.5). This can be calculated using only
296 uint8_t integer_math(uint16_t c) {
297 unsigned c0 = c + 128;
298 return (c0 - (c0 >> 8)) >> 8;
302 color.bytes[0] = r >> 8;
303 color.bytes[1] = g >> 8;
304 color.bytes[2] = b >> 8;
305 color.bytes[3] = a >> 8;
308 convert_px (color.pixel,
309 convert_mode_invert (convert_mode_to_rgba (dpy->screen->bitmap_info)));
314 jwxyz_query_color (Display *dpy, unsigned long pixel, uint8_t *rgba)
316 union color_bytes color;
317 color.pixel = convert_px ((uint32_t)pixel,
318 convert_mode_to_rgba (dpy->screen->bitmap_info));
319 for (unsigned i = 0; i != 4; ++i)
320 rgba[i] = color.bytes[i];
325 query_color_float (Display *dpy, unsigned long pixel, float *rgba)
328 jwxyz_query_color (dpy, pixel, rgba8);
329 for (unsigned i = 0; i != 4; ++i)
330 rgba[i] = rgba8[i] * (1.0f / 255.0f);
335 jwxyz_make_display (Window w)
337 CGContextRef cgc = w->cgc;
339 Display *d = (Display *) calloc (1, sizeof(*d));
340 d->screen = (Screen *) calloc (1, sizeof(Screen));
343 d->screen->bitmap_info = CGBitmapContextGetBitmapInfo (cgc);
344 d->screen->black = jwxyz_alloc_color (d, 0x0000, 0x0000, 0x0000, 0xFFFF);
345 d->screen->white = jwxyz_alloc_color (d, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF);
348 // Tests for the image conversion modes.
350 const uint32_t key = 0x04030201;
351 # ifdef __LITTLE_ENDIAN__
352 assert (convert_px (key, 0) == key);
353 assert (convert_px (key, 1) == 0x03020104);
354 assert (convert_px (key, 3) == 0x01040302);
355 assert (convert_px (key, 4) == 0x01020304);
356 assert (convert_px (key, 5) == 0x04010203);
358 for (unsigned i = 0; i != 8; ++i) {
359 assert (convert_px(convert_px(key, i), convert_mode_invert(i)) == key);
360 assert (convert_mode_invert(convert_mode_invert(i)) == i);
363 for (unsigned i = 0; i != 8; ++i) {
364 for (unsigned j = 0; j != 8; ++j)
365 assert (convert_px(convert_px(key, i), j) ==
366 convert_px(key, convert_mode_merge(i, j)));
371 Visual *v = (Visual *) calloc (1, sizeof(Visual));
372 v->class = TrueColor;
373 v->red_mask = jwxyz_alloc_color (d, 0xFFFF, 0x0000, 0x0000, 0x0000);
374 v->green_mask = jwxyz_alloc_color (d, 0x0000, 0xFFFF, 0x0000, 0x0000);
375 v->blue_mask = jwxyz_alloc_color (d, 0x0000, 0x0000, 0xFFFF, 0x0000);
376 CGBitmapInfo byte_order = d->screen->bitmap_info & kCGBitmapByteOrderMask;
377 Assert ( ! (d->screen->bitmap_info & kCGBitmapFloatComponents) &&
378 (byte_order == kCGBitmapByteOrder32Little ||
379 byte_order == kCGBitmapByteOrder32Big),
380 "invalid bits per channel");
381 d->screen->visual = v;
383 d->timers_data = jwxyz_sources_init (XtDisplayToApplicationContext (d));
385 d->window_background = BlackPixel(d,0);
389 Assert (cgc, "no CGContext");
394 jwxyz_free_display (Display *dpy)
396 jwxyz_sources_free (dpy->timers_data);
398 free (dpy->screen->visual);
404 /* Call this after any modification to the bits on a Pixmap or Window.
405 Most Pixmaps are used frequently as sources and infrequently as
406 destinations, so it pays to cache the data as a CGImage as needed.
409 invalidate_drawable_cache (Drawable d)
412 CGImageRelease (d->cgi);
418 /* Call this when the View changes size or position.
421 jwxyz_window_resized (Display *dpy)
423 Window w = dpy->main_window;
426 // Figure out which screen the window is currently on.
429 XTranslateCoordinates (dpy, w, NULL, 0, 0, &wx, &wy, NULL);
435 CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n);
439 CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n);
441 Assert (dpy->cgdpy, "unable to find CGDisplay");
443 # endif // USE_IPHONE
447 // Figure out this screen's colorspace, and use that for every CGImage.
449 CMProfileRef profile = 0;
450 CMGetProfileByAVID ((CMDisplayIDType) dpy->cgdpy, &profile);
451 Assert (profile, "unable to find colorspace profile");
452 dpy->colorspace = CGColorSpaceCreateWithPlatformColorSpace (profile);
453 Assert (dpy->colorspace, "unable to find colorspace");
457 // WTF? It's faster if we *do not* use the screen's colorspace!
459 dpy->colorspace = CGColorSpaceCreateDeviceRGB();
461 invalidate_drawable_cache (w);
466 jwxyz_flush_context (Display *dpy)
468 // CGContextSynchronize is another possibility.
469 CGContextFlush(dpy->main_window->cgc);
473 display_sources_data (Display *dpy)
475 return dpy->timers_data;
480 XRootWindow (Display *dpy, int screen)
482 return dpy ? dpy->main_window : 0;
486 XDefaultScreenOfDisplay (Display *dpy)
488 return dpy ? dpy->screen : 0;
492 XDefaultVisualOfScreen (Screen *screen)
494 return screen ? screen->visual : 0;
498 XDisplayOfScreen (Screen *s)
500 return s ? s->dpy : 0;
504 XDisplayNumberOfScreen (Screen *s)
510 XScreenNumberOfScreen (Screen *s)
516 XBlackPixelOfScreen(Screen *screen)
518 return screen->black;
522 XWhitePixelOfScreen(Screen *screen)
524 return screen->white;
528 XCellsOfScreen(Screen *screen)
530 Visual *v = screen->visual;
531 return v->red_mask | v->green_mask | v->blue_mask;
536 set_color (Display *dpy, CGContextRef cgc, unsigned long argb,
537 unsigned int depth, Bool alpha_allowed_p, Bool fill_p)
539 jwxyz_validate_pixel (dpy, argb, depth, alpha_allowed_p);
542 CGContextSetGrayFillColor (cgc, (argb ? 1.0 : 0.0), 1.0);
544 CGContextSetGrayStrokeColor (cgc, (argb ? 1.0 : 0.0), 1.0);
547 query_color_float (dpy, argb, rgba);
549 CGContextSetRGBFillColor (cgc, rgba[0], rgba[1], rgba[2], rgba[3]);
551 CGContextSetRGBStrokeColor (cgc, rgba[0], rgba[1], rgba[2], rgba[3]);
556 set_line_mode (CGContextRef cgc, XGCValues *gcv)
558 CGContextSetLineWidth (cgc, gcv->line_width ? gcv->line_width : 1);
559 CGContextSetLineJoin (cgc,
560 gcv->join_style == JoinMiter ? kCGLineJoinMiter :
561 gcv->join_style == JoinRound ? kCGLineJoinRound :
563 CGContextSetLineCap (cgc,
564 gcv->cap_style == CapNotLast ? kCGLineCapButt :
565 gcv->cap_style == CapButt ? kCGLineCapButt :
566 gcv->cap_style == CapRound ? kCGLineCapRound :
571 set_clip_mask (Drawable d, GC gc)
573 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
575 Pixmap p = gc->gcv.clip_mask;
577 Assert (p->type == PIXMAP, "not a pixmap");
579 XRectangle wr = d->frame;
581 to.origin.x = wr.x + gc->gcv.clip_x_origin;
582 to.origin.y = wr.y + wr.height - gc->gcv.clip_y_origin
584 to.size.width = p->frame.width;
585 to.size.height = p->frame.height;
587 CGContextClipToMask (d->cgc, to, gc->clip_mask);
591 /* Pushes a GC context; sets BlendMode and ClipMask.
594 push_gc (Drawable d, GC gc)
596 CGContextRef cgc = d->cgc;
597 CGContextSaveGState (cgc);
599 switch (gc->gcv.function) {
602 case GXcopy:/*CGContextSetBlendMode (cgc, kCGBlendModeNormal);*/ break;
603 case GXxor: CGContextSetBlendMode (cgc, kCGBlendModeDifference); break;
604 case GXor: CGContextSetBlendMode (cgc, kCGBlendModeLighten); break;
605 case GXand: CGContextSetBlendMode (cgc, kCGBlendModeDarken); break;
606 default: Assert(0, "unknown gcv function"); break;
609 if (gc->gcv.clip_mask)
610 set_clip_mask (d, gc);
614 /* Pushes a GC context; sets BlendMode, ClipMask, Fill, and Stroke colors.
617 push_color_gc (Display *dpy, Drawable d, GC gc, unsigned long color,
618 Bool antialias_p, Bool fill_p)
622 int depth = gc->depth;
623 switch (gc->gcv.function) {
624 case GXset: color = (depth == 1 ? 1 : WhitePixel(dpy,0)); break;
625 case GXclear: color = (depth == 1 ? 0 : BlackPixel(dpy,0)); break;
628 CGContextRef cgc = d->cgc;
629 set_color (dpy, cgc, color, depth, gc->gcv.alpha_allowed_p, fill_p);
630 CGContextSetShouldAntialias (cgc, antialias_p);
634 /* Pushes a GC context; sets Fill and Stroke colors to the foreground color.
637 push_fg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p)
639 push_color_gc (dpy, d, gc, gc->gcv.foreground, gc->gcv.antialias_p, fill_p);
643 bitmap_context_p (Drawable d)
650 /* You've got to be fucking kidding me!
652 It is *way* faster to draw points by creating and drawing a 1x1 CGImage
653 with repeated calls to CGContextDrawImage than it is to make a single
654 call to CGContextFillRects() with a list of 1x1 rectangles!
656 I still wouldn't call it *fast*, however...
658 #define XDRAWPOINTS_IMAGES
660 /* Update, 2012: Kurt Revis <krevis@snoize.com> points out that diddling
661 the bitmap data directly is faster. This only works on Pixmaps, though,
662 not Windows. (Fortunately, on iOS, the Window is really a Pixmap.)
664 #define XDRAWPOINTS_CGDATA
667 XDrawPoints (Display *dpy, Drawable d, GC gc,
668 XPoint *points, int count, int mode)
671 XRectangle wr = d->frame;
673 # ifdef XDRAWPOINTS_CGDATA
675 if (bitmap_context_p (d))
677 CGContextRef cgc = d->cgc;
678 void *data = CGBitmapContextGetData (cgc);
679 size_t bpr = CGBitmapContextGetBytesPerRow (cgc);
680 size_t w = CGBitmapContextGetWidth (cgc);
681 size_t h = CGBitmapContextGetHeight (cgc);
683 Assert (data, "no bitmap data in Drawable");
685 unsigned long argb = gc->gcv.foreground;
686 jwxyz_validate_pixel (dpy, argb, gc->depth, gc->gcv.alpha_allowed_p);
688 argb = (gc->gcv.foreground ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
691 CGFloat y0 = wr.y; // Y axis is refreshingly not flipped.
693 // It's uglier, but faster, to hoist the conditional out of the loop.
694 if (mode == CoordModePrevious) {
695 CGFloat x = x0, y = y0;
696 for (i = 0; i < count; i++, points++) {
700 if (x >= 0 && x < w && y >= 0 && y < h) {
701 unsigned int *p = (unsigned int *)
702 ((char *) data + (size_t) y * bpr + (size_t) x * 4);
703 *p = (unsigned int) argb;
707 for (i = 0; i < count; i++, points++) {
708 CGFloat x = x0 + points->x;
709 CGFloat y = y0 + points->y;
711 if (x >= 0 && x < w && y >= 0 && y < h) {
712 unsigned int *p = (unsigned int *)
713 ((char *) data + (size_t) y * bpr + (size_t) x * 4);
714 *p = (unsigned int) argb;
719 } else /* d->type == WINDOW */
721 # endif /* XDRAWPOINTS_CGDATA */
723 push_fg_gc (dpy, d, gc, YES);
725 # ifdef XDRAWPOINTS_IMAGES
727 unsigned long argb = gc->gcv.foreground;
728 jwxyz_validate_pixel (dpy, argb, gc->depth, gc->gcv.alpha_allowed_p);
730 argb = (gc->gcv.foreground ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
732 CGDataProviderRef prov = CGDataProviderCreateWithData (NULL, &argb, 4,
734 CGImageRef cgi = CGImageCreate (1, 1,
737 /* Host-ordered, since we're using the
738 address of an int as the color data. */
739 dpy->screen->bitmap_info,
742 NO, /* interpolate */
743 kCGRenderingIntentDefault);
744 CGDataProviderRelease (prov);
746 CGContextRef cgc = d->cgc;
748 rect.size.width = rect.size.height = 1;
749 for (i = 0; i < count; i++) {
750 if (i > 0 && mode == CoordModePrevious) {
751 rect.origin.x += points->x;
752 rect.origin.x -= points->y;
754 rect.origin.x = wr.x + points->x;
755 rect.origin.y = wr.y + wr.height - points->y - 1;
758 //Assert(CGImageGetColorSpace (cgi) == dpy->colorspace,"bad colorspace");
759 CGContextDrawImage (cgc, rect, cgi);
763 CGImageRelease (cgi);
765 # else /* ! XDRAWPOINTS_IMAGES */
767 CGRect *rects = (CGRect *) malloc (count * sizeof(CGRect));
770 for (i = 0; i < count; i++) {
771 r->size.width = r->size.height = 1;
772 if (i > 0 && mode == CoordModePrevious) {
773 r->origin.x = r[-1].origin.x + points->x;
774 r->origin.y = r[-1].origin.x - points->y;
776 r->origin.x = wr.origin.x + points->x;
777 r->origin.y = wr.origin.y + wr.size.height - points->y;
783 CGContextFillRects (d->cgc, rects, count);
786 # endif /* ! XDRAWPOINTS_IMAGES */
791 invalidate_drawable_cache (d);
798 map_point (Drawable d, int x, int y)
800 const XRectangle *wr = &d->frame;
803 p.y = wr->y + wr->height - y;
809 adjust_point_for_line (GC gc, CGPoint *p)
811 // Here's the authoritative discussion on how X draws lines:
812 // http://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:CreateGC:line-width
813 if (gc->gcv.line_width <= 1) {
814 /* Thin lines are "drawn using an unspecified, device-dependent
815 algorithm", but seriously though, Bresenham's algorithm. Bresenham's
816 algorithm runs to and from pixel centers.
818 There's a few screenhacks (Maze, at the very least) that set line_width
819 to 1 when it probably should be set to 0, so it's line_width <= 1
825 /* Thick lines OTOH run from the upper-left corners of pixels. This means
826 that a horizontal thick line of width 1 straddles two scan lines.
827 Aliasing requires one of these scan lines be chosen; the following
828 nudges the point so that the right choice is made. */
835 point_for_line (Drawable d, GC gc, int x, int y)
837 CGPoint result = map_point (d, x, y);
838 adjust_point_for_line (gc, &result);
844 XDrawLine (Display *dpy, Drawable d, GC gc, int x1, int y1, int x2, int y2)
846 // when drawing a zero-length line, obey line-width and cap-style.
847 if (x1 == x2 && y1 == y2) {
848 int w = gc->gcv.line_width;
851 if (gc->gcv.line_width > 1 && gc->gcv.cap_style == CapRound)
852 return XFillArc (dpy, d, gc, x1, y1, w, w, 0, 360*64);
855 w = 1; // Actually show zero-length lines.
856 return XFillRectangle (dpy, d, gc, x1, y1, w, w);
860 CGPoint p = point_for_line (d, gc, x1, y1);
862 push_fg_gc (dpy, d, gc, NO);
864 CGContextRef cgc = d->cgc;
865 set_line_mode (cgc, &gc->gcv);
866 CGContextBeginPath (cgc);
867 CGContextMoveToPoint (cgc, p.x, p.y);
868 p = point_for_line(d, gc, x2, y2);
869 CGContextAddLineToPoint (cgc, p.x, p.y);
870 CGContextStrokePath (cgc);
872 invalidate_drawable_cache (d);
877 XDrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count,
882 push_fg_gc (dpy, d, gc, NO);
884 CGContextRef cgc = d->cgc;
886 set_line_mode (cgc, &gc->gcv);
888 // if the first and last points coincide, use closepath to get
889 // the proper line-joining.
890 BOOL closed_p = (points[0].x == points[count-1].x &&
891 points[0].y == points[count-1].y);
892 if (closed_p) count--;
894 p = point_for_line(d, gc, points->x, points->y);
896 CGContextBeginPath (cgc);
897 CGContextMoveToPoint (cgc, p.x, p.y);
898 for (i = 1; i < count; i++) {
899 if (mode == CoordModePrevious) {
903 p = point_for_line(d, gc, points->x, points->y);
905 CGContextAddLineToPoint (cgc, p.x, p.y);
908 if (closed_p) CGContextClosePath (cgc);
909 CGContextStrokePath (cgc);
911 invalidate_drawable_cache (d);
917 XDrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count)
921 CGContextRef cgc = d->cgc;
923 push_fg_gc (dpy, d, gc, NO);
924 set_line_mode (cgc, &gc->gcv);
925 CGContextBeginPath (cgc);
926 for (i = 0; i < count; i++) {
927 CGPoint p = point_for_line (d, gc, segments->x1, segments->y1);
928 CGContextMoveToPoint (cgc, p.x, p.y);
929 p = point_for_line (d, gc, segments->x2, segments->y2);
930 CGContextAddLineToPoint (cgc, p.x, p.y);
933 CGContextStrokePath (cgc);
935 invalidate_drawable_cache (d);
941 XClearWindow (Display *dpy, Window win)
943 Assert (win && win->type == WINDOW, "not a window");
944 XRectangle wr = win->frame;
945 return XClearArea (dpy, win, 0, 0, wr.width, wr.height, 0);
949 jwxyz_window_background (Display *dpy)
951 return dpy->window_background;
955 XSetWindowBackground (Display *dpy, Window w, unsigned long pixel)
957 Assert (w && w->type == WINDOW, "not a window");
958 jwxyz_validate_pixel (dpy, pixel, 32, NO);
959 dpy->window_background = pixel;
964 jwxyz_fill_rects (Display *dpy, Drawable d, GC gc,
965 const XRectangle *rectangles, unsigned long nrectangles,
968 Assert (!gc || gc->depth == jwxyz_drawable_depth (d), "depth mismatch");
970 CGContextRef cgc = d->cgc;
973 bitmap_context_p (d) &&
974 (!gc || (gc->gcv.function == GXcopy &&
975 !gc->gcv.alpha_allowed_p &&
976 !gc->gcv.clip_mask));
980 push_color_gc (dpy, d, gc, pixel, gc->gcv.antialias_p, YES);
982 set_color (dpy, d->cgc, pixel, jwxyz_drawable_depth (d), NO, YES);
985 for (unsigned i = 0; i != nrectangles; ++i) {
987 int x = rectangles[i].x;
988 int y = rectangles[i].y;
989 unsigned long width = rectangles[i].width;
990 unsigned long height = rectangles[i].height;
993 long // negative_int > unsigned_int == 1
994 dw = CGBitmapContextGetWidth (cgc),
995 dh = CGBitmapContextGetHeight (cgc);
997 if (x >= dw || y >= dh)
1010 if (width <= 0 || height <= 0)
1013 unsigned long max_width = dw - x;
1014 if (width > max_width)
1016 unsigned long max_height = dh - y;
1017 if (height > max_height)
1018 height = max_height;
1020 if (jwxyz_drawable_depth (d) == 1)
1021 pixel = pixel ? WhitePixel(dpy, 0) : BlackPixel(dpy, 0);
1023 size_t dst_bytes_per_row = CGBitmapContextGetBytesPerRow (d->cgc);
1024 void *dst = seek_xy (CGBitmapContextGetData (d->cgc),
1025 dst_bytes_per_row, x, y);
1027 Assert(sizeof(wchar_t) == 4, "somebody changed the ABI");
1029 // Would be nice if Apple used SSE/NEON in wmemset. Maybe someday.
1030 wmemset (dst, (wchar_t) pixel, width);
1032 dst = (char *) dst + dst_bytes_per_row;
1037 r.origin = map_point (d, x, y);
1038 r.origin.y -= height;
1039 r.size.width = width;
1040 r.size.height = height;
1041 CGContextFillRect (cgc, r);
1045 if (!fast_fill_p && gc)
1047 invalidate_drawable_cache (d);
1052 XClearArea (Display *dpy, Window win, int x, int y, int w, int h, Bool exp)
1054 Assert (win && win->type == WINDOW, "not a window");
1055 jwxyz_fill_rect (dpy, win, 0, x, y, w, h, dpy->window_background);
1061 XFillPolygon (Display *dpy, Drawable d, GC gc,
1062 XPoint *points, int npoints, int shape, int mode)
1064 XRectangle wr = d->frame;
1066 push_fg_gc (dpy, d, gc, YES);
1067 CGContextRef cgc = d->cgc;
1068 CGContextBeginPath (cgc);
1070 for (i = 0; i < npoints; i++) {
1071 if (i > 0 && mode == CoordModePrevious) {
1075 x = wr.x + points[i].x;
1076 y = wr.y + wr.height - points[i].y;
1080 CGContextMoveToPoint (cgc, x, y);
1082 CGContextAddLineToPoint (cgc, x, y);
1084 CGContextClosePath (cgc);
1085 if (gc->gcv.fill_rule == EvenOddRule)
1086 CGContextEOFillPath (cgc);
1088 CGContextFillPath (cgc);
1090 invalidate_drawable_cache (d);
1094 #define radians(DEG) ((DEG) * M_PI / 180.0)
1095 #define degrees(RAD) ((RAD) * 180.0 / M_PI)
1098 jwxyz_draw_arc (Display *dpy, Drawable d, GC gc, int x, int y,
1099 unsigned int width, unsigned int height,
1100 int angle1, int angle2, Bool fill_p)
1102 XRectangle wr = d->frame;
1104 bound.origin.x = wr.x + x;
1105 bound.origin.y = wr.y + wr.height - y - (int)height;
1106 bound.size.width = width;
1107 bound.size.height = height;
1110 ctr.x = bound.origin.x + bound.size.width /2;
1111 ctr.y = bound.origin.y + bound.size.height/2;
1113 float r1 = radians (angle1/64.0);
1114 float r2 = radians (angle2/64.0) + r1;
1115 BOOL clockwise = angle2 < 0;
1116 BOOL closed_p = (angle2 >= 360*64 || angle2 <= -360*64);
1118 push_fg_gc (dpy, d, gc, fill_p);
1120 CGContextRef cgc = d->cgc;
1121 CGContextBeginPath (cgc);
1123 CGContextSaveGState(cgc);
1124 CGContextTranslateCTM (cgc, ctr.x, ctr.y);
1125 CGContextScaleCTM (cgc, width/2.0, height/2.0);
1127 CGContextMoveToPoint (cgc, 0, 0);
1129 CGContextAddArc (cgc, 0.0, 0.0, 1, r1, r2, clockwise);
1130 CGContextRestoreGState (cgc); // restore before stroke, for line width
1133 CGContextClosePath (cgc); // for proper line joining
1136 CGContextFillPath (cgc);
1138 set_line_mode (cgc, &gc->gcv);
1139 CGContextStrokePath (cgc);
1143 invalidate_drawable_cache (d);
1149 jwxyz_gc_gcv (GC gc)
1156 jwxyz_gc_depth (GC gc)
1163 XCreateGC (Display *dpy, Drawable d, unsigned long mask, XGCValues *xgcv)
1165 struct jwxyz_GC *gc = (struct jwxyz_GC *) calloc (1, sizeof(*gc));
1166 gc->depth = jwxyz_drawable_depth (d);
1168 jwxyz_gcv_defaults (dpy, &gc->gcv, gc->depth);
1169 XChangeGC (dpy, gc, mask, xgcv);
1175 XFreeGC (Display *dpy, GC gc)
1178 XUnloadFont (dpy, gc->gcv.font);
1180 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
1182 if (gc->gcv.clip_mask) {
1183 XFreePixmap (dpy, gc->gcv.clip_mask);
1184 CGImageRelease (gc->clip_mask);
1192 flipbits (unsigned const char *in, unsigned char *out, int length)
1194 static const unsigned char table[256] = {
1195 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0,
1196 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
1197 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8,
1198 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
1199 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4,
1200 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
1201 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC,
1202 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
1203 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2,
1204 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
1205 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA,
1206 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
1207 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6,
1208 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
1209 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE,
1210 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
1211 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1,
1212 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
1213 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9,
1214 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
1215 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5,
1216 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
1217 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED,
1218 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
1219 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3,
1220 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
1221 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB,
1222 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
1223 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7,
1224 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
1225 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF,
1226 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
1228 while (length-- > 0)
1229 *out++ = table[*in++];
1234 XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
1235 int src_x, int src_y, int dest_x, int dest_y,
1236 unsigned int w, unsigned int h)
1238 XRectangle wr = d->frame;
1240 Assert (gc, "no GC");
1241 Assert ((w < 65535), "improbably large width");
1242 Assert ((h < 65535), "improbably large height");
1243 Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x");
1244 Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y");
1245 Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x");
1246 Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y");
1248 // Clip width and height to the bounds of the Drawable
1250 if (dest_x + (int)w > wr.width) {
1251 if (dest_x > wr.width)
1253 w = wr.width - dest_x;
1255 if (dest_y + (int)h > wr.height) {
1256 if (dest_y > wr.height)
1258 h = wr.height - dest_y;
1260 if (w <= 0 || h <= 0)
1263 // Clip width and height to the bounds of the XImage
1265 if (src_x + w > ximage->width) {
1266 if (src_x > ximage->width)
1268 w = ximage->width - src_x;
1270 if (src_y + h > ximage->height) {
1271 if (src_y > ximage->height)
1273 h = ximage->height - src_y;
1275 if (w <= 0 || h <= 0)
1278 CGContextRef cgc = d->cgc;
1280 if (jwxyz_dumb_drawing_mode(dpy, d, gc, dest_x, dest_y, w, h))
1283 int bpl = ximage->bytes_per_line;
1284 int bpp = ximage->bits_per_pixel;
1285 int bsize = bpl * h;
1286 char *data = ximage->data;
1289 r.origin.x = wr.x + dest_x;
1290 r.origin.y = wr.y + wr.height - dest_y - (int)h;
1296 /* Take advantage of the fact that it's ok for (bpl != w * bpp)
1297 to create a CGImage from a sub-rectagle of the XImage.
1299 data += (src_y * bpl) + (src_x * 4);
1300 CGDataProviderRef prov =
1301 CGDataProviderCreateWithData (NULL, data, bsize, NULL);
1303 CGImageRef cgi = CGImageCreate (w, h,
1306 dpy->screen->bitmap_info,
1308 NULL, /* decode[] */
1309 NO, /* interpolate */
1310 kCGRenderingIntentDefault);
1311 CGDataProviderRelease (prov);
1312 //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
1313 CGContextDrawImage (cgc, r, cgi);
1314 CGImageRelease (cgi);
1316 } else { // (bpp == 1)
1318 /* To draw a 1bpp image, we use it as a mask and fill two rectangles.
1320 #### However, the bit order within a byte in a 1bpp XImage is
1321 the wrong way around from what Quartz expects, so first we
1322 have to copy the data to reverse it. Shit! Maybe it
1323 would be worthwhile to go through the hacks and #ifdef
1324 each one that diddles 1bpp XImage->data directly...
1326 Assert ((src_x % 8) == 0,
1327 "XPutImage with non-byte-aligned 1bpp X offset not implemented");
1329 data += (src_y * bpl) + (src_x / 8); // move to x,y within the data
1330 unsigned char *flipped = (unsigned char *) malloc (bsize);
1332 flipbits ((unsigned char *) data, flipped, bsize);
1334 CGDataProviderRef prov =
1335 CGDataProviderCreateWithData (NULL, flipped, bsize, NULL);
1336 CGImageRef mask = CGImageMaskCreate (w, h,
1339 NULL, /* decode[] */
1340 NO); /* interpolate */
1341 push_fg_gc (dpy, d, gc, YES);
1343 CGContextFillRect (cgc, r); // foreground color
1344 CGContextClipToMask (cgc, r, mask);
1345 set_color (dpy, cgc, gc->gcv.background, gc->depth, NO, YES);
1346 CGContextFillRect (cgc, r); // background color
1350 CGDataProviderRelease (prov);
1351 CGImageRelease (mask);
1354 invalidate_drawable_cache (d);
1361 XGetSubImage (Display *dpy, Drawable d, int x, int y,
1362 unsigned int width, unsigned int height,
1363 unsigned long plane_mask, int format,
1364 XImage *image, int dest_x, int dest_y)
1366 const unsigned char *data = 0;
1367 size_t depth, ibpp, ibpl;
1368 convert_mode_t mode;
1370 Assert ((width < 65535), "improbably large width");
1371 Assert ((height < 65535), "improbably large height");
1372 Assert ((x < 65535 && x > -65535), "improbably large x");
1373 Assert ((y < 65535 && y > -65535), "improbably large y");
1375 CGContextRef cgc = d->cgc;
1378 depth = jwxyz_drawable_depth (d);
1379 mode = convert_mode_to_rgba (dpy->screen->bitmap_info);
1380 ibpp = CGBitmapContextGetBitsPerPixel (cgc);
1381 ibpl = CGBitmapContextGetBytesPerRow (cgc);
1382 data = CGBitmapContextGetData (cgc);
1383 Assert (data, "CGBitmapContextGetData failed");
1386 // data points at (x,y) with ibpl rowstride. ignore x,y from now on.
1387 data += (y * ibpl) + (x * (ibpp/8));
1389 format = (depth == 1 ? XYPixmap : ZPixmap);
1391 int obpl = image->bytes_per_line;
1393 /* both PPC and Intel use word-ordered ARGB frame buffers, which
1394 means that on Intel it is BGRA when viewed by bytes (And BGR
1395 when using 24bpp packing).
1397 BUT! Intel-64 stores alpha at the other end! 32bit=RGBA, 64bit=ARGB.
1398 The NSAlphaFirstBitmapFormat bit in bitmapFormat seems to be the
1399 indicator of this latest kink.
1403 const unsigned char *iline = data;
1404 for (yy = 0; yy < height; yy++) {
1406 const unsigned char *iline2 = iline;
1407 for (xx = 0; xx < width; xx++) {
1409 iline2++; // ignore R or A or A or B
1410 iline2++; // ignore G or B or R or G
1411 unsigned char r = *iline2++; // use B or G or G or R
1412 if (ibpp == 32) iline2++; // ignore A or R or B or A
1414 XPutPixel (image, xx + dest_x, yy + dest_y, (r ? 1 : 0));
1419 const unsigned char *iline = data;
1420 unsigned char *oline = (unsigned char *) image->data + dest_y * obpl +
1423 mode = convert_mode_merge (mode,
1424 convert_mode_invert (
1425 convert_mode_to_rgba (dpy->screen->bitmap_info)));
1427 for (yy = 0; yy < height; yy++) {
1429 convert_row ((uint32_t *)oline, iline, width, mode, ibpp);
1441 /* Returns a transformation matrix to do rotation as per the provided
1442 EXIF "Orientation" value.
1444 static CGAffineTransform
1445 exif_rotate (int rot, CGSize rect)
1447 CGAffineTransform trans = CGAffineTransformIdentity;
1449 case 2: // flip horizontal
1450 trans = CGAffineTransformMakeTranslation (rect.width, 0);
1451 trans = CGAffineTransformScale (trans, -1, 1);
1454 case 3: // rotate 180
1455 trans = CGAffineTransformMakeTranslation (rect.width, rect.height);
1456 trans = CGAffineTransformRotate (trans, M_PI);
1459 case 4: // flip vertical
1460 trans = CGAffineTransformMakeTranslation (0, rect.height);
1461 trans = CGAffineTransformScale (trans, 1, -1);
1464 case 5: // transpose (UL-to-LR axis)
1465 trans = CGAffineTransformMakeTranslation (rect.height, rect.width);
1466 trans = CGAffineTransformScale (trans, -1, 1);
1467 trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
1470 case 6: // rotate 90
1471 trans = CGAffineTransformMakeTranslation (0, rect.width);
1472 trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
1475 case 7: // transverse (UR-to-LL axis)
1476 trans = CGAffineTransformMakeScale (-1, 1);
1477 trans = CGAffineTransformRotate (trans, M_PI / 2);
1480 case 8: // rotate 270
1481 trans = CGAffineTransformMakeTranslation (rect.height, 0);
1482 trans = CGAffineTransformRotate (trans, M_PI / 2);
1494 jwxyz_draw_NSImage_or_CGImage (Display *dpy, Drawable d,
1495 Bool nsimg_p, void *img_arg,
1496 XRectangle *geom_ret, int exif_rotation)
1500 CGImageSourceRef cgsrc;
1501 # endif // USE_IPHONE
1504 CGContextRef cgc = d->cgc;
1508 NSImage *nsimg = (NSImage *) img_arg;
1509 imgr = [nsimg size];
1512 // convert the NSImage to a CGImage via the toll-free-bridging
1513 // of NSData and CFData...
1515 NSData *nsdata = [NSBitmapImageRep
1516 TIFFRepresentationOfImageRepsInArray:
1517 [nsimg representations]];
1518 CFDataRef cfdata = (CFDataRef) nsdata;
1519 cgsrc = CGImageSourceCreateWithData (cfdata, NULL);
1520 cgi = CGImageSourceCreateImageAtIndex (cgsrc, 0, NULL);
1521 # else // USE_IPHONE
1522 cgi = nsimg.CGImage;
1523 # endif // USE_IPHONE
1526 cgi = (CGImageRef) img_arg;
1527 imgr.width = CGImageGetWidth (cgi);
1528 imgr.height = CGImageGetHeight (cgi);
1531 Bool rot_p = (exif_rotation >= 5);
1534 imgr = NSMakeSize (imgr.height, imgr.width);
1536 XRectangle winr = d->frame;
1537 float rw = winr.width / imgr.width;
1538 float rh = winr.height / imgr.height;
1539 float r = (rw < rh ? rw : rh);
1542 dst.size.width = imgr.width * r;
1543 dst.size.height = imgr.height * r;
1544 dst.origin.x = (winr.width - dst.size.width) / 2;
1545 dst.origin.y = (winr.height - dst.size.height) / 2;
1547 dst2.origin.x = dst2.origin.y = 0;
1549 dst2.size.width = dst.size.height;
1550 dst2.size.height = dst.size.width;
1552 dst2.size = dst.size;
1555 // Clear the part not covered by the image to background or black.
1557 if (d->type == WINDOW)
1558 XClearWindow (dpy, d);
1560 jwxyz_fill_rect (dpy, d, 0, 0, 0, winr.width, winr.height,
1561 jwxyz_drawable_depth (d) == 1 ? 0 : BlackPixel(dpy,0));
1564 CGAffineTransform trans =
1565 exif_rotate (exif_rotation, rot_p ? dst2.size : dst.size);
1567 CGContextSaveGState (cgc);
1568 CGContextConcatCTM (cgc,
1569 CGAffineTransformMakeTranslation (dst.origin.x,
1571 CGContextConcatCTM (cgc, trans);
1572 //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
1573 CGContextDrawImage (cgc, dst2, cgi);
1574 CGContextRestoreGState (cgc);
1579 CGImageRelease (cgi);
1581 # endif // USE_IPHONE
1584 geom_ret->x = dst.origin.x;
1585 geom_ret->y = dst.origin.y;
1586 geom_ret->width = dst.size.width;
1587 geom_ret->height = dst.size.height;
1590 invalidate_drawable_cache (d);
1596 XCreatePixmap (Display *dpy, Drawable d,
1597 unsigned int width, unsigned int height, unsigned int depth)
1600 char *data = (char *) malloc (width * height * 4);
1601 if (! data) return 0;
1603 Pixmap p = (Pixmap) calloc (1, sizeof(*p));
1605 p->frame.width = width;
1606 p->frame.height = height;
1607 p->pixmap.depth = depth;
1608 p->pixmap.cgc_buffer = data;
1610 /* Quartz doesn't have a 1bpp image type.
1611 Used to use 8bpp gray images instead of 1bpp, but some Mac video cards
1612 don't support that! So we always use 32bpp, regardless of depth. */
1614 p->cgc = CGBitmapContextCreate (data, width, height,
1615 8, /* bits per component */
1616 width * 4, /* bpl */
1618 dpy->screen->bitmap_info);
1619 Assert (p->cgc, "could not create CGBitmapContext");
1625 XFreePixmap (Display *d, Pixmap p)
1627 Assert (p && p->type == PIXMAP, "not a pixmap");
1628 invalidate_drawable_cache (p);
1629 CGContextRelease (p->cgc);
1630 if (p->pixmap.cgc_buffer)
1631 free (p->pixmap.cgc_buffer);
1638 copy_pixmap (Display *dpy, Pixmap p)
1641 Assert (p->type == PIXMAP, "not a pixmap");
1647 unsigned int width, height, border_width, depth;
1648 if (XGetGeometry (dpy, p, &root,
1649 &x, &y, &width, &height, &border_width, &depth)) {
1651 gcv.function = GXcopy;
1652 GC gc = XCreateGC (dpy, p, GCFunction, &gcv);
1654 p2 = XCreatePixmap (dpy, p, width, height, depth);
1656 XCopyArea (dpy, p, p2, gc, 0, 0, width, height, 0, 0);
1661 Assert (p2, "could not copy pixmap");
1667 // Returns the verbose Unicode name of this character, like "agrave" or
1668 // "daggerdouble". Used by fontglide debugMetrics.
1671 jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
1674 NSFont *nsfont = (NSFont *) jwxyz_native_font (fid);
1676 CTFontCreateWithName ((CFStringRef) [nsfont fontName],
1679 Assert (ctfont, "no CTFontRef for UIFont");
1682 if (CTFontGetGlyphsForCharacters (ctfont, (UniChar *) &uc, &cgglyph, 1)) {
1683 CGFontRef cgfont = CTFontCopyGraphicsFont (ctfont, 0);
1684 NSString *name = (NSString *) CGFontCopyGlyphNameForGlyph(cgfont, cgglyph);
1685 ret = (name ? strdup ([name UTF8String]) : 0);
1686 CGFontRelease (cgfont);
1696 jwxyz_draw_string (Display *dpy, Drawable d, GC gc, int x, int y,
1697 const char *str, size_t len, int utf8_p)
1699 NSString *nsstr = nsstring_from (str, len, utf8_p);
1701 if (! nsstr) return 1;
1703 XRectangle wr = d->frame;
1704 CGContextRef cgc = d->cgc;
1706 unsigned long argb = gc->gcv.foreground;
1707 if (gc->depth == 1) argb = (argb ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
1709 query_color_float (dpy, argb, rgba);
1710 NSColor *fg = [NSColor colorWithDeviceRed:rgba[0]
1715 if (!gc->gcv.font) {
1716 Assert (0, "no font");
1720 /* This crashes on iOS 5.1 because NSForegroundColorAttributeName,
1721 NSFontAttributeName, and NSAttributedString are only present on iOS 6
1722 and later. We could resurrect the Quartz code from v5.29 and do a
1723 runtime conditional on that, but that would be a pain in the ass.
1724 Probably time to just make iOS 6 a requirement.
1727 NSDictionary *attr =
1728 [NSDictionary dictionaryWithObjectsAndKeys:
1729 (NSFont *) jwxyz_native_font (gc->gcv.font), NSFontAttributeName,
1730 fg, NSForegroundColorAttributeName,
1733 // Don't understand why we have to do both set_color and
1734 // NSForegroundColorAttributeName, but we do.
1736 set_color (dpy, cgc, argb, 32, NO, YES);
1738 NSAttributedString *astr = [[NSAttributedString alloc]
1739 initWithString:nsstr
1741 CTLineRef dl = CTLineCreateWithAttributedString (
1742 (__bridge CFAttributedStringRef) astr);
1744 // Not sure why this is necessary, but xoff is positive when the first
1745 // character on the line has a negative lbearing. Without this, the
1746 // string is rendered with the first ink at 0 instead of at lbearing.
1747 // I have not seen xoff be negative, so I'm not sure if that can happen.
1749 // Test case: "Combining Double Tilde" U+0360 (\315\240) followed by
1752 CGFloat xoff = CTLineGetOffsetForStringIndex (dl, 0, NULL);
1753 Assert (xoff >= 0, "unexpected CTLineOffset");
1756 CGContextSetTextPosition (cgc,
1758 wr.y + wr.height - y);
1759 CGContextSetShouldAntialias (cgc, gc->gcv.antialias_p);
1761 CTLineDraw (dl, cgc);
1765 invalidate_drawable_cache (d);
1771 XSetClipMask (Display *dpy, GC gc, Pixmap m)
1773 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
1775 if (gc->gcv.clip_mask) {
1776 XFreePixmap (dpy, gc->gcv.clip_mask);
1777 CGImageRelease (gc->clip_mask);
1780 gc->gcv.clip_mask = copy_pixmap (dpy, m);
1781 if (gc->gcv.clip_mask)
1783 CGBitmapContextCreateImage (gc->gcv.clip_mask->cgc);
1791 XSetClipOrigin (Display *dpy, GC gc, int x, int y)
1793 gc->gcv.clip_x_origin = x;
1794 gc->gcv.clip_y_origin = y;
1798 #endif // JWXYZ_QUARTZ -- entire file