1 /* 2 * Copyright (c) 2008, Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 33 #include "AffineTransform.h" 34 #include "BitmapImage.h" 35 #include "BitmapImageSingleFrameSkia.h" 36 #include "FloatConversion.h" 37 #include "FloatRect.h" 38 #include "GLES2Canvas.h" 39 #include "GraphicsContext.h" 40 #include "Logging.h" 41 #include "NativeImageSkia.h" 42 #include "PlatformContextSkia.h" 43 #include "PlatformString.h" 44 #include "SkPixelRef.h" 45 #include "SkRect.h" 46 #include "SkShader.h" 47 #include "SkiaUtils.h" 48 #include "Texture.h" 49 50 #include "skia/ext/image_operations.h" 51 #include "skia/ext/platform_canvas.h" 52 53 namespace WebCore { 54 55 // Used by computeResamplingMode to tell how bitmaps should be resampled. 56 enum ResamplingMode { 57 // Nearest neighbor resampling. Used when we detect that the page is 58 // trying to make a pattern by stretching a small bitmap very large. 59 RESAMPLE_NONE, 60 61 // Default skia resampling. Used for large growing of images where high 62 // quality resampling doesn't get us very much except a slowdown. 63 RESAMPLE_LINEAR, 64 65 // High quality resampling. 66 RESAMPLE_AWESOME, 67 }; 68 69 #if !ENABLE(SKIA_GPU) 70 static ResamplingMode computeResamplingMode(PlatformContextSkia* platformContext, const NativeImageSkia& bitmap, int srcWidth, int srcHeight, float destWidth, float destHeight) 71 { 72 if (platformContext->hasImageResamplingHint()) { 73 IntSize srcSize; 74 FloatSize dstSize; 75 platformContext->getImageResamplingHint(&srcSize, &dstSize); 76 srcWidth = srcSize.width(); 77 srcHeight = srcSize.height(); 78 destWidth = dstSize.width(); 79 destHeight = dstSize.height(); 80 } 81 82 int destIWidth = static_cast<int>(destWidth); 83 int destIHeight = static_cast<int>(destHeight); 84 85 // The percent change below which we will not resample. This usually means 86 // an off-by-one error on the web page, and just doing nearest neighbor 87 // sampling is usually good enough. 88 const float kFractionalChangeThreshold = 0.025f; 89 90 // Images smaller than this in either direction are considered "small" and 91 // are not resampled ever (see below). 92 const int kSmallImageSizeThreshold = 8; 93 94 // The amount an image can be stretched in a single direction before we 95 // say that it is being stretched so much that it must be a line or 96 // background that doesn't need resampling. 97 const float kLargeStretch = 3.0f; 98 99 // Figure out if we should resample this image. We try to prune out some 100 // common cases where resampling won't give us anything, since it is much 101 // slower than drawing stretched. 102 if (srcWidth == destIWidth && srcHeight == destIHeight) { 103 // We don't need to resample if the source and destination are the same. 104 return RESAMPLE_NONE; 105 } 106 107 if (srcWidth <= kSmallImageSizeThreshold 108 || srcHeight <= kSmallImageSizeThreshold 109 || destWidth <= kSmallImageSizeThreshold 110 || destHeight <= kSmallImageSizeThreshold) { 111 // Never resample small images. These are often used for borders and 112 // rules (think 1x1 images used to make lines). 113 return RESAMPLE_NONE; 114 } 115 116 if (srcHeight * kLargeStretch <= destHeight || srcWidth * kLargeStretch <= destWidth) { 117 // Large image detected. 118 119 // Don't resample if it is being stretched a lot in only one direction. 120 // This is trying to catch cases where somebody has created a border 121 // (which might be large) and then is stretching it to fill some part 122 // of the page. 123 if (srcWidth == destWidth || srcHeight == destHeight) 124 return RESAMPLE_NONE; 125 126 // The image is growing a lot and in more than one direction. Resampling 127 // is slow and doesn't give us very much when growing a lot. 128 return RESAMPLE_LINEAR; 129 } 130 131 if ((fabs(destWidth - srcWidth) / srcWidth < kFractionalChangeThreshold) 132 && (fabs(destHeight - srcHeight) / srcHeight < kFractionalChangeThreshold)) { 133 // It is disappointingly common on the web for image sizes to be off by 134 // one or two pixels. We don't bother resampling if the size difference 135 // is a small fraction of the original size. 136 return RESAMPLE_NONE; 137 } 138 139 // When the image is not yet done loading, use linear. We don't cache the 140 // partially resampled images, and as they come in incrementally, it causes 141 // us to have to resample the whole thing every time. 142 if (!bitmap.isDataComplete()) 143 return RESAMPLE_LINEAR; 144 145 // Everything else gets resampled. 146 // If the platform context permits high quality interpolation, use it. 147 // High quality interpolation only enabled for scaling and translation. 148 if (platformContext->interpolationQuality() == InterpolationHigh 149 && !(platformContext->canvas()->getTotalMatrix().getType() & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask))) 150 return RESAMPLE_AWESOME; 151 152 return RESAMPLE_LINEAR; 153 } 154 #endif 155 156 // Draws the given bitmap to the given canvas. The subset of the source bitmap 157 // identified by src_rect is drawn to the given destination rect. The bitmap 158 // will be resampled to resample_width * resample_height (this is the size of 159 // the whole image, not the subset). See shouldResampleBitmap for more. 160 // 161 // This does a lot of computation to resample only the portion of the bitmap 162 // that will only be drawn. This is critical for performance since when we are 163 // scrolling, for example, we are only drawing a small strip of the image. 164 // Resampling the whole image every time is very slow, so this speeds up things 165 // dramatically. 166 static void drawResampledBitmap(SkCanvas& canvas, SkPaint& paint, const NativeImageSkia& bitmap, const SkIRect& srcIRect, const SkRect& destRect) 167 { 168 // First get the subset we need. This is efficient and does not copy pixels. 169 SkBitmap subset; 170 bitmap.extractSubset(&subset, srcIRect); 171 SkRect srcRect; 172 srcRect.set(srcIRect); 173 174 // Whether we're doing a subset or using the full source image. 175 bool srcIsFull = srcIRect.fLeft == 0 && srcIRect.fTop == 0 176 && srcIRect.width() == bitmap.width() 177 && srcIRect.height() == bitmap.height(); 178 179 // We will always draw in integer sizes, so round the destination rect. 180 SkIRect destRectRounded; 181 destRect.round(&destRectRounded); 182 SkIRect resizedImageRect = // Represents the size of the resized image. 183 { 0, 0, destRectRounded.width(), destRectRounded.height() }; 184 185 // Apply forward transform to destRect to estimate required size of 186 // re-sampled bitmap, and use only in calls required to resize, or that 187 // check for the required size. 188 SkRect destRectTransformed; 189 canvas.getTotalMatrix().mapRect(&destRectTransformed, destRect); 190 SkIRect destRectTransformedRounded; 191 destRectTransformed.round(&destRectTransformedRounded); 192 193 if (srcIsFull && bitmap.hasResizedBitmap(destRectTransformedRounded.width(), destRectTransformedRounded.height())) { 194 // Yay, this bitmap frame already has a resized version. 195 SkBitmap resampled = bitmap.resizedBitmap(destRectTransformedRounded.width(), destRectTransformedRounded.height()); 196 canvas.drawBitmapRect(resampled, 0, destRect, &paint); 197 return; 198 } 199 200 // Compute the visible portion of our rect. 201 // We also need to compute the transformed portion of the 202 // visible portion for use below. 203 SkRect destBitmapSubsetSk; 204 ClipRectToCanvas(canvas, destRect, &destBitmapSubsetSk); 205 SkRect destBitmapSubsetTransformed; 206 canvas.getTotalMatrix().mapRect(&destBitmapSubsetTransformed, destBitmapSubsetSk); 207 destBitmapSubsetSk.offset(-destRect.fLeft, -destRect.fTop); 208 SkIRect destBitmapSubsetTransformedRounded; 209 destBitmapSubsetTransformed.round(&destBitmapSubsetTransformedRounded); 210 destBitmapSubsetTransformedRounded.offset(-destRectTransformedRounded.fLeft, -destRectTransformedRounded.fTop); 211 212 // The matrix inverting, etc. could have introduced rounding error which 213 // causes the bounds to be outside of the resized bitmap. We round outward 214 // so we always lean toward it being larger rather than smaller than we 215 // need, and then clamp to the bitmap bounds so we don't get any invalid 216 // data. 217 SkIRect destBitmapSubsetSkI; 218 destBitmapSubsetSk.roundOut(&destBitmapSubsetSkI); 219 if (!destBitmapSubsetSkI.intersect(resizedImageRect)) 220 return; // Resized image does not intersect. 221 222 if (srcIsFull && bitmap.shouldCacheResampling( 223 resizedImageRect.width(), 224 resizedImageRect.height(), 225 destBitmapSubsetSkI.width(), 226 destBitmapSubsetSkI.height())) { 227 // We're supposed to resize the entire image and cache it, even though 228 // we don't need all of it. 229 SkBitmap resampled = bitmap.resizedBitmap(destRectTransformedRounded.width(), 230 destRectTransformedRounded.height()); 231 canvas.drawBitmapRect(resampled, 0, destRect, &paint); 232 } else { 233 // We should only resize the exposed part of the bitmap to do the 234 // minimal possible work. 235 236 // Resample the needed part of the image. 237 // Transforms above plus rounding may cause destBitmapSubsetTransformedRounded 238 // to go outside the image, so need to clip to avoid problems. 239 if (destBitmapSubsetTransformedRounded.intersect(0, 0, 240 destRectTransformedRounded.width(), destRectTransformedRounded.height())) { 241 242 SkBitmap resampled = skia::ImageOperations::Resize(subset, 243 skia::ImageOperations::RESIZE_LANCZOS3, 244 destRectTransformedRounded.width(), destRectTransformedRounded.height(), 245 destBitmapSubsetTransformedRounded); 246 247 // Compute where the new bitmap should be drawn. Since our new bitmap 248 // may be smaller than the original, we have to shift it over by the 249 // same amount that we cut off the top and left. 250 destBitmapSubsetSkI.offset(destRect.fLeft, destRect.fTop); 251 SkRect offsetDestRect; 252 offsetDestRect.set(destBitmapSubsetSkI); 253 254 canvas.drawBitmapRect(resampled, 0, offsetDestRect, &paint); 255 } 256 } 257 } 258 259 static void paintSkBitmap(PlatformContextSkia* platformContext, const NativeImageSkia& bitmap, const SkIRect& srcRect, const SkRect& destRect, const SkXfermode::Mode& compOp) 260 { 261 SkPaint paint; 262 paint.setXfermodeMode(compOp); 263 paint.setFilterBitmap(true); 264 paint.setAlpha(platformContext->getNormalizedAlpha()); 265 paint.setLooper(platformContext->getDrawLooper()); 266 267 SkCanvas* canvas = platformContext->canvas(); 268 269 ResamplingMode resampling; 270 #if ENABLE(SKIA_GPU) 271 resampling = RESAMPLE_LINEAR; 272 #else 273 resampling = platformContext->printing() ? RESAMPLE_NONE : 274 computeResamplingMode(platformContext, bitmap, srcRect.width(), srcRect.height(), 275 SkScalarToFloat(destRect.width()), 276 SkScalarToFloat(destRect.height())); 277 #endif 278 if (resampling == RESAMPLE_AWESOME) { 279 drawResampledBitmap(*canvas, paint, bitmap, srcRect, destRect); 280 } else { 281 // No resampling necessary, we can just draw the bitmap. We want to 282 // filter it if we decided to do linear interpolation above, or if there 283 // is something interesting going on with the matrix (like a rotation). 284 // Note: for serialization, we will want to subset the bitmap first so 285 // we don't send extra pixels. 286 canvas->drawBitmapRect(bitmap, &srcRect, destRect, &paint); 287 } 288 } 289 290 // Transforms the given dimensions with the given matrix. Used to see how big 291 // images will be once transformed. 292 static void TransformDimensions(const SkMatrix& matrix, float srcWidth, float srcHeight, float* destWidth, float* destHeight) { 293 // Transform 3 points to see how long each side of the bitmap will be. 294 SkPoint src_points[3]; // (0, 0), (width, 0), (0, height). 295 src_points[0].set(0, 0); 296 src_points[1].set(SkFloatToScalar(srcWidth), 0); 297 src_points[2].set(0, SkFloatToScalar(srcHeight)); 298 299 // Now measure the length of the two transformed vectors relative to the 300 // transformed origin to see how big the bitmap will be. Note: for skews, 301 // this isn't the best thing, but we don't have skews. 302 SkPoint dest_points[3]; 303 matrix.mapPoints(dest_points, src_points, 3); 304 *destWidth = SkScalarToFloat((dest_points[1] - dest_points[0]).length()); 305 *destHeight = SkScalarToFloat((dest_points[2] - dest_points[0]).length()); 306 } 307 308 // A helper method for translating negative width and height values. 309 FloatRect normalizeRect(const FloatRect& rect) 310 { 311 FloatRect norm = rect; 312 if (norm.width() < 0) { 313 norm.setX(norm.x() + norm.width()); 314 norm.setWidth(-norm.width()); 315 } 316 if (norm.height() < 0) { 317 norm.setY(norm.y() + norm.height()); 318 norm.setHeight(-norm.height()); 319 } 320 return norm; 321 } 322 323 bool FrameData::clear(bool clearMetadata) 324 { 325 if (clearMetadata) 326 m_haveMetadata = false; 327 328 if (m_frame) { 329 // ImageSource::createFrameAtIndex() allocated |m_frame| and passed 330 // ownership to BitmapImage; we must delete it here. 331 delete m_frame; 332 m_frame = 0; 333 return true; 334 } 335 return false; 336 } 337 338 void Image::drawPattern(GraphicsContext* context, 339 const FloatRect& floatSrcRect, 340 const AffineTransform& patternTransform, 341 const FloatPoint& phase, 342 ColorSpace styleColorSpace, 343 CompositeOperator compositeOp, 344 const FloatRect& destRect) 345 { 346 FloatRect normSrcRect = normalizeRect(floatSrcRect); 347 if (destRect.isEmpty() || normSrcRect.isEmpty()) 348 return; // nothing to draw 349 350 NativeImageSkia* bitmap = nativeImageForCurrentFrame(); 351 if (!bitmap) 352 return; 353 354 // This is a very inexpensive operation. It will generate a new bitmap but 355 // it will internally reference the old bitmap's pixels, adjusting the row 356 // stride so the extra pixels appear as padding to the subsetted bitmap. 357 SkBitmap srcSubset; 358 SkIRect srcRect = enclosingIntRect(normSrcRect); 359 bitmap->extractSubset(&srcSubset, srcRect); 360 361 SkBitmap resampled; 362 SkShader* shader; 363 364 // Figure out what size the bitmap will be in the destination. The 365 // destination rect is the bounds of the pattern, we need to use the 366 // matrix to see how bit it will be. 367 float destBitmapWidth, destBitmapHeight; 368 TransformDimensions(patternTransform, srcRect.width(), srcRect.height(), 369 &destBitmapWidth, &destBitmapHeight); 370 371 // Compute the resampling mode. 372 ResamplingMode resampling; 373 #if ENABLE(SKIA_GPU) 374 resampling = RESAMPLE_LINEAR; 375 #else 376 if (context->platformContext()->printing()) 377 resampling = RESAMPLE_LINEAR; 378 else { 379 resampling = computeResamplingMode(context->platformContext(), *bitmap, 380 srcRect.width(), srcRect.height(), 381 destBitmapWidth, destBitmapHeight); 382 } 383 #endif 384 385 // Load the transform WebKit requested. 386 SkMatrix matrix(patternTransform); 387 388 if (resampling == RESAMPLE_AWESOME) { 389 // Do nice resampling. 390 SkBitmap resampled; 391 int width = static_cast<int>(destBitmapWidth); 392 int height = static_cast<int>(destBitmapHeight); 393 if (!srcRect.fLeft && !srcRect.fTop 394 && srcRect.fRight == bitmap->width() && srcRect.fBottom == bitmap->height() 395 && (bitmap->hasResizedBitmap(width, height) 396 || bitmap->shouldCacheResampling(width, height, width, height))) { 397 // resizedBitmap() caches resized image. 398 resampled = bitmap->resizedBitmap(width, height); 399 } else { 400 resampled = skia::ImageOperations::Resize(srcSubset, 401 skia::ImageOperations::RESIZE_LANCZOS3, width, height); 402 } 403 shader = SkShader::CreateBitmapShader(resampled, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode); 404 405 // Since we just resized the bitmap, we need to undo the scale set in 406 // the image transform. 407 matrix.setScaleX(SkIntToScalar(1)); 408 matrix.setScaleY(SkIntToScalar(1)); 409 } else { 410 // No need to do nice resampling. 411 shader = SkShader::CreateBitmapShader(srcSubset, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode); 412 } 413 414 // We also need to translate it such that the origin of the pattern is the 415 // origin of the destination rect, which is what WebKit expects. Skia uses 416 // the coordinate system origin as the base for the patter. If WebKit wants 417 // a shifted image, it will shift it from there using the patternTransform. 418 float adjustedX = phase.x() + normSrcRect.x() * 419 narrowPrecisionToFloat(patternTransform.a()); 420 float adjustedY = phase.y() + normSrcRect.y() * 421 narrowPrecisionToFloat(patternTransform.d()); 422 matrix.postTranslate(SkFloatToScalar(adjustedX), 423 SkFloatToScalar(adjustedY)); 424 shader->setLocalMatrix(matrix); 425 426 SkPaint paint; 427 paint.setShader(shader)->unref(); 428 paint.setXfermodeMode(WebCoreCompositeToSkiaComposite(compositeOp)); 429 paint.setFilterBitmap(resampling == RESAMPLE_LINEAR); 430 431 context->platformContext()->paintSkPaint(destRect, paint); 432 } 433 434 static void drawBitmapGLES2(GraphicsContext* ctxt, NativeImageSkia* bitmap, const FloatRect& srcRect, const FloatRect& dstRect, ColorSpace styleColorSpace, CompositeOperator compositeOp) 435 { 436 ctxt->platformContext()->prepareForHardwareDraw(); 437 GLES2Canvas* gpuCanvas = ctxt->platformContext()->gpuCanvas(); 438 Texture* texture = gpuCanvas->getTexture(bitmap); 439 if (!texture) { 440 ASSERT(bitmap->config() == SkBitmap::kARGB_8888_Config); 441 ASSERT(bitmap->rowBytes() == bitmap->width() * 4); 442 texture = gpuCanvas->createTexture(bitmap, Texture::BGRA8, bitmap->width(), bitmap->height()); 443 SkAutoLockPixels lock(*bitmap); 444 ASSERT(bitmap->getPixels()); 445 texture->load(bitmap->getPixels()); 446 } 447 gpuCanvas->drawTexturedRect(texture, srcRect, dstRect, styleColorSpace, compositeOp); 448 } 449 450 // ================================================ 451 // BitmapImage Class 452 // ================================================ 453 454 // FIXME: These should go to BitmapImageSkia.cpp 455 456 void BitmapImage::initPlatformData() 457 { 458 // This is not used. On Mac, the "platform" data is a cache of some OS 459 // specific versions of the image that are created is some cases. These 460 // aren't normally used, it is equivalent to getHBITMAP on Windows, and 461 // the platform data is the cache. 462 } 463 464 void BitmapImage::invalidatePlatformData() 465 { 466 // See initPlatformData above. 467 } 468 469 void BitmapImage::checkForSolidColor() 470 { 471 m_checkedForSolidColor = true; 472 } 473 474 void BitmapImage::draw(GraphicsContext* ctxt, const FloatRect& dstRect, 475 const FloatRect& srcRect, ColorSpace colorSpace, CompositeOperator compositeOp) 476 { 477 if (!m_source.initialized()) 478 return; 479 480 // Spin the animation to the correct frame before we try to draw it, so we 481 // don't draw an old frame and then immediately need to draw a newer one, 482 // causing flicker and wasting CPU. 483 startAnimation(); 484 485 NativeImageSkia* bm = nativeImageForCurrentFrame(); 486 if (!bm) 487 return; // It's too early and we don't have an image yet. 488 489 FloatRect normDstRect = normalizeRect(dstRect); 490 FloatRect normSrcRect = normalizeRect(srcRect); 491 492 if (normSrcRect.isEmpty() || normDstRect.isEmpty()) 493 return; // Nothing to draw. 494 495 if (ctxt->platformContext()->useGPU() && ctxt->platformContext()->canAccelerate()) { 496 drawBitmapGLES2(ctxt, bm, normSrcRect, normDstRect, colorSpace, compositeOp); 497 return; 498 } 499 500 ctxt->platformContext()->prepareForSoftwareDraw(); 501 502 paintSkBitmap(ctxt->platformContext(), 503 *bm, 504 enclosingIntRect(normSrcRect), 505 normDstRect, 506 WebCoreCompositeToSkiaComposite(compositeOp)); 507 } 508 509 // FIXME: These should go into BitmapImageSingleFrameSkia.cpp 510 511 void BitmapImageSingleFrameSkia::draw(GraphicsContext* ctxt, 512 const FloatRect& dstRect, 513 const FloatRect& srcRect, 514 ColorSpace styleColorSpace, 515 CompositeOperator compositeOp) 516 { 517 FloatRect normDstRect = normalizeRect(dstRect); 518 FloatRect normSrcRect = normalizeRect(srcRect); 519 520 if (normSrcRect.isEmpty() || normDstRect.isEmpty()) 521 return; // Nothing to draw. 522 523 if (ctxt->platformContext()->useGPU() && ctxt->platformContext()->canAccelerate()) { 524 drawBitmapGLES2(ctxt, &m_nativeImage, srcRect, dstRect, styleColorSpace, compositeOp); 525 return; 526 } 527 528 ctxt->platformContext()->prepareForSoftwareDraw(); 529 530 paintSkBitmap(ctxt->platformContext(), 531 m_nativeImage, 532 enclosingIntRect(normSrcRect), 533 normDstRect, 534 WebCoreCompositeToSkiaComposite(compositeOp)); 535 } 536 537 BitmapImageSingleFrameSkia::BitmapImageSingleFrameSkia(const SkBitmap& bitmap) 538 : m_nativeImage(bitmap) 539 { 540 } 541 542 PassRefPtr<BitmapImageSingleFrameSkia> BitmapImageSingleFrameSkia::create(const SkBitmap& bitmap, bool copyPixels) 543 { 544 if (copyPixels) { 545 SkBitmap temp; 546 bitmap.copyTo(&temp, bitmap.config()); 547 return adoptRef(new BitmapImageSingleFrameSkia(temp)); 548 } 549 return adoptRef(new BitmapImageSingleFrameSkia(bitmap)); 550 } 551 552 } // namespace WebCore 553