| 1 | /* |
| 2 | * Copyright (C) 2010 Igalia S.L. |
| 3 | * Copyright (C) 2011 ProFUSION embedded systems |
| 4 | * |
| 5 | * Redistribution and use in source and binary forms, with or without |
| 6 | * modification, are permitted provided that the following conditions |
| 7 | * are met: |
| 8 | * 1. Redistributions of source code must retain the above copyright |
| 9 | * notice, this list of conditions and the following disclaimer. |
| 10 | * 2. Redistributions in binary form must reproduce the above copyright |
| 11 | * notice, this list of conditions and the following disclaimer in the |
| 12 | * documentation and/or other materials provided with the distribution. |
| 13 | * |
| 14 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| 15 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 17 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| 18 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 19 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 20 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 21 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 22 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 25 | */ |
| 26 | |
| 27 | #include "config.h" |
| 28 | #include "CairoUtilities.h" |
| 29 | |
| 30 | #if USE(CAIRO) |
| 31 | |
| 32 | #include "AffineTransform.h" |
| 33 | #include "Color.h" |
| 34 | #include "FloatPoint.h" |
| 35 | #include "FloatRect.h" |
| 36 | #include "IntRect.h" |
| 37 | #include "Path.h" |
| 38 | #include "PlatformPathCairo.h" |
| 39 | #include "RefPtrCairo.h" |
| 40 | #include "Region.h" |
| 41 | #include <wtf/Assertions.h> |
| 42 | #include <wtf/NeverDestroyed.h> |
| 43 | #include <wtf/UniqueArray.h> |
| 44 | #include <wtf/Vector.h> |
| 45 | |
| 46 | #if ENABLE(ACCELERATED_2D_CANVAS) |
| 47 | #if USE(EGL) && USE(LIBEPOXY) |
| 48 | #include "EpoxyEGL.h" |
| 49 | #endif |
| 50 | #include <cairo-gl.h> |
| 51 | #endif |
| 52 | |
| 53 | #if OS(WINDOWS) |
| 54 | #include <cairo-win32.h> |
| 55 | #endif |
| 56 | |
| 57 | namespace WebCore { |
| 58 | |
| 59 | #if USE(FREETYPE) && !PLATFORM(GTK) |
| 60 | const cairo_font_options_t* getDefaultCairoFontOptions() |
| 61 | { |
| 62 | static NeverDestroyed<cairo_font_options_t*> options = cairo_font_options_create(); |
| 63 | return options; |
| 64 | } |
| 65 | #endif |
| 66 | |
| 67 | void copyContextProperties(cairo_t* srcCr, cairo_t* dstCr) |
| 68 | { |
| 69 | cairo_set_antialias(dstCr, cairo_get_antialias(srcCr)); |
| 70 | |
| 71 | size_t dashCount = cairo_get_dash_count(srcCr); |
| 72 | Vector<double> dashes(dashCount); |
| 73 | |
| 74 | double offset; |
| 75 | cairo_get_dash(srcCr, dashes.data(), &offset); |
| 76 | cairo_set_dash(dstCr, dashes.data(), dashCount, offset); |
| 77 | cairo_set_line_cap(dstCr, cairo_get_line_cap(srcCr)); |
| 78 | cairo_set_line_join(dstCr, cairo_get_line_join(srcCr)); |
| 79 | cairo_set_line_width(dstCr, cairo_get_line_width(srcCr)); |
| 80 | cairo_set_miter_limit(dstCr, cairo_get_miter_limit(srcCr)); |
| 81 | cairo_set_fill_rule(dstCr, cairo_get_fill_rule(srcCr)); |
| 82 | } |
| 83 | |
| 84 | void setSourceRGBAFromColor(cairo_t* context, const Color& color) |
| 85 | { |
| 86 | if (color.isExtended()) |
| 87 | cairo_set_source_rgba(context, color.asExtended().red(), color.asExtended().green(), color.asExtended().blue(), color.asExtended().alpha()); |
| 88 | else { |
| 89 | float red, green, blue, alpha; |
| 90 | color.getRGBA(red, green, blue, alpha); |
| 91 | cairo_set_source_rgba(context, red, green, blue, alpha); |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | void appendPathToCairoContext(cairo_t* to, cairo_t* from) |
| 96 | { |
| 97 | auto cairoPath = cairo_copy_path(from); |
| 98 | cairo_append_path(to, cairoPath); |
| 99 | cairo_path_destroy(cairoPath); |
| 100 | } |
| 101 | |
| 102 | void setPathOnCairoContext(cairo_t* to, cairo_t* from) |
| 103 | { |
| 104 | cairo_new_path(to); |
| 105 | appendPathToCairoContext(to, from); |
| 106 | } |
| 107 | |
| 108 | void appendWebCorePathToCairoContext(cairo_t* context, const Path& path) |
| 109 | { |
| 110 | if (path.isEmpty()) |
| 111 | return; |
| 112 | appendPathToCairoContext(context, path.platformPath()->context()); |
| 113 | } |
| 114 | |
| 115 | void appendRegionToCairoContext(cairo_t* to, const cairo_region_t* region) |
| 116 | { |
| 117 | if (!region) |
| 118 | return; |
| 119 | |
| 120 | const int rectCount = cairo_region_num_rectangles(region); |
| 121 | for (int i = 0; i < rectCount; ++i) { |
| 122 | cairo_rectangle_int_t rect; |
| 123 | cairo_region_get_rectangle(region, i, &rect); |
| 124 | cairo_rectangle(to, rect.x, rect.y, rect.width, rect.height); |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | static cairo_operator_t toCairoCompositeOperator(CompositeOperator op) |
| 129 | { |
| 130 | switch (op) { |
| 131 | case CompositeClear: |
| 132 | return CAIRO_OPERATOR_CLEAR; |
| 133 | case CompositeCopy: |
| 134 | return CAIRO_OPERATOR_SOURCE; |
| 135 | case CompositeSourceOver: |
| 136 | return CAIRO_OPERATOR_OVER; |
| 137 | case CompositeSourceIn: |
| 138 | return CAIRO_OPERATOR_IN; |
| 139 | case CompositeSourceOut: |
| 140 | return CAIRO_OPERATOR_OUT; |
| 141 | case CompositeSourceAtop: |
| 142 | return CAIRO_OPERATOR_ATOP; |
| 143 | case CompositeDestinationOver: |
| 144 | return CAIRO_OPERATOR_DEST_OVER; |
| 145 | case CompositeDestinationIn: |
| 146 | return CAIRO_OPERATOR_DEST_IN; |
| 147 | case CompositeDestinationOut: |
| 148 | return CAIRO_OPERATOR_DEST_OUT; |
| 149 | case CompositeDestinationAtop: |
| 150 | return CAIRO_OPERATOR_DEST_ATOP; |
| 151 | case CompositeXOR: |
| 152 | return CAIRO_OPERATOR_XOR; |
| 153 | case CompositePlusDarker: |
| 154 | return CAIRO_OPERATOR_DARKEN; |
| 155 | case CompositePlusLighter: |
| 156 | return CAIRO_OPERATOR_ADD; |
| 157 | case CompositeDifference: |
| 158 | return CAIRO_OPERATOR_DIFFERENCE; |
| 159 | default: |
| 160 | return CAIRO_OPERATOR_SOURCE; |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | cairo_operator_t toCairoOperator(CompositeOperator op, BlendMode blendOp) |
| 165 | { |
| 166 | switch (blendOp) { |
| 167 | case BlendMode::Normal: |
| 168 | return toCairoCompositeOperator(op); |
| 169 | case BlendMode::Multiply: |
| 170 | return CAIRO_OPERATOR_MULTIPLY; |
| 171 | case BlendMode::Screen: |
| 172 | return CAIRO_OPERATOR_SCREEN; |
| 173 | case BlendMode::Overlay: |
| 174 | return CAIRO_OPERATOR_OVERLAY; |
| 175 | case BlendMode::Darken: |
| 176 | return CAIRO_OPERATOR_DARKEN; |
| 177 | case BlendMode::Lighten: |
| 178 | return CAIRO_OPERATOR_LIGHTEN; |
| 179 | case BlendMode::ColorDodge: |
| 180 | return CAIRO_OPERATOR_COLOR_DODGE; |
| 181 | case BlendMode::ColorBurn: |
| 182 | return CAIRO_OPERATOR_COLOR_BURN; |
| 183 | case BlendMode::HardLight: |
| 184 | return CAIRO_OPERATOR_HARD_LIGHT; |
| 185 | case BlendMode::SoftLight: |
| 186 | return CAIRO_OPERATOR_SOFT_LIGHT; |
| 187 | case BlendMode::Difference: |
| 188 | return CAIRO_OPERATOR_DIFFERENCE; |
| 189 | case BlendMode::Exclusion: |
| 190 | return CAIRO_OPERATOR_EXCLUSION; |
| 191 | case BlendMode::Hue: |
| 192 | return CAIRO_OPERATOR_HSL_HUE; |
| 193 | case BlendMode::Saturation: |
| 194 | return CAIRO_OPERATOR_HSL_SATURATION; |
| 195 | case BlendMode::Color: |
| 196 | return CAIRO_OPERATOR_HSL_COLOR; |
| 197 | case BlendMode::Luminosity: |
| 198 | return CAIRO_OPERATOR_HSL_LUMINOSITY; |
| 199 | default: |
| 200 | return CAIRO_OPERATOR_OVER; |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | void drawPatternToCairoContext(cairo_t* cr, cairo_surface_t* image, const IntSize& imageSize, const FloatRect& tileRect, |
| 205 | const AffineTransform& patternTransform, const FloatPoint& phase, cairo_operator_t op, const FloatRect& destRect) |
| 206 | { |
| 207 | // Avoid NaN |
| 208 | if (!std::isfinite(phase.x()) || !std::isfinite(phase.y())) |
| 209 | return; |
| 210 | |
| 211 | cairo_save(cr); |
| 212 | |
| 213 | RefPtr<cairo_surface_t> clippedImageSurface = 0; |
| 214 | if (tileRect.size() != imageSize) { |
| 215 | IntRect imageRect = enclosingIntRect(tileRect); |
| 216 | clippedImageSurface = adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, imageRect.width(), imageRect.height())); |
| 217 | RefPtr<cairo_t> clippedImageContext = adoptRef(cairo_create(clippedImageSurface.get())); |
| 218 | cairo_set_source_surface(clippedImageContext.get(), image, -tileRect.x(), -tileRect.y()); |
| 219 | cairo_paint(clippedImageContext.get()); |
| 220 | image = clippedImageSurface.get(); |
| 221 | } |
| 222 | |
| 223 | cairo_pattern_t* pattern = cairo_pattern_create_for_surface(image); |
| 224 | cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); |
| 225 | |
| 226 | // Due to a limitation in pixman, cairo cannot handle transformation matrices with values bigger than 32768. If the value is |
| 227 | // bigger, cairo is not able to paint anything, and this is the reason for the missing backgrounds reported in |
| 228 | // https://bugs.webkit.org/show_bug.cgi?id=154283. |
| 229 | |
| 230 | // When drawing a pattern there are 2 matrices that can overflow this limitation, and they are the current transformation |
| 231 | // matrix (which translates user space coordinates to coordinates of the output device) and the pattern matrix (which translates |
| 232 | // user space coordinates to pattern coordinates). The overflow happens only in the translation components of the matrices. |
| 233 | |
| 234 | // To avoid the problem in the transformation matrix what we do is remove the translation components of the transformation matrix |
| 235 | // and perform the translation by moving the destination rectangle instead. For this, we calculate such a translation amount (dx, dy) |
| 236 | // that its opposite translate (-dx, -dy) will zero the translation components of the transformation matrix. We move the current |
| 237 | // transformation matrix by (-dx, -dy) and move the destination rectangle by (dx, dy). We also need to apply the same translation to |
| 238 | // the pattern matrix, so we get the same pattern coordinates for the new destination rectangle. (dx, dy) is caclucated by transforming |
| 239 | // the current translation components by the inverse matrix of the current transformation matrix. |
| 240 | |
| 241 | cairo_matrix_t ctm; |
| 242 | cairo_get_matrix(cr, &ctm); |
| 243 | double dx = 0, dy = 0; |
| 244 | cairo_matrix_transform_point(&ctm, &dx, &dy); |
| 245 | cairo_matrix_t inv = ctm; |
| 246 | if (cairo_matrix_invert(&inv) == CAIRO_STATUS_SUCCESS) |
| 247 | cairo_matrix_transform_distance(&inv, &dx, &dy); |
| 248 | |
| 249 | cairo_translate(cr, -dx, -dy); |
| 250 | FloatRect adjustedDestRect(destRect); |
| 251 | adjustedDestRect.move(dx, dy); |
| 252 | |
| 253 | // Regarding the pattern matrix, what we do is reduce the translation component of the matrix taking advantage of the fact that we |
| 254 | // are drawing a repeated pattern. This means that, assuming that (w, h) is the size of the pattern, samplig it at (x, y) is the same |
| 255 | // than sampling it at (x mod w, y mod h), so we transform the translation component of the pattern matrix in that way. |
| 256 | |
| 257 | cairo_matrix_t patternMatrix = toCairoMatrix(patternTransform); |
| 258 | // dx and dy are added here as well to compensate the previous translation of the destination rectangle. |
| 259 | double phaseOffsetX = phase.x() + tileRect.x() * patternTransform.a() + dx; |
| 260 | double phaseOffsetY = phase.y() + tileRect.y() * patternTransform.d() + dy; |
| 261 | // this is where we perform the (x mod w, y mod h) metioned above, but with floats instead of integers. |
| 262 | phaseOffsetX -= std::trunc(phaseOffsetX / (tileRect.width() * patternTransform.a())) * tileRect.width() * patternTransform.a(); |
| 263 | phaseOffsetY -= std::trunc(phaseOffsetY / (tileRect.height() * patternTransform.d())) * tileRect.height() * patternTransform.d(); |
| 264 | cairo_matrix_t phaseMatrix = {1, 0, 0, 1, phaseOffsetX, phaseOffsetY}; |
| 265 | cairo_matrix_t combined; |
| 266 | cairo_matrix_multiply(&combined, &patternMatrix, &phaseMatrix); |
| 267 | cairo_matrix_invert(&combined); |
| 268 | cairo_pattern_set_matrix(pattern, &combined); |
| 269 | |
| 270 | cairo_set_operator(cr, op); |
| 271 | cairo_set_source(cr, pattern); |
| 272 | cairo_pattern_destroy(pattern); |
| 273 | cairo_rectangle(cr, adjustedDestRect.x(), adjustedDestRect.y(), adjustedDestRect.width(), adjustedDestRect.height()); |
| 274 | cairo_fill(cr); |
| 275 | |
| 276 | cairo_restore(cr); |
| 277 | } |
| 278 | |
| 279 | RefPtr<cairo_surface_t> copyCairoImageSurface(cairo_surface_t* originalSurface) |
| 280 | { |
| 281 | // Cairo doesn't provide a way to copy a cairo_surface_t. |
| 282 | // See http://lists.cairographics.org/archives/cairo/2007-June/010877.html |
| 283 | // Once cairo provides the way, use the function instead of this. |
| 284 | IntSize size = cairoSurfaceSize(originalSurface); |
| 285 | RefPtr<cairo_surface_t> newSurface = adoptRef(cairo_surface_create_similar(originalSurface, |
| 286 | cairo_surface_get_content(originalSurface), size.width(), size.height())); |
| 287 | |
| 288 | RefPtr<cairo_t> cr = adoptRef(cairo_create(newSurface.get())); |
| 289 | cairo_set_source_surface(cr.get(), originalSurface, 0, 0); |
| 290 | cairo_set_operator(cr.get(), CAIRO_OPERATOR_SOURCE); |
| 291 | cairo_paint(cr.get()); |
| 292 | return newSurface; |
| 293 | } |
| 294 | |
| 295 | void copyRectFromCairoSurfaceToContext(cairo_surface_t* from, cairo_t* to, const IntSize& offset, const IntRect& rect) |
| 296 | { |
| 297 | cairo_set_source_surface(to, from, offset.width(), offset.height()); |
| 298 | cairo_rectangle(to, rect.x(), rect.y(), rect.width(), rect.height()); |
| 299 | cairo_fill(to); |
| 300 | } |
| 301 | |
| 302 | void copyRectFromOneSurfaceToAnother(cairo_surface_t* from, cairo_surface_t* to, const IntSize& sourceOffset, const IntRect& rect, const IntSize& destOffset, cairo_operator_t cairoOperator) |
| 303 | { |
| 304 | RefPtr<cairo_t> context = adoptRef(cairo_create(to)); |
| 305 | cairo_translate(context.get(), destOffset.width(), destOffset.height()); |
| 306 | cairo_set_operator(context.get(), cairoOperator); |
| 307 | copyRectFromCairoSurfaceToContext(from, context.get(), sourceOffset, rect); |
| 308 | } |
| 309 | |
| 310 | IntSize cairoSurfaceSize(cairo_surface_t* surface) |
| 311 | { |
| 312 | switch (cairo_surface_get_type(surface)) { |
| 313 | case CAIRO_SURFACE_TYPE_IMAGE: |
| 314 | return IntSize(cairo_image_surface_get_width(surface), cairo_image_surface_get_height(surface)); |
| 315 | #if ENABLE(ACCELERATED_2D_CANVAS) |
| 316 | case CAIRO_SURFACE_TYPE_GL: |
| 317 | return IntSize(cairo_gl_surface_get_width(surface), cairo_gl_surface_get_height(surface)); |
| 318 | #endif |
| 319 | #if OS(WINDOWS) |
| 320 | case CAIRO_SURFACE_TYPE_WIN32: |
| 321 | surface = cairo_win32_surface_get_image(surface); |
| 322 | ASSERT(surface); |
| 323 | ASSERT(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE); |
| 324 | return IntSize(cairo_image_surface_get_width(surface), cairo_image_surface_get_height(surface)); |
| 325 | #endif |
| 326 | default: |
| 327 | ASSERT_NOT_REACHED(); |
| 328 | return IntSize(); |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | void flipImageSurfaceVertically(cairo_surface_t* surface) |
| 333 | { |
| 334 | ASSERT(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE); |
| 335 | |
| 336 | IntSize size = cairoSurfaceSize(surface); |
| 337 | ASSERT(!size.isEmpty()); |
| 338 | |
| 339 | int stride = cairo_image_surface_get_stride(surface); |
| 340 | int halfHeight = size.height() / 2; |
| 341 | |
| 342 | uint8_t* source = static_cast<uint8_t*>(cairo_image_surface_get_data(surface)); |
| 343 | auto tmp = makeUniqueArray<uint8_t>(stride); |
| 344 | |
| 345 | for (int i = 0; i < halfHeight; ++i) { |
| 346 | uint8_t* top = source + (i * stride); |
| 347 | uint8_t* bottom = source + ((size.height()-i-1) * stride); |
| 348 | |
| 349 | memcpy(tmp.get(), top, stride); |
| 350 | memcpy(top, bottom, stride); |
| 351 | memcpy(bottom, tmp.get(), stride); |
| 352 | } |
| 353 | } |
| 354 | |
| 355 | void cairoSurfaceSetDeviceScale(cairo_surface_t* surface, double xScale, double yScale) |
| 356 | { |
| 357 | // This function was added pretty much simultaneous to when 1.13 was branched. |
| 358 | #if HAVE(CAIRO_SURFACE_SET_DEVICE_SCALE) |
| 359 | cairo_surface_set_device_scale(surface, xScale, yScale); |
| 360 | #else |
| 361 | UNUSED_PARAM(surface); |
| 362 | ASSERT_UNUSED(xScale, 1 == xScale); |
| 363 | ASSERT_UNUSED(yScale, 1 == yScale); |
| 364 | #endif |
| 365 | } |
| 366 | |
| 367 | void cairoSurfaceGetDeviceScale(cairo_surface_t* surface, double& xScale, double& yScale) |
| 368 | { |
| 369 | #if HAVE(CAIRO_SURFACE_SET_DEVICE_SCALE) |
| 370 | cairo_surface_get_device_scale(surface, &xScale, &yScale); |
| 371 | #else |
| 372 | UNUSED_PARAM(surface); |
| 373 | xScale = 1; |
| 374 | yScale = 1; |
| 375 | #endif |
| 376 | } |
| 377 | |
| 378 | RefPtr<cairo_region_t> toCairoRegion(const Region& region) |
| 379 | { |
| 380 | RefPtr<cairo_region_t> cairoRegion = adoptRef(cairo_region_create()); |
| 381 | for (const auto& rect : region.rects()) { |
| 382 | cairo_rectangle_int_t cairoRect = rect; |
| 383 | cairo_region_union_rectangle(cairoRegion.get(), &cairoRect); |
| 384 | } |
| 385 | return cairoRegion; |
| 386 | } |
| 387 | |
| 388 | cairo_matrix_t toCairoMatrix(const AffineTransform& transform) |
| 389 | { |
| 390 | return cairo_matrix_t { transform.a(), transform.b(), transform.c(), transform.d(), transform.e(), transform.f() }; |
| 391 | } |
| 392 | |
| 393 | } // namespace WebCore |
| 394 | |
| 395 | #endif // USE(CAIRO) |
| 396 | |