1 /* xscreensaver, Copyright (c) 1991-2018 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 {
62 const struct jwxyz_vtbl *vtbl; // Must come first.
65 CGBitmapInfo bitmap_info;
67 struct jwxyz_sources_data *timers_data;
70 CGDirectDisplayID cgdpy; /* ...of the one and only Window, main_window.
71 This can change if the window is dragged to
72 a different screen. */
75 CGColorSpaceRef colorspace; /* Color space of this screen. We tag all of
76 our images with this to avoid translation
79 unsigned long window_background;
85 CGImageRef clip_mask; // CGImage copy of the Pixmap in gcv.clip_mask
89 // 8/16/24/32bpp -> 32bpp image conversion.
90 // Any of RGBA, BGRA, ABGR, or ARGB can be represented by a rotate of 0/8/16/24
91 // bits and an optional byte order swap.
93 // This type encodes such a conversion.
94 typedef unsigned convert_mode_t;
96 // It's rotate, then swap.
97 // A rotation here shifts bytes forward in memory. On x86/ARM, that's a left
98 // rotate, and on PowerPC, a rightward rotation.
99 static const convert_mode_t CONVERT_MODE_ROTATE_MASK = 0x3;
100 static const convert_mode_t CONVERT_MODE_SWAP = 0x4;
103 #if defined __LITTLE_ENDIAN__
104 # define PAD(r, g, b, a) ((r) | ((g) << 8) | ((b) << 16) | ((a) << 24))
105 #elif defined __BIG_ENDIAN__
106 # define PAD(r, g, b, a) (((r) << 24) | ((g) << 16) | ((b) << 8) | (a))
108 # error "Can't determine system endianness."
112 // Converts an array of pixels ('src') from one format to another, placing the
113 // result in 'dest', according to the pixel conversion mode 'mode'.
115 convert_row (uint32_t *dest, const void *src, size_t count,
116 convert_mode_t mode, size_t src_bpp)
118 Assert (src_bpp == 8 || src_bpp == 24 || src_bpp == 16 || src_bpp == 32,
121 // This works OK iff src == dest or src and dest do not overlap.
123 if (!mode && src_bpp == 32) {
125 memcpy (dest, src, count * 4);
129 // This is correct, but not fast.
130 convert_mode_t rot = (mode & CONVERT_MODE_ROTATE_MASK) * 8;
131 convert_mode_t flip = mode & CONVERT_MODE_SWAP;
135 uint32_t *dest_end = dest + count;
136 while (dest != dest_end) {
139 const uint8_t *src8 = (const uint8_t *)src;
142 x = *(const uint32_t *)src;
145 x = PAD(src8[0], src8[1], src8[2], 0xff);
148 x = PAD(src8[0], src8[0], src8[0], src8[1]);
151 x = PAD(src8[0], src8[0], src8[0], 0xff);
155 src = (const uint8_t *)src + src_bpp;
157 /* The naive (i.e. ubiquitous) portable implementation of bitwise rotation,
158 for 32-bit integers, is:
160 (x << rot) | (x >> (32 - rot))
162 This works nearly everywhere. Compilers on x86 wil generally recognize
163 the idiom and convert it to a ROL instruction. But there's a problem
164 here: according to the C specification, bit shifts greater than or equal
165 to the length of the integer are undefined. And if rot = 0:
166 1. (x << 0) | (x >> (32 - 0))
167 2. (x << 0) | (x >> 32)
168 3. (x << 0) | (Undefined!)
170 Still, when the compiler converts this to a ROL on x86, everything works
171 as intended. But, there are two additional problems when Clang does
172 compile-time constant expression evaluation with the (x >> 32)
174 1. Instead of evaluating it to something reasonable (either 0, like a
175 human would intuitively expect, or x, like x86 would with SHR), Clang
176 seems to pull a value out of nowhere, like -1, or some other random
178 2. Clang's warning for this, -Wshift-count-overflow, only works when the
179 shift count is a literal constant, as opposed to an arbitrary
180 expression that is optimized down to a constant.
181 Put together, this means that the assertions in
182 jwxyz_quartz_make_display with convert_px break with the above naive
183 rotation, but only for a release build.
185 http://blog.regehr.org/archives/1063
186 http://llvm.org/bugs/show_bug.cgi?id=17332
187 As described in those links, there is a solution here: Masking the
188 undefined shift with '& 31' as below makes the experesion well-defined
189 again. And LLVM is set to pick up on this safe version of the idiom and
190 use a rotation instruction on architectures (like x86) that support it,
191 just like it does with the unsafe version.
193 Too bad LLVM doesn't want to pick up on that particular optimization
194 here. Oh well. At least this code usually isn't critical w.r.t.
198 # if defined __LITTLE_ENDIAN__
199 x = (x << rot) | (x >> ((32 - rot) & 31));
200 # elif defined __BIG_ENDIAN__
201 x = (x >> rot) | (x << ((32 - rot) & 31));
205 x = __builtin_bswap32(x); // LLVM/GCC built-in function.
213 // Converts a single pixel.
215 convert_px (uint32_t px, convert_mode_t mode)
217 convert_row (&px, &px, 1, mode, 32);
222 // This returns the inverse conversion mode, such that:
224 // == convert_px(convert_px(pixel, mode), convert_mode_invert(mode))
225 // == convert_px(convert_px(pixel, convert_mode_invert(mode)), mode)
226 static convert_mode_t
227 convert_mode_invert (convert_mode_t mode)
229 // swap(0); rot(n) == rot(n); swap(0)
230 // swap(1); rot(n) == rot(-n); swap(1)
231 return mode & CONVERT_MODE_SWAP ? mode : CONVERT_MODE_ROTATE_MASK & -mode;
235 // This combines two conversions into one, such that:
236 // convert_px(convert_px(pixel, mode0), mode1)
237 // == convert_px(pixel, convert_mode_merge(mode0, mode1))
238 static convert_mode_t
239 convert_mode_merge (convert_mode_t m0, convert_mode_t m1)
241 // rot(r0); swap(s0); rot(r1); swap(s1)
242 // rot(r0); rot(s0 ? -r1 : r1); swap(s0); swap(s1)
243 // rot(r0 + (s0 ? -r1 : r1)); swap(s0 + s1)
245 ((m0 + (m0 & CONVERT_MODE_SWAP ? -m1 : m1)) & CONVERT_MODE_ROTATE_MASK) |
246 ((m0 ^ m1) & CONVERT_MODE_SWAP);
250 // This returns a conversion mode that converts an arbitrary 32-bit format
251 // specified by bitmap_info to RGBA.
252 static convert_mode_t
253 convert_mode_to_rgba (CGBitmapInfo bitmap_info)
255 // Former default: kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little
258 // green = 0x0000FF00;
259 // blue = 0x000000FF;
261 // RGBA: kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big
263 CGImageAlphaInfo alpha_info =
264 (CGImageAlphaInfo)(bitmap_info & kCGBitmapAlphaInfoMask);
266 Assert (! (bitmap_info & kCGBitmapFloatComponents),
267 "kCGBitmapFloatComponents unsupported");
268 Assert (alpha_info != kCGImageAlphaOnly, "kCGImageAlphaOnly not supported");
270 convert_mode_t rot = alpha_info == kCGImageAlphaFirst ||
271 alpha_info == kCGImageAlphaPremultipliedFirst ||
272 alpha_info == kCGImageAlphaNoneSkipFirst ?
275 CGBitmapInfo byte_order = bitmap_info & kCGBitmapByteOrderMask;
277 Assert (byte_order == kCGBitmapByteOrder32Little ||
278 byte_order == kCGBitmapByteOrder32Big,
279 "byte order not supported");
281 convert_mode_t swap = byte_order == kCGBitmapByteOrder32Little ?
282 CONVERT_MODE_SWAP : 0;
284 rot = CONVERT_MODE_ROTATE_MASK & -rot;
297 query_color_float (Display *dpy, unsigned long pixel, CGFloat *rgba)
299 JWXYZ_QUERY_COLOR (dpy, pixel, (CGFloat)1, rgba);
303 extern const struct jwxyz_vtbl quartz_vtbl;
306 jwxyz_quartz_make_display (Window w)
308 CGContextRef cgc = w->cgc;
310 Display *d = (Display *) calloc (1, sizeof(*d));
311 d->vtbl = &quartz_vtbl;
313 d->bitmap_info = CGBitmapContextGetBitmapInfo (cgc);
316 // Tests for the image conversion modes.
318 const uint32_t key = 0x04030201;
319 # ifdef __LITTLE_ENDIAN__
320 assert (convert_px (key, 0) == key);
321 assert (convert_px (key, 1) == 0x03020104);
322 assert (convert_px (key, 3) == 0x01040302);
323 assert (convert_px (key, 4) == 0x01020304);
324 assert (convert_px (key, 5) == 0x04010203);
326 for (unsigned i = 0; i != 8; ++i) {
327 assert (convert_px(convert_px(key, i), convert_mode_invert(i)) == key);
328 assert (convert_mode_invert(convert_mode_invert(i)) == i);
331 for (unsigned i = 0; i != 8; ++i) {
332 for (unsigned j = 0; j != 8; ++j)
333 assert (convert_px(convert_px(key, i), j) ==
334 convert_px(key, convert_mode_merge(i, j)));
339 Visual *v = &d->visual;
340 v->class = TrueColor;
342 union color_bytes color;
343 convert_mode_t mode =
344 convert_mode_invert (convert_mode_to_rgba (d->bitmap_info));
345 for (unsigned i = 0; i != 4; ++i) {
347 color.bytes[i] = 0xff;
348 v->rgba_masks[i] = convert_px (color.pixel, mode);
351 CGBitmapInfo byte_order = d->bitmap_info & kCGBitmapByteOrderMask;
352 Assert ( ! (d->bitmap_info & kCGBitmapFloatComponents) &&
353 (byte_order == kCGBitmapByteOrder32Little ||
354 byte_order == kCGBitmapByteOrder32Big),
355 "invalid bits per channel");
357 d->timers_data = jwxyz_sources_init (XtDisplayToApplicationContext (d));
359 d->window_background = BlackPixel(d,0);
363 Assert (cgc, "no CGContext");
368 jwxyz_quartz_free_display (Display *dpy)
370 jwxyz_sources_free (dpy->timers_data);
376 /* Call this after any modification to the bits on a Pixmap or Window.
377 Most Pixmaps are used frequently as sources and infrequently as
378 destinations, so it pays to cache the data as a CGImage as needed.
381 invalidate_drawable_cache (Drawable d)
384 CGImageRelease (d->cgi);
390 /* Call this when the View changes size or position.
393 jwxyz_window_resized (Display *dpy)
395 Window w = dpy->main_window;
398 // Figure out which screen the window is currently on.
401 XTranslateCoordinates (dpy, w, NULL, 0, 0, &wx, &wy, NULL);
407 CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n);
411 CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n);
413 Assert (dpy->cgdpy, "unable to find CGDisplay");
415 # endif // USE_IPHONE
419 // Figure out this screen's colorspace, and use that for every CGImage.
421 CMProfileRef profile = 0;
422 CMGetProfileByAVID ((CMDisplayIDType) dpy->cgdpy, &profile);
423 Assert (profile, "unable to find colorspace profile");
424 dpy->colorspace = CGColorSpaceCreateWithPlatformColorSpace (profile);
425 Assert (dpy->colorspace, "unable to find colorspace");
429 // WTF? It's faster if we *do not* use the screen's colorspace!
431 dpy->colorspace = CGColorSpaceCreateDeviceRGB();
433 invalidate_drawable_cache (w);
438 jwxyz_flush_context (Display *dpy)
440 // CGContextSynchronize is another possibility.
441 CGContextFlush(dpy->main_window->cgc);
444 static jwxyz_sources_data *
445 display_sources_data (Display *dpy)
447 return dpy->timers_data;
454 return dpy->main_window;
458 visual (Display *dpy)
465 set_color (Display *dpy, CGContextRef cgc, unsigned long argb,
466 unsigned int depth, Bool alpha_allowed_p, Bool fill_p)
468 jwxyz_validate_pixel (dpy, argb, depth, alpha_allowed_p);
471 CGContextSetGrayFillColor (cgc, (argb ? 1.0 : 0.0), 1.0);
473 CGContextSetGrayStrokeColor (cgc, (argb ? 1.0 : 0.0), 1.0);
476 query_color_float (dpy, argb, rgba);
478 CGContextSetRGBFillColor (cgc, rgba[0], rgba[1], rgba[2], rgba[3]);
480 CGContextSetRGBStrokeColor (cgc, rgba[0], rgba[1], rgba[2], rgba[3]);
485 set_line_mode (CGContextRef cgc, XGCValues *gcv)
487 CGContextSetLineWidth (cgc, gcv->line_width ? gcv->line_width : 1);
488 CGContextSetLineJoin (cgc,
489 gcv->join_style == JoinMiter ? kCGLineJoinMiter :
490 gcv->join_style == JoinRound ? kCGLineJoinRound :
492 CGContextSetLineCap (cgc,
493 gcv->cap_style == CapNotLast ? kCGLineCapButt :
494 gcv->cap_style == CapButt ? kCGLineCapButt :
495 gcv->cap_style == CapRound ? kCGLineCapRound :
500 set_clip_mask (Drawable d, GC gc)
502 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
504 Pixmap p = gc->gcv.clip_mask;
506 Assert (p->type == PIXMAP, "not a pixmap");
508 XRectangle wr = d->frame;
510 to.origin.x = wr.x + gc->gcv.clip_x_origin;
511 to.origin.y = wr.y + wr.height - gc->gcv.clip_y_origin
513 to.size.width = p->frame.width;
514 to.size.height = p->frame.height;
516 CGContextClipToMask (d->cgc, to, gc->clip_mask);
520 /* Pushes a GC context; sets BlendMode and ClipMask.
523 push_gc (Drawable d, GC gc)
525 CGContextRef cgc = d->cgc;
526 CGContextSaveGState (cgc);
528 switch (gc->gcv.function) {
531 case GXcopy:/*CGContextSetBlendMode (cgc, kCGBlendModeNormal);*/ break;
532 case GXxor: CGContextSetBlendMode (cgc, kCGBlendModeDifference); break;
533 case GXor: CGContextSetBlendMode (cgc, kCGBlendModeLighten); break;
534 case GXand: CGContextSetBlendMode (cgc, kCGBlendModeDarken); break;
535 default: Assert(0, "unknown gcv function"); break;
538 if (gc->gcv.clip_mask)
539 set_clip_mask (d, gc);
543 /* Pushes a GC context; sets BlendMode, ClipMask, Fill, and Stroke colors.
546 push_color_gc (Display *dpy, Drawable d, GC gc, unsigned long color,
547 Bool antialias_p, Bool fill_p)
551 int depth = gc->depth;
552 switch (gc->gcv.function) {
553 case GXset: color = (depth == 1 ? 1 : WhitePixel(dpy,0)); break;
554 case GXclear: color = (depth == 1 ? 0 : BlackPixel(dpy,0)); break;
557 CGContextRef cgc = d->cgc;
558 set_color (dpy, cgc, color, depth, gc->gcv.alpha_allowed_p, fill_p);
559 CGContextSetShouldAntialias (cgc, antialias_p);
563 /* Pushes a GC context; sets Fill and Stroke colors to the foreground color.
566 push_fg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p)
568 push_color_gc (dpy, d, gc, gc->gcv.foreground, gc->gcv.antialias_p, fill_p);
572 bitmap_context_p (Drawable d)
579 /* You've got to be fucking kidding me!
581 It is *way* faster to draw points by creating and drawing a 1x1 CGImage
582 with repeated calls to CGContextDrawImage than it is to make a single
583 call to CGContextFillRects() with a list of 1x1 rectangles!
585 I still wouldn't call it *fast*, however...
587 #define XDRAWPOINTS_IMAGES
589 /* Update, 2012: Kurt Revis <krevis@snoize.com> points out that diddling
590 the bitmap data directly is faster. This only works on Pixmaps, though,
591 not Windows. (Fortunately, on iOS, the Window is really a Pixmap.)
593 #define XDRAWPOINTS_CGDATA
596 DrawPoints (Display *dpy, Drawable d, GC gc,
597 XPoint *points, int count, int mode)
600 XRectangle wr = d->frame;
602 # ifdef XDRAWPOINTS_CGDATA
604 if (bitmap_context_p (d))
606 CGContextRef cgc = d->cgc;
607 void *data = CGBitmapContextGetData (cgc);
608 size_t bpr = CGBitmapContextGetBytesPerRow (cgc);
609 size_t w = CGBitmapContextGetWidth (cgc);
610 size_t h = CGBitmapContextGetHeight (cgc);
612 Assert (data, "no bitmap data in Drawable");
614 unsigned long argb = gc->gcv.foreground;
615 jwxyz_validate_pixel (dpy, argb, gc->depth, gc->gcv.alpha_allowed_p);
617 argb = (gc->gcv.foreground ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
620 CGFloat y0 = wr.y; // Y axis is refreshingly not flipped.
622 // It's uglier, but faster, to hoist the conditional out of the loop.
623 if (mode == CoordModePrevious) {
624 CGFloat x = x0, y = y0;
625 for (i = 0; i < count; i++, points++) {
629 if (x >= 0 && x < w && y >= 0 && y < h) {
630 unsigned int *p = (unsigned int *)
631 ((char *) data + (size_t) y * bpr + (size_t) x * 4);
632 *p = (unsigned int) argb;
636 for (i = 0; i < count; i++, points++) {
637 CGFloat x = x0 + points->x;
638 CGFloat y = y0 + points->y;
640 if (x >= 0 && x < w && y >= 0 && y < h) {
641 unsigned int *p = (unsigned int *)
642 ((char *) data + (size_t) y * bpr + (size_t) x * 4);
643 *p = (unsigned int) argb;
648 } else /* d->type == WINDOW */
650 # endif /* XDRAWPOINTS_CGDATA */
652 push_fg_gc (dpy, d, gc, YES);
654 # ifdef XDRAWPOINTS_IMAGES
656 unsigned long argb = gc->gcv.foreground;
657 jwxyz_validate_pixel (dpy, argb, gc->depth, gc->gcv.alpha_allowed_p);
659 argb = (gc->gcv.foreground ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
661 CGDataProviderRef prov = CGDataProviderCreateWithData (NULL, &argb, 4,
663 CGImageRef cgi = CGImageCreate (1, 1,
666 /* Host-ordered, since we're using the
667 address of an int as the color data. */
671 NO, /* interpolate */
672 kCGRenderingIntentDefault);
673 CGDataProviderRelease (prov);
675 CGContextRef cgc = d->cgc;
677 rect.size.width = rect.size.height = 1;
678 for (i = 0; i < count; i++) {
679 if (i > 0 && mode == CoordModePrevious) {
680 rect.origin.x += points->x;
681 rect.origin.x -= points->y;
683 rect.origin.x = wr.x + points->x;
684 rect.origin.y = wr.y + wr.height - points->y - 1;
687 //Assert(CGImageGetColorSpace (cgi) == dpy->colorspace,"bad colorspace");
688 CGContextDrawImage (cgc, rect, cgi);
692 CGImageRelease (cgi);
694 # else /* ! XDRAWPOINTS_IMAGES */
696 CGRect *rects = (CGRect *) malloc (count * sizeof(CGRect));
699 for (i = 0; i < count; i++) {
700 r->size.width = r->size.height = 1;
701 if (i > 0 && mode == CoordModePrevious) {
702 r->origin.x = r[-1].origin.x + points->x;
703 r->origin.y = r[-1].origin.x - points->y;
705 r->origin.x = wr.origin.x + points->x;
706 r->origin.y = wr.origin.y + wr.size.height - points->y;
712 CGContextFillRects (d->cgc, rects, count);
715 # endif /* ! XDRAWPOINTS_IMAGES */
720 invalidate_drawable_cache (d);
727 map_point (Drawable d, int x, int y)
729 const XRectangle *wr = &d->frame;
732 p.y = wr->y + wr->height - y;
738 adjust_point_for_line (GC gc, CGPoint *p)
740 // Here's the authoritative discussion on how X draws lines:
741 // http://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:CreateGC:line-width
742 if (gc->gcv.line_width <= 1) {
743 /* Thin lines are "drawn using an unspecified, device-dependent
744 algorithm", but seriously though, Bresenham's algorithm. Bresenham's
745 algorithm runs to and from pixel centers.
747 There's a few screenhacks (Maze, at the very least) that set line_width
748 to 1 when it probably should be set to 0, so it's line_width <= 1
754 /* Thick lines OTOH run from the upper-left corners of pixels. This means
755 that a horizontal thick line of width 1 straddles two scan lines.
756 Aliasing requires one of these scan lines be chosen; the following
757 nudges the point so that the right choice is made. */
764 point_for_line (Drawable d, GC gc, int x, int y)
766 CGPoint result = map_point (d, x, y);
767 adjust_point_for_line (gc, &result);
773 DrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count,
778 push_fg_gc (dpy, d, gc, NO);
780 CGContextRef cgc = d->cgc;
782 set_line_mode (cgc, &gc->gcv);
784 // if the first and last points coincide, use closepath to get
785 // the proper line-joining.
786 BOOL closed_p = (points[0].x == points[count-1].x &&
787 points[0].y == points[count-1].y);
788 if (closed_p) count--;
790 p = point_for_line(d, gc, points->x, points->y);
792 CGContextBeginPath (cgc);
793 CGContextMoveToPoint (cgc, p.x, p.y);
794 for (i = 1; i < count; i++) {
795 if (mode == CoordModePrevious) {
799 p = point_for_line(d, gc, points->x, points->y);
801 CGContextAddLineToPoint (cgc, p.x, p.y);
804 if (closed_p) CGContextClosePath (cgc);
805 CGContextStrokePath (cgc);
807 invalidate_drawable_cache (d);
813 DrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count)
817 CGContextRef cgc = d->cgc;
819 push_fg_gc (dpy, d, gc, NO);
820 set_line_mode (cgc, &gc->gcv);
821 CGContextBeginPath (cgc);
822 for (i = 0; i < count; i++) {
823 // when drawing a zero-length line, obey line-width and cap-style.
824 if (segments->x1 == segments->x2 && segments->y1 == segments->y2) {
825 int w = gc->gcv.line_width;
826 int x1 = segments->x1 - w/2;
827 int y1 = segments->y1 - w/2;
828 if (gc->gcv.line_width > 1 && gc->gcv.cap_style == CapRound)
829 XFillArc (dpy, d, gc, x1, y1, w, w, 0, 360*64);
832 w = 1; // Actually show zero-length lines.
833 XFillRectangle (dpy, d, gc, x1, y1, w, w);
836 CGPoint p = point_for_line (d, gc, segments->x1, segments->y1);
837 CGContextMoveToPoint (cgc, p.x, p.y);
838 p = point_for_line (d, gc, segments->x2, segments->y2);
839 CGContextAddLineToPoint (cgc, p.x, p.y);
844 CGContextStrokePath (cgc);
846 invalidate_drawable_cache (d);
852 ClearWindow (Display *dpy, Window win)
854 Assert (win && win->type == WINDOW, "not a window");
855 XRectangle wr = win->frame;
856 return XClearArea (dpy, win, 0, 0, wr.width, wr.height, 0);
859 static unsigned long *
860 window_background (Display *dpy)
862 return &dpy->window_background;
866 fill_rects (Display *dpy, Drawable d, GC gc,
867 const XRectangle *rectangles, unsigned long nrectangles,
870 Assert (!gc || gc->depth == jwxyz_drawable_depth (d), "depth mismatch");
872 CGContextRef cgc = d->cgc;
875 bitmap_context_p (d) &&
876 (!gc || (gc->gcv.function == GXcopy &&
877 !gc->gcv.alpha_allowed_p &&
878 !gc->gcv.clip_mask));
882 push_color_gc (dpy, d, gc, pixel, gc->gcv.antialias_p, YES);
884 set_color (dpy, d->cgc, pixel, jwxyz_drawable_depth (d), NO, YES);
887 for (unsigned i = 0; i != nrectangles; ++i) {
889 int x = rectangles[i].x;
890 int y = rectangles[i].y;
891 unsigned long width = rectangles[i].width;
892 unsigned long height = rectangles[i].height;
895 long // negative_int > unsigned_int == 1
896 dw = CGBitmapContextGetWidth (cgc),
897 dh = CGBitmapContextGetHeight (cgc);
899 if (x >= dw || y >= dh)
912 if (width <= 0 || height <= 0)
915 unsigned long max_width = dw - x;
916 if (width > max_width)
918 unsigned long max_height = dh - y;
919 if (height > max_height)
922 if (jwxyz_drawable_depth (d) == 1)
923 pixel = pixel ? WhitePixel(dpy, 0) : BlackPixel(dpy, 0);
925 size_t dst_bytes_per_row = CGBitmapContextGetBytesPerRow (d->cgc);
926 void *dst = SEEK_XY (CGBitmapContextGetData (d->cgc),
927 dst_bytes_per_row, x, y);
929 Assert(sizeof(wchar_t) == 4, "somebody changed the ABI");
931 // Would be nice if Apple used SSE/NEON in wmemset. Maybe someday.
932 wmemset (dst, (wchar_t) pixel, width);
934 dst = (char *) dst + dst_bytes_per_row;
939 r.origin = map_point (d, x, y);
940 r.origin.y -= height;
941 r.size.width = width;
942 r.size.height = height;
943 CGContextFillRect (cgc, r);
947 if (!fast_fill_p && gc)
949 invalidate_drawable_cache (d);
954 FillPolygon (Display *dpy, Drawable d, GC gc,
955 XPoint *points, int npoints, int shape, int mode)
957 XRectangle wr = d->frame;
959 push_fg_gc (dpy, d, gc, YES);
960 CGContextRef cgc = d->cgc;
961 CGContextBeginPath (cgc);
963 for (i = 0; i < npoints; i++) {
964 if (i > 0 && mode == CoordModePrevious) {
968 x = wr.x + points[i].x;
969 y = wr.y + wr.height - points[i].y;
973 CGContextMoveToPoint (cgc, x, y);
975 CGContextAddLineToPoint (cgc, x, y);
977 CGContextClosePath (cgc);
978 if (gc->gcv.fill_rule == EvenOddRule)
979 CGContextEOFillPath (cgc);
981 CGContextFillPath (cgc);
983 invalidate_drawable_cache (d);
987 #define radians(DEG) ((DEG) * M_PI / 180.0)
988 #define degrees(RAD) ((RAD) * 180.0 / M_PI)
991 draw_arc (Display *dpy, Drawable d, GC gc, int x, int y,
992 unsigned int width, unsigned int height,
993 int angle1, int angle2, Bool fill_p)
995 XRectangle wr = d->frame;
997 bound.origin.x = wr.x + x;
998 bound.origin.y = wr.y + wr.height - y - (int)height;
999 bound.size.width = width;
1000 bound.size.height = height;
1003 ctr.x = bound.origin.x + bound.size.width /2;
1004 ctr.y = bound.origin.y + bound.size.height/2;
1006 float r1 = radians (angle1/64.0);
1007 float r2 = radians (angle2/64.0) + r1;
1008 BOOL clockwise = angle2 < 0;
1009 BOOL closed_p = (angle2 >= 360*64 || angle2 <= -360*64);
1011 push_fg_gc (dpy, d, gc, fill_p);
1013 CGContextRef cgc = d->cgc;
1014 CGContextBeginPath (cgc);
1016 CGContextSaveGState(cgc);
1017 CGContextTranslateCTM (cgc, ctr.x, ctr.y);
1018 CGContextScaleCTM (cgc, width/2.0, height/2.0);
1020 CGContextMoveToPoint (cgc, 0, 0);
1022 CGContextAddArc (cgc, 0.0, 0.0, 1, r1, r2, clockwise);
1023 CGContextRestoreGState (cgc); // restore before stroke, for line width
1026 CGContextClosePath (cgc); // for proper line joining
1029 CGContextFillPath (cgc);
1031 set_line_mode (cgc, &gc->gcv);
1032 CGContextStrokePath (cgc);
1036 invalidate_drawable_cache (d);
1056 CreateGC (Display *dpy, Drawable d, unsigned long mask, XGCValues *xgcv)
1058 struct jwxyz_GC *gc = (struct jwxyz_GC *) calloc (1, sizeof(*gc));
1059 gc->depth = jwxyz_drawable_depth (d);
1061 jwxyz_gcv_defaults (dpy, &gc->gcv, gc->depth);
1062 XChangeGC (dpy, gc, mask, xgcv);
1068 FreeGC (Display *dpy, GC gc)
1071 XUnloadFont (dpy, gc->gcv.font);
1073 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
1075 if (gc->gcv.clip_mask) {
1076 XFreePixmap (dpy, gc->gcv.clip_mask);
1077 CGImageRelease (gc->clip_mask);
1085 flipbits (unsigned const char *in, unsigned char *out, int length)
1087 static const unsigned char table[256] = {
1088 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0,
1089 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
1090 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8,
1091 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
1092 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4,
1093 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
1094 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC,
1095 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
1096 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2,
1097 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
1098 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA,
1099 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
1100 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6,
1101 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
1102 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE,
1103 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
1104 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1,
1105 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
1106 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9,
1107 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
1108 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5,
1109 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
1110 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED,
1111 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
1112 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3,
1113 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
1114 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB,
1115 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
1116 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7,
1117 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
1118 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF,
1119 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
1121 while (length-- > 0)
1122 *out++ = table[*in++];
1127 PutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
1128 int src_x, int src_y, int dest_x, int dest_y,
1129 unsigned int w, unsigned int h)
1131 XRectangle wr = d->frame;
1133 Assert (gc, "no GC");
1134 Assert ((w < 65535), "improbably large width");
1135 Assert ((h < 65535), "improbably large height");
1136 Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x");
1137 Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y");
1138 Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x");
1139 Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y");
1141 // Clip width and height to the bounds of the Drawable
1143 if (dest_x + (int)w > wr.width) {
1144 if (dest_x > wr.width)
1146 w = wr.width - dest_x;
1148 if (dest_y + (int)h > wr.height) {
1149 if (dest_y > wr.height)
1151 h = wr.height - dest_y;
1153 if (w <= 0 || h <= 0)
1156 // Clip width and height to the bounds of the XImage
1158 if (src_x + w > ximage->width) {
1159 if (src_x > ximage->width)
1161 w = ximage->width - src_x;
1163 if (src_y + h > ximage->height) {
1164 if (src_y > ximage->height)
1166 h = ximage->height - src_y;
1168 if (w <= 0 || h <= 0)
1171 CGContextRef cgc = d->cgc;
1173 if (jwxyz_dumb_drawing_mode(dpy, d, gc, dest_x, dest_y, w, h))
1176 int bpl = ximage->bytes_per_line;
1177 int bpp = ximage->bits_per_pixel;
1178 int bsize = bpl * h;
1179 char *data = ximage->data;
1182 r.origin.x = wr.x + dest_x;
1183 r.origin.y = wr.y + wr.height - dest_y - (int)h;
1189 /* Take advantage of the fact that it's ok for (bpl != w * bpp)
1190 to create a CGImage from a sub-rectagle of the XImage.
1192 data += (src_y * bpl) + (src_x * 4);
1193 CGDataProviderRef prov =
1194 CGDataProviderCreateWithData (NULL, data, bsize, NULL);
1196 CGImageRef cgi = CGImageCreate (w, h,
1201 NULL, /* decode[] */
1202 NO, /* interpolate */
1203 kCGRenderingIntentDefault);
1204 CGDataProviderRelease (prov);
1205 //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
1206 CGContextDrawImage (cgc, r, cgi);
1207 CGImageRelease (cgi);
1209 } else { // (bpp == 1)
1211 /* To draw a 1bpp image, we use it as a mask and fill two rectangles.
1213 #### However, the bit order within a byte in a 1bpp XImage is
1214 the wrong way around from what Quartz expects, so first we
1215 have to copy the data to reverse it. Shit! Maybe it
1216 would be worthwhile to go through the hacks and #ifdef
1217 each one that diddles 1bpp XImage->data directly...
1219 Assert ((src_x % 8) == 0,
1220 "XPutImage with non-byte-aligned 1bpp X offset not implemented");
1222 data += (src_y * bpl) + (src_x / 8); // move to x,y within the data
1223 unsigned char *flipped = (unsigned char *) malloc (bsize);
1225 flipbits ((unsigned char *) data, flipped, bsize);
1227 CGDataProviderRef prov =
1228 CGDataProviderCreateWithData (NULL, flipped, bsize, NULL);
1229 CGImageRef mask = CGImageMaskCreate (w, h,
1232 NULL, /* decode[] */
1233 NO); /* interpolate */
1234 push_fg_gc (dpy, d, gc, YES);
1236 CGContextFillRect (cgc, r); // foreground color
1237 CGContextClipToMask (cgc, r, mask);
1238 set_color (dpy, cgc, gc->gcv.background, gc->depth, NO, YES);
1239 CGContextFillRect (cgc, r); // background color
1243 CGDataProviderRelease (prov);
1244 CGImageRelease (mask);
1247 invalidate_drawable_cache (d);
1254 GetSubImage (Display *dpy, Drawable d, int x, int y,
1255 unsigned int width, unsigned int height,
1256 unsigned long plane_mask, int format,
1257 XImage *image, int dest_x, int dest_y)
1259 const unsigned char *data = 0;
1260 size_t depth, ibpp, ibpl;
1261 convert_mode_t mode;
1263 Assert ((width < 65535), "improbably large width");
1264 Assert ((height < 65535), "improbably large height");
1265 Assert ((x < 65535 && x > -65535), "improbably large x");
1266 Assert ((y < 65535 && y > -65535), "improbably large y");
1268 CGContextRef cgc = d->cgc;
1271 depth = jwxyz_drawable_depth (d);
1272 mode = convert_mode_to_rgba (dpy->bitmap_info);
1273 ibpp = CGBitmapContextGetBitsPerPixel (cgc);
1274 ibpl = CGBitmapContextGetBytesPerRow (cgc);
1275 data = CGBitmapContextGetData (cgc);
1276 Assert (data, "CGBitmapContextGetData failed");
1279 // data points at (x,y) with ibpl rowstride. ignore x,y from now on.
1280 data += (y * ibpl) + (x * (ibpp/8));
1282 format = (depth == 1 ? XYPixmap : ZPixmap);
1284 int obpl = image->bytes_per_line;
1286 /* both PPC and Intel use word-ordered ARGB frame buffers, which
1287 means that on Intel it is BGRA when viewed by bytes (And BGR
1288 when using 24bpp packing).
1290 BUT! Intel-64 stores alpha at the other end! 32bit=RGBA, 64bit=ARGB.
1291 The NSAlphaFirstBitmapFormat bit in bitmapFormat seems to be the
1292 indicator of this latest kink.
1296 const unsigned char *iline = data;
1297 for (yy = 0; yy < height; yy++) {
1299 const unsigned char *iline2 = iline;
1300 for (xx = 0; xx < width; xx++) {
1302 iline2++; // ignore R or A or A or B
1303 iline2++; // ignore G or B or R or G
1304 unsigned char r = *iline2++; // use B or G or G or R
1305 if (ibpp == 32) iline2++; // ignore A or R or B or A
1307 XPutPixel (image, xx + dest_x, yy + dest_y, (r ? 1 : 0));
1312 const unsigned char *iline = data;
1313 unsigned char *oline = (unsigned char *) image->data + dest_y * obpl +
1316 mode = convert_mode_merge (mode,
1317 convert_mode_invert (
1318 convert_mode_to_rgba (dpy->bitmap_info)));
1320 for (yy = 0; yy < height; yy++) {
1322 convert_row ((uint32_t *)oline, iline, width, mode, ibpp);
1334 /* Returns a transformation matrix to do rotation as per the provided
1335 EXIF "Orientation" value.
1337 static CGAffineTransform
1338 exif_rotate (int rot, CGSize rect)
1340 CGAffineTransform trans = CGAffineTransformIdentity;
1342 case 2: // flip horizontal
1343 trans = CGAffineTransformMakeTranslation (rect.width, 0);
1344 trans = CGAffineTransformScale (trans, -1, 1);
1347 case 3: // rotate 180
1348 trans = CGAffineTransformMakeTranslation (rect.width, rect.height);
1349 trans = CGAffineTransformRotate (trans, M_PI);
1352 case 4: // flip vertical
1353 trans = CGAffineTransformMakeTranslation (0, rect.height);
1354 trans = CGAffineTransformScale (trans, 1, -1);
1357 case 5: // transpose (UL-to-LR axis)
1358 trans = CGAffineTransformMakeTranslation (rect.height, rect.width);
1359 trans = CGAffineTransformScale (trans, -1, 1);
1360 trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
1363 case 6: // rotate 90
1364 trans = CGAffineTransformMakeTranslation (0, rect.width);
1365 trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
1368 case 7: // transverse (UR-to-LL axis)
1369 trans = CGAffineTransformMakeScale (-1, 1);
1370 trans = CGAffineTransformRotate (trans, M_PI / 2);
1373 case 8: // rotate 270
1374 trans = CGAffineTransformMakeTranslation (rect.height, 0);
1375 trans = CGAffineTransformRotate (trans, M_PI / 2);
1387 jwxyz_draw_NSImage_or_CGImage (Display *dpy, Drawable d,
1388 Bool nsimg_p, void *img_arg,
1389 XRectangle *geom_ret, int exif_rotation)
1393 CGImageSourceRef cgsrc;
1394 # endif // USE_IPHONE
1397 CGContextRef cgc = d->cgc;
1401 NSImage *nsimg = (NSImage *) img_arg;
1402 imgr = [nsimg size];
1405 // convert the NSImage to a CGImage via the toll-free-bridging
1406 // of NSData and CFData...
1408 NSData *nsdata = [NSBitmapImageRep
1409 TIFFRepresentationOfImageRepsInArray:
1410 [nsimg representations]];
1411 CFDataRef cfdata = (CFDataRef) nsdata;
1412 cgsrc = CGImageSourceCreateWithData (cfdata, NULL);
1413 cgi = CGImageSourceCreateImageAtIndex (cgsrc, 0, NULL);
1414 # else // USE_IPHONE
1415 cgi = nsimg.CGImage;
1416 # endif // USE_IPHONE
1419 cgi = (CGImageRef) img_arg;
1420 imgr.width = CGImageGetWidth (cgi);
1421 imgr.height = CGImageGetHeight (cgi);
1424 Bool rot_p = (exif_rotation >= 5);
1427 imgr = NSMakeSize (imgr.height, imgr.width);
1429 XRectangle winr = d->frame;
1430 float rw = winr.width / imgr.width;
1431 float rh = winr.height / imgr.height;
1432 float r = (rw < rh ? rw : rh);
1434 /* If the window is a goofy aspect ratio, take a middle slice of
1435 the image instead. */
1436 if (winr.width > winr.height * 5 ||
1437 winr.width > winr.width * 5) {
1438 r *= (winr.width > winr.height
1439 ? winr.width / (double) winr.height
1440 : winr.height / (double) winr.width);
1441 // NSLog (@"weird aspect: scaling by %.1f\n", r);
1445 dst.size.width = imgr.width * r;
1446 dst.size.height = imgr.height * r;
1447 dst.origin.x = (winr.width - dst.size.width) / 2;
1448 dst.origin.y = (winr.height - dst.size.height) / 2;
1450 dst2.origin.x = dst2.origin.y = 0;
1452 dst2.size.width = dst.size.height;
1453 dst2.size.height = dst.size.width;
1455 dst2.size = dst.size;
1458 // Clear the part not covered by the image to background or black.
1460 if (d->type == WINDOW)
1461 XClearWindow (dpy, d);
1463 jwxyz_fill_rect (dpy, d, 0, 0, 0, winr.width, winr.height,
1464 jwxyz_drawable_depth (d) == 1 ? 0 : BlackPixel(dpy,0));
1467 CGAffineTransform trans =
1468 exif_rotate (exif_rotation, rot_p ? dst2.size : dst.size);
1470 CGContextSaveGState (cgc);
1471 CGContextConcatCTM (cgc,
1472 CGAffineTransformMakeTranslation (dst.origin.x,
1474 CGContextConcatCTM (cgc, trans);
1475 //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
1476 CGContextDrawImage (cgc, dst2, cgi);
1477 CGContextRestoreGState (cgc);
1482 CGImageRelease (cgi);
1484 # endif // USE_IPHONE
1487 geom_ret->x = dst.origin.x;
1488 geom_ret->y = dst.origin.y;
1489 geom_ret->width = dst.size.width;
1490 geom_ret->height = dst.size.height;
1493 invalidate_drawable_cache (d);
1498 jwxyz_png_to_ximage (Display *dpy, Visual *visual,
1499 const unsigned char *png_data, unsigned long data_size)
1501 NSImage *img = [[NSImage alloc] initWithData:
1502 [NSData dataWithBytes:png_data
1505 NSBitmapImageRep *bm = [NSBitmapImageRep
1508 TIFFRepresentationOfImageRepsInArray:
1509 [img representations]]];
1510 int width = [img size].width;
1511 int height = [img size].height;
1512 size_t ibpp = [bm bitsPerPixel];
1513 size_t ibpl = [bm bytesPerRow];
1514 const unsigned char *data = [bm bitmapData];
1515 convert_mode_t mode = (([bm bitmapFormat] & NSAlphaFirstBitmapFormat)
1516 ? CONVERT_MODE_ROTATE_MASK
1519 CGImageRef cgi = [img CGImage];
1520 int width = CGImageGetWidth (cgi);
1521 int height = CGImageGetHeight (cgi);
1523 size_t ibpl = ibpp/4 * width;
1524 unsigned char *data = (unsigned char *) calloc (ibpl, height);
1525 const CGBitmapInfo bitmap_info =
1526 kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
1528 CGBitmapContextCreate (data, width, height,
1529 8, /* bits per component */
1530 ibpl, dpy->colorspace,
1532 CGContextDrawImage (cgc, CGRectMake (0, 0, width, height), cgi);
1534 convert_mode_t mode = convert_mode_to_rgba (bitmap_info);
1536 #endif // USE_IPHONE
1538 XImage *image = XCreateImage (dpy, visual, 32, ZPixmap, 0, 0,
1539 width, height, 8, 0);
1540 image->data = (char *) malloc (image->height * image->bytes_per_line);
1542 // data points at (x,y) with ibpl rowstride.
1544 int obpl = image->bytes_per_line;
1545 const unsigned char *iline = data;
1546 unsigned char *oline = (unsigned char *) image->data;
1548 for (yy = 0; yy < height; yy++) {
1549 convert_row ((uint32_t *)oline, iline, width, mode, ibpp);
1559 CGContextRelease (cgc);
1568 XCreatePixmap (Display *dpy, Drawable d,
1569 unsigned int width, unsigned int height, unsigned int depth)
1572 char *data = (char *) malloc (width * height * 4);
1573 if (! data) return 0;
1575 Pixmap p = (Pixmap) calloc (1, sizeof(*p));
1577 p->frame.width = width;
1578 p->frame.height = height;
1579 p->pixmap.depth = depth;
1580 p->pixmap.cgc_buffer = data;
1582 /* Quartz doesn't have a 1bpp image type.
1583 Used to use 8bpp gray images instead of 1bpp, but some Mac video cards
1584 don't support that! So we always use 32bpp, regardless of depth. */
1586 p->cgc = CGBitmapContextCreate (data, width, height,
1587 8, /* bits per component */
1588 width * 4, /* bpl */
1591 Assert (p->cgc, "could not create CGBitmapContext");
1597 XFreePixmap (Display *d, Pixmap p)
1599 Assert (p && p->type == PIXMAP, "not a pixmap");
1600 invalidate_drawable_cache (p);
1601 CGContextRelease (p->cgc);
1602 if (p->pixmap.cgc_buffer)
1603 free (p->pixmap.cgc_buffer);
1610 copy_pixmap (Display *dpy, Pixmap p)
1613 Assert (p->type == PIXMAP, "not a pixmap");
1619 unsigned int width, height, border_width, depth;
1620 if (XGetGeometry (dpy, p, &root,
1621 &x, &y, &width, &height, &border_width, &depth)) {
1623 gcv.function = GXcopy;
1624 GC gc = XCreateGC (dpy, p, GCFunction, &gcv);
1626 p2 = XCreatePixmap (dpy, p, width, height, depth);
1628 XCopyArea (dpy, p, p2, gc, 0, 0, width, height, 0, 0);
1633 Assert (p2, "could not copy pixmap");
1639 // Returns the verbose Unicode name of this character, like "agrave" or
1640 // "daggerdouble". Used by fontglide debugMetrics.
1643 jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
1646 NSFont *nsfont = (NSFont *) jwxyz_native_font (fid);
1648 CTFontCreateWithName ((CFStringRef) [nsfont fontName],
1651 Assert (ctfont, "no CTFontRef for UIFont");
1654 if (CTFontGetGlyphsForCharacters (ctfont, (UniChar *) &uc, &cgglyph, 1)) {
1655 CGFontRef cgfont = CTFontCopyGraphicsFont (ctfont, 0);
1656 NSString *name = (NSString *) CGFontCopyGlyphNameForGlyph(cgfont, cgglyph);
1657 ret = (name ? strdup ([name UTF8String]) : 0);
1658 CGFontRelease (cgfont);
1668 draw_string (Display *dpy, Drawable d, GC gc, int x, int y,
1669 const char *str, size_t len, int utf8_p)
1671 NSString *nsstr = nsstring_from (str, len, utf8_p);
1673 if (! nsstr) return 1;
1675 XRectangle wr = d->frame;
1676 CGContextRef cgc = d->cgc;
1678 unsigned long argb = gc->gcv.foreground;
1679 if (gc->depth == 1) argb = (argb ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
1681 query_color_float (dpy, argb, rgba);
1682 NSColor *fg = [NSColor colorWithDeviceRed:rgba[0]
1687 if (!gc->gcv.font) {
1688 Assert (0, "no font");
1692 /* This crashes on iOS 5.1 because NSForegroundColorAttributeName,
1693 NSFontAttributeName, and NSAttributedString are only present on iOS 6
1694 and later. We could resurrect the Quartz code from v5.29 and do a
1695 runtime conditional on that, but that would be a pain in the ass.
1696 Probably time to just make iOS 6 a requirement.
1699 NSDictionary *attr =
1700 [NSDictionary dictionaryWithObjectsAndKeys:
1701 (NSFont *) jwxyz_native_font (gc->gcv.font), NSFontAttributeName,
1702 fg, NSForegroundColorAttributeName,
1705 // Don't understand why we have to do both set_color and
1706 // NSForegroundColorAttributeName, but we do.
1708 set_color (dpy, cgc, argb, 32, NO, YES);
1710 NSAttributedString *astr = [[NSAttributedString alloc]
1711 initWithString:nsstr
1713 CTLineRef dl = CTLineCreateWithAttributedString (
1714 (__bridge CFAttributedStringRef) astr);
1716 // Not sure why this is necessary, but xoff is positive when the first
1717 // character on the line has a negative lbearing. Without this, the
1718 // string is rendered with the first ink at 0 instead of at lbearing.
1719 // I have not seen xoff be negative, so I'm not sure if that can happen.
1721 // Test case: "Combining Double Tilde" U+0360 (\315\240) followed by
1724 CGFloat xoff = CTLineGetOffsetForStringIndex (dl, 0, NULL);
1725 Assert (xoff >= 0, "unexpected CTLineOffset");
1728 CGContextSetTextPosition (cgc,
1730 wr.y + wr.height - y);
1731 CGContextSetShouldAntialias (cgc, gc->gcv.antialias_p);
1733 CTLineDraw (dl, cgc);
1737 invalidate_drawable_cache (d);
1743 SetClipMask (Display *dpy, GC gc, Pixmap m)
1745 Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
1747 if (gc->gcv.clip_mask) {
1748 XFreePixmap (dpy, gc->gcv.clip_mask);
1749 CGImageRelease (gc->clip_mask);
1752 gc->gcv.clip_mask = copy_pixmap (dpy, m);
1753 if (gc->gcv.clip_mask)
1755 CGBitmapContextCreateImage (gc->gcv.clip_mask->cgc);
1763 SetClipOrigin (Display *dpy, GC gc, int x, int y)
1765 gc->gcv.clip_x_origin = x;
1766 gc->gcv.clip_y_origin = y;
1771 const struct jwxyz_vtbl quartz_vtbl = {
1774 display_sources_data,
1783 jwxyz_quartz_copy_area,
1798 #endif // JWXYZ_QUARTZ -- entire file