mirror of
https://gitlab.freedesktop.org/cairo/cairo.git
synced 2026-05-25 16:28:14 +02:00
This fixes a huge performance bug (entire image was being pushed to X server in order to copy a tiny piece of it). I see up to 50x improvement from subimage_copy (which was designed to expose this problem) but also a 5x improvement in some text performance cases. xlib-rgba subimage_copy-512 3.93 2.46% -> 0.07 2.71%: 52.91x faster ███████████████████████████████████████████████████▉ xlib-rgb subimage_copy-512 4.03 1.97% -> 0.09 2.61%: 44.74x faster ███████████████████████████████████████████▊ xlib-rgba subimage_copy-256 1.02 2.25% -> 0.07 0.56%: 14.42x faster █████████████▍ xlib-rgba text_image_rgb_over-256 63.21 1.53% -> 11.87 2.17%: 5.33x faster ████▍ xlib-rgba text_image_rgba_over-256 62.31 0.72% -> 11.87 2.82%: 5.25x faster ████▎ xlib-rgba text_image_rgba_source-256 67.97 0.85% -> 16.48 2.23%: 4.13x faster ███▏ xlib-rgba text_image_rgb_source-256 68.82 0.55% -> 16.93 2.10%: 4.07x faster ███▏ xlib-rgba subimage_copy-128 0.19 1.72% -> 0.06 0.85%: 3.10x faster ██▏
1809 lines
54 KiB
C
1809 lines
54 KiB
C
/* -*- 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Corporation.
|
|
*
|
|
* Contributor(s):
|
|
* Vladimir Vukicevic <vladimir@mozilla.com>
|
|
*/
|
|
|
|
#include <Carbon/Carbon.h>
|
|
|
|
#ifdef CAIRO_NQUARTZ_SUPPORT_AGL
|
|
#include <AGL/agl.h>
|
|
#include <OpenGL/gl.h>
|
|
#endif
|
|
|
|
#include "cairoint.h"
|
|
#include "cairo-private.h"
|
|
#include "cairo-nquartz.h"
|
|
|
|
#include "cairo-quartz-private.h"
|
|
|
|
#undef NQUARTZ_DEBUG
|
|
|
|
#ifdef NQUARTZ_DEBUG
|
|
#define ND(_x) fprintf _x
|
|
#else
|
|
#define ND(_x) do {} while(0)
|
|
#endif
|
|
|
|
/* 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))
|
|
};
|
|
typedef enum PrivateCGCompositeMode PrivateCGCompositeMode;
|
|
CG_EXTERN void CGContextSetCompositeOperation (CGContextRef, PrivateCGCompositeMode);
|
|
CG_EXTERN void CGContextResetCTM (CGContextRef);
|
|
CG_EXTERN void CGContextSetCTM (CGContextRef, CGAffineTransform);
|
|
CG_EXTERN void CGContextResetClip (CGContextRef);
|
|
CG_EXTERN CGSize CGContextGetPatternPhase (CGContextRef);
|
|
|
|
/* We need to work with the 10.3 SDK as well (and 10.3 machines; luckily, 10.3.9
|
|
* has all the stuff we care about, just some of it isn't exported in the SDK.
|
|
*/
|
|
#ifndef kCGBitmapByteOrder32Host
|
|
#define USE_10_3_WORKAROUNDS
|
|
#define kCGBitmapAlphaInfoMask 0x1F
|
|
#define kCGBitmapByteOrderMask 0x7000
|
|
#define kCGBitmapByteOrder32Host 0
|
|
|
|
typedef uint32_t CGBitmapInfo;
|
|
|
|
/* public in 10.4, present in 10.3.9 */
|
|
CG_EXTERN void CGContextReplacePathWithStrokedPath (CGContextRef);
|
|
CG_EXTERN CGImageRef CGBitmapContextCreateImage (CGContextRef);
|
|
#endif
|
|
|
|
|
|
typedef struct cairo_nquartz_surface {
|
|
cairo_surface_t base;
|
|
|
|
void *imageData;
|
|
|
|
CGContextRef cgContext;
|
|
CGAffineTransform cgContextBaseCTM;
|
|
|
|
cairo_rectangle_int16_t extents;
|
|
|
|
/* These are stored while drawing operations are in place, set up
|
|
* by nquartz_setup_source() and nquartz_finish_source()
|
|
*/
|
|
CGAffineTransform imageTransform;
|
|
CGImageRef sourceImage;
|
|
CGShadingRef sourceShading;
|
|
CGPatternRef sourcePattern;
|
|
#ifdef CAIRO_NQUARTZ_SUPPORT_AGL
|
|
AGLContext aglContext;
|
|
#else
|
|
void *_unused;
|
|
#endif
|
|
} cairo_nquartz_surface_t;
|
|
|
|
/**
|
|
** Utility functions
|
|
**/
|
|
|
|
void nquartz_surface_to_png (cairo_nquartz_surface_t *nq, char *dest);
|
|
void nquartz_image_to_png (CGImageRef, char *dest);
|
|
|
|
/*
|
|
* Cairo path -> Quartz path conversion helpers
|
|
*/
|
|
|
|
/* cairo path -> mutable path */
|
|
static cairo_status_t
|
|
_cairo_path_to_quartz_path_move_to (void *closure, cairo_point_t *point)
|
|
{
|
|
CGPathMoveToPoint ((CGMutablePathRef) closure, NULL,
|
|
_cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y));
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static cairo_status_t
|
|
_cairo_path_to_quartz_path_line_to (void *closure, cairo_point_t *point)
|
|
{
|
|
CGPathAddLineToPoint ((CGMutablePathRef) closure, NULL,
|
|
_cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y));
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static cairo_status_t
|
|
_cairo_path_to_quartz_path_curve_to (void *closure, cairo_point_t *p0, cairo_point_t *p1, cairo_point_t *p2)
|
|
{
|
|
CGPathAddCurveToPoint ((CGMutablePathRef) closure, NULL,
|
|
_cairo_fixed_to_double(p0->x), _cairo_fixed_to_double(p0->y),
|
|
_cairo_fixed_to_double(p1->x), _cairo_fixed_to_double(p1->y),
|
|
_cairo_fixed_to_double(p2->x), _cairo_fixed_to_double(p2->y));
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static cairo_status_t
|
|
_cairo_path_to_quartz_path_close_path (void *closure)
|
|
{
|
|
CGPathCloseSubpath ((CGMutablePathRef) closure);
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
/* cairo path -> execute in context */
|
|
static cairo_status_t
|
|
_cairo_path_to_quartz_context_move_to (void *closure, cairo_point_t *point)
|
|
{
|
|
//ND((stderr, "moveto: %f %f\n", _cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y)));
|
|
CGContextMoveToPoint ((CGContextRef) closure,
|
|
_cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y));
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static cairo_status_t
|
|
_cairo_path_to_quartz_context_line_to (void *closure, cairo_point_t *point)
|
|
{
|
|
//ND((stderr, "lineto: %f %f\n", _cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y)));
|
|
if (CGContextIsPathEmpty ((CGContextRef) closure))
|
|
CGContextMoveToPoint ((CGContextRef) closure,
|
|
_cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y));
|
|
else
|
|
CGContextAddLineToPoint ((CGContextRef) closure,
|
|
_cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y));
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static cairo_status_t
|
|
_cairo_path_to_quartz_context_curve_to (void *closure, cairo_point_t *p0, cairo_point_t *p1, cairo_point_t *p2)
|
|
{
|
|
//ND( (stderr, "curveto: %f,%f %f,%f %f,%f\n",
|
|
// _cairo_fixed_to_double(p0->x), _cairo_fixed_to_double(p0->y),
|
|
// _cairo_fixed_to_double(p1->x), _cairo_fixed_to_double(p1->y),
|
|
// _cairo_fixed_to_double(p2->x), _cairo_fixed_to_double(p2->y)));
|
|
|
|
CGContextAddCurveToPoint ((CGContextRef) closure,
|
|
_cairo_fixed_to_double(p0->x), _cairo_fixed_to_double(p0->y),
|
|
_cairo_fixed_to_double(p1->x), _cairo_fixed_to_double(p1->y),
|
|
_cairo_fixed_to_double(p2->x), _cairo_fixed_to_double(p2->y));
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static cairo_status_t
|
|
_cairo_path_to_quartz_context_close_path (void *closure)
|
|
{
|
|
//ND((stderr, "closepath\n"));
|
|
CGContextClosePath ((CGContextRef) closure);
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static cairo_status_t
|
|
_cairo_nquartz_cairo_path_to_quartz_path (cairo_path_fixed_t *path,
|
|
CGMutablePathRef cgPath)
|
|
{
|
|
return _cairo_path_fixed_interpret (path,
|
|
CAIRO_DIRECTION_FORWARD,
|
|
_cairo_path_to_quartz_path_move_to,
|
|
_cairo_path_to_quartz_path_line_to,
|
|
_cairo_path_to_quartz_path_curve_to,
|
|
_cairo_path_to_quartz_path_close_path,
|
|
cgPath);
|
|
}
|
|
|
|
static cairo_status_t
|
|
_cairo_nquartz_cairo_path_to_quartz_context (cairo_path_fixed_t *path,
|
|
CGContextRef cgc)
|
|
{
|
|
return _cairo_path_fixed_interpret (path,
|
|
CAIRO_DIRECTION_FORWARD,
|
|
_cairo_path_to_quartz_context_move_to,
|
|
_cairo_path_to_quartz_context_line_to,
|
|
_cairo_path_to_quartz_context_curve_to,
|
|
_cairo_path_to_quartz_context_close_path,
|
|
cgc);
|
|
}
|
|
|
|
/*
|
|
* Misc helpers/callbacks
|
|
*/
|
|
|
|
static PrivateCGCompositeMode
|
|
_cairo_nquartz_cairo_operator_to_quartz (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:
|
|
/* XXX This doesn't match image output */
|
|
return kPrivateCGCompositeSourceIn;
|
|
case CAIRO_OPERATOR_OUT:
|
|
/* XXX This doesn't match image output */
|
|
return kPrivateCGCompositeSourceOut;
|
|
case CAIRO_OPERATOR_ATOP:
|
|
return kPrivateCGCompositeSourceAtop;
|
|
|
|
case CAIRO_OPERATOR_DEST:
|
|
/* XXX this is handled specially (noop)! */
|
|
return kPrivateCGCompositeCopy;
|
|
case CAIRO_OPERATOR_DEST_OVER:
|
|
return kPrivateCGCompositeDestinationOver;
|
|
case CAIRO_OPERATOR_DEST_IN:
|
|
/* XXX This doesn't match image output */
|
|
return kPrivateCGCompositeDestinationIn;
|
|
case CAIRO_OPERATOR_DEST_OUT:
|
|
return kPrivateCGCompositeDestinationOut;
|
|
case CAIRO_OPERATOR_DEST_ATOP:
|
|
/* XXX This doesn't match image output */
|
|
return kPrivateCGCompositeDestinationAtop;
|
|
|
|
case CAIRO_OPERATOR_XOR:
|
|
return kPrivateCGCompositeXOR; /* This will generate strange results */
|
|
case CAIRO_OPERATOR_ADD:
|
|
return kPrivateCGCompositePlusLighter;
|
|
case CAIRO_OPERATOR_SATURATE:
|
|
/* XXX This doesn't match image output for SATURATE; there's no equivalent */
|
|
return kPrivateCGCompositePlusDarker; /* ??? */
|
|
}
|
|
|
|
|
|
return kPrivateCGCompositeCopy;
|
|
}
|
|
|
|
static CGLineCap
|
|
_cairo_nquartz_cairo_line_cap_to_quartz (cairo_line_cap_t ccap)
|
|
{
|
|
switch (ccap) {
|
|
case CAIRO_LINE_CAP_BUTT: return kCGLineCapButt; break;
|
|
case CAIRO_LINE_CAP_ROUND: return kCGLineCapRound; break;
|
|
case CAIRO_LINE_CAP_SQUARE: return kCGLineCapSquare; break;
|
|
}
|
|
|
|
return kCGLineCapButt;
|
|
}
|
|
|
|
static CGLineJoin
|
|
_cairo_nquartz_cairo_line_join_to_quartz (cairo_line_join_t cjoin)
|
|
{
|
|
switch (cjoin) {
|
|
case CAIRO_LINE_JOIN_MITER: return kCGLineJoinMiter; break;
|
|
case CAIRO_LINE_JOIN_ROUND: return kCGLineJoinRound; break;
|
|
case CAIRO_LINE_JOIN_BEVEL: return kCGLineJoinBevel; break;
|
|
}
|
|
|
|
return kCGLineJoinMiter;
|
|
}
|
|
|
|
static void
|
|
_cairo_nquartz_cairo_matrix_to_quartz (const cairo_matrix_t *src,
|
|
CGAffineTransform *dst)
|
|
{
|
|
dst->a = src->xx;
|
|
dst->b = src->xy;
|
|
dst->c = src->yx;
|
|
dst->d = src->yy;
|
|
dst->tx = src->x0;
|
|
dst->ty = src->y0;
|
|
}
|
|
|
|
/**
|
|
** Source -> Quartz setup and finish functions
|
|
**/
|
|
|
|
static void
|
|
ComputeGradientValue (void *info, const float *in, float *out)
|
|
{
|
|
float fdist = *in; /* 0.0 .. 1.0 */
|
|
cairo_fixed_16_16_t fdist_fix = _cairo_fixed_from_double(*in);
|
|
cairo_gradient_pattern_t *grad = (cairo_gradient_pattern_t*) info;
|
|
int i;
|
|
|
|
for (i = 0; i < grad->n_stops; i++) {
|
|
if (grad->stops[i].x > fdist_fix)
|
|
break;
|
|
}
|
|
|
|
if (i == 0 || i == grad->n_stops) {
|
|
if (i == grad->n_stops)
|
|
--i;
|
|
out[0] = grad->stops[i].color.red / 65535.;
|
|
out[1] = grad->stops[i].color.green / 65535.;
|
|
out[2] = grad->stops[i].color.blue / 65535.;
|
|
out[3] = grad->stops[i].color.alpha / 65535.;
|
|
} else {
|
|
float ax = _cairo_fixed_to_double(grad->stops[i-1].x);
|
|
float bx = _cairo_fixed_to_double(grad->stops[i].x) - ax;
|
|
float bp = (fdist - ax)/bx;
|
|
float ap = 1.0 - bp;
|
|
|
|
out[0] =
|
|
(grad->stops[i-1].color.red / 65535.) * ap +
|
|
(grad->stops[i].color.red / 65535.) * bp;
|
|
out[1] =
|
|
(grad->stops[i-1].color.green / 65535.) * ap +
|
|
(grad->stops[i].color.green / 65535.) * bp;
|
|
out[2] =
|
|
(grad->stops[i-1].color.blue / 65535.) * ap +
|
|
(grad->stops[i].color.blue / 65535.) * bp;
|
|
out[3] =
|
|
(grad->stops[i-1].color.alpha / 65535.) * ap +
|
|
(grad->stops[i].color.alpha / 65535.) * bp;
|
|
}
|
|
}
|
|
|
|
static CGFunctionRef
|
|
CreateGradientFunction (cairo_gradient_pattern_t *gpat)
|
|
{
|
|
static const float input_value_range[2] = { 0.f, 1.f };
|
|
static const float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
|
|
static const CGFunctionCallbacks callbacks = {
|
|
0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
|
|
};
|
|
|
|
return CGFunctionCreate (gpat,
|
|
1,
|
|
input_value_range,
|
|
4,
|
|
output_value_ranges,
|
|
&callbacks);
|
|
}
|
|
|
|
static CGShadingRef
|
|
_cairo_nquartz_cairo_gradient_pattern_to_quartz (cairo_pattern_t *abspat)
|
|
{
|
|
cairo_matrix_t mat;
|
|
double x0, y0;
|
|
|
|
if (abspat->type != CAIRO_PATTERN_TYPE_LINEAR &&
|
|
abspat->type != CAIRO_PATTERN_TYPE_RADIAL)
|
|
return NULL;
|
|
|
|
/* We can only do this if we have an identity pattern matrix;
|
|
* otherwise fall back through to the generic pattern case.
|
|
* XXXperf we could optimize this by creating a pattern with the shading;
|
|
* but we'd need to know the extents to do that.
|
|
* ... but we don't care; we can use the surface extents for it
|
|
* XXXtodo - implement gradients with non-identity pattern matrices
|
|
*/
|
|
cairo_pattern_get_matrix (abspat, &mat);
|
|
if (mat.xx != 1.0 || mat.yy != 1.0 || mat.xy != 0.0 || mat.yx != 0.0)
|
|
return NULL;
|
|
|
|
x0 = mat.x0;
|
|
y0 = mat.y0;
|
|
|
|
if (abspat->type == CAIRO_PATTERN_TYPE_LINEAR) {
|
|
cairo_linear_pattern_t *lpat = (cairo_linear_pattern_t*) abspat;
|
|
CGShadingRef shading;
|
|
CGPoint start, end;
|
|
CGFunctionRef gradFunc;
|
|
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
|
|
|
|
start = CGPointMake (_cairo_fixed_to_double (lpat->gradient.p1.x) - x0,
|
|
_cairo_fixed_to_double (lpat->gradient.p1.y) - y0);
|
|
end = CGPointMake (_cairo_fixed_to_double (lpat->gradient.p2.x) - x0,
|
|
_cairo_fixed_to_double (lpat->gradient.p2.y) - y0);
|
|
|
|
cairo_pattern_reference (abspat);
|
|
gradFunc = CreateGradientFunction ((cairo_gradient_pattern_t*) lpat);
|
|
shading = CGShadingCreateAxial (rgb,
|
|
start, end,
|
|
gradFunc,
|
|
true, true);
|
|
CGColorSpaceRelease(rgb);
|
|
CGFunctionRelease(gradFunc);
|
|
|
|
return shading;
|
|
}
|
|
|
|
if (abspat->type == CAIRO_PATTERN_TYPE_RADIAL) {
|
|
cairo_radial_pattern_t *rpat = (cairo_radial_pattern_t*) abspat;
|
|
CGShadingRef shading;
|
|
CGPoint start, end;
|
|
CGFunctionRef gradFunc;
|
|
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
|
|
|
|
start = CGPointMake (_cairo_fixed_to_double (rpat->gradient.inner.x) - x0,
|
|
_cairo_fixed_to_double (rpat->gradient.inner.y) - y0);
|
|
end = CGPointMake (_cairo_fixed_to_double (rpat->gradient.outer.x) - x0,
|
|
_cairo_fixed_to_double (rpat->gradient.outer.y) - y0);
|
|
|
|
cairo_pattern_reference (abspat);
|
|
gradFunc = CreateGradientFunction ((cairo_gradient_pattern_t*) rpat);
|
|
shading = CGShadingCreateRadial (rgb,
|
|
start,
|
|
_cairo_fixed_to_double (rpat->gradient.inner.radius),
|
|
end,
|
|
_cairo_fixed_to_double (rpat->gradient.outer.radius),
|
|
gradFunc,
|
|
true, true);
|
|
CGColorSpaceRelease(rgb);
|
|
CGFunctionRelease(gradFunc);
|
|
|
|
return shading;
|
|
}
|
|
|
|
/* Shouldn't be reached */
|
|
ASSERT_NOT_REACHED;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Generic cairo_pattern -> CGPattern function */
|
|
static void
|
|
SurfacePatternDrawFunc (void *info, CGContextRef context)
|
|
{
|
|
cairo_surface_pattern_t *spat = (cairo_surface_pattern_t *) info;
|
|
cairo_surface_t *pat_surf = spat->surface;
|
|
cairo_rectangle_int16_t extents;
|
|
cairo_status_t status;
|
|
|
|
cairo_nquartz_surface_t *quartz_surf = NULL;
|
|
|
|
cairo_bool_t flip = FALSE;
|
|
|
|
CGImageRef img;
|
|
|
|
if (!cairo_surface_is_nquartz (pat_surf)) {
|
|
ND((stderr, "-- source is not nquartz surface\n"));
|
|
/* This sucks; we should really store a dummy nquartz surface
|
|
* for passing in here
|
|
* XXXtodo store a dummy nquartz surface somewhere for handing off to clone_similar
|
|
* XXXtodo/perf don't use clone if the source surface is an image surface! Instead,
|
|
* just create the CGImage directly!
|
|
*/
|
|
|
|
cairo_surface_t *dummy = cairo_nquartz_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
|
|
|
|
cairo_rectangle_int16_t rect;
|
|
_cairo_surface_get_extents (pat_surf, &rect);
|
|
|
|
cairo_surface_t *new_surf = NULL;
|
|
|
|
_cairo_surface_clone_similar (dummy, pat_surf, rect.x, rect.y,
|
|
rect.width, rect.height, &new_surf);
|
|
|
|
cairo_surface_destroy(dummy);
|
|
|
|
quartz_surf = (cairo_nquartz_surface_t *) new_surf;
|
|
} else {
|
|
ND((stderr, "-- source is nquartz surface\n"));
|
|
/* If it's a nquartz surface, we can try to see if it's a CGBitmapContext;
|
|
* we do this when we call CGBitmapContextCreateImage below.
|
|
*/
|
|
cairo_surface_reference (pat_surf);
|
|
quartz_surf = (cairo_nquartz_surface_t*) pat_surf;
|
|
|
|
/* XXXtodo WHY does this need to be flipped? Writing this stuff
|
|
* to disk shows that in both this path and the path above the source image
|
|
* has an identical orientation, and the destination context at all times has a Y
|
|
* flip. So why do we need to flip in this case?
|
|
*/
|
|
flip = TRUE;
|
|
}
|
|
|
|
/* this is a 10.4 API, present in 10.3.9 */
|
|
CGContextFlush (quartz_surf->cgContext);
|
|
img = CGBitmapContextCreateImage (quartz_surf->cgContext);
|
|
|
|
if (!img) {
|
|
// ... give up.
|
|
ND((stderr, "CGBitmapContextCreateImage failed\n"));
|
|
cairo_surface_destroy ((cairo_surface_t*)quartz_surf);
|
|
return;
|
|
}
|
|
|
|
if (flip) {
|
|
CGContextTranslateCTM (context, 0, CGImageGetHeight(img));
|
|
CGContextScaleCTM (context, 1, -1);
|
|
}
|
|
|
|
CGRect imageBounds;
|
|
imageBounds.size = CGSizeMake (CGImageGetWidth(img), CGImageGetHeight(img));
|
|
imageBounds.origin.x = 0;
|
|
imageBounds.origin.y = 0;
|
|
|
|
CGContextDrawImage (context, imageBounds, img);
|
|
|
|
CGImageRelease (img);
|
|
|
|
cairo_surface_destroy ((cairo_surface_t*) quartz_surf);
|
|
}
|
|
|
|
static CGPatternRef
|
|
_cairo_nquartz_cairo_repeating_surface_pattern_to_quartz (cairo_nquartz_surface_t *dest,
|
|
cairo_pattern_t *abspat)
|
|
{
|
|
cairo_surface_pattern_t *spat;
|
|
cairo_surface_t *pat_surf;
|
|
cairo_rectangle_int16_t extents;
|
|
|
|
CGRect pbounds;
|
|
CGAffineTransform ptransform, stransform;
|
|
CGPatternCallbacks cb = { 0,
|
|
SurfacePatternDrawFunc,
|
|
(CGFunctionReleaseInfoCallback) cairo_pattern_destroy };
|
|
CGPatternRef cgpat;
|
|
float rw, rh;
|
|
|
|
/* SURFACE is the only type we'll handle here */
|
|
if (abspat->type != CAIRO_PATTERN_TYPE_SURFACE)
|
|
return NULL;
|
|
|
|
spat = (cairo_surface_pattern_t *) abspat;
|
|
pat_surf = spat->surface;
|
|
|
|
_cairo_surface_get_extents (pat_surf, &extents);
|
|
pbounds.origin.x = 0;
|
|
pbounds.origin.y = 0;
|
|
pbounds.size.width = extents.width;
|
|
pbounds.size.height = extents.height;
|
|
|
|
cairo_matrix_t m = spat->base.matrix;
|
|
cairo_matrix_invert(&m);
|
|
_cairo_nquartz_cairo_matrix_to_quartz (&m, &stransform);
|
|
|
|
/* 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);
|
|
|
|
#ifdef NQUARTZ_DEBUG
|
|
ND((stderr, " pbounds: %f %f %f %f\n", pbounds.origin.x, pbounds.origin.y, pbounds.size.width, pbounds.size.height));
|
|
ND((stderr, " pattern xform: t: %f %f xx: %f xy: %f yx: %f yy: %f\n", ptransform.tx, ptransform.ty, ptransform.a, ptransform.b, ptransform.c, ptransform.d));
|
|
CGAffineTransform xform = CGContextGetCTM(dest->cgContext);
|
|
ND((stderr, " context xform: t: %f %f xx: %f xy: %f yx: %f yy: %f\n", xform.tx, xform.ty, xform.a, xform.b, xform.c, xform.d));
|
|
#endif
|
|
|
|
// kjs seems to indicate this should work (setting to 0,0 to avoid
|
|
// tiling); however, the pattern CTM scaling ends up being NaN in
|
|
// the pattern draw function if either rw or rh are 0.
|
|
// XXXtodo get pattern drawing working with extend options
|
|
// XXXtodo/perf optimize CAIRO_EXTEND_NONE to a single DrawImage instead of a pattern
|
|
#if 0
|
|
if (spat->base.extend == CAIRO_EXTEND_NONE) {
|
|
/* XXX wasteful; this will keep drawing the pattern in the
|
|
* original location. We need to set up the clip region
|
|
* instead to do this right.
|
|
*/
|
|
rw = 0;
|
|
rh = 0;
|
|
} else if (spat->base.extend == CAIRO_EXTEND_REPEAT) {
|
|
rw = extents.width;
|
|
rh = extents.height;
|
|
} else if (spat->base.extend == CAIRO_EXTEND_REFLECT) {
|
|
/* XXX broken; need to emulate by reflecting the image into 4 quadrants
|
|
* and then tiling that
|
|
*/
|
|
rw = extents.width;
|
|
rh = extents.height;
|
|
} else {
|
|
/* CAIRO_EXTEND_PAD */
|
|
/* XXX broken. */
|
|
rw = 0;
|
|
rh = 0;
|
|
}
|
|
#else
|
|
rw = extents.width;
|
|
rh = extents.height;
|
|
#endif
|
|
|
|
cairo_pattern_reference (abspat);
|
|
cgpat = CGPatternCreate (abspat,
|
|
pbounds,
|
|
ptransform,
|
|
rw, rh,
|
|
kCGPatternTilingConstantSpacing, /* kCGPatternTilingNoDistortion, */
|
|
TRUE,
|
|
&cb);
|
|
return cgpat;
|
|
}
|
|
|
|
typedef enum {
|
|
DO_SOLID,
|
|
DO_SHADING,
|
|
DO_PATTERN,
|
|
DO_UNSUPPORTED
|
|
} cairo_nquartz_action_t;
|
|
|
|
static cairo_nquartz_action_t
|
|
_cairo_nquartz_setup_source (cairo_nquartz_surface_t *surface,
|
|
cairo_pattern_t *source)
|
|
{
|
|
assert (!(surface->sourceImage || surface->sourceShading || surface->sourcePattern));
|
|
|
|
if (source->type == CAIRO_PATTERN_TYPE_SOLID) {
|
|
cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) source;
|
|
|
|
CGContextSetRGBStrokeColor (surface->cgContext,
|
|
solid->color.red,
|
|
solid->color.green,
|
|
solid->color.blue,
|
|
solid->color.alpha);
|
|
CGContextSetRGBFillColor (surface->cgContext,
|
|
solid->color.red,
|
|
solid->color.green,
|
|
solid->color.blue,
|
|
solid->color.alpha);
|
|
|
|
return DO_SOLID;
|
|
} else if (source->type == CAIRO_PATTERN_TYPE_LINEAR ||
|
|
source->type == CAIRO_PATTERN_TYPE_RADIAL)
|
|
{
|
|
CGShadingRef shading = _cairo_nquartz_cairo_gradient_pattern_to_quartz (source);
|
|
if (!shading)
|
|
return CAIRO_INT_STATUS_UNSUPPORTED;
|
|
|
|
surface->sourceShading = shading;
|
|
|
|
return DO_SHADING;
|
|
} else if (source->type == CAIRO_PATTERN_TYPE_SURFACE) {
|
|
CGPatternRef pattern = _cairo_nquartz_cairo_repeating_surface_pattern_to_quartz (surface, source);
|
|
if (!pattern)
|
|
return CAIRO_INT_STATUS_UNSUPPORTED;
|
|
|
|
float patternAlpha = 1.0f;
|
|
|
|
// 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)
|
|
CGContextSaveGState(surface->cgContext);
|
|
|
|
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
|
|
CGContextSetFillColorSpace (surface->cgContext, patternSpace);
|
|
CGContextSetFillPattern (surface->cgContext, pattern, &patternAlpha);
|
|
CGColorSpaceRelease (patternSpace);
|
|
|
|
/* 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 (surface->cgContext, CGSizeMake(0,0));
|
|
|
|
surface->sourcePattern = pattern;
|
|
|
|
return DO_PATTERN;
|
|
} else {
|
|
return DO_UNSUPPORTED;
|
|
}
|
|
|
|
ASSERT_NOT_REACHED;
|
|
}
|
|
|
|
static void
|
|
_cairo_nquartz_teardown_source (cairo_nquartz_surface_t *surface,
|
|
cairo_pattern_t *source)
|
|
{
|
|
if (surface->sourceImage) {
|
|
// nothing to do; we don't use sourceImage yet
|
|
}
|
|
|
|
if (surface->sourceShading) {
|
|
CGShadingRelease(surface->sourceShading);
|
|
surface->sourceShading = NULL;
|
|
}
|
|
|
|
if (surface->sourcePattern) {
|
|
CGPatternRelease(surface->sourcePattern);
|
|
// To tear down the pattern and colorspace
|
|
CGContextRestoreGState(surface->cgContext);
|
|
|
|
surface->sourcePattern = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
** get source/dest image implementation
|
|
**/
|
|
|
|
static void
|
|
ImageDataReleaseFunc(void *info, const void *data, size_t size)
|
|
{
|
|
if (data != NULL) {
|
|
free((void *) data);
|
|
}
|
|
}
|
|
|
|
/* Read the image from the surface's front buffer */
|
|
static cairo_int_status_t
|
|
_cairo_nquartz_get_image (cairo_nquartz_surface_t *surface,
|
|
cairo_image_surface_t **image_out,
|
|
unsigned char **data_out)
|
|
{
|
|
unsigned char *imageData;
|
|
cairo_image_surface_t *isurf;
|
|
|
|
/* If we weren't constructed with an AGL Context
|
|
* or a CCGBitmapContext, then we have no way
|
|
* of doing this
|
|
*/
|
|
#ifdef CAIRO_NQUARTZ_SUPPORT_AGL
|
|
if (surface->aglContext) {
|
|
AGLContext oldContext;
|
|
cairo_format_masks_t masks = { 32, 0xff << 24, 0xff << 16, 0xff << 8, 0xff << 0 };
|
|
unsigned int i;
|
|
|
|
oldContext = aglGetCurrentContext();
|
|
if (oldContext != surface->aglContext)
|
|
aglSetCurrentContext(surface->aglContext);
|
|
|
|
imageData = (unsigned char *) malloc (surface->extents.width * surface->extents.height * 4);
|
|
if (!imageData)
|
|
return CAIRO_STATUS_NO_MEMORY;
|
|
|
|
glReadBuffer (GL_FRONT);
|
|
glReadPixels (0, 0, surface->extents.width, surface->extents.height,
|
|
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
|
|
imageData);
|
|
|
|
/* swap lines */
|
|
/* XXX find some fast code to do this */
|
|
unsigned int lineSize = surface->extents.width * 4;
|
|
unsigned char *tmpLine = malloc(lineSize);
|
|
for (i = 0; i < surface->extents.height / 2; i++) {
|
|
unsigned char *l0 = imageData + lineSize * i;
|
|
unsigned char *l1 = imageData + (lineSize * (surface->extents.height - i - 1));
|
|
memcpy (tmpLine, l0, lineSize);
|
|
memcpy (l0, l1, lineSize);
|
|
memcpy (l1, tmpLine, lineSize);
|
|
}
|
|
free (tmpLine);
|
|
|
|
if (oldContext && oldContext != surface->aglContext)
|
|
aglSetCurrentContext(oldContext);
|
|
|
|
isurf = (cairo_image_surface_t *)_cairo_image_surface_create_with_masks
|
|
(imageData,
|
|
&masks,
|
|
surface->extents.width,
|
|
surface->extents.height,
|
|
surface->extents.width * 4);
|
|
|
|
if (data_out)
|
|
*data_out = imageData;
|
|
else
|
|
_cairo_image_surface_assume_ownership_of_data (isurf);
|
|
#else
|
|
/* no AGL */
|
|
if (0) {
|
|
#endif
|
|
} else if (CGBitmapContextGetBitsPerPixel(surface->cgContext) != 0) {
|
|
unsigned int stride;
|
|
unsigned int bitinfo;
|
|
unsigned int bpc, bpp;
|
|
CGColorSpaceRef colorspace;
|
|
unsigned int color_comps;
|
|
|
|
imageData = (unsigned char *) CGBitmapContextGetData(surface->cgContext);
|
|
#ifdef USE_10_3_WORKAROUNDS
|
|
bitinfo = CGBitmapContextGetAlphaInfo (surface->cgContext);
|
|
#else
|
|
bitinfo = CGBitmapContextGetBitmapInfo (surface->cgContext);
|
|
#endif
|
|
stride = CGBitmapContextGetBytesPerRow (surface->cgContext);
|
|
bpp = CGBitmapContextGetBitsPerPixel (surface->cgContext);
|
|
bpc = CGBitmapContextGetBitsPerComponent (surface->cgContext);
|
|
|
|
// 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 (bpp == 32 && color_comps == 3 &&
|
|
(bitinfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaPremultipliedFirst &&
|
|
(bitinfo & kCGBitmapByteOrderMask) == kCGBitmapByteOrder32Host)
|
|
{
|
|
isurf = (cairo_image_surface_t *)
|
|
cairo_image_surface_create_for_data (imageData,
|
|
CAIRO_FORMAT_ARGB32,
|
|
surface->extents.width,
|
|
surface->extents.height,
|
|
stride);
|
|
} else if (bpp == 32 && color_comps == 3 &&
|
|
(bitinfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaNoneSkipFirst &&
|
|
(bitinfo & kCGBitmapByteOrderMask) == kCGBitmapByteOrder32Host)
|
|
{
|
|
isurf = (cairo_image_surface_t *)
|
|
cairo_image_surface_create_for_data (imageData,
|
|
CAIRO_FORMAT_RGB24,
|
|
surface->extents.width,
|
|
surface->extents.height,
|
|
stride);
|
|
} else if (bpp == 8 && color_comps == 1)
|
|
{
|
|
isurf = (cairo_image_surface_t *)
|
|
cairo_image_surface_create_for_data (imageData,
|
|
CAIRO_FORMAT_A8,
|
|
surface->extents.width,
|
|
surface->extents.height,
|
|
stride);
|
|
} else {
|
|
return CAIRO_INT_STATUS_UNSUPPORTED;
|
|
}
|
|
} else {
|
|
return CAIRO_INT_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
*image_out = isurf;
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
** Cairo surface backend implementations
|
|
**/
|
|
|
|
static cairo_status_t
|
|
_cairo_nquartz_surface_finish (void *abstract_surface)
|
|
{
|
|
cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface;
|
|
|
|
ND((stderr, "_cairo_nquartz_surface_finish[%p] cgc: %p\n", surface, surface->cgContext));
|
|
|
|
#ifdef CAIRO_NQUARTZ_SUPPORT_AGL
|
|
if (surface->aglContext)
|
|
aglSetCurrentContext(surface->aglContext);
|
|
#endif
|
|
|
|
CGContextFlush (surface->cgContext);
|
|
|
|
/* Restore our saved gstate that we use to reset clipping */
|
|
CGContextRestoreGState (surface->cgContext);
|
|
|
|
CGContextRelease (surface->cgContext);
|
|
|
|
surface->cgContext = NULL;
|
|
|
|
#ifdef CAIRO_NQUARTZ_SUPPORT_AGL
|
|
if (surface->aglContext)
|
|
glFlush();
|
|
|
|
surface->aglContext = NULL;
|
|
#endif
|
|
|
|
if (surface->imageData) {
|
|
free (surface->imageData);
|
|
surface->imageData = NULL;
|
|
}
|
|
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static cairo_status_t
|
|
_cairo_nquartz_surface_acquire_source_image (void *abstract_surface,
|
|
cairo_image_surface_t **image_out,
|
|
void **image_extra)
|
|
{
|
|
cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface;
|
|
|
|
//ND((stderr, "%p _cairo_nquartz_surface_acquire_source_image\n", surface));
|
|
|
|
*image_extra = NULL;
|
|
|
|
return _cairo_nquartz_get_image (surface, image_out, NULL);
|
|
}
|
|
|
|
static cairo_status_t
|
|
_cairo_nquartz_surface_acquire_dest_image (void *abstract_surface,
|
|
cairo_rectangle_int16_t *interest_rect,
|
|
cairo_image_surface_t **image_out,
|
|
cairo_rectangle_int16_t *image_rect,
|
|
void **image_extra)
|
|
{
|
|
cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface;
|
|
cairo_int_status_t status;
|
|
unsigned char *data;
|
|
|
|
ND((stderr, "%p _cairo_nquartz_surface_acquire_dest_image\n", surface));
|
|
|
|
*image_rect = surface->extents;
|
|
|
|
status = _cairo_nquartz_get_image (surface, image_out, &data);
|
|
if (status)
|
|
return status;
|
|
|
|
*image_extra = data;
|
|
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
_cairo_nquartz_surface_release_dest_image (void *abstract_surface,
|
|
cairo_rectangle_int16_t *interest_rect,
|
|
cairo_image_surface_t *image,
|
|
cairo_rectangle_int16_t *image_rect,
|
|
void *image_extra)
|
|
{
|
|
cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface;
|
|
unsigned char *imageData = (unsigned char *) image_extra;
|
|
|
|
//ND((stderr, "%p _cairo_nquartz_surface_release_dest_image\n", surface));
|
|
|
|
if (!CGBitmapContextGetData (surface->cgContext)) {
|
|
CGDataProviderRef dataProvider;
|
|
CGImageRef img;
|
|
|
|
dataProvider = CGDataProviderCreateWithData (NULL, imageData,
|
|
surface->extents.width * surface->extents.height * 4,
|
|
ImageDataReleaseFunc);
|
|
|
|
img = CGImageCreate (surface->extents.width, surface->extents.height,
|
|
8, 32,
|
|
surface->extents.width * 4,
|
|
CGColorSpaceCreateDeviceRGB(),
|
|
kCGImageAlphaPremultipliedFirst,
|
|
dataProvider,
|
|
NULL,
|
|
false,
|
|
kCGRenderingIntentDefault);
|
|
|
|
CGContextSetCompositeOperation (surface->cgContext, kPrivateCGCompositeCopy);
|
|
|
|
CGContextDrawImage (surface->cgContext,
|
|
CGRectMake (0, 0, surface->extents.width, surface->extents.height),
|
|
img);
|
|
|
|
CGImageRelease (img);
|
|
CGDataProviderRelease (dataProvider);
|
|
|
|
ND((stderr, "Image for surface %p was recovered from a bitmap\n", surface));
|
|
}
|
|
|
|
cairo_surface_destroy ((cairo_surface_t *) image);
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
_cairo_nquartz_surface_create_similar (void *abstract_surface,
|
|
cairo_content_t content,
|
|
int width,
|
|
int height)
|
|
{
|
|
cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface;
|
|
|
|
cairo_format_t format;
|
|
|
|
if (content == CAIRO_CONTENT_COLOR_ALPHA)
|
|
format = CAIRO_FORMAT_ARGB32;
|
|
else if (content == CAIRO_CONTENT_COLOR)
|
|
format = CAIRO_FORMAT_RGB24;
|
|
else if (content == CAIRO_CONTENT_ALPHA)
|
|
format = CAIRO_FORMAT_A8;
|
|
else
|
|
return NULL;
|
|
|
|
return cairo_nquartz_surface_create (format, width, height);
|
|
}
|
|
|
|
static cairo_status_t
|
|
_cairo_nquartz_surface_clone_similar (void *abstract_surface,
|
|
cairo_surface_t *src,
|
|
int src_x,
|
|
int src_y,
|
|
int width,
|
|
int height,
|
|
cairo_surface_t **clone_out)
|
|
{
|
|
cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface;
|
|
cairo_nquartz_surface_t *new_surface = NULL;
|
|
cairo_format_t new_format;
|
|
|
|
CGImageRef quartz_image = NULL;
|
|
cairo_surface_t *surface_to_release = NULL;
|
|
|
|
if (cairo_surface_is_nquartz (src)) {
|
|
cairo_nquartz_surface_t *qsurf = (cairo_nquartz_surface_t *) src;
|
|
quartz_image = CGBitmapContextCreateImage (qsurf->cgContext);
|
|
new_format = CAIRO_FORMAT_ARGB32; /* XXX bogus; recover a real format from the image */
|
|
} else if (_cairo_surface_is_image (src)) {
|
|
cairo_image_surface_t *isurf = (cairo_image_surface_t *) src;
|
|
CGDataProviderRef dataProvider;
|
|
CGColorSpaceRef cgColorspace;
|
|
CGBitmapInfo bitinfo;
|
|
int bitsPerComponent, bitsPerPixel;
|
|
|
|
if (isurf->format == CAIRO_FORMAT_ARGB32) {
|
|
cgColorspace = CGColorSpaceCreateDeviceRGB();
|
|
bitinfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
|
|
bitsPerComponent = 8;
|
|
bitsPerPixel = 32;
|
|
} else if (isurf->format == CAIRO_FORMAT_RGB24) {
|
|
cgColorspace = CGColorSpaceCreateDeviceRGB();
|
|
bitinfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
|
|
bitsPerComponent = 8;
|
|
bitsPerPixel = 32;
|
|
} else if (isurf->format == CAIRO_FORMAT_A8) {
|
|
cgColorspace = CGColorSpaceCreateDeviceGray();
|
|
bitinfo = kCGImageAlphaNone;
|
|
bitsPerComponent = 8;
|
|
bitsPerPixel = 8;
|
|
} else {
|
|
/* SUPPORT A1, maybe */
|
|
return CAIRO_INT_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
new_format = isurf->format;
|
|
|
|
dataProvider = CGDataProviderCreateWithData (NULL,
|
|
isurf->data,
|
|
isurf->height * isurf->stride,
|
|
NULL);
|
|
|
|
quartz_image = CGImageCreate (isurf->width, isurf->height,
|
|
bitsPerComponent,
|
|
bitsPerPixel,
|
|
isurf->stride,
|
|
cgColorspace,
|
|
bitinfo,
|
|
dataProvider,
|
|
NULL,
|
|
false,
|
|
kCGRenderingIntentDefault);
|
|
CGDataProviderRelease (dataProvider);
|
|
CGColorSpaceRelease (cgColorspace);
|
|
} else {
|
|
return CAIRO_INT_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
if (!quartz_image)
|
|
return CAIRO_INT_STATUS_UNSUPPORTED;
|
|
|
|
new_surface = (cairo_nquartz_surface_t *)
|
|
cairo_nquartz_surface_create (new_format,
|
|
CGImageGetWidth (quartz_image),
|
|
CGImageGetHeight (quartz_image));
|
|
if (!new_surface || new_surface->base.status)
|
|
return CAIRO_INT_STATUS_UNSUPPORTED;
|
|
|
|
CGContextSetCompositeOperation (new_surface->cgContext,
|
|
kPrivateCGCompositeCopy);
|
|
|
|
nquartz_image_to_png (quartz_image, NULL);
|
|
|
|
CGContextDrawImage (new_surface->cgContext,
|
|
CGRectMake (src_x, src_y, width, height),
|
|
quartz_image);
|
|
CGImageRelease (quartz_image);
|
|
|
|
*clone_out = (cairo_surface_t*) new_surface;
|
|
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static cairo_int_status_t
|
|
_cairo_nquartz_surface_get_extents (void *abstract_surface,
|
|
cairo_rectangle_int16_t *extents)
|
|
{
|
|
cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface;
|
|
|
|
*extents = surface->extents;
|
|
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static cairo_int_status_t
|
|
_cairo_nquartz_surface_paint (void *abstract_surface,
|
|
cairo_operator_t op,
|
|
cairo_pattern_t *source)
|
|
{
|
|
cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface;
|
|
cairo_int_status_t rv = CAIRO_STATUS_SUCCESS;
|
|
cairo_nquartz_action_t action;
|
|
|
|
ND((stderr, "%p _cairo_nquartz_surface_paint op %d source->type %d\n", surface, op, source->type));
|
|
|
|
if (op == CAIRO_OPERATOR_DEST)
|
|
return CAIRO_STATUS_SUCCESS;
|
|
|
|
CGContextSetCompositeOperation (surface->cgContext, _cairo_nquartz_cairo_operator_to_quartz (op));
|
|
|
|
CGRect bounds = CGContextGetClipBoundingBox (surface->cgContext);
|
|
|
|
action = _cairo_nquartz_setup_source (surface, source);
|
|
|
|
if (action == DO_SOLID || action == DO_PATTERN) {
|
|
CGContextFillRect (surface->cgContext, CGRectMake(surface->extents.x,
|
|
surface->extents.y,
|
|
surface->extents.width,
|
|
surface->extents.height));
|
|
} else if (action == DO_SHADING) {
|
|
CGContextDrawShading (surface->cgContext, surface->sourceShading);
|
|
} else {
|
|
rv = CAIRO_INT_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
_cairo_nquartz_teardown_source (surface, source);
|
|
|
|
ND((stderr, "-- paint\n"));
|
|
return rv;
|
|
}
|
|
|
|
static cairo_int_status_t
|
|
_cairo_nquartz_surface_fill (void *abstract_surface,
|
|
cairo_operator_t op,
|
|
cairo_pattern_t *source,
|
|
cairo_path_fixed_t *path,
|
|
cairo_fill_rule_t fill_rule,
|
|
double tolerance,
|
|
cairo_antialias_t antialias)
|
|
{
|
|
cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface;
|
|
cairo_int_status_t rv = CAIRO_STATUS_SUCCESS;
|
|
cairo_nquartz_action_t action;
|
|
|
|
ND((stderr, "%p _cairo_nquartz_surface_fill op %d source->type %d\n", surface, op, source->type));
|
|
|
|
if (op == CAIRO_OPERATOR_DEST)
|
|
return CAIRO_STATUS_SUCCESS;
|
|
|
|
CGContextSaveGState (surface->cgContext);
|
|
|
|
CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE));
|
|
CGContextSetCompositeOperation (surface->cgContext, _cairo_nquartz_cairo_operator_to_quartz (op));
|
|
|
|
action = _cairo_nquartz_setup_source (surface, source);
|
|
if (action == DO_UNSUPPORTED) {
|
|
CGContextRestoreGState (surface->cgContext);
|
|
return CAIRO_INT_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
CGContextBeginPath (surface->cgContext);
|
|
_cairo_nquartz_cairo_path_to_quartz_context (path, surface->cgContext);
|
|
|
|
if (action == DO_SOLID || action == DO_PATTERN) {
|
|
if (fill_rule == CAIRO_FILL_RULE_WINDING)
|
|
CGContextFillPath (surface->cgContext);
|
|
else
|
|
CGContextEOFillPath (surface->cgContext);
|
|
} else if (action == DO_SHADING) {
|
|
|
|
// we have to clip and then paint the shading; we can't fill
|
|
// with the shading
|
|
if (fill_rule == CAIRO_FILL_RULE_WINDING)
|
|
CGContextClip (surface->cgContext);
|
|
else
|
|
CGContextEOClip (surface->cgContext);
|
|
|
|
CGContextDrawShading (surface->cgContext, surface->sourceShading);
|
|
} else {
|
|
rv = CAIRO_INT_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
_cairo_nquartz_teardown_source (surface, source);
|
|
|
|
CGContextRestoreGState (surface->cgContext);
|
|
|
|
ND((stderr, "-- fill\n"));
|
|
return rv;
|
|
}
|
|
|
|
static cairo_int_status_t
|
|
_cairo_nquartz_surface_stroke (void *abstract_surface,
|
|
cairo_operator_t op,
|
|
cairo_pattern_t *source,
|
|
cairo_path_fixed_t *path,
|
|
cairo_stroke_style_t *style,
|
|
cairo_matrix_t *ctm,
|
|
cairo_matrix_t *ctm_inverse,
|
|
double tolerance,
|
|
cairo_antialias_t antialias)
|
|
{
|
|
cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface;
|
|
cairo_int_status_t rv = CAIRO_STATUS_SUCCESS;
|
|
cairo_nquartz_action_t action;
|
|
|
|
ND((stderr, "%p _cairo_nquartz_surface_stroke op %d source->type %d\n", surface, op, source->type));
|
|
|
|
if (op == CAIRO_OPERATOR_DEST)
|
|
return CAIRO_STATUS_SUCCESS;
|
|
|
|
CGContextSaveGState (surface->cgContext);
|
|
|
|
CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE));
|
|
CGContextSetLineWidth (surface->cgContext, style->line_width);
|
|
CGContextSetLineCap (surface->cgContext, _cairo_nquartz_cairo_line_cap_to_quartz (style->line_cap));
|
|
CGContextSetLineJoin (surface->cgContext, _cairo_nquartz_cairo_line_join_to_quartz (style->line_join));
|
|
CGContextSetMiterLimit (surface->cgContext, style->miter_limit);
|
|
|
|
if (style->dash && style->num_dashes) {
|
|
#define STATIC_DASH 32
|
|
float sdash[STATIC_DASH];
|
|
float *fdash = sdash;
|
|
int k;
|
|
if (style->num_dashes > STATIC_DASH)
|
|
fdash = malloc (sizeof(float)*style->num_dashes);
|
|
|
|
for (k = 0; k < style->num_dashes; k++)
|
|
fdash[k] = (float) style->dash[k];
|
|
|
|
CGContextSetLineDash (surface->cgContext, style->dash_offset, fdash, style->num_dashes);
|
|
|
|
if (fdash != sdash)
|
|
free (fdash);
|
|
}
|
|
|
|
CGContextSetCompositeOperation (surface->cgContext, _cairo_nquartz_cairo_operator_to_quartz (op));
|
|
|
|
action = _cairo_nquartz_setup_source (surface, source);
|
|
if (action == DO_UNSUPPORTED) {
|
|
CGContextRestoreGState (surface->cgContext);
|
|
return CAIRO_INT_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
CGContextBeginPath (surface->cgContext);
|
|
_cairo_nquartz_cairo_path_to_quartz_context (path, surface->cgContext);
|
|
|
|
if (action == DO_SOLID || action == DO_PATTERN) {
|
|
CGContextStrokePath (surface->cgContext);
|
|
} else if (action == DO_SHADING) {
|
|
// we have to clip and then paint the shading; first we have to convert
|
|
// the stroke to a path that we can fill
|
|
CGContextReplacePathWithStrokedPath (surface->cgContext);
|
|
CGContextClip (surface->cgContext);
|
|
|
|
CGContextDrawShading (surface->cgContext, surface->sourceShading);
|
|
} else {
|
|
rv = CAIRO_INT_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
CGContextRestoreGState (surface->cgContext);
|
|
|
|
ND((stderr, "-- stroke\n"));
|
|
return rv;
|
|
}
|
|
|
|
static cairo_int_status_t
|
|
_cairo_nquartz_surface_show_glyphs (void *abstract_surface,
|
|
cairo_operator_t op,
|
|
cairo_pattern_t *source,
|
|
const cairo_glyph_t *glyphs,
|
|
int num_glyphs,
|
|
cairo_scaled_font_t *scaled_font)
|
|
{
|
|
cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface;
|
|
cairo_int_status_t rv = CAIRO_STATUS_SUCCESS;
|
|
cairo_nquartz_action_t action;
|
|
int i;
|
|
|
|
if (num_glyphs <= 0)
|
|
return CAIRO_STATUS_SUCCESS;
|
|
|
|
if (op == CAIRO_OPERATOR_DEST)
|
|
return CAIRO_STATUS_SUCCESS;
|
|
|
|
if (!_cairo_scaled_font_is_atsui (scaled_font))
|
|
return CAIRO_INT_STATUS_UNSUPPORTED;
|
|
|
|
CGContextSaveGState (surface->cgContext);
|
|
|
|
action = _cairo_nquartz_setup_source (surface, source);
|
|
if (action == DO_SOLID || action == DO_PATTERN) {
|
|
CGContextSetTextDrawingMode (surface->cgContext, kCGTextFill);
|
|
} else if (action == DO_SHADING) {
|
|
CGContextSetTextDrawingMode (surface->cgContext, kCGTextClip);
|
|
} else {
|
|
/* Unsupported */
|
|
CGContextRestoreGState (surface->cgContext);
|
|
return CAIRO_INT_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
double y_height = surface->extents.height;
|
|
|
|
CGContextSetCompositeOperation (surface->cgContext, _cairo_nquartz_cairo_operator_to_quartz (op));
|
|
|
|
ATSUFontID fid = _cairo_atsui_scaled_font_get_atsu_font_id (scaled_font);
|
|
ATSFontRef atsfref = FMGetATSFontRefFromFont (fid);
|
|
CGFontRef cgfref = CGFontCreateWithPlatformFont (&atsfref);
|
|
|
|
CGContextSetFont (surface->cgContext, cgfref);
|
|
CGFontRelease (cgfref);
|
|
|
|
/* So this should include the size; I don't know if I need to extract the
|
|
* size from this and call CGContextSetFontSize.. will I get crappy hinting
|
|
* with this 1.0 size business? Or will CG just multiply that size into the
|
|
* text matrix?
|
|
*/
|
|
//ND((stderr, "show_glyphs: glyph 0 at: %f, %f\n", glyphs[0].x, glyphs[0].y));
|
|
CGAffineTransform cairoTextTransform, textTransform;
|
|
_cairo_nquartz_cairo_matrix_to_quartz (&scaled_font->font_matrix, &cairoTextTransform);
|
|
|
|
textTransform = CGAffineTransformMakeTranslation (glyphs[0].x, glyphs[0].y);
|
|
textTransform = CGAffineTransformScale (textTransform, 1.0, -1.0);
|
|
textTransform = CGAffineTransformConcat (cairoTextTransform, textTransform);
|
|
|
|
CGContextSetTextMatrix (surface->cgContext, textTransform);
|
|
CGContextSetFontSize (surface->cgContext, 1.0);
|
|
|
|
// XXXtodo/perf: stack storage for glyphs/sizes
|
|
CGGlyph *cg_glyphs = (CGGlyph*) malloc(sizeof(CGGlyph) * num_glyphs);
|
|
CGSize *cg_advances = (CGSize*) malloc(sizeof(CGSize) * num_glyphs);
|
|
|
|
double xprev = glyphs[0].x;
|
|
double yprev = glyphs[0].y;
|
|
|
|
cg_glyphs[0] = glyphs[0].index;
|
|
cg_advances[0].width = 0;
|
|
cg_advances[0].height = 0;
|
|
|
|
for (i = 1; i < num_glyphs; i++) {
|
|
cg_glyphs[i] = glyphs[i].index;
|
|
cg_advances[i-1].width = glyphs[i].x - xprev;
|
|
cg_advances[i-1].height = glyphs[i].y - yprev;
|
|
xprev = glyphs[i].x;
|
|
yprev = glyphs[i].y;
|
|
}
|
|
|
|
#if 0
|
|
for (i = 0; i < num_glyphs; i++) {
|
|
ND((stderr, "[%d: %d %f,%f]\n", i, cg_glyphs[i], cg_advances[i].width, cg_advances[i].height));
|
|
}
|
|
#endif
|
|
|
|
CGContextShowGlyphsWithAdvances (surface->cgContext,
|
|
cg_glyphs,
|
|
cg_advances,
|
|
num_glyphs);
|
|
|
|
free (cg_glyphs);
|
|
free (cg_advances);
|
|
|
|
if (action == DO_SHADING)
|
|
CGContextDrawShading (surface->cgContext, surface->sourceShading);
|
|
|
|
_cairo_nquartz_teardown_source (surface, source);
|
|
|
|
CGContextRestoreGState (surface->cgContext);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static cairo_int_status_t
|
|
_cairo_nquartz_surface_mask (void *abstract_surface,
|
|
cairo_operator_t op,
|
|
cairo_pattern_t *source,
|
|
cairo_pattern_t *mask)
|
|
{
|
|
cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface;
|
|
cairo_int_status_t rv = CAIRO_STATUS_SUCCESS;
|
|
|
|
ND((stderr, "%p _cairo_nquartz_surface_mask op %d source->type %d mask->type %d\n", surface, op, source->type, mask->type));
|
|
|
|
if (mask->type == CAIRO_PATTERN_TYPE_SOLID) {
|
|
/* This is easy; we just need to paint with the alpha. */
|
|
cairo_solid_pattern_t *solid_mask = (cairo_solid_pattern_t *) mask;
|
|
|
|
CGContextSetAlpha (surface->cgContext, solid_mask->color.alpha);
|
|
} else {
|
|
/* So, CGContextClipToMask is not present in 10.3.9, so we're doomed; we could
|
|
* do fallback, if we implemented _composite, but for now let's just not support
|
|
* this. (But pretend we did.)
|
|
*/
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
rv = _cairo_nquartz_surface_paint (surface, op, source);
|
|
|
|
if (mask->type == CAIRO_PATTERN_TYPE_SOLID) {
|
|
CGContextSetAlpha (surface->cgContext, 1.0);
|
|
}
|
|
|
|
ND((stderr, "-- mask\n"));
|
|
|
|
return rv;
|
|
}
|
|
|
|
static cairo_int_status_t
|
|
_cairo_nquartz_surface_intersect_clip_path (void *abstract_surface,
|
|
cairo_path_fixed_t *path,
|
|
cairo_fill_rule_t fill_rule,
|
|
double tolerance,
|
|
cairo_antialias_t antialias)
|
|
{
|
|
cairo_nquartz_surface_t *surface = (cairo_nquartz_surface_t *) abstract_surface;
|
|
|
|
ND((stderr, "%p _cairo_nquartz_surface_intersect_clip_path path: %p\n", surface, path));
|
|
|
|
if (path == NULL) {
|
|
/* If we're being asked to reset the clip, we can only do it
|
|
* by restoring the gstate to our previous saved one, and
|
|
* saving it again.
|
|
*
|
|
* Note that this assumes that ALL nquartz surface creation
|
|
* functions will do a SaveGState first; we do this in create_internal.
|
|
*/
|
|
CGContextRestoreGState (surface->cgContext);
|
|
CGContextSaveGState (surface->cgContext);
|
|
} else {
|
|
CGContextBeginPath (surface->cgContext);
|
|
_cairo_nquartz_cairo_path_to_quartz_context (path, surface->cgContext);
|
|
if (fill_rule == CAIRO_FILL_RULE_WINDING)
|
|
CGContextClip (surface->cgContext);
|
|
else
|
|
CGContextEOClip (surface->cgContext);
|
|
}
|
|
|
|
ND((stderr, "-- intersect_clip_path\n"));
|
|
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
// XXXtodo implement show_page; need to figure out how to handle begin/end
|
|
|
|
static const struct _cairo_surface_backend cairo_nquartz_surface_backend = {
|
|
CAIRO_SURFACE_TYPE_NQUARTZ,
|
|
_cairo_nquartz_surface_create_similar,
|
|
_cairo_nquartz_surface_finish,
|
|
_cairo_nquartz_surface_acquire_source_image,
|
|
NULL, /* release_source_image */
|
|
_cairo_nquartz_surface_acquire_dest_image,
|
|
_cairo_nquartz_surface_release_dest_image,
|
|
_cairo_nquartz_surface_clone_similar,
|
|
NULL, /* composite */
|
|
NULL, /* fill_rectangles */
|
|
NULL, /* composite_trapezoids */
|
|
NULL, /* copy_page */
|
|
NULL, /* show_page */
|
|
NULL, /* set_clip_region */
|
|
_cairo_nquartz_surface_intersect_clip_path,
|
|
_cairo_nquartz_surface_get_extents,
|
|
NULL, /* old_show_glyphs */
|
|
NULL, /* get_font_options */
|
|
NULL, /* flush */
|
|
NULL, /* mark_dirty_rectangle */
|
|
NULL, /* scaled_font_fini */
|
|
NULL, /* scaled_glyph_fini */
|
|
|
|
_cairo_nquartz_surface_paint,
|
|
_cairo_nquartz_surface_mask,
|
|
_cairo_nquartz_surface_stroke,
|
|
_cairo_nquartz_surface_fill,
|
|
_cairo_nquartz_surface_show_glyphs,
|
|
|
|
NULL, /* snapshot */
|
|
};
|
|
|
|
cairo_bool_t
|
|
cairo_surface_is_nquartz (cairo_surface_t *surf)
|
|
{
|
|
return (surf->backend == &cairo_nquartz_surface_backend);
|
|
}
|
|
|
|
static cairo_nquartz_surface_t *
|
|
_cairo_nquartz_surface_create_internal (CGContextRef cgContext,
|
|
#ifdef CAIRO_NQUARTZ_SUPPORT_AGL
|
|
AGLContext aglContext,
|
|
#else
|
|
void * unused,
|
|
#endif
|
|
cairo_content_t content,
|
|
unsigned int width,
|
|
unsigned int height,
|
|
cairo_bool_t y_grows_down)
|
|
{
|
|
cairo_nquartz_surface_t *surface;
|
|
|
|
/* Init the base surface */
|
|
surface = malloc(sizeof(cairo_nquartz_surface_t));
|
|
if (surface == NULL) {
|
|
_cairo_error (CAIRO_STATUS_NO_MEMORY);
|
|
return NULL;
|
|
}
|
|
|
|
memset(surface, 0, sizeof(cairo_nquartz_surface_t));
|
|
|
|
_cairo_surface_init(&surface->base, &cairo_nquartz_surface_backend,
|
|
content);
|
|
|
|
/* Save our extents */
|
|
surface->extents.x = surface->extents.y = 0;
|
|
surface->extents.width = width;
|
|
surface->extents.height = height;
|
|
|
|
if (!y_grows_down) {
|
|
/* Then make the CGContext sane */
|
|
CGContextTranslateCTM (cgContext, 0.0, surface->extents.height);
|
|
CGContextScaleCTM (cgContext, 1.0, -1.0);
|
|
}
|
|
|
|
/* Save so we can always get back to a known-good CGContext -- this is
|
|
* required for proper behaviour of intersect_clip_path(NULL)
|
|
*/
|
|
CGContextSaveGState (cgContext);
|
|
|
|
#ifdef CAIRO_NQUARTZ_SUPPORT_AGL
|
|
surface->aglContext = aglContext;
|
|
#endif
|
|
surface->cgContext = cgContext;
|
|
surface->cgContextBaseCTM = CGContextGetCTM (cgContext);
|
|
|
|
surface->imageData = NULL;
|
|
|
|
return surface;
|
|
}
|
|
|
|
#ifdef CAIRO_NQUARTZ_SUPPORT_AGL
|
|
cairo_surface_t *
|
|
cairo_nquartz_surface_create_for_agl_context (AGLContext aglContext,
|
|
unsigned int width,
|
|
unsigned int height,
|
|
cairo_bool_t y_grows_down)
|
|
{
|
|
cairo_nquartz_surface_t *surf;
|
|
CGSize sz;
|
|
|
|
/* Make our CGContext from the AGL context */
|
|
sz.width = width;
|
|
sz.height = height;
|
|
|
|
CGContextRef cgc = CGGLContextCreate (aglContext, sz, NULL /* device RGB colorspace */);
|
|
|
|
surf = _cairo_nquartz_surface_create_internal (cgc, aglContext, CAIRO_CONTENT_COLOR_ALPHA,
|
|
width, height, y_grows_down);
|
|
if (!surf) {
|
|
CGContextRelease (cgc);
|
|
// create_internal will have set an error
|
|
return (cairo_surface_t*) &_cairo_surface_nil;
|
|
}
|
|
|
|
return (cairo_surface_t *) surf;
|
|
}
|
|
#endif
|
|
|
|
cairo_surface_t *
|
|
cairo_nquartz_surface_create_for_cg_context (CGContextRef cgContext,
|
|
unsigned int width,
|
|
unsigned int height,
|
|
cairo_bool_t y_grows_down)
|
|
{
|
|
cairo_nquartz_surface_t *surf;
|
|
|
|
CGContextRetain (cgContext);
|
|
|
|
surf = _cairo_nquartz_surface_create_internal (cgContext, NULL, CAIRO_CONTENT_COLOR_ALPHA,
|
|
width, height, y_grows_down);
|
|
if (!surf) {
|
|
CGContextRelease (cgContext);
|
|
// create_internal will have set an error
|
|
return (cairo_surface_t*) &_cairo_surface_nil;
|
|
}
|
|
|
|
return (cairo_surface_t *) surf;
|
|
}
|
|
|
|
cairo_surface_t *
|
|
cairo_nquartz_surface_create (cairo_format_t format,
|
|
unsigned int width,
|
|
unsigned int height)
|
|
{
|
|
cairo_nquartz_surface_t *surf;
|
|
CGContextRef cgc;
|
|
CGColorSpaceRef cgColorspace;
|
|
CGBitmapInfo bitinfo;
|
|
void *imageData;
|
|
int stride;
|
|
int bitsPerComponent;
|
|
|
|
if (format == CAIRO_FORMAT_ARGB32) {
|
|
cgColorspace = CGColorSpaceCreateDeviceRGB();
|
|
stride = width * 4;
|
|
bitinfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
|
|
bitsPerComponent = 8;
|
|
} else if (format == CAIRO_FORMAT_RGB24) {
|
|
cgColorspace = CGColorSpaceCreateDeviceRGB();
|
|
stride = width * 4;
|
|
bitinfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
|
|
bitsPerComponent = 8;
|
|
} else if (format == CAIRO_FORMAT_A8) {
|
|
cgColorspace = CGColorSpaceCreateDeviceGray();
|
|
if (width % 4 == 0)
|
|
stride = width;
|
|
else
|
|
stride = (width & 3) + 1;
|
|
bitinfo = kCGImageAlphaNone;
|
|
bitsPerComponent = 8;
|
|
} else if (format == CAIRO_FORMAT_A1) {
|
|
/* I don't think we can usefully support this, as defined by
|
|
* cairo_format_t -- these are 1-bit pixels stored in 32-bit
|
|
* quantities.
|
|
*/
|
|
_cairo_error (CAIRO_STATUS_INVALID_FORMAT);
|
|
return (cairo_surface_t*) &_cairo_surface_nil;
|
|
} else {
|
|
_cairo_error (CAIRO_STATUS_INVALID_FORMAT);
|
|
return (cairo_surface_t*) &_cairo_surface_nil;
|
|
}
|
|
|
|
imageData = malloc (height * stride);
|
|
if (!imageData) {
|
|
CGColorSpaceRelease (cgColorspace);
|
|
_cairo_error (CAIRO_STATUS_NO_MEMORY);
|
|
return (cairo_surface_t*) &_cairo_surface_nil;
|
|
}
|
|
|
|
cgc = CGBitmapContextCreate (imageData,
|
|
width,
|
|
height,
|
|
bitsPerComponent,
|
|
stride,
|
|
cgColorspace,
|
|
bitinfo);
|
|
CGColorSpaceRelease (cgColorspace);
|
|
|
|
if (!cgc) {
|
|
_cairo_error (CAIRO_STATUS_NO_MEMORY);
|
|
return (cairo_surface_t*) &_cairo_surface_nil;
|
|
}
|
|
|
|
surf = _cairo_nquartz_surface_create_internal (cgc, NULL, _cairo_content_from_format (format),
|
|
width, height, FALSE);
|
|
if (!surf) {
|
|
CGContextRelease (cgc);
|
|
// create_internal will have set an error
|
|
return (cairo_surface_t*) &_cairo_surface_nil;
|
|
}
|
|
|
|
surf->imageData = imageData;
|
|
|
|
return (cairo_surface_t *) surf;
|
|
}
|
|
|
|
CGContextRef
|
|
cairo_nquartz_surface_get_cg_context (cairo_surface_t *surf)
|
|
{
|
|
cairo_nquartz_surface_t *nquartz = (cairo_nquartz_surface_t*)surf;
|
|
|
|
if (!cairo_surface_is_nquartz(surf))
|
|
return NULL;
|
|
|
|
return nquartz->cgContext;
|
|
}
|
|
|
|
|
|
/* Debug stuff */
|
|
|
|
#ifdef NQUARTZ_DEBUG
|
|
|
|
#include <Movies.h>
|
|
|
|
void ExportCGImageToPNGFile(CGImageRef inImageRef, char* dest)
|
|
{
|
|
Handle dataRef = NULL;
|
|
OSType dataRefType;
|
|
CFStringRef inPath = CFStringCreateWithCString(NULL, dest, kCFStringEncodingASCII);
|
|
|
|
GraphicsExportComponent grex = 0;
|
|
unsigned long sizeWritten;
|
|
|
|
ComponentResult result;
|
|
|
|
// create the data reference
|
|
result = QTNewDataReferenceFromFullPathCFString(inPath, kQTNativeDefaultPathStyle,
|
|
0, &dataRef, &dataRefType);
|
|
|
|
if (NULL != dataRef && noErr == result) {
|
|
// get the PNG exporter
|
|
result = OpenADefaultComponent(GraphicsExporterComponentType, kQTFileTypePNG,
|
|
&grex);
|
|
|
|
if (grex) {
|
|
// tell the exporter where to find its source image
|
|
result = GraphicsExportSetInputCGImage(grex, inImageRef);
|
|
|
|
if (noErr == result) {
|
|
// tell the exporter where to save the exporter image
|
|
result = GraphicsExportSetOutputDataReference(grex, dataRef,
|
|
dataRefType);
|
|
|
|
if (noErr == result) {
|
|
// write the PNG file
|
|
result = GraphicsExportDoExport(grex, &sizeWritten);
|
|
}
|
|
}
|
|
|
|
// remember to close the component
|
|
CloseComponent(grex);
|
|
}
|
|
|
|
// remember to dispose of the data reference handle
|
|
DisposeHandle(dataRef);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void
|
|
nquartz_image_to_png (CGImageRef imgref, char *dest)
|
|
{
|
|
#if 0
|
|
static int sctr = 0;
|
|
char sptr[] = "/Users/vladimir/Desktop/barXXXXX.png";
|
|
|
|
if (dest == NULL) {
|
|
fprintf (stderr, "** Writing %p to bar%d\n", imgref, sctr);
|
|
sprintf (sptr, "/Users/vladimir/Desktop/bar%d.png", sctr);
|
|
sctr++;
|
|
dest = sptr;
|
|
}
|
|
|
|
ExportCGImageToPNGFile(imgref, dest);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
nquartz_surface_to_png (cairo_nquartz_surface_t *nq, char *dest)
|
|
{
|
|
#if 0
|
|
static int sctr = 0;
|
|
char sptr[] = "/Users/vladimir/Desktop/fooXXXXX.png";
|
|
|
|
if (nq->base.type != CAIRO_SURFACE_TYPE_NQUARTZ) {
|
|
fprintf (stderr, "** nquartz_surface_to_png: surface %p isn't nquartz!\n", nq);
|
|
return;
|
|
}
|
|
|
|
if (dest == NULL) {
|
|
fprintf (stderr, "** Writing %p to foo%d\n", nq, sctr);
|
|
sprintf (sptr, "/Users/vladimir/Desktop/foo%d.png", sctr);
|
|
sctr++;
|
|
dest = sptr;
|
|
}
|
|
|
|
CGImageRef imgref = CGBitmapContextCreateImage (nq->cgContext);
|
|
if (imgref == NULL) {
|
|
fprintf (stderr, "nquartz surface at %p is not a bitmap context!\n", nq);
|
|
return;
|
|
}
|
|
|
|
ExportCGImageToPNGFile(imgref, dest);
|
|
|
|
CGImageRelease(imgref);
|
|
#endif
|
|
}
|
|
|