| /* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */ |
| /* cairo - a vector graphics library with display and print output |
| * |
| * Copyright © 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 Corporation. |
| * |
| * Contributor(s): |
| * Vladimir Vukicevic <vladimir@mozilla.com> |
| */ |
| |
| #include "cairoint.h" |
| |
| #include "cairo-skia.h" |
| |
| #include "cairo-surface-clipper-private.h" |
| |
| #include <SkBitmap.h> |
| #include <SkCanvas.h> |
| #include <SkPaint.h> |
| #include <SkPath.h> |
| |
| #include <SkColorShader.h> |
| |
| #include <SkGradientShader.h> |
| #include <SkDashPathEffect.h> |
| |
| #if (CAIRO_FIXED_BITS == 32) && (CAIRO_FIXED_FRAC_BITS == 16) && defined(SK_SCALAR_IS_FIXED) |
| # define CAIRO_FIXED_TO_SK_SCALAR(x) (x) |
| #elif defined(SK_SCALAR_IS_FIXED) |
| /* This can be done better, but this will do for now */ |
| # define CAIRO_FIXED_TO_SK_SCALAR(x) SkFloatToScalar(_cairo_fixed_to_double(x)) |
| #else |
| # define CAIRO_FIXED_TO_SK_SCALAR(x) SkFloatToScalar(_cairo_fixed_to_double(x)) |
| #endif |
| |
| #ifndef CAIRO_INT_STATUS_SUCCESS |
| # define CAIRO_INT_STATUS_SUCCESS ((cairo_int_status_t) CAIRO_STATUS_SUCCESS) |
| #endif |
| |
| #define CAIRO_MAYBE_UNSUPPORTED CAIRO_INT_STATUS_UNSUPPORTED |
| //#define CAIRO_MAYBE_UNSUPPORTED _skia_unsupported () |
| |
| static cairo_int_status_t _skia_unsupported () { |
| printf ("unsupported!\n"); |
| return CAIRO_INT_STATUS_UNSUPPORTED; |
| } |
| |
| typedef struct cairo_skia_surface { |
| cairo_surface_t base; |
| |
| SkBitmap *bitmap; |
| SkCanvas *canvas; |
| |
| cairo_surface_clipper_t clipper; |
| |
| cairo_image_surface_t *_image_surface; /* wrapper around bitmap */ |
| } cairo_skia_surface_t; |
| |
| static cairo_skia_surface_t * |
| _cairo_skia_surface_create_internal (SkBitmap::Config config, |
| bool opaque, |
| unsigned char *data, |
| int width, |
| int height, |
| int stride); |
| |
| /* |
| * conversion methods |
| */ |
| |
| /* |
| * format conversion |
| */ |
| static inline bool |
| format_to_sk_config (cairo_format_t format, |
| SkBitmap::Config& config, |
| bool& opaque) |
| { |
| opaque = false; |
| |
| switch (format) { |
| case CAIRO_FORMAT_ARGB32: |
| config = SkBitmap::kARGB_8888_Config; |
| break; |
| case CAIRO_FORMAT_RGB24: |
| config = SkBitmap::kARGB_8888_Config; |
| opaque = true; |
| break; |
| case CAIRO_FORMAT_A8: |
| config = SkBitmap::kA8_Config; |
| break; |
| case CAIRO_FORMAT_A1: |
| config = SkBitmap::kA1_Config; |
| break; |
| default: |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static inline cairo_format_t |
| sk_config_to_format (SkBitmap::Config config, |
| bool opaque) |
| { |
| switch (config) { |
| case SkBitmap::kARGB_8888_Config: |
| if (opaque) |
| return CAIRO_FORMAT_RGB24; |
| return CAIRO_FORMAT_ARGB32; |
| |
| case SkBitmap::kA8_Config: |
| return CAIRO_FORMAT_A8; |
| |
| case SkBitmap::kA1_Config: |
| return CAIRO_FORMAT_A1; |
| |
| case SkBitmap::kNo_Config: |
| case SkBitmap::kIndex8_Config: |
| case SkBitmap::kRLE_Index8_Config: |
| case SkBitmap::kRGB_565_Config: |
| case SkBitmap::kARGB_4444_Config: |
| case SkBitmap::kConfigCount: |
| default: |
| return (cairo_format_t) -1; |
| } |
| } |
| |
| /* |
| * image surface wrapping |
| */ |
| static inline bool |
| surface_to_sk_bitmap (cairo_surface_t *surface, SkBitmap& bitmap) |
| { |
| cairo_image_surface_t *img = (cairo_image_surface_t *) surface; |
| SkBitmap::Config config; |
| bool opaque; |
| |
| if (unlikely (! format_to_sk_config (img->format, config, opaque))) |
| return false; |
| |
| bitmap.reset (); |
| bitmap.setConfig (config, img->width, img->height, img->stride); |
| bitmap.setIsOpaque (opaque); |
| bitmap.setPixels (img->data); |
| |
| return true; |
| } |
| |
| /* |
| * operator conversion |
| */ |
| |
| static inline SkXfermode::Mode |
| operator_to_sk (cairo_operator_t op) |
| { |
| static const SkXfermode::Mode modeMap[] = { |
| SkXfermode::kClear_Mode, |
| |
| SkXfermode::kSrc_Mode, |
| SkXfermode::kSrcOver_Mode, |
| SkXfermode::kSrcIn_Mode, |
| SkXfermode::kSrcOut_Mode, |
| SkXfermode::kSrcATop_Mode, |
| |
| SkXfermode::kDst_Mode, |
| SkXfermode::kDstOver_Mode, |
| SkXfermode::kDstIn_Mode, |
| SkXfermode::kDstOut_Mode, |
| SkXfermode::kDstATop_Mode, |
| |
| SkXfermode::kXor_Mode, |
| SkXfermode::kPlus_Mode, // XXX Add? |
| SkXfermode::kPlus_Mode, // XXX SATURATE |
| |
| SkXfermode::kPlus_Mode, |
| SkXfermode::kMultiply_Mode, |
| SkXfermode::kScreen_Mode, |
| SkXfermode::kOverlay_Mode, |
| SkXfermode::kDarken_Mode, |
| SkXfermode::kLighten_Mode, |
| SkXfermode::kColorDodge_Mode, |
| SkXfermode::kColorBurn_Mode, |
| SkXfermode::kHardLight_Mode, |
| SkXfermode::kSoftLight_Mode, |
| SkXfermode::kDifference_Mode, |
| SkXfermode::kExclusion_Mode, |
| |
| SkXfermode::kSrcOver_Mode, // XXX: CAIRO_OPERATOR_HSL_HUE |
| SkXfermode::kSrcOver_Mode, // XXX: CAIRO_OPERATOR_HSL_SATURATION, |
| SkXfermode::kSrcOver_Mode, // XXX: CAIRO_OPERATOR_HSL_COLOR, |
| SkXfermode::kSrcOver_Mode, // XXX: CAIRO_OPERATOR_HSL_LUMINOSITY |
| }; |
| |
| return modeMap[op]; |
| } |
| |
| /* |
| * tiling mode conversion |
| */ |
| static SkShader::TileMode |
| extend_to_sk (cairo_extend_t extend) |
| { |
| static const SkShader::TileMode modeMap[] = { |
| SkShader::kClamp_TileMode, // NONE behaves like PAD, because noone wants NONE |
| SkShader::kRepeat_TileMode, |
| SkShader::kMirror_TileMode, |
| SkShader::kClamp_TileMode |
| }; |
| |
| return modeMap[extend]; |
| } |
| |
| /* |
| * color conversion |
| */ |
| static inline SkColor |
| color_to_sk (const cairo_color_t& c) |
| { |
| /* Need unpremultiplied 1-byte values */ |
| return SkColorSetARGB ((U8CPU) (c.alpha * 255), |
| (U8CPU) (c.red * 255), |
| (U8CPU) (c.green * 255), |
| (U8CPU) (c.blue * 255)); |
| } |
| |
| /* |
| * matrix conversion |
| */ |
| static inline SkMatrix |
| matrix_to_sk (const cairo_matrix_t& mat) |
| { |
| SkMatrix skm; |
| |
| skm.reset (); |
| skm.set (SkMatrix::kMScaleX, SkFloatToScalar (mat.xx)); |
| skm.set (SkMatrix::kMSkewX, SkFloatToScalar (mat.xy)); |
| skm.set (SkMatrix::kMTransX, SkFloatToScalar (mat.x0)); |
| skm.set (SkMatrix::kMSkewY, SkFloatToScalar (mat.yx)); |
| skm.set (SkMatrix::kMScaleY, SkFloatToScalar (mat.yy)); |
| skm.set (SkMatrix::kMTransY, SkFloatToScalar (mat.y0)); |
| |
| /* |
| skm[6] = SkFloatToScalar (0.0); |
| skm[7] = SkFloatToScalar (0.0); |
| skm[8] = SkFloatToScalar (1.0); -- this isn't right, it wants a magic value in there that it'll set itself. It wants Sk_Fract1 (2.30), not Sk_Scalar1 |
| */ |
| |
| return skm; |
| } |
| |
| static inline SkMatrix |
| matrix_inverse_to_sk (const cairo_matrix_t& mat) |
| { |
| cairo_matrix_t inv = mat; |
| cairo_status_t status = cairo_matrix_invert (&inv); |
| assert (status == CAIRO_STATUS_SUCCESS); |
| return matrix_to_sk (inv); |
| } |
| |
| /* |
| * pattern conversion |
| */ |
| static inline cairo_surface_t * |
| surface_from_pattern (const cairo_pattern_t *pattern) |
| { |
| return (reinterpret_cast <const cairo_surface_pattern_t *> (pattern))->surface; |
| } |
| |
| static SkShader* |
| pattern_to_sk_shader (cairo_skia_surface_t *dst, const cairo_pattern_t *pattern, |
| cairo_image_surface_t **image, void **image_extra) |
| { |
| SkShader *shader = NULL; |
| |
| if (pattern->type == CAIRO_PATTERN_TYPE_SOLID) { |
| cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) pattern; |
| return new SkColorShader (color_to_sk (solid->color)); |
| } else if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) { |
| cairo_surface_t *surface = surface_from_pattern (pattern); |
| |
| if (surface->type == CAIRO_SURFACE_TYPE_SKIA) { |
| cairo_skia_surface_t *esurf = (cairo_skia_surface_t *) surface; |
| |
| shader = SkShader::CreateBitmapShader (*esurf->bitmap, |
| extend_to_sk (pattern->extend), |
| extend_to_sk (pattern->extend)); |
| } else { |
| SkBitmap bitmap; |
| |
| if (! _cairo_surface_is_image (surface)) { |
| cairo_status_t status; |
| |
| status = _cairo_surface_acquire_source_image (surface, |
| image, image_extra); |
| if (status) |
| return NULL; |
| |
| surface = &(*image)->base; |
| } |
| |
| |
| if (unlikely (! surface_to_sk_bitmap (surface, bitmap))) |
| return NULL; |
| |
| shader = SkShader::CreateBitmapShader (bitmap, |
| extend_to_sk (pattern->extend), |
| extend_to_sk (pattern->extend)); |
| } |
| } else if (pattern->type == CAIRO_PATTERN_TYPE_LINEAR |
| /* || pattern->type == CAIRO_PATTERN_TYPE_RADIAL */) |
| { |
| cairo_gradient_pattern_t *gradient = (cairo_gradient_pattern_t *) pattern; |
| SkColor colors_stack[10]; |
| SkScalar pos_stack[10]; |
| SkColor *colors = colors_stack; |
| SkScalar *pos = pos_stack; |
| |
| if (gradient->n_stops > 10) { |
| colors = new SkColor[gradient->n_stops]; |
| pos = new SkScalar[gradient->n_stops]; |
| } |
| |
| for (unsigned int i = 0; i < gradient->n_stops; i++) { |
| pos[i] = CAIRO_FIXED_TO_SK_SCALAR (gradient->stops[i].offset); |
| colors[i] = color_to_sk (gradient->stops[i].color); |
| } |
| |
| if (pattern->type == CAIRO_PATTERN_TYPE_LINEAR) { |
| cairo_linear_pattern_t *linear = (cairo_linear_pattern_t *) gradient; |
| SkPoint points[2]; |
| |
| points[0].set (SkFloatToScalar (linear->pd1.x), |
| SkFloatToScalar (linear->pd1.y)); |
| points[1].set (SkFloatToScalar (linear->pd2.x), |
| SkFloatToScalar (linear->pd2.y)); |
| shader = SkGradientShader::CreateLinear (points, colors, pos, gradient->n_stops, |
| extend_to_sk (pattern->extend)); |
| } else { |
| // XXX todo -- implement real radial shaders in Skia |
| } |
| |
| if (gradient->n_stops > 10) { |
| delete [] colors; |
| delete [] pos; |
| } |
| } |
| |
| if (shader && ! _cairo_matrix_is_identity (&pattern->matrix)) |
| shader->setLocalMatrix (matrix_inverse_to_sk (pattern->matrix)); |
| |
| return shader; |
| } |
| |
| static inline bool |
| pattern_filter_to_sk (const cairo_pattern_t *pattern) |
| { |
| switch (pattern->filter) { |
| case CAIRO_FILTER_GOOD: |
| case CAIRO_FILTER_BEST: |
| case CAIRO_FILTER_BILINEAR: |
| case CAIRO_FILTER_GAUSSIAN: |
| return true; |
| default: |
| case CAIRO_FILTER_FAST: |
| case CAIRO_FILTER_NEAREST: |
| return false; |
| } |
| } |
| |
| static inline bool |
| pattern_to_sk_color (const cairo_pattern_t *pattern, SkColor& color) |
| { |
| if (pattern->type != CAIRO_PATTERN_TYPE_SOLID) |
| return false; |
| |
| color = color_to_sk (((cairo_solid_pattern_t *) pattern)->color); |
| return true; |
| } |
| |
| /* |
| * path conversion |
| */ |
| |
| struct cpc { |
| SkPath skPath; |
| cairo_matrix_t *matrix; |
| }; |
| |
| static cairo_status_t |
| cpc_move_to (void *closure, const cairo_point_t *point) |
| { |
| struct cpc *cpc = static_cast <struct cpc *> (closure); |
| if (cpc->matrix) { |
| double x = _cairo_fixed_to_double (point->x); |
| double y = _cairo_fixed_to_double (point->y); |
| cairo_matrix_transform_point (cpc->matrix, &x, &y); |
| cpc->skPath.moveTo (SkFloatToScalar (x), SkFloatToScalar (y)); |
| } else { |
| cpc->skPath.moveTo (CAIRO_FIXED_TO_SK_SCALAR (point->x), |
| CAIRO_FIXED_TO_SK_SCALAR (point->y)); |
| } |
| return CAIRO_STATUS_SUCCESS; |
| } |
| |
| static cairo_status_t |
| cpc_line_to (void *closure, const cairo_point_t *point) |
| { |
| struct cpc *cpc = static_cast <struct cpc *> (closure); |
| if (cpc->matrix) { |
| double x = _cairo_fixed_to_double (point->x); |
| double y = _cairo_fixed_to_double (point->y); |
| cairo_matrix_transform_point (cpc->matrix, &x, &y); |
| cpc->skPath.lineTo (SkFloatToScalar (x), SkFloatToScalar (y)); |
| } else { |
| cpc->skPath.lineTo (CAIRO_FIXED_TO_SK_SCALAR (point->x), |
| CAIRO_FIXED_TO_SK_SCALAR (point->y)); |
| } |
| return CAIRO_STATUS_SUCCESS; |
| } |
| |
| static cairo_status_t |
| cpc_curve_to (void *closure, |
| const cairo_point_t *p0, |
| const cairo_point_t *p1, |
| const cairo_point_t *p2) |
| { |
| struct cpc *cpc = static_cast <struct cpc *> (closure); |
| if (cpc->matrix) { |
| double x0 = _cairo_fixed_to_double (p0->x); |
| double y0 = _cairo_fixed_to_double (p0->y); |
| double x1 = _cairo_fixed_to_double (p1->x); |
| double y1 = _cairo_fixed_to_double (p1->y); |
| double x2 = _cairo_fixed_to_double (p2->x); |
| double y2 = _cairo_fixed_to_double (p2->y); |
| cairo_matrix_transform_point (cpc->matrix, &x0, &y0); |
| cairo_matrix_transform_point (cpc->matrix, &x1, &y1); |
| cairo_matrix_transform_point (cpc->matrix, &x2, &y2); |
| |
| cpc->skPath.cubicTo (SkFloatToScalar (x0), |
| SkFloatToScalar (y0), |
| SkFloatToScalar (x1), |
| SkFloatToScalar (y1), |
| SkFloatToScalar (x2), |
| SkFloatToScalar (y2)); |
| } else { |
| cpc->skPath.cubicTo (CAIRO_FIXED_TO_SK_SCALAR (p0->x), |
| CAIRO_FIXED_TO_SK_SCALAR (p0->y), |
| CAIRO_FIXED_TO_SK_SCALAR (p1->x), |
| CAIRO_FIXED_TO_SK_SCALAR (p1->y), |
| CAIRO_FIXED_TO_SK_SCALAR (p2->x), |
| CAIRO_FIXED_TO_SK_SCALAR (p2->y)); |
| } |
| return CAIRO_STATUS_SUCCESS; |
| } |
| |
| static cairo_status_t |
| cpc_close_path (void *closure) |
| { |
| struct cpc *cpc = static_cast <struct cpc *> (closure); |
| cpc->skPath.close (); |
| return CAIRO_STATUS_SUCCESS; |
| } |
| |
| static inline SkPath |
| path_to_sk (cairo_path_fixed_t *path, |
| cairo_matrix_t *mat = NULL) |
| { |
| struct cpc data; |
| cairo_status_t status; |
| |
| if (mat && _cairo_matrix_is_identity (mat)) |
| mat = NULL; |
| data.matrix = mat; |
| |
| status = _cairo_path_fixed_interpret (path, |
| cpc_move_to, |
| cpc_line_to, |
| cpc_curve_to, |
| cpc_close_path, |
| &data); |
| assert (status == CAIRO_STATUS_SUCCESS); |
| |
| return data.skPath; |
| } |
| |
| static inline SkPath |
| path_to_sk (cairo_path_fixed_t *path, |
| cairo_fill_rule_t fill_rule, |
| cairo_matrix_t *mat = NULL) |
| { |
| SkPath skPath = path_to_sk (path, mat); |
| |
| if (fill_rule == CAIRO_FILL_RULE_EVEN_ODD) |
| skPath.setFillType (SkPath::kEvenOdd_FillType); |
| else |
| skPath.setFillType (SkPath::kWinding_FillType); |
| |
| return skPath; |
| } |
| |
| /* |
| * cairo surface methods |
| */ |
| |
| static cairo_surface_t * |
| _cairo_skia_surface_create_similar (void *asurface, |
| cairo_content_t content, |
| int width, |
| int height) |
| { |
| SkBitmap::Config config; |
| bool opaque; |
| |
| if (! format_to_sk_config (_cairo_format_from_content (content), |
| config, opaque)) |
| { |
| _skia_unsupported (); |
| return NULL; |
| } |
| |
| return &_cairo_skia_surface_create_internal (config, opaque, |
| NULL, |
| width, height, |
| 0)->base; |
| } |
| |
| static cairo_status_t |
| _cairo_skia_surface_finish (void *asurface) |
| { |
| cairo_skia_surface_t *surface = (cairo_skia_surface_t *) asurface; |
| |
| _cairo_surface_clipper_reset (&surface->clipper); |
| cairo_surface_destroy (&surface->_image_surface->base); |
| |
| delete surface->canvas; |
| delete surface->bitmap; |
| |
| return CAIRO_STATUS_SUCCESS; |
| } |
| |
| static cairo_image_surface_t * |
| _get_image_surface (cairo_skia_surface_t *surface) |
| { |
| if (! surface->_image_surface) { |
| SkBitmap *bitmap = surface->bitmap; |
| surface->_image_surface = (cairo_image_surface_t *) |
| cairo_image_surface_create_for_data ((unsigned char *) bitmap->getPixels (), |
| sk_config_to_format (bitmap->config (), |
| bitmap->isOpaque ()), |
| bitmap->width (), |
| bitmap->height (), |
| bitmap->rowBytes ()); |
| } |
| |
| return surface->_image_surface; |
| } |
| |
| static cairo_status_t |
| _cairo_skia_surface_acquire_source_image (void *asurface, |
| cairo_image_surface_t **image_out, |
| void **image_extra) |
| { |
| cairo_skia_surface_t *surface = (cairo_skia_surface_t *) asurface; |
| cairo_image_surface_t *image = _get_image_surface (surface); |
| |
| if (unlikely (image->base.status)) |
| return image->base.status; |
| |
| surface->bitmap->lockPixels (); |
| |
| *image_out = image; |
| *image_extra = NULL; |
| return CAIRO_STATUS_SUCCESS; |
| } |
| |
| static void |
| _cairo_skia_surface_release_source_image (void *asurface, |
| cairo_image_surface_t *image, |
| void *image_extra) |
| { |
| cairo_skia_surface_t *surface = (cairo_skia_surface_t *) asurface; |
| |
| surface->bitmap->unlockPixels (); |
| } |
| |
| static cairo_status_t |
| _cairo_skia_surface_acquire_dest_image (void *asurface, |
| cairo_rectangle_int_t *interest_rect, |
| cairo_image_surface_t **image_out, |
| cairo_rectangle_int_t *image_rect, |
| void **image_extra) |
| { |
| cairo_skia_surface_t *surface = (cairo_skia_surface_t *) asurface; |
| cairo_image_surface_t *image = _get_image_surface (surface); |
| |
| if (unlikely (image->base.status)) |
| return image->base.status; |
| |
| image_rect->x = 0; |
| image_rect->y = 0; |
| image_rect->width = image->width; |
| image_rect->height = image->height; |
| |
| surface->bitmap->lockPixels (); |
| |
| *image_out = image; |
| *image_extra = NULL; |
| return CAIRO_STATUS_SUCCESS; |
| } |
| |
| static void |
| _cairo_skia_surface_release_dest_image (void *asurface, |
| cairo_rectangle_int_t *interest_rect, |
| cairo_image_surface_t *image, |
| cairo_rectangle_int_t *image_rect, |
| void *image_extra) |
| { |
| cairo_skia_surface_t *surface = (cairo_skia_surface_t *) asurface; |
| |
| surface->bitmap->notifyPixelsChanged (); |
| surface->bitmap->unlockPixels (); |
| } |
| |
| #if 0 |
| static cairo_status_t |
| _cairo_skia_surface_clone_similar (void *asurface, |
| cairo_surface_t *src, |
| cairo_content_t content, |
| int src_x, |
| int src_y, |
| int width, |
| int height, |
| int *clone_offset_x, |
| int *clone_offset_y, |
| cairo_surface_t **clone_out) |
| { |
| if (src->type == CAIRO_SURFACE_TYPE_SKIA || _cairo_surface_is_image (src)) { |
| *clone_offset_x = 0; |
| *clone_offset_y = 0; |
| *clone_out = cairo_surface_reference (src); |
| return CAIRO_STATUS_SUCCESS; |
| } |
| |
| return (cairo_status_t) CAIRO_INT_STATUS_UNSUPPORTED; |
| } |
| #endif |
| |
| static cairo_status_t |
| _cairo_skia_surface_clipper_intersect_clip_path (cairo_surface_clipper_t *clipper, |
| cairo_path_fixed_t *path, |
| cairo_fill_rule_t fill_rule, |
| double tolerance, |
| cairo_antialias_t antialias) |
| { |
| cairo_skia_surface_t *surface = cairo_container_of (clipper, |
| cairo_skia_surface_t, |
| clipper); |
| |
| if (path == NULL) { |
| /* XXX TODO: teach Skia how to reset the clip path */ |
| surface->canvas->restore (); |
| surface->canvas->save (); |
| } else { |
| surface->canvas->clipPath (path_to_sk (path, fill_rule)); |
| } |
| |
| return CAIRO_STATUS_SUCCESS; |
| } |
| |
| static cairo_bool_t |
| _cairo_skia_surface_get_extents (void *asurface, |
| cairo_rectangle_int_t *extents) |
| { |
| cairo_skia_surface_t *surface = (cairo_skia_surface_t *) asurface; |
| |
| extents->x = 0; |
| extents->y = 0; |
| extents->width = surface->bitmap->width (); |
| extents->height = surface->bitmap->height (); |
| |
| return TRUE; |
| } |
| |
| /* |
| * Core drawing operations |
| */ |
| |
| static SkBitmap * |
| pattern_to_sk_bitmap (cairo_skia_surface_t *dst, |
| const cairo_pattern_t *pattern, |
| SkMatrix *matrix, |
| cairo_image_surface_t **image, |
| void **image_extra) |
| { |
| if (pattern->type != CAIRO_PATTERN_TYPE_SURFACE) |
| return NULL; |
| |
| if (pattern->extend != CAIRO_EXTEND_NONE) |
| return NULL; |
| |
| cairo_surface_t *surface = surface_from_pattern (pattern); |
| SkBitmap *bitmap; |
| |
| if (surface->type == CAIRO_SURFACE_TYPE_SKIA) { |
| bitmap = new SkBitmap (*((cairo_skia_surface_t *) surface)->bitmap); |
| } else { |
| if (surface->type != CAIRO_SURFACE_TYPE_IMAGE) { |
| cairo_status_t status; |
| |
| status = _cairo_surface_acquire_source_image (surface, |
| image, image_extra); |
| if (unlikely (status)) |
| return NULL; |
| |
| surface = &(*image)->base; |
| } |
| |
| bitmap = new SkBitmap; |
| if (unlikely (! surface_to_sk_bitmap (surface, *bitmap))) |
| return NULL; |
| } |
| |
| *matrix = matrix_inverse_to_sk (pattern->matrix); |
| return bitmap; |
| } |
| |
| static cairo_int_status_t |
| _cairo_skia_surface_paint (void *asurface, |
| cairo_operator_t op, |
| const cairo_pattern_t *source, |
| cairo_clip_t *clip) |
| { |
| cairo_skia_surface_t *surface = (cairo_skia_surface_t *) asurface; |
| cairo_image_surface_t *image = NULL; |
| cairo_status_t status; |
| void *image_extra; |
| SkColor color; |
| |
| status = _cairo_surface_clipper_set_clip (&surface->clipper, clip); |
| if (unlikely (status)) |
| return (cairo_int_status_t) status; |
| |
| if (pattern_to_sk_color (source, color)) { |
| surface->canvas->drawColor (color, operator_to_sk (op)); |
| return CAIRO_INT_STATUS_SUCCESS; |
| } |
| |
| SkMatrix bitmapMatrix; |
| SkBitmap *bitmap = pattern_to_sk_bitmap (surface, source, &bitmapMatrix, |
| &image, &image_extra); |
| SkShader *shader = NULL; |
| if (!bitmap) |
| shader = pattern_to_sk_shader (surface, source, &image, &image_extra); |
| |
| if (!bitmap && !shader) |
| return CAIRO_MAYBE_UNSUPPORTED; |
| |
| SkPaint paint; |
| paint.setFilterBitmap (pattern_filter_to_sk (source)); |
| paint.setXfermodeMode (operator_to_sk (op)); |
| |
| if (shader) { |
| paint.setShader (shader); |
| surface->canvas->drawPaint (paint); |
| } else { |
| surface->canvas->drawBitmapMatrix (*bitmap, bitmapMatrix, &paint); |
| } |
| |
| if (bitmap) |
| delete bitmap; |
| if (shader) |
| shader->unref (); |
| |
| if (image != NULL) { |
| _cairo_surface_release_source_image (&surface->base, |
| image, image_extra); |
| } |
| |
| return CAIRO_INT_STATUS_SUCCESS; |
| } |
| |
| static cairo_int_status_t |
| _cairo_skia_surface_stroke (void *asurface, |
| cairo_operator_t op, |
| const 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_clip_t *clip) |
| { |
| cairo_skia_surface_t *surface = (cairo_skia_surface_t *) asurface; |
| cairo_image_surface_t *image = NULL; |
| cairo_status_t status; |
| void *image_extra; |
| |
| status = _cairo_surface_clipper_set_clip (&surface->clipper, clip); |
| if (unlikely (status)) |
| return (cairo_int_status_t) status; |
| |
| SkPaint paint; |
| paint.setStyle (SkPaint::kStroke_Style); |
| |
| SkColor color; |
| if (pattern_to_sk_color (source, color)) { |
| paint.setColor (color); |
| } else { |
| SkShader *shader = pattern_to_sk_shader (surface, |
| source, &image, &image_extra); |
| if (shader == NULL) |
| return CAIRO_MAYBE_UNSUPPORTED; |
| |
| paint.setShader (shader); |
| shader->unref (); |
| |
| paint.setFilterBitmap (pattern_filter_to_sk (source)); |
| } |
| |
| paint.setXfermodeMode (operator_to_sk (op)); |
| paint.setAntiAlias (antialias != CAIRO_ANTIALIAS_NONE); |
| |
| /* Convert the various stroke rendering bits */ |
| paint.setStrokeWidth (SkFloatToScalar (style->line_width)); |
| paint.setStrokeMiter (SkFloatToScalar (style->miter_limit)); |
| |
| static const SkPaint::Cap capMap[] = { |
| SkPaint::kButt_Cap, |
| SkPaint::kRound_Cap, |
| SkPaint::kSquare_Cap |
| }; |
| paint.setStrokeCap (capMap[style->line_cap]); |
| |
| static const SkPaint::Join joinMap[] = { |
| SkPaint::kMiter_Join, |
| SkPaint::kRound_Join, |
| SkPaint::kBevel_Join |
| }; |
| paint.setStrokeJoin (joinMap[style->line_join]); |
| |
| /* If we have a dash pattern, we need to |
| * create a SkDashPathEffect and set it on the Paint. |
| */ |
| if (style->dash != NULL) { |
| SkScalar intervals_static[20]; |
| SkScalar *intervals = intervals_static; |
| |
| int loop = 0; |
| unsigned int dash_count = style->num_dashes; |
| if ((dash_count & 1) != 0) { |
| loop = 1; |
| dash_count <<= 1; |
| } |
| |
| if (dash_count > 20) |
| intervals = new SkScalar[dash_count]; |
| |
| unsigned int i = 0; |
| do { |
| for (unsigned int j = 0; i < style->num_dashes; j++) |
| intervals[i++] = SkFloatToScalar (style->dash[j]); |
| } while (loop--); |
| |
| SkDashPathEffect *dash = new SkDashPathEffect (intervals, |
| dash_count, |
| SkFloatToScalar (style->dash_offset)); |
| |
| paint.setPathEffect (dash); |
| dash->unref (); |
| } |
| |
| surface->canvas->save (SkCanvas::kMatrix_SaveFlag); |
| surface->canvas->concat (matrix_to_sk (*ctm)); |
| surface->canvas->drawPath (path_to_sk (path, ctm_inverse), paint); |
| surface->canvas->restore (); |
| |
| if (image != NULL) { |
| _cairo_surface_release_source_image (&surface->base, |
| image, image_extra); |
| } |
| |
| return CAIRO_INT_STATUS_SUCCESS; |
| } |
| |
| static cairo_int_status_t |
| _cairo_skia_surface_fill (void *asurface, |
| cairo_operator_t op, |
| const cairo_pattern_t *source, |
| cairo_path_fixed_t *path, |
| cairo_fill_rule_t fill_rule, |
| double tolerance, |
| cairo_antialias_t antialias, |
| cairo_clip_t *clip) |
| { |
| cairo_skia_surface_t *surface = (cairo_skia_surface_t *) asurface; |
| cairo_image_surface_t *image = NULL; |
| cairo_status_t status; |
| void *image_extra; |
| |
| status = _cairo_surface_clipper_set_clip (&surface->clipper, clip); |
| if (unlikely (status)) |
| return (cairo_int_status_t) status; |
| |
| |
| SkPaint paint; |
| paint.setStyle (SkPaint::kFill_Style); |
| |
| SkColor color; |
| if (pattern_to_sk_color (source, color)) { |
| paint.setColor (color); |
| } else { |
| SkShader *shader = pattern_to_sk_shader (surface, |
| source, &image, &image_extra); |
| if (shader == NULL) |
| return CAIRO_MAYBE_UNSUPPORTED; |
| |
| paint.setShader (shader); |
| shader->unref (); |
| |
| paint.setFilterBitmap (pattern_filter_to_sk (source)); |
| } |
| |
| paint.setXfermodeMode (operator_to_sk (op)); |
| paint.setAntiAlias (antialias != CAIRO_ANTIALIAS_NONE); |
| |
| surface->canvas->drawPath (path_to_sk (path, fill_rule), paint); |
| |
| if (image != NULL) { |
| _cairo_surface_release_source_image (&surface->base, |
| image, image_extra); |
| } |
| |
| return CAIRO_INT_STATUS_SUCCESS; |
| } |
| |
| static const struct _cairo_surface_backend |
| cairo_skia_surface_backend = { |
| CAIRO_SURFACE_TYPE_SKIA, |
| _cairo_skia_surface_create_similar, |
| _cairo_skia_surface_finish, |
| _cairo_skia_surface_acquire_source_image, |
| _cairo_skia_surface_release_source_image, |
| _cairo_skia_surface_acquire_dest_image, |
| _cairo_skia_surface_release_dest_image, |
| |
| NULL, // _cairo_skia_surface_clone_similar, |
| NULL, /* composite */ |
| NULL, /* fill_rectangles */ |
| NULL, /* composite_trapezoids */ |
| NULL, /* create_span_renderer */ |
| NULL, /* check_span_renderer */ |
| |
| NULL, /* copy_page */ |
| NULL, /* show_page */ |
| |
| _cairo_skia_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_skia_surface_paint, |
| NULL, /* mask? */ |
| _cairo_skia_surface_stroke, |
| _cairo_skia_surface_fill, |
| NULL, /* show_glyphs */ |
| |
| NULL, /* snapshot */ |
| NULL, /* is_similar */ |
| NULL, /* reset */ |
| }; |
| |
| /* |
| * Surface constructors |
| */ |
| |
| static cairo_skia_surface_t * |
| _cairo_skia_surface_create_internal (SkBitmap::Config config, |
| bool opaque, |
| unsigned char *data, |
| int width, |
| int height, |
| int stride) |
| { |
| cairo_skia_surface_t *surface; |
| cairo_format_t format; |
| |
| surface = (cairo_skia_surface_t *) malloc (sizeof (cairo_skia_surface_t)); |
| if (surface == NULL) |
| return (cairo_skia_surface_t *) _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); |
| |
| memset (surface, 0, sizeof (cairo_skia_surface_t)); |
| |
| format = sk_config_to_format (config, opaque); |
| assert (format != -1); |
| |
| _cairo_surface_init (&surface->base, |
| &cairo_skia_surface_backend, |
| NULL, /* device */ |
| _cairo_content_from_format (format)); |
| |
| _cairo_surface_clipper_init (&surface->clipper, |
| _cairo_skia_surface_clipper_intersect_clip_path); |
| |
| surface->bitmap = new SkBitmap; |
| if (data == NULL) |
| stride = cairo_format_stride_for_width (format, width); |
| surface->bitmap->setConfig (config, width, height, stride); |
| surface->bitmap->setIsOpaque (opaque); |
| if (data != NULL) |
| surface->bitmap->setPixels (data); |
| else |
| surface->bitmap->allocPixels (); |
| |
| surface->canvas = new SkCanvas (*surface->bitmap); |
| //surface->canvas->translate (SkIntToScalar (0), SkIntToScalar (height)); |
| //surface->canvas->scale (SkIntToScalar (1), SkIntToScalar (-1)); |
| surface->canvas->save (); |
| |
| return surface; |
| } |
| |
| cairo_surface_t * |
| cairo_skia_surface_create (cairo_format_t format, |
| int width, |
| int height) |
| { |
| SkBitmap::Config config; |
| bool opaque; |
| |
| if (! CAIRO_FORMAT_VALID (format) || |
| ! format_to_sk_config (format, config, opaque)) |
| { |
| return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT)); |
| } |
| |
| return &_cairo_skia_surface_create_internal (config, opaque, |
| NULL, |
| width, height, 0)->base; |
| } |
| |
| cairo_surface_t * |
| cairo_skia_surface_create_for_data (unsigned char *data, |
| cairo_format_t format, |
| int width, |
| int height, |
| int stride) |
| { |
| SkBitmap::Config config; |
| bool opaque; |
| |
| if (! CAIRO_FORMAT_VALID (format) || |
| ! format_to_sk_config (format, config, opaque)) |
| { |
| return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT)); |
| } |
| |
| return &_cairo_skia_surface_create_internal (config, opaque, |
| data, |
| width, height, stride)->base; |
| } |
| |
| unsigned char * |
| cairo_skia_surface_get_data (cairo_surface_t *surface) |
| { |
| if (surface->type != CAIRO_SURFACE_TYPE_SKIA) |
| return NULL; |
| |
| cairo_skia_surface_t *esurf = (cairo_skia_surface_t *) surface; |
| return (unsigned char *) esurf->bitmap->getPixels (); |
| } |
| |
| cairo_format_t |
| cairo_skia_surface_get_format (cairo_surface_t *surface) |
| { |
| if (surface->type != CAIRO_SURFACE_TYPE_SKIA) |
| return (cairo_format_t) -1; |
| |
| cairo_skia_surface_t *esurf = (cairo_skia_surface_t *) surface; |
| return sk_config_to_format (esurf->bitmap->config (), |
| esurf->bitmap->isOpaque ()); |
| } |
| |
| int |
| cairo_skia_surface_get_width (cairo_surface_t *surface) |
| { |
| if (surface->type != CAIRO_SURFACE_TYPE_SKIA) |
| return 0; |
| |
| cairo_skia_surface_t *esurf = (cairo_skia_surface_t *) surface; |
| return esurf->bitmap->width (); |
| } |
| |
| int |
| cairo_skia_surface_get_height (cairo_surface_t *surface) |
| { |
| if (surface->type != CAIRO_SURFACE_TYPE_SKIA) |
| return 0; |
| |
| cairo_skia_surface_t *esurf = (cairo_skia_surface_t *) surface; |
| return esurf->bitmap->height (); |
| } |
| |
| int |
| cairo_skia_surface_get_stride (cairo_surface_t *surface) |
| { |
| if (surface->type != CAIRO_SURFACE_TYPE_SKIA) |
| return 0; |
| |
| cairo_skia_surface_t *esurf = (cairo_skia_surface_t *) surface; |
| return esurf->bitmap->rowBytes (); |
| } |
| |
| cairo_surface_t * |
| cairo_skia_surface_get_image (cairo_surface_t *surface) |
| { |
| if (surface->type != CAIRO_SURFACE_TYPE_SKIA) |
| return NULL; |
| |
| cairo_skia_surface_t *esurf = (cairo_skia_surface_t *) surface; |
| return &_get_image_surface (esurf)->base; |
| } |
| |
| /*** |
| |
| Todo: |
| |
| *** Skia: |
| |
| - mask() |
| |
| *** Sk: |
| |
| High: |
| - antialiased clipping? |
| |
| Medium: |
| - implement clip path reset (to avoid restore/save) |
| - implement complex radial patterns (2 centers and 2 radii) |
| |
| Low: |
| - implement EXTEND_NONE |
| |
| ***/ |