diff options
author | pinkerton@google.com <pinkerton@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-09-10 17:46:38 +0000 |
---|---|---|
committer | pinkerton@google.com <pinkerton@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-09-10 17:46:38 +0000 |
commit | 93b5730bc11d944d3e364b08294350d88e4ed284 (patch) | |
tree | d8a80c2ff0d09ea9033424fcc9809825d2a89324 /webkit/port | |
parent | 6501779376fc890a5613519050756a9d6cf48ee8 (diff) | |
download | chromium_src-93b5730bc11d944d3e364b08294350d88e4ed284.zip chromium_src-93b5730bc11d944d3e364b08294350d88e4ed284.tar.gz chromium_src-93b5730bc11d944d3e364b08294350d88e4ed284.tar.bz2 |
include SkGraphicsContextMac.mm and PlatformContextSkia
Review URL: http://codereview.chromium.org/1897
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@1991 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/port')
-rw-r--r-- | webkit/port/platform/graphics/SkGraphicsContextMac.cpp | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/webkit/port/platform/graphics/SkGraphicsContextMac.cpp b/webkit/port/platform/graphics/SkGraphicsContextMac.cpp new file mode 100644 index 0000000..d66bd55 --- /dev/null +++ b/webkit/port/platform/graphics/SkGraphicsContextMac.cpp @@ -0,0 +1,399 @@ +// Copyright (c) 2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "SkGraphicsContext.h" + +#include "base/gfx/platform_canvas_mac.h" +#include "base/gfx/image_operations.h" +#include "base/gfx/skia_utils_mac.h" +#include "base/logging.h" +#include "GraphicsContextPrivate.h" +#include "SkBitmap.h" +#include "NativeImageSkia.h" +#include "SkiaUtils.h" + +// These need to be moved +#include "ThemeData.h" + +#define kSkiaOrientation kHIThemeOrientationNormal + +namespace { + +// 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. +void DrawResampledBitmap(SkCanvas& canvas, + SkPaint& paint, + const NativeImageSkia& bitmap, + const SkIRect& src_irect, + const SkRect& dest_rect) { + // First get the subset we need. This is efficient and does not copy pixels. + SkBitmap subset; + bitmap.extractSubset(&subset, src_irect); + SkRect src_rect; + src_rect.set(src_irect); + + // Whether we're doing a subset or using the full source image. + bool src_is_full = src_irect.fLeft == 0 && src_irect.fTop == 0 && + src_irect.width() == bitmap.width() && + src_irect.height() == bitmap.height(); + + // We will always draw in integer sizes, so round the destination rect. + SkIRect dest_rect_rounded; + dest_rect.round(&dest_rect_rounded); + SkIRect resized_image_rect; // Represents the size of the resized image. + resized_image_rect.set(0, 0, + dest_rect_rounded.width(), dest_rect_rounded.height()); + + if (src_is_full && + bitmap.hasResizedBitmap(dest_rect_rounded.width(), + dest_rect_rounded.height())) { + // Yay, this bitmap frame already has a resized version appropriate for us. + SkBitmap resampled = bitmap.resizedBitmap(dest_rect_rounded.width(), + dest_rect_rounded.height()); + canvas.drawBitmapRect(resampled, NULL, dest_rect, &paint); + return; + } + + // Compute the visible portion of our rect. + SkRect dest_bitmap_subset_sk; + ClipRectToCanvas(canvas, dest_rect, &dest_bitmap_subset_sk); + dest_bitmap_subset_sk.offset(-dest_rect.fLeft, -dest_rect.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 dest_bitmap_subset_sk_i; + dest_bitmap_subset_sk.roundOut(&dest_bitmap_subset_sk_i); + if (!dest_bitmap_subset_sk_i.intersect(resized_image_rect)) + return; // Resized image does not intersect. + + if (src_is_full && bitmap.shouldCacheResampling( + resized_image_rect.width(), + resized_image_rect.height(), + dest_bitmap_subset_sk_i.width(), + dest_bitmap_subset_sk_i.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(dest_rect_rounded.width(), + dest_rect_rounded.height()); + canvas.drawBitmapRect(resampled, NULL, dest_rect, &paint); + } else { + // We should only resize the exposed part of the bitmap to do the minimal + // possible work. + gfx::Rect dest_bitmap_subset(dest_bitmap_subset_sk_i.fLeft, + dest_bitmap_subset_sk_i.fTop, + dest_bitmap_subset_sk_i.width(), + dest_bitmap_subset_sk_i.height()); + + // Resample the needed part of the image. + SkBitmap resampled = gfx::ImageOperations::Resize(subset, + gfx::ImageOperations::RESIZE_LANCZOS3, + gfx::Size(dest_rect_rounded.width(), dest_rect_rounded.height()), + dest_bitmap_subset); + + // 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. + SkRect offset_dest_rect = { + dest_bitmap_subset.x() + dest_rect.fLeft, + dest_bitmap_subset.y() + dest_rect.fTop, + dest_bitmap_subset.right() + dest_rect.fLeft, + dest_bitmap_subset.bottom() + dest_rect.fTop }; + + canvas.drawBitmapRect(resampled, NULL, offset_dest_rect, &paint); + } +} + +} // namespace + +SkGraphicsContext::SkGraphicsContext(gfx::PlatformCanvas* canvas) + : canvas_(canvas), + paint_context_(NULL), + own_canvas_(false) { +} + +SkGraphicsContext::~SkGraphicsContext() { + if (own_canvas_) + delete canvas_; +} + +const gfx::NativeTheme* SkGraphicsContext::nativeTheme() { + // TODO(pinkerton): fix me + return nil; +} + +void SkGraphicsContext::paintIcon(CGImageRef icon, const SkIRect& rect) { + CGContextRef context = canvas_->beginPlatformPaint(); + CGRect r = gfx::SkIRectToCGRect(rect); + CGContextDrawImage(context, r, icon); + canvas_->endPlatformPaint(); +} + + +void SkGraphicsContext::paintButton(const SkIRect& widgetRect, + const ThemeData& themeData) { + CGContextRef context = canvas_->beginPlatformPaint(); + + int state = themeData.m_state; + CGRect rect(gfx::SkIRectToCGRect(widgetRect)); + HIThemeButtonDrawInfo button_draw_info = { 0 }; + button_draw_info.state = state; + CGRect label_rect; + + HIThemeDrawButton(&rect, &button_draw_info, context, + kSkiaOrientation, &label_rect); + + canvas_->endPlatformPaint(); +} + +void SkGraphicsContext::paintTextField(const SkIRect& widgetRect, + const ThemeData& themeData, + SkColor c, + bool drawEdges) { + CGContextRef context = canvas_->beginPlatformPaint(); + + int state = themeData.m_state; + CGRect rect(gfx::SkIRectToCGRect(widgetRect)); + CGContextSaveGState(context); + CGColorRef color = gfx::SkColorToCGColorRef(c); + CGContextSetFillColorWithColor(context, color); + CGContextFillRect(context, rect); + CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, 1.0); // black border for now + CGContextStrokeRect(context, rect); + CGContextRestoreGState(context); + CGColorRelease(color); + canvas_->endPlatformPaint(); +} + +void SkGraphicsContext::paintMenuListArrowButton(const SkIRect& widgetRect, + unsigned state, + unsigned classic_state) { + CGContextRef context = canvas_->beginPlatformPaint(); + + CGRect rect(gfx::SkIRectToCGRect(widgetRect)); + HIThemePopupArrowDrawInfo arrow_draw_info = { 0 }; + arrow_draw_info.state = state; + CGRect label_rect; + + HIThemeDrawPopupArrow(&rect, &arrow_draw_info, context, kSkiaOrientation); + + canvas_->endPlatformPaint(); +} + +void SkGraphicsContext::paintComplexText(UniscribeStateTextRun& state, + const SkPoint& point, + int from, + int to, + int ascent) { + SkColor sk_color(paint_context_->fillColor()); + uint8 alpha = SkColorGetA(sk_color); + // Skip 100% transparent text; no need to draw anything. + if (!alpha) + return; + + CGContextRef context = canvas_->beginPlatformPaint(); + CGContextSaveGState(context); + CGColorRef color = gfx::SkColorToCGColorRef(sk_color); + CGContextSetFillColorWithColor(context, color); + + //TODO: remove use of UniscribeStateTextRun; show placeholder to test other + // code for the moment + CGContextShowTextAtPoint(context, point.fX, point.fY, "complex", 7); + CGContextRestoreGState(context); + CGColorRelease(color); + canvas_->endPlatformPaint(); +} + +bool SkGraphicsContext::paintText(FontHandle font, + int number_glyph, + const uint16* chars, + const int* advances, + const SkPoint& origin) { + SkColor sk_color(paint_context_->fillColor()); + uint8 alpha = SkColorGetA(sk_color); + // Skip 100% transparent text; no need to draw anything. + if (!alpha) + return true; + + bool success = false; + CGContextRef context = canvas_->beginPlatformPaint(); + CGContextSaveGState(context); + CGColorRef color = gfx::SkColorToCGColorRef(sk_color); + CGContextSetFillColorWithColor(context, color); + CGContextMoveToPoint(context, origin.fX, origin.fY); + CGFontRef cg_font = CTFontCopyGraphicsFont(font, NULL); + CGContextSetFont(context, cg_font); + + CGSize cg_advances[number_glyph]; + CGGlyph cg_glyphs[number_glyph]; + + CTFontGetGlyphsForCharacters(font, chars, cg_glyphs, number_glyph); + CGContextShowGlyphsAtPoint(context, origin.fX, origin.fY, + cg_glyphs, number_glyph); + CGContextRestoreGState(context); + CGColorRelease(color); + CGFontRelease(cg_font); + canvas_->endPlatformPaint(); + return success; +} + +void SkGraphicsContext::paintSkPaint(const SkRect& rect, + const SkPaint& paint) { + canvas_->drawRect(rect, paint); +} + +// Returns smallest multiple of two of the dest size that is more than a small +// multiple larger than src_size. +// +// Used to determine the size that source should be high-quality upsampled to, +// after which we use linear interpolation. Making sure that the linear +// interpolation is a factor of two reduces artifacts, and doing the lowest +// level of resampling +static int GetResamplingThreshold(int src_size, int dest_size) { + int lower_bound = src_size * 3 / 2; // Minimum size we'll resample to (1.5x). + int cur = dest_size; + + // Find the largest multiple of two of the destination size less than our + // threshold + while (cur > lower_bound) + cur /= 2; + + // We want the next size above that, or just the destination size if it's + // smaller. + cur *= 2; + if (cur > dest_size) + return dest_size; + return cur; +} + +// static +SkGraphicsContext::ResamplingMode SkGraphicsContext::computeResamplingMode( + const NativeImageSkia& bitmap, + int src_width, int src_height, + float dest_width, float dest_height) { + int dest_iwidth = static_cast<int>(dest_width); + int dest_iheight = static_cast<int>(dest_height); + + // The percent change below which we will not resample. This usually means + // an off-by-one error on the web page, and just doing nearest neighbor + // sampling is usually good enough. + const float kFractionalChangeThreshold = 0.025f; + + // Images smaller than this in either direction are considered "small" and + // are not resampled ever (see below). + const int kSmallImageSizeThreshold = 8; + + // The amount an image can be stretched in a single direction before we + // say that it is being stretched so much that it must be a line or + // background that doesn't need resampling. + const float kLargeStretch = 3.0f; + + // Figure out if we should resample this image. We try to prune out some + // common cases where resampling won't give us anything, since it is much + // slower than drawing stretched. + if (src_width == dest_iwidth && src_height == dest_iheight) { + // We don't need to resample if the source and destination are the same. + return RESAMPLE_NONE; + } + + if (src_width <= kSmallImageSizeThreshold || + src_height <= kSmallImageSizeThreshold || + dest_width <= kSmallImageSizeThreshold || + dest_height <= kSmallImageSizeThreshold) { + // Never resample small images. These are often used for borders and + // rules (think 1x1 images used to make lines). + return RESAMPLE_NONE; + } + + if (src_height * kLargeStretch <= dest_height || + src_width * kLargeStretch <= dest_width) { + // Large image detected. + + // Don't resample if it is being stretched a lot in only one direction. + // This is trying to catch cases where somebody has created a border + // (which might be large) and then is stretching it to fill some part + // of the page. + if (src_width == dest_width || src_height == dest_height) + return RESAMPLE_NONE; + + // The image is growing a lot and in more than one direction. Resampling + // is slow and doesn't give us very much when growing a lot. + return RESAMPLE_LINEAR; + } + + if ((fabs(dest_width - src_width) / src_width < + kFractionalChangeThreshold) && + (fabs(dest_height - src_height) / src_height < + kFractionalChangeThreshold)) { + // It is disappointingly common on the web for image sizes to be off by + // one or two pixels. We don't bother resampling if the size difference + // is a small fraction of the original size. + return RESAMPLE_NONE; + } + + // When the image is not yet done loading, use linear. We don't cache the + // partially resampled images, and as they come in incrementally, it causes + // us to have to resample the whole thing every time. + if (!bitmap.isDataComplete()) + return RESAMPLE_LINEAR; + + // Everything else gets resampled. + return RESAMPLE_AWESOME; +} + +void SkGraphicsContext::paintSkBitmap(const NativeImageSkia& bitmap, + const SkIRect& src_rect, + const SkRect& dest_rect, + const SkPorterDuff::Mode& comp_op) { + SkPaint paint; + paint.setPorterDuffXfermode(comp_op); + + ResamplingMode resampling = IsPrinting() ? RESAMPLE_NONE : + computeResamplingMode(bitmap, src_rect.width(), src_rect.height(), + SkScalarToFloat(dest_rect.width()), + SkScalarToFloat(dest_rect.height())); + if (resampling == RESAMPLE_AWESOME) { + paint.setFilterBitmap(false); + DrawResampledBitmap(*canvas_, paint, bitmap, src_rect, dest_rect); + } else { + // No resampling necessary, we can just draw the bitmap. + // Note: for serialization, we will want to subset the bitmap first so + // we don't send extra pixels. + paint.setFilterBitmap(resampling == RESAMPLE_LINEAR); + canvas_->drawBitmapRect(bitmap, &src_rect, dest_rect, &paint); + } +} + +void SkGraphicsContext::setDashPathEffect(SkDashPathEffect *dash) { + paint_context_->setDashPathEffect(dash); +} + +void SkGraphicsContext::setGradient(SkShader *gradient) { + paint_context_->setGradient(gradient); +} + +void SkGraphicsContext::setPattern(SkShader *pattern) { + paint_context_->setPattern(pattern); +} + +const SkBitmap* SkGraphicsContext::bitmap() const { + return &canvas_->getDevice()->accessBitmap(false); +} + +gfx::PlatformCanvas* SkGraphicsContext::canvas() const { + return canvas_; +} + +bool SkGraphicsContext::IsPrinting() { + return canvas_->getTopPlatformDevice().IsVectorial(); +} |