// Draws the given bitmap to the given canvas. The subset of the source bitmap // identified by src_rect is drawn to the given destination rect. The bitmap // will be resampled to resample_width * resample_height (this is the size of // the whole image, not the subset). See shouldResampleBitmap for more. // // This does a lot of computation to resample only the portion of the bitmap // that will only be drawn. This is critical for performance since when we are // scrolling, for example, we are only drawing a small strip of the image. // Resampling the whole image every time is very slow, so this speeds up things // dramatically. // // Note: this code is only used when the canvas transformation is limited to // scaling or translation. static void drawResampledBitmap(SkCanvas& canvas, SkPaint& paint, const NativeImageSkia& bitmap, const SkIRect& srcIRect, const SkRect& destRect) { #if PLATFORM(CHROMIUM) TRACE_EVENT0("skia", "drawResampledBitmap"); #endif // Apply forward transform to destRect to estimate required size of // re-sampled bitmap, and use only in calls required to resize, or that // check for the required size. SkRect destRectTransformed; canvas.getTotalMatrix().mapRect(&destRectTransformed, destRect); SkIRect destRectTransformedRounded; destRectTransformed.round(&destRectTransformedRounded); // Compute the visible portion of our rect. SkRect destRectVisibleSubset; ClipRectToCanvas(canvas, destRect, &destRectVisibleSubset); // ClipRectToCanvas often overshoots, resulting in a larger region than our // original destRect. Intersecting gets us back inside. if (!destRectVisibleSubset.intersect(destRect)) return; // Nothing visible in destRect. // Compute the image-relative (bitmap space) subset. SkRect destBitmapSubset = destRectVisibleSubset; destBitmapSubset.offset(-destRect.x(), -destRect.y()); // Scale the subset to the requested size. The canvas scale can be negative, // but the resampling code is only interested in positive scaling in its normal space. SkMatrix subsetTransform; subsetTransform.setScale(SkScalarAbs(canvas.getTotalMatrix().getScaleX()), SkScalarAbs(canvas.getTotalMatrix().getScaleY())); SkRect destBitmapSubsetTransformed; subsetTransform.mapRect(&destBitmapSubsetTransformed, destBitmapSubset); SkIRect destBitmapSubsetTransformedRounded; destBitmapSubsetTransformed.round(&destBitmapSubsetTransformedRounded); // Transforms above plus rounding may cause destBitmapSubsetTransformedRounded // to go outside the image, so need to clip to avoid problems. if (!destBitmapSubsetTransformedRounded.intersect( 0, 0, destRectTransformedRounded.width(), destRectTransformedRounded.height())) return; // Image is not visible. SkBitmap resampled = bitmap.resizedBitmap(srcIRect, destRectTransformedRounded.width(), destRectTransformedRounded.height(), destBitmapSubsetTransformedRounded); canvas.drawBitmapRect(resampled, 0, destRectVisibleSubset, &paint); }
// Draws the given bitmap to the given canvas. The subset of the source bitmap // identified by src_rect is drawn to the given destination rect. The bitmap // will be resampled to resample_width * resample_height (this is the size of // the whole image, not the subset). See shouldResampleBitmap for more. // // This does a lot of computation to resample only the portion of the bitmap // that will only be drawn. This is critical for performance since when we are // scrolling, for example, we are only drawing a small strip of the image. // Resampling the whole image every time is very slow, so this speeds up things // dramatically. // // Note: this code is only used when the canvas transformation is limited to // scaling or translation. static void drawResampledBitmap(SkCanvas& canvas, SkPaint& paint, const NativeImageSkia& bitmap, const SkIRect& srcIRect, const SkRect& destRect) { #if PLATFORM(CHROMIUM) TRACE_EVENT("drawResampledBitmap", &canvas, 0); #endif // Apply forward transform to destRect to estimate required size of // re-sampled bitmap, and use only in calls required to resize, or that // check for the required size. SkRect destRectTransformed; canvas.getTotalMatrix().mapRect(&destRectTransformed, destRect); SkIRect destRectTransformedRounded; destRectTransformed.round(&destRectTransformedRounded); // Compute the visible portion of our rect. SkRect destRectVisibleSubset; ClipRectToCanvas(canvas, destRect, &destRectVisibleSubset); // ClipRectToCanvas often overshoots, resulting in a larger region than our // original destRect. Intersecting gets us back inside. if (!destRectVisibleSubset.intersect(destRect)) return; // Nothing visible in destRect. // Compute the transformed (screen space) portion of the visible portion for // use below. SkRect destRectVisibleSubsetTransformed; canvas.getTotalMatrix().mapRect(&destRectVisibleSubsetTransformed, destRectVisibleSubset); SkRect destBitmapSubsetTransformed = destRectVisibleSubsetTransformed; destBitmapSubsetTransformed.offset(-destRectTransformed.fLeft, -destRectTransformed.fTop); SkIRect destBitmapSubsetTransformedRounded; destBitmapSubsetTransformed.round(&destBitmapSubsetTransformedRounded); // Transforms above plus rounding may cause destBitmapSubsetTransformedRounded // to go outside the image, so need to clip to avoid problems. if (!destBitmapSubsetTransformedRounded.intersect( 0, 0, destRectTransformedRounded.width(), destRectTransformedRounded.height())) return; // Image is not visible. SkBitmap resampled = bitmap.resizedBitmap(srcIRect, destRectTransformedRounded.width(), destRectTransformedRounded.height(), destBitmapSubsetTransformedRounded); canvas.drawBitmapRect(resampled, 0, destRectVisibleSubset, &paint); }
// This function is used to scale an image and extract a scaled fragment. // // ALGORITHM // // Because the scaled image size has to be integers, we approximate the real // scale with the following formula (only X direction is shown): // // scaledImageWidth = round(scaleX * imageRect.width()) // approximateScaleX = scaledImageWidth / imageRect.width() // // With this method we maintain a constant scale factor among fragments in // the scaled image. This allows fragments to stitch together to form the // full scaled image. The downside is there will be a small difference // between |scaleX| and |approximateScaleX|. // // A scaled image fragment is identified by: // // - Scaled image size // - Scaled image fragment rectangle (IntRect) // // Scaled image size has been determined and the next step is to compute the // rectangle for the scaled image fragment which needs to be an IntRect. // // scaledSrcRect = srcRect * (approximateScaleX, approximateScaleY) // enclosingScaledSrcRect = enclosingIntRect(scaledSrcRect) // // Finally we extract the scaled image fragment using // (scaledImageSize, enclosingScaledSrcRect). // static SkBitmap extractScaledImageFragment(const NativeImageSkia& bitmap, const SkRect& srcRect, float scaleX, float scaleY, SkRect* scaledSrcRect, SkIRect* enclosingScaledSrcRect) { SkISize imageSize = SkISize::Make(bitmap.bitmap().width(), bitmap.bitmap().height()); SkISize scaledImageSize = SkISize::Make(clampToInteger(roundf(imageSize.width() * scaleX)), clampToInteger(roundf(imageSize.height() * scaleY))); SkRect imageRect = SkRect::MakeWH(imageSize.width(), imageSize.height()); SkRect scaledImageRect = SkRect::MakeWH(scaledImageSize.width(), scaledImageSize.height()); SkMatrix scaleTransform; scaleTransform.setRectToRect(imageRect, scaledImageRect, SkMatrix::kFill_ScaleToFit); scaleTransform.mapRect(scaledSrcRect, srcRect); scaledSrcRect->intersect(scaledImageRect); *enclosingScaledSrcRect = enclosingIntRect(*scaledSrcRect); // |enclosingScaledSrcRect| can be larger than |scaledImageSize| because // of float inaccuracy so clip to get inside. enclosingScaledSrcRect->intersect(SkIRect::MakeSize(scaledImageSize)); return bitmap.resizedBitmap(scaledImageSize, *enclosingScaledSrcRect); }
void Image::drawPattern(GraphicsContext* context, const FloatRect& floatSrcRect, const AffineTransform& patternTransform, const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator compositeOp, const FloatRect& destRect) { FloatRect normSrcRect = normalizeRect(floatSrcRect); if (destRect.isEmpty() || normSrcRect.isEmpty()) return; // nothing to draw NativeImageSkia* bitmap = nativeImageForCurrentFrame(); if (!bitmap) return; SkIRect srcRect = enclosingIntRect(normSrcRect); // Figure out what size the bitmap will be in the destination. The // destination rect is the bounds of the pattern, we need to use the // matrix to see how big it will be. float destBitmapWidth, destBitmapHeight; TransformDimensions(patternTransform, srcRect.width(), srcRect.height(), &destBitmapWidth, &destBitmapHeight); // Compute the resampling mode. ResamplingMode resampling; if (context->platformContext()->isAccelerated() || context->platformContext()->printing()) resampling = RESAMPLE_LINEAR; else resampling = computeResamplingMode(context->platformContext(), *bitmap, srcRect.width(), srcRect.height(), destBitmapWidth, destBitmapHeight); // Load the transform WebKit requested. SkMatrix matrix(patternTransform); SkShader* shader; if (resampling == RESAMPLE_AWESOME) { // Do nice resampling. int width = static_cast<int>(destBitmapWidth); int height = static_cast<int>(destBitmapHeight); SkBitmap resampled = bitmap->resizedBitmap(srcRect, width, height); shader = SkShader::CreateBitmapShader(resampled, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode); // Since we just resized the bitmap, we need to undo the scale set in // the image transform. matrix.setScaleX(SkIntToScalar(1)); matrix.setScaleY(SkIntToScalar(1)); } else { // No need to do nice resampling. SkBitmap srcSubset; bitmap->bitmap().extractSubset(&srcSubset, srcRect); shader = SkShader::CreateBitmapShader(srcSubset, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode); } // We also need to translate it such that the origin of the pattern is the // origin of the destination rect, which is what WebKit expects. Skia uses // the coordinate system origin as the base for the patter. If WebKit wants // a shifted image, it will shift it from there using the patternTransform. float adjustedX = phase.x() + normSrcRect.x() * narrowPrecisionToFloat(patternTransform.a()); float adjustedY = phase.y() + normSrcRect.y() * narrowPrecisionToFloat(patternTransform.d()); matrix.postTranslate(SkFloatToScalar(adjustedX), SkFloatToScalar(adjustedY)); shader->setLocalMatrix(matrix); SkPaint paint; paint.setShader(shader)->unref(); paint.setXfermodeMode(WebCoreCompositeToSkiaComposite(compositeOp)); paint.setFilterBitmap(resampling == RESAMPLE_LINEAR); context->platformContext()->paintSkPaint(destRect, paint); }
// Draws the given bitmap to the given canvas. The subset of the source bitmap // identified by src_rect is drawn to the given destination rect. The bitmap // will be resampled to resample_width * resample_height (this is the size of // the whole image, not the subset). See shouldResampleBitmap for more. // // This does a lot of computation to resample only the portion of the bitmap // that will only be drawn. This is critical for performance since when we are // scrolling, for example, we are only drawing a small strip of the image. // Resampling the whole image every time is very slow, so this speeds up things // dramatically. static void drawResampledBitmap(SkCanvas& canvas, SkPaint& paint, const NativeImageSkia& bitmap, const SkIRect& srcIRect, const SkRect& destRect) { // First get the subset we need. This is efficient and does not copy pixels. SkBitmap subset; bitmap.extractSubset(&subset, srcIRect); SkRect srcRect; srcRect.set(srcIRect); // Whether we're doing a subset or using the full source image. bool srcIsFull = srcIRect.fLeft == 0 && srcIRect.fTop == 0 && srcIRect.width() == bitmap.width() && srcIRect.height() == bitmap.height(); // We will always draw in integer sizes, so round the destination rect. SkIRect destRectRounded; destRect.round(&destRectRounded); SkIRect resizedImageRect = // Represents the size of the resized image. { 0, 0, destRectRounded.width(), destRectRounded.height() }; if (srcIsFull && bitmap.hasResizedBitmap(destRectRounded.width(), destRectRounded.height())) { // Yay, this bitmap frame already has a resized version. SkBitmap resampled = bitmap.resizedBitmap(destRectRounded.width(), destRectRounded.height()); canvas.drawBitmapRect(resampled, 0, destRect, &paint); return; } // Compute the visible portion of our rect. SkRect destBitmapSubsetSk; ClipRectToCanvas(canvas, destRect, &destBitmapSubsetSk); destBitmapSubsetSk.offset(-destRect.fLeft, -destRect.fTop); // The matrix inverting, etc. could have introduced rounding error which // causes the bounds to be outside of the resized bitmap. We round outward // so we always lean toward it being larger rather than smaller than we // need, and then clamp to the bitmap bounds so we don't get any invalid // data. SkIRect destBitmapSubsetSkI; destBitmapSubsetSk.roundOut(&destBitmapSubsetSkI); if (!destBitmapSubsetSkI.intersect(resizedImageRect)) return; // Resized image does not intersect. if (srcIsFull && bitmap.shouldCacheResampling( resizedImageRect.width(), resizedImageRect.height(), destBitmapSubsetSkI.width(), destBitmapSubsetSkI.height())) { // We're supposed to resize the entire image and cache it, even though // we don't need all of it. SkBitmap resampled = bitmap.resizedBitmap(destRectRounded.width(), destRectRounded.height()); canvas.drawBitmapRect(resampled, 0, destRect, &paint); } else { // We should only resize the exposed part of the bitmap to do the // minimal possible work. // Resample the needed part of the image. SkBitmap resampled = skia::ImageOperations::Resize(subset, skia::ImageOperations::RESIZE_LANCZOS3, destRectRounded.width(), destRectRounded.height(), destBitmapSubsetSkI); // Compute where the new bitmap should be drawn. Since our new bitmap // may be smaller than the original, we have to shift it over by the // same amount that we cut off the top and left. destBitmapSubsetSkI.offset(destRect.fLeft, destRect.fTop); SkRect offsetDestRect; offsetDestRect.set(destBitmapSubsetSkI); canvas.drawBitmapRect(resampled, 0, offsetDestRect, &paint); } }
void Image::drawPattern(GraphicsContext* context, const FloatRect& floatSrcRect, const AffineTransform& patternTransform, const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator compositeOp, const FloatRect& destRect) { #if PLATFORM(CHROMIUM) TRACE_EVENT0("skia", "Image::drawPattern"); #endif FloatRect normSrcRect = normalizeRect(floatSrcRect); if (destRect.isEmpty() || normSrcRect.isEmpty()) return; // nothing to draw NativeImageSkia* bitmap = nativeImageForCurrentFrame(); if (!bitmap) return; SkIRect srcRect = enclosingIntRect(normSrcRect); SkMatrix ctm = context->platformContext()->canvas()->getTotalMatrix(); SkMatrix totalMatrix; totalMatrix.setConcat(ctm, patternTransform); // Figure out what size the bitmap will be in the destination. The // destination rect is the bounds of the pattern, we need to use the // matrix to see how big it will be. SkRect destRectTarget; totalMatrix.mapRect(&destRectTarget, normSrcRect); float destBitmapWidth = SkScalarToFloat(destRectTarget.width()); float destBitmapHeight = SkScalarToFloat(destRectTarget.height()); // Compute the resampling mode. ResamplingMode resampling; if (context->platformContext()->isAccelerated() || context->platformContext()->printing()) resampling = RESAMPLE_LINEAR; else resampling = computeResamplingMode(totalMatrix, *bitmap, srcRect.width(), srcRect.height(), destBitmapWidth, destBitmapHeight); resampling = limitResamplingMode(context->platformContext(), resampling); // Load the transform WebKit requested. SkMatrix matrix(patternTransform); SkShader* shader; if (resampling == RESAMPLE_AWESOME) { // Do nice resampling. int width = static_cast<int>(destBitmapWidth); int height = static_cast<int>(destBitmapHeight); SkBitmap resampled = bitmap->resizedBitmap(srcRect, width, height); shader = SkShader::CreateBitmapShader(resampled, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode); // Since we just resized the bitmap, we need to remove the scale // applied to the pixels in the bitmap shader. This means we need // CTM * patternTransform to have identity scale. Since we // can't modify CTM (or the rectangle will be drawn in the wrong // place), we must set patternTransform's scale to the inverse of // CTM scale. matrix.setScaleX(ctm.getScaleX() ? 1 / ctm.getScaleX() : 1); matrix.setScaleY(ctm.getScaleY() ? 1 / ctm.getScaleY() : 1); } else { // No need to do nice resampling. SkBitmap srcSubset; bitmap->bitmap().extractSubset(&srcSubset, srcRect); shader = SkShader::CreateBitmapShader(srcSubset, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode); } // We also need to translate it such that the origin of the pattern is the // origin of the destination rect, which is what WebKit expects. Skia uses // the coordinate system origin as the base for the patter. If WebKit wants // a shifted image, it will shift it from there using the patternTransform. float adjustedX = phase.x() + normSrcRect.x() * narrowPrecisionToFloat(patternTransform.a()); float adjustedY = phase.y() + normSrcRect.y() * narrowPrecisionToFloat(patternTransform.d()); matrix.postTranslate(SkFloatToScalar(adjustedX), SkFloatToScalar(adjustedY)); shader->setLocalMatrix(matrix); SkPaint paint; paint.setShader(shader)->unref(); paint.setXfermodeMode(WebCoreCompositeToSkiaComposite(compositeOp)); paint.setFilterBitmap(resampling == RESAMPLE_LINEAR); context->platformContext()->paintSkPaint(destRect, paint); }