/* gdkdrawable-quartz.c * * Copyright (C) 2005 Imendio AB * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include "gdkprivate-quartz.h" static gpointer parent_class; typedef struct { GdkDrawable *drawable; CGContextRef context; } SurfaceInfo; static cairo_user_data_key_t surface_info_key; static void surface_info_destroy (void *data) { SurfaceInfo *info = data; _gdk_quartz_drawable_release_context (info->drawable, info->context); g_free (info); } static cairo_surface_t * gdk_quartz_ref_cairo_surface (GdkDrawable *drawable) { GdkDrawableImplQuartz *impl = GDK_DRAWABLE_IMPL_QUARTZ (drawable); CGContextRef context; int width, height; cairo_surface_t *surface; SurfaceInfo *info; if (GDK_IS_WINDOW_IMPL_QUARTZ (drawable) && GDK_WINDOW_DESTROYED (impl->wrapper)) return NULL; context = _gdk_quartz_drawable_get_context (drawable, TRUE, FALSE); if (!context) return NULL; gdk_drawable_get_size (drawable, &width, &height); surface = cairo_quartz_surface_create (context, TRUE, width, height); info = g_new (SurfaceInfo, 1); info->drawable = drawable; info->context = context; cairo_surface_set_user_data (surface, &surface_info_key, info, surface_info_destroy); return surface; } static void gdk_quartz_set_colormap (GdkDrawable *drawable, GdkColormap *colormap) { GdkDrawableImplQuartz *impl = GDK_DRAWABLE_IMPL_QUARTZ (drawable); if (impl->colormap == colormap) return; if (impl->colormap) g_object_unref (impl->colormap); impl->colormap = colormap; if (impl->colormap) g_object_ref (impl->colormap); } static GdkColormap* gdk_quartz_get_colormap (GdkDrawable *drawable) { return GDK_DRAWABLE_IMPL_QUARTZ (drawable)->colormap; } static GdkScreen* gdk_quartz_get_screen (GdkDrawable *drawable) { return _gdk_screen; } static GdkVisual* gdk_quartz_get_visual (GdkDrawable *drawable) { return gdk_drawable_get_visual (GDK_DRAWABLE_IMPL_QUARTZ (drawable)->wrapper); } static int gdk_quartz_get_depth (GdkDrawable *drawable) { /* This is a bit bogus but I'm not sure the other way is better */ return gdk_drawable_get_depth (GDK_DRAWABLE_IMPL_QUARTZ (drawable)->wrapper); } static void gdk_quartz_draw_rectangle (GdkDrawable *drawable, GdkGC *gc, gboolean filled, gint x, gint y, gint width, gint height) { CGContextRef context = _gdk_quartz_drawable_get_context (drawable, FALSE, TRUE); CGRect rect = CGRectMake (x, y, width, height); if (!context) return; _gdk_quartz_update_context_from_gc (context, gc); if (filled) { _gdk_quartz_set_context_fill_color_from_pixel (context, gdk_drawable_get_colormap (drawable), _gdk_gc_get_fg_pixel (gc)); CGContextFillRect (context, rect); } else { _gdk_quartz_set_context_stroke_color_from_pixel (context, gdk_drawable_get_colormap (drawable), _gdk_gc_get_fg_pixel (gc)); CGContextStrokeRect (context, rect); } _gdk_quartz_drawable_release_context (drawable, context); } static void gdk_quartz_draw_arc (GdkDrawable *drawable, GdkGC *gc, gboolean filled, gint x, gint y, gint width, gint height, gint angle1, gint angle2) { CGContextRef context = _gdk_quartz_drawable_get_context (drawable, FALSE, TRUE); float start_angle, end_angle; if (!context) return; _gdk_quartz_update_context_from_gc (context, gc); CGContextSaveGState (context); CGContextTranslateCTM (context, x + width / 2, y + height / 2); CGContextScaleCTM (context, 1.0, (float)height / width); start_angle = (2 - (angle1 / (180.0 * 64.0))) * G_PI; end_angle = start_angle - (angle2 / (180.0 * 64.0)) * G_PI; if (filled) { _gdk_quartz_set_context_fill_color_from_pixel (context, gdk_drawable_get_colormap (drawable), _gdk_gc_get_fg_pixel (gc)); CGContextMoveToPoint (context, 0, 0); CGContextAddArc (context, 0, 0, width / 2, start_angle, end_angle, TRUE); CGContextClosePath (context); CGContextFillPath (context); } else { _gdk_quartz_set_context_stroke_color_from_pixel (context, gdk_drawable_get_colormap (drawable), _gdk_gc_get_fg_pixel (gc)); CGContextAddArc (context, 0, 0, width / 2, start_angle, end_angle, TRUE); CGContextStrokePath (context); } CGContextRestoreGState (context); _gdk_quartz_drawable_release_context (drawable, context); } static void gdk_quartz_draw_polygon (GdkDrawable *drawable, GdkGC *gc, gboolean filled, GdkPoint *points, gint npoints) { CGContextRef context = _gdk_quartz_drawable_get_context (drawable, FALSE, TRUE); int i; if (!context) return; _gdk_quartz_update_context_from_gc (context, gc); if (filled) _gdk_quartz_set_context_fill_color_from_pixel (context, gdk_drawable_get_colormap (drawable), _gdk_gc_get_fg_pixel (gc)); else _gdk_quartz_set_context_stroke_color_from_pixel (context, gdk_drawable_get_colormap (drawable), _gdk_gc_get_fg_pixel (gc)); CGContextMoveToPoint (context, points[0].x, points[0].y); for (i = 1; i < npoints; i++) CGContextAddLineToPoint (context, points[i].x, points[i].y); CGContextClosePath (context); if (filled) CGContextFillPath (context); else CGContextStrokePath (context); _gdk_quartz_drawable_release_context (drawable, context); } static void gdk_quartz_draw_text (GdkDrawable *drawable, GdkFont *font, GdkGC *gc, gint x, gint y, const gchar *text, gint text_length) { /* FIXME: Implement */ } static void gdk_quartz_draw_text_wc (GdkDrawable *drawable, GdkFont *font, GdkGC *gc, gint x, gint y, const GdkWChar *text, gint text_length) { /* FIXME: Implement */ } static void gdk_quartz_draw_drawable (GdkDrawable *drawable, GdkGC *gc, GdkPixmap *src, gint xsrc, gint ysrc, gint xdest, gint ydest, gint width, gint height) { int src_depth = gdk_drawable_get_depth (src); int dest_depth = gdk_drawable_get_depth (drawable); GdkDrawableImplQuartz *impl; GdkDrawableImplQuartz *src_impl; impl = GDK_DRAWABLE_IMPL_QUARTZ (drawable); if (GDK_IS_DRAWABLE_IMPL_QUARTZ (src)) src_impl = GDK_DRAWABLE_IMPL_QUARTZ (src); else src_impl = GDK_DRAWABLE_IMPL_QUARTZ (GDK_PIXMAP_OBJECT (src)->impl); if (src_depth == 1) { /* FIXME: src depth 1 is not supported yet */ } else if (dest_depth != 0 && src_depth == dest_depth) { CGContextRef context = _gdk_quartz_drawable_get_context (drawable, FALSE, FALSE); if (!context) return; _gdk_quartz_update_context_from_gc (context, gc); CGContextSetBlendMode (context, kCGBlendModeNormal); CGContextClipToRect (context, CGRectMake (xdest, ydest, width, height)); CGContextTranslateCTM (context, xdest - xsrc, ydest - ysrc); CGContextDrawImage (context, CGRectMake(0, 0, GDK_PIXMAP_IMPL_QUARTZ (src_impl)->width, GDK_PIXMAP_IMPL_QUARTZ (src_impl)->height), GDK_PIXMAP_IMPL_QUARTZ (src_impl)->image); _gdk_quartz_drawable_release_context (drawable, context); } else g_warning ("Attempt to draw a drawable with depth %d to a drawable with depth %d", src_depth, dest_depth); } static void gdk_quartz_draw_points (GdkDrawable *drawable, GdkGC *gc, GdkPoint *points, gint npoints) { int i; /* Just draw 1x1 rectangles */ for (i = 0; i < npoints; i++) { gdk_draw_rectangle (drawable, gc, TRUE, points[i].x, points[i].y, 1, 1); } } static void gdk_quartz_draw_segments (GdkDrawable *drawable, GdkGC *gc, GdkSegment *segs, gint nsegs) { CGContextRef context = _gdk_quartz_drawable_get_context (drawable, FALSE, TRUE); int i; if (!context) return; _gdk_quartz_update_context_from_gc (context, gc); _gdk_quartz_set_context_stroke_color_from_pixel (context, gdk_drawable_get_colormap (drawable), _gdk_gc_get_fg_pixel (gc)); for (i = 0; i < nsegs; i++) { CGContextMoveToPoint (context, segs[i].x1, segs[i].y1); CGContextAddLineToPoint (context, segs[i].x2, segs[i].y2); } CGContextStrokePath (context); _gdk_quartz_drawable_release_context (drawable, context); } static void gdk_quartz_draw_lines (GdkDrawable *drawable, GdkGC *gc, GdkPoint *points, gint npoints) { CGContextRef context = _gdk_quartz_drawable_get_context (drawable, FALSE, TRUE); int i; if (!context) return; _gdk_quartz_update_context_from_gc (context, gc); _gdk_quartz_set_context_stroke_color_from_pixel (context, gdk_drawable_get_colormap (drawable), _gdk_gc_get_fg_pixel (gc)); for (i = 1; i < npoints; i++) { CGContextMoveToPoint (context, points[i - 1].x, points[i - 1].y); CGContextAddLineToPoint (context, points[i].x, points[i].y); } CGContextStrokePath (context); _gdk_quartz_drawable_release_context (drawable, context); } static void gdk_quartz_draw_pixbuf (GdkDrawable *drawable, GdkGC *gc, GdkPixbuf *pixbuf, gint src_x, gint src_y, gint dest_x, gint dest_y, gint width, gint height, GdkRgbDither dither, gint x_dither, gint y_dither) { CGContextRef context = _gdk_quartz_drawable_get_context (drawable, FALSE, FALSE); CGColorSpaceRef colorspace; CGDataProviderRef data_provider; CGImageRef image; void *data; int rowstride, pixbuf_width, pixbuf_height; gboolean has_alpha; if (!context) return; pixbuf_width = gdk_pixbuf_get_width (pixbuf); pixbuf_height = gdk_pixbuf_get_height (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); data = gdk_pixbuf_get_pixels (pixbuf); colorspace = CGColorSpaceCreateDeviceRGB (); data_provider = CGDataProviderCreateWithData (NULL, data, pixbuf_height * rowstride, NULL); image = CGImageCreate (pixbuf_width, pixbuf_height, 8, has_alpha ? 32 : 24, rowstride, colorspace, has_alpha ? kCGImageAlphaLast : 0, data_provider, NULL, FALSE, kCGRenderingIntentDefault); CGDataProviderRelease (data_provider); CGColorSpaceRelease (colorspace); _gdk_quartz_update_context_from_gc (context, gc); CGContextSetBlendMode (context, kCGBlendModeNormal); CGContextClipToRect (context, CGRectMake (dest_x, dest_y, width, height)); CGContextTranslateCTM (context, dest_x - src_x, dest_y - src_y + pixbuf_height); CGContextScaleCTM (context, 1, -1); CGContextDrawImage (context, CGRectMake(0, 0, pixbuf_width, pixbuf_height), image); CGImageRelease (image); _gdk_quartz_drawable_release_context (drawable, context); } static void gdk_quartz_draw_image (GdkDrawable *drawable, GdkGC *gc, GdkImage *image, gint xsrc, gint ysrc, gint xdest, gint ydest, gint width, gint height) { CGContextRef context = _gdk_quartz_drawable_get_context (drawable, FALSE, FALSE); CGColorSpaceRef colorspace; CGDataProviderRef data_provider; CGImageRef cgimage; if (!context) return; colorspace = CGColorSpaceCreateDeviceRGB (); data_provider = CGDataProviderCreateWithData (NULL, image->mem, image->height * image->bpl, NULL); /* FIXME: Make sure that this function draws 32-bit images correctly, * also check endianness wrt kCGImageAlphaNoneSkipFirst */ cgimage = CGImageCreate (image->width, image->height, 8, 32, image->bpl, colorspace, kCGImageAlphaNoneSkipFirst, data_provider, NULL, FALSE, kCGRenderingIntentDefault); CGDataProviderRelease (data_provider); CGColorSpaceRelease (colorspace); _gdk_quartz_update_context_from_gc (context, gc); CGContextSetBlendMode (context, kCGBlendModeNormal); CGContextClipToRect (context, CGRectMake (xdest, ydest, width, height)); CGContextTranslateCTM (context, xdest - xsrc, ydest - ysrc + image->height); CGContextScaleCTM (context, 1, -1); CGContextDrawImage (context, CGRectMake (0, 0, image->width, image->height), cgimage); CGImageRelease (cgimage); _gdk_quartz_drawable_release_context (drawable, context); } static void gdk_drawable_impl_quartz_finalize (GObject *object) { GdkDrawableImplQuartz *impl = GDK_DRAWABLE_IMPL_QUARTZ (object); if (impl->colormap) g_object_unref (impl->colormap); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gdk_drawable_impl_quartz_class_init (GdkDrawableImplQuartzClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GdkDrawableClass *drawable_class = GDK_DRAWABLE_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->finalize = gdk_drawable_impl_quartz_finalize; drawable_class->create_gc = _gdk_quartz_gc_new; drawable_class->draw_rectangle = gdk_quartz_draw_rectangle; drawable_class->draw_arc = gdk_quartz_draw_arc; drawable_class->draw_polygon = gdk_quartz_draw_polygon; drawable_class->draw_text = gdk_quartz_draw_text; drawable_class->draw_text_wc = gdk_quartz_draw_text_wc; drawable_class->draw_drawable = gdk_quartz_draw_drawable; drawable_class->draw_points = gdk_quartz_draw_points; drawable_class->draw_segments = gdk_quartz_draw_segments; drawable_class->draw_lines = gdk_quartz_draw_lines; drawable_class->draw_image = gdk_quartz_draw_image; drawable_class->draw_pixbuf = gdk_quartz_draw_pixbuf; drawable_class->ref_cairo_surface = gdk_quartz_ref_cairo_surface; drawable_class->set_colormap = gdk_quartz_set_colormap; drawable_class->get_colormap = gdk_quartz_get_colormap; drawable_class->get_depth = gdk_quartz_get_depth; drawable_class->get_screen = gdk_quartz_get_screen; drawable_class->get_visual = gdk_quartz_get_visual; drawable_class->_copy_to_image = _gdk_quartz_copy_to_image; } GType gdk_drawable_impl_quartz_get_type (void) { static GType object_type = 0; if (!object_type) { static const GTypeInfo object_info = { sizeof (GdkDrawableImplQuartzClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) gdk_drawable_impl_quartz_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GdkDrawableImplQuartz), 0, /* n_preallocs */ (GInstanceInitFunc) NULL, }; object_type = g_type_register_static (GDK_TYPE_DRAWABLE, "GdkDrawableImplQuartz", &object_info, 0); } return object_type; } CGContextRef _gdk_quartz_drawable_get_context (GdkDrawable *drawable, gboolean antialias, gboolean y_axis_is_off_by_one) { if (GDK_IS_WINDOW_IMPL_QUARTZ (drawable)) { GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (drawable); CGContextRef context; impl->pool = [[NSAutoreleasePool alloc] init]; if (![impl->view lockFocusIfCanDraw]) { [impl->pool release]; return NULL; } context = [[NSGraphicsContext currentContext] graphicsPort]; CGContextSaveGState (context); CGContextSetAllowsAntialiasing (context, antialias); /* Sometimes when drawing certain primitives there's a one pixel * difference when drawing to a CGImage and when drawing to a window, * so we translate the ctm by 1 pixel here. */ if (y_axis_is_off_by_one) CGContextTranslateCTM (context, 0, 1); return context; } else if (GDK_IS_PIXMAP_IMPL_QUARTZ (drawable)) { GdkPixmapImplQuartz *impl = GDK_PIXMAP_IMPL_QUARTZ (drawable); CGContextRef context; context = CGBitmapContextCreate (impl->data, CGImageGetWidth (impl->image), CGImageGetHeight (impl->image), CGImageGetBitsPerComponent (impl->image), CGImageGetBytesPerRow (impl->image), CGImageGetColorSpace (impl->image), CGImageGetBitmapInfo (impl->image)); CGContextSetAllowsAntialiasing (context, antialias); return context; } g_assert_not_reached (); return NULL; } void _gdk_quartz_drawable_release_context (GdkDrawable *drawable, CGContextRef context) { if (!context) return; if (GDK_IS_WINDOW_IMPL_QUARTZ (drawable)) { GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (drawable); CGContextRestoreGState (context); CGContextSetAllowsAntialiasing (context, TRUE); [[NSGraphicsContext currentContext] flushGraphics]; [impl->view unlockFocus]; [impl->pool release]; } else if (GDK_IS_PIXMAP_IMPL_QUARTZ (drawable)) { CGContextRelease (context); } }