/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */ /* cairo - a vector graphics library with display and print output * * Copyright � 2006, 2007 Mozilla Corporation * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation * (the "LGPL") or, at your option, under the terms of the Mozilla * Public License Version 1.1 (the "MPL"). If you do not alter this * notice, a recipient may use your version of this file under either * the MPL or the LGPL. * * You should have received a copy of the LGPL along with this library * in the file COPYING-LGPL-2.1; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA * You should have received a copy of the MPL along with this library * in the file COPYING-MPL-1.1 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY * OF ANY KIND, either express or implied. See the LGPL or the MPL for * the specific language governing rights and limitations. * * The Original Code is the cairo graphics library. * * The Initial Developer of the Original Code is Mozilla Foundation. * * Contributor(s): * Vladimir Vukicevic <vladimir@mozilla.com>
*/
#define _GNU_SOURCE /* required for RTLD_DEFAULT */ #include"cairoint.h"
/** * SECTION:cairo-quartz * @Title: Quartz Surfaces * @Short_Description: Rendering to Quartz surfaces * @See_Also: #cairo_surface_t * * The Quartz surface is used to render cairo graphics targeting the * Apple OS X Quartz rendering system.
**/
/** * CAIRO_HAS_QUARTZ_SURFACE: * * Defined if the Quartz surface backend is available. * This macro can be used to conditionally compile backend-specific code. * * Since: 1.6
**/
#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1050 /* This method is private, but it exists. Its params are are exposed * as args to the NS* method, but not as CG.
*/ enum PrivateCGCompositeMode {
kPrivateCGCompositeClear = 0,
kPrivateCGCompositeCopy = 1,
kPrivateCGCompositeSourceOver = 2,
kPrivateCGCompositeSourceIn = 3,
kPrivateCGCompositeSourceOut = 4,
kPrivateCGCompositeSourceAtop = 5,
kPrivateCGCompositeDestinationOver = 6,
kPrivateCGCompositeDestinationIn = 7,
kPrivateCGCompositeDestinationOut = 8,
kPrivateCGCompositeDestinationAtop = 9,
kPrivateCGCompositeXOR = 10,
kPrivateCGCompositePlusDarker = 11, // (max (0, (1-d) + (1-s)))
kPrivateCGCompositePlusLighter = 12, // (min (1, s + d))
}; typedefenum PrivateCGCompositeMode PrivateCGCompositeMode;
CG_EXTERN void CGContextSetCompositeOperation (CGContextRef, PrivateCGCompositeMode); #endif
/* Some of these are present in earlier versions of the OS than where * they are public; other are not public at all
*/ /* public since 10.5 */ staticvoid (*CGContextDrawTiledImagePtr) (CGContextRef, CGRect, CGImageRef) = NULL;
/* public since 10.6 */ static CGPathRef (*CGContextCopyPathPtr) (CGContextRef) = NULL; staticvoid (*CGContextSetAllowsFontSmoothingPtr) (CGContextRef, bool) = NULL;
/* not yet public */ staticunsignedint (*CGContextGetTypePtr) (CGContextRef) = NULL; staticbool (*CGContextGetAllowsFontSmoothingPtr) (CGContextRef) = NULL;
/* CTFontDrawGlyphs is not available until 10.7 */ staticvoid (*CTFontDrawGlyphsPtr) (CTFontRef, const CGGlyph[], const CGPoint[], size_t, CGContextRef) = NULL;
#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1050 static PrivateCGCompositeMode
_cairo_quartz_cairo_operator_to_quartz_composite (cairo_operator_t op)
{ switch (op) { case CAIRO_OPERATOR_CLEAR: return kPrivateCGCompositeClear; case CAIRO_OPERATOR_SOURCE: return kPrivateCGCompositeCopy; case CAIRO_OPERATOR_OVER: return kPrivateCGCompositeSourceOver; case CAIRO_OPERATOR_IN: return kPrivateCGCompositeSourceIn; case CAIRO_OPERATOR_OUT: return kPrivateCGCompositeSourceOut; case CAIRO_OPERATOR_ATOP: return kPrivateCGCompositeSourceAtop; case CAIRO_OPERATOR_DEST_OVER: return kPrivateCGCompositeDestinationOver; case CAIRO_OPERATOR_DEST_IN: return kPrivateCGCompositeDestinationIn; case CAIRO_OPERATOR_DEST_OUT: return kPrivateCGCompositeDestinationOut; case CAIRO_OPERATOR_DEST_ATOP: return kPrivateCGCompositeDestinationAtop; case CAIRO_OPERATOR_XOR: return kPrivateCGCompositeXOR; case CAIRO_OPERATOR_ADD: return kPrivateCGCompositePlusLighter;
case CAIRO_OPERATOR_DEST: case CAIRO_OPERATOR_SATURATE: case CAIRO_OPERATOR_MULTIPLY: case CAIRO_OPERATOR_SCREEN: case CAIRO_OPERATOR_OVERLAY: case CAIRO_OPERATOR_DARKEN: case CAIRO_OPERATOR_LIGHTEN: case CAIRO_OPERATOR_COLOR_DODGE: case CAIRO_OPERATOR_COLOR_BURN: case CAIRO_OPERATOR_HARD_LIGHT: case CAIRO_OPERATOR_SOFT_LIGHT: case CAIRO_OPERATOR_DIFFERENCE: case CAIRO_OPERATOR_EXCLUSION: case CAIRO_OPERATOR_HSL_HUE: case CAIRO_OPERATOR_HSL_SATURATION: case CAIRO_OPERATOR_HSL_COLOR: case CAIRO_OPERATOR_HSL_LUMINOSITY: default:
ASSERT_NOT_REACHED;
}
} #endif
static CGBlendMode
_cairo_quartz_cairo_operator_to_quartz_blend (cairo_operator_t op)
{ switch (op) { case CAIRO_OPERATOR_MULTIPLY: return kCGBlendModeMultiply; case CAIRO_OPERATOR_SCREEN: return kCGBlendModeScreen; case CAIRO_OPERATOR_OVERLAY: return kCGBlendModeOverlay; case CAIRO_OPERATOR_DARKEN: return kCGBlendModeDarken; case CAIRO_OPERATOR_LIGHTEN: return kCGBlendModeLighten; case CAIRO_OPERATOR_COLOR_DODGE: return kCGBlendModeColorDodge; case CAIRO_OPERATOR_COLOR_BURN: return kCGBlendModeColorBurn; case CAIRO_OPERATOR_HARD_LIGHT: return kCGBlendModeHardLight; case CAIRO_OPERATOR_SOFT_LIGHT: return kCGBlendModeSoftLight; case CAIRO_OPERATOR_DIFFERENCE: return kCGBlendModeDifference; case CAIRO_OPERATOR_EXCLUSION: return kCGBlendModeExclusion; case CAIRO_OPERATOR_HSL_HUE: return kCGBlendModeHue; case CAIRO_OPERATOR_HSL_SATURATION: return kCGBlendModeSaturation; case CAIRO_OPERATOR_HSL_COLOR: return kCGBlendModeColor; case CAIRO_OPERATOR_HSL_LUMINOSITY: return kCGBlendModeLuminosity;
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 case CAIRO_OPERATOR_CLEAR: return kCGBlendModeClear; case CAIRO_OPERATOR_SOURCE: return kCGBlendModeCopy; case CAIRO_OPERATOR_OVER: return kCGBlendModeNormal; case CAIRO_OPERATOR_IN: return kCGBlendModeSourceIn; case CAIRO_OPERATOR_OUT: return kCGBlendModeSourceOut; case CAIRO_OPERATOR_ATOP: return kCGBlendModeSourceAtop; case CAIRO_OPERATOR_DEST_OVER: return kCGBlendModeDestinationOver; case CAIRO_OPERATOR_DEST_IN: return kCGBlendModeDestinationIn; case CAIRO_OPERATOR_DEST_OUT: return kCGBlendModeDestinationOut; case CAIRO_OPERATOR_DEST_ATOP: return kCGBlendModeDestinationAtop; case CAIRO_OPERATOR_XOR: return kCGBlendModeXOR; case CAIRO_OPERATOR_ADD: return kCGBlendModePlusLighter; #else case CAIRO_OPERATOR_CLEAR: case CAIRO_OPERATOR_SOURCE: case CAIRO_OPERATOR_OVER: case CAIRO_OPERATOR_IN: case CAIRO_OPERATOR_OUT: case CAIRO_OPERATOR_ATOP: case CAIRO_OPERATOR_DEST_OVER: case CAIRO_OPERATOR_DEST_IN: case CAIRO_OPERATOR_DEST_OUT: case CAIRO_OPERATOR_DEST_ATOP: case CAIRO_OPERATOR_XOR: case CAIRO_OPERATOR_ADD: #endif
case CAIRO_OPERATOR_DEST: case CAIRO_OPERATOR_SATURATE: default:
ASSERT_NOT_REACHED;
} return kCGBlendModeNormal; /* just to silence clang warning [-Wreturn-type] */
}
/* Quartz doesn't support SATURATE at all. COLOR_DODGE and * COLOR_BURN in Quartz follow the ISO32000 definition, but cairo * uses the definition from the Adobe Supplement. Also fallback * on SOFT_LIGHT and HSL_HUE, because their results are * significantly different from those provided by pixman.
*/ if (op == CAIRO_OPERATOR_SATURATE ||
op == CAIRO_OPERATOR_SOFT_LIGHT ||
op == CAIRO_OPERATOR_HSL_HUE ||
op == CAIRO_OPERATOR_COLOR_DODGE ||
op == CAIRO_OPERATOR_COLOR_BURN)
{ return CAIRO_INT_STATUS_UNSUPPORTED;
}
/* When the destination has no color components, we can avoid some * fallbacks, but we have to workaround operators which behave
* differently in Quartz. */ if (surface->base.content == CAIRO_CONTENT_ALPHA) {
assert (op != CAIRO_OPERATOR_ATOP); /* filtered by surface layer */
if (op == CAIRO_OPERATOR_SOURCE ||
op == CAIRO_OPERATOR_IN ||
op == CAIRO_OPERATOR_OUT ||
op == CAIRO_OPERATOR_DEST_IN ||
op == CAIRO_OPERATOR_DEST_ATOP ||
op == CAIRO_OPERATOR_XOR)
{ return CAIRO_INT_STATUS_UNSUPPORTED;
}
if (op == CAIRO_OPERATOR_DEST_OVER)
op = CAIRO_OPERATOR_OVER; elseif (op == CAIRO_OPERATOR_SATURATE)
op = CAIRO_OPERATOR_ADD; elseif (op == CAIRO_OPERATOR_COLOR_DODGE)
op = CAIRO_OPERATOR_OVER; elseif (op == CAIRO_OPERATOR_COLOR_BURN)
op = CAIRO_OPERATOR_OVER;
}
/* Quartz computes a small number of samples of the gradient color * function. On MacOS X 10.5 it apparently computes only 1024
* samples. */ #define MAX_GRADIENT_RANGE 1024
if (gradient->base.extend == CAIRO_EXTEND_PAD) {
t[0] = MAX (t[0], -0.5);
t[1] = MIN (t[1], 1.5);
} elseif (t[1] - t[0] > MAX_GRADIENT_RANGE) return NULL;
/* set the input range for the function -- the function knows how
to map values outside of 0.0 .. 1.0 to the correct color */
input_value_range[0] = t[0];
input_value_range[1] = t[1];
} else {
input_value_range[0] = 0;
input_value_range[1] = 1;
}
image_data = _cairo_malloc_ab (image_surface->height, image_surface->stride); if (unlikely (!image_data))
{ if (acquired)
_cairo_surface_release_source_image (source, image_surface, image_extra); else
cairo_surface_destroy (&image_surface->base);
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
}
// The last row of data may have less than stride bytes so make sure we // only copy the minimum amount required from that row.
memcpy (image_data, image_surface->data,
(image_surface->height - 1) * image_surface->stride +
cairo_format_stride_for_width (image_surface->format,
image_surface->width));
*image_out = CairoQuartzCreateCGImage (image_surface->format,
image_surface->width,
image_surface->height,
image_surface->stride,
image_data, TRUE,
NULL,
DataProviderReleaseCallback,
image_data);
/* TODO: differentiate memory error and unsupported surface type */ if (unlikely (*image_out == NULL))
status = CAIRO_INT_STATUS_UNSUPPORTED;
if (acquired)
_cairo_surface_release_source_image (source, image_surface, image_extra); else
cairo_surface_destroy (&image_surface->base);
return status;
}
/* Generic #cairo_pattern_t -> CGPattern function */
CGContextDrawImage (context, info->imageBounds, info->image); if (info->do_reflect) { /* draw 3 more copies of the image, flipped. * DrawImage draws the image according to the current Y-direction into the rectangle given * (imageBounds); at the time of the first DrawImage above, the origin is at the bottom left * of the base image position, and the Y axis is extending upwards.
*/
/* Make the y axis extend downwards, and draw a flipped image below */
CGContextScaleCTM (context, 1, -1);
CGContextDrawImage (context, info->imageBounds, info->image);
/* Shift over to the right, and flip vertically (translation is 2x, * since we'll be flipping and thus rendering the rectangle "backwards"
*/
CGContextTranslateCTM (context, 2 * info->imageBounds.size.width, 0);
CGContextScaleCTM (context, -1, 1);
CGContextDrawImage (context, info->imageBounds, info->image);
/* Then unflip the Y-axis again, and draw the image above the point. */
CGContextScaleCTM (context, 1, -1);
CGContextDrawImage (context, info->imageBounds, info->image);
}
}
m = spattern->base.matrix;
status = _cairo_surface_to_cgimage (pat_surf, &extents, format,
&m, clip, &image); if (unlikely (status)) return status;
info = _cairo_malloc (sizeof (SurfacePatternDrawInfo)); if (unlikely (!info)) return CAIRO_STATUS_NO_MEMORY;
/* XXX -- if we're printing, we may need to call CGImageCreateCopy to make sure * that the data will stick around for this image when the printer gets to it. * Otherwise, the underlying data store may disappear from under us! * * _cairo_surface_to_cgimage will copy when it converts non-Quartz surfaces, * since the Quartz surfaces have a higher chance of sticking around. If the * source is a quartz image surface, then it's set up to retain a ref to the * image surface that it's backed by.
*/
info->image = image;
info->imageBounds = CGRectMake (0, 0, extents.width, extents.height);
info->do_reflect = FALSE;
/* The pattern matrix is relative to the bottom left, again; the * incoming cairo pattern matrix is relative to the upper left. * So we take the pattern matrix and the original context matrix, * which gives us the correct base translation/y flip.
*/
ptransform = CGAffineTransformConcat (stransform, dest->cgContextBaseCTM);
/* Source layer to be rendered when using DO_LAYER. Unlike 'layer' above, this is not owned by the drawing state
but by the source surface. */
CGLayerRef sourceLayer;
} cairo_quartz_drawing_state_t;
/* Quartz does not support repeating radients. We handle repeating gradients by manually extending the gradient and repeating color stops. We need to minimize the number of repetitions since Quartz seems to sample our color function across the entire range, even if part of that range is not needed for the visible area of the gradient, and it samples with some fixed resolution, so if the gradient range is too large it samples with very low resolution and the gradient is very coarse. _cairo_quartz_create_gradient_function computes the number of repetitions needed based on the extents.
*/ static cairo_int_status_t
_cairo_quartz_setup_gradient_source (cairo_quartz_drawing_state_t *state, const cairo_gradient_pattern_t *gradient, const cairo_rectangle_int_t *extents)
{
cairo_matrix_t mat;
cairo_circle_double_t start, end;
CGFunctionRef gradFunc;
CGColorSpaceRef rgb; bool extend = gradient->base.extend != CAIRO_EXTEND_NONE;
assert (gradient->n_stops > 0);
mat = gradient->base.matrix;
cairo_matrix_invert (&mat);
_cairo_quartz_cairo_matrix_to_quartz (&mat, &state->transform);
status = _cairo_surface_clipper_set_clip (&surface->clipper, clip); if (unlikely (status)) return status;
status = _cairo_quartz_surface_set_cairo_operator (surface, op); if (unlikely (status)) return status;
/* Save before we change the pattern, colorspace, etc. so that * we can restore and make sure that quartz releases our * pattern (which may be stack allocated)
*/
/* * To implement mask unbounded operations Quartz needs a temporary * surface which will be composited entirely (ignoring the mask). * To implement source unbounded operations Quartz needs a * temporary surface which allows extending the source to a size * covering the whole mask, but there are some optimization * opportunities: * * - CLEAR completely ignores the source, thus we can just use a * solid color fill. * * - SOURCE can be implemented by drawing the source and clearing * outside of the source as long as the two regions have no * intersection. This happens when the source is a pixel-aligned * rectangle. If the source is at least as big as the * intersection between the clip rectangle and the mask * rectangle, no clear operation is needed.
*/
needs_temp = ! _cairo_operator_bounded_by_mask (op);
/* Quartz seems to tile images at pixel-aligned regions only -- this * leads to seams if the image doesn't end up scaling to fill the * space exactly. The CGPattern tiling approach doesn't have this * problem. Check if we're going to fill up the space (within some * epsilon), and if not, fall back to the CGPattern type.
*/
if ((fw & CAIRO_FIXED_FRAC_MASK) <= CAIRO_FIXED_EPSILON &&
(fh & CAIRO_FIXED_FRAC_MASK) <= CAIRO_FIXED_EPSILON)
{ /* We're good to use DrawTiledImage, but ensure that
* the math works out */
/* Quartz likes to munge the pattern phase (as yet unexplained * why); force it to 0,0 as we've already baked in the correct * pattern translation into the pattern matrix
*/
CGContextSetPatternPhase (state->cgDrawContext, CGSizeMake (0, 0));
if (state->action == DO_LAYER) { /* Note that according to Apple docs it's completely legal to draw a CGLayer * to any CGContext, even one it wasn't created for.
*/
assert (state->sourceLayer);
CGContextDrawLayerAtPoint (state->cgDrawContext, state->rect.origin,
state->sourceLayer);
} elseif (state->action == DO_IMAGE) {
CGContextDrawImage (state->cgDrawContext, state->rect, state->image); if (op == CAIRO_OPERATOR_SOURCE &&
state->cgDrawContext == state->cgMaskContext)
{
CGContextBeginPath (state->cgDrawContext);
CGContextAddRect (state->cgDrawContext, state->rect);
// let's hope they don't add YUV under us
colorspace = CGBitmapContextGetColorSpace (surface->cgContext);
color_comps = CGColorSpaceGetNumberOfComponents(colorspace);
// XXX TODO: We can handle all of these by converting to // pixman masks, including non-native-endian masks if (bpc != 8) return CAIRO_INT_STATUS_UNSUPPORTED;
if (bpp != 32 && bpp != 8) return CAIRO_INT_STATUS_UNSUPPORTED;
if (color_comps != 3 && color_comps != 1) return CAIRO_INT_STATUS_UNSUPPORTED;
if (IS_EMPTY (surface)) return CAIRO_STATUS_SUCCESS;
/* Restore our saved gstate that we use to reset clipping */
CGContextRestoreGState (surface->cgContext);
_cairo_surface_clipper_reset (&surface->clipper);
/* ClipToMask is essentially drawing an image, so we need to flip the CTM
* to get the image to appear oriented the right way */
CGContextConcatCTM (state.cgMaskContext, CGAffineTransformInvert (mask_matrix));
CGContextTranslateCTM (state.cgMaskContext, 0.0, rect.size.height);
CGContextScaleCTM (state.cgMaskContext, 1.0, -1.0);
if (! need_temp) {
mask_surf = extents->mask_pattern.surface.surface;
/* When an opaque surface used as a mask in Quartz, its * luminosity is used as the alpha value, so we con only use
* surfaces with alpha without creating a temporary mask. */
need_temp = ! (mask_surf->content & CAIRO_CONTENT_ALPHA);
}
if (! need_temp) {
CGInterpolationQuality mask_filter;
cairo_bool_t simple_transform;
/* Quartz only allows one interpolation to be set for mask and * source, so we can skip the temp surface only if the source
* filtering makes the mask look correct. */ if (source->type == CAIRO_PATTERN_TYPE_SURFACE)
need_temp = ! (simple_transform || filter == mask_filter); else
filter = mask_filter;
}
if (need_temp) { /* Render the mask to a surface */
mask_surf = _cairo_quartz_surface_create_similar (surface,
CAIRO_CONTENT_ALPHA,
surface->extents.width,
surface->extents.height);
status = mask_surf->status; if (unlikely (status)) goto BAIL;
/* mask_surf is clear, so use OVER instead of SOURCE to avoid a
* temporary layer or fallback to cairo-image. */
status = _cairo_surface_paint (mask_surf, CAIRO_OPERATOR_OVER, mask, NULL); if (unlikely (status)) goto BAIL;
cairo_matrix_init_identity (&matrix);
}
status = _cairo_quartz_cg_mask_with_surface (extents,
mask_surf, &matrix, filter);
rv = _cairo_quartz_setup_state (&state, extents); if (unlikely (rv)) goto BAIL;
// Turning antialiasing off used to cause misrendering with // single-pixel lines (e.g. 20,10.5 -> 21,10.5 end up being rendered as 2 pixels). // That's been since fixed in at least 10.5, and in the latest 10.4 dot releases.
CGContextSetShouldAntialias (state.cgMaskContext, (antialias != CAIRO_ANTIALIAS_NONE));
CGContextSetLineWidth (state.cgMaskContext, style->line_width);
CGContextSetLineCap (state.cgMaskContext, _cairo_quartz_cairo_line_cap_to_quartz (style->line_cap));
CGContextSetLineJoin (state.cgMaskContext, _cairo_quartz_cairo_line_join_to_quartz (style->line_join));
CGContextSetMiterLimit (state.cgMaskContext, style->miter_limit);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.