blob: e3862ec3b35f0bc2728a4149ddad8f01b77263f5 [file] [log] [blame]
// Copyright 2017 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 "platform/mac/GraphicsContextCanvas.h"
#import <AppKit/AppKit.h>
#import <CoreGraphics/CoreGraphics.h>
#include "platform/wtf/RetainPtr.h"
#include "skia/ext/skia_utils_mac.h"
namespace blink {
GraphicsContextCanvas::GraphicsContextCanvas(PaintCanvas* canvas,
const SkIRect& user_clip_rect,
SkScalar bitmap_scale_factor)
: canvas_(canvas),
cg_context_(0),
bitmap_scale_factor_(bitmap_scale_factor),
use_device_bits_(false),
bitmap_is_dummy_(false) {
canvas_->save();
canvas_->clipRect(SkRect::MakeFromIRect(user_clip_rect));
}
GraphicsContextCanvas::~GraphicsContextCanvas() {
ReleaseIfNeeded();
canvas_->restore();
}
SkIRect GraphicsContextCanvas::ComputeDirtyRect() {
// If the user specified a clip region, assume that it was tight and that the
// dirty rect is approximately the whole bitmap.
return SkIRect::MakeWH(offscreen_.width(), offscreen_.height());
}
// This must be called to balance calls to cgContext
void GraphicsContextCanvas::ReleaseIfNeeded() {
if (!cg_context_)
return;
if (!use_device_bits_ && !bitmap_is_dummy_) {
// Find the bits that were drawn to.
SkIRect bounds = ComputeDirtyRect();
SkBitmap subset;
if (!offscreen_.extractSubset(&subset, bounds)) {
return;
}
subset.setImmutable(); // Prevents a defensive copy inside Skia.
canvas_->save();
canvas_->setMatrix(SkMatrix::I()); // Reset back to device space.
canvas_->translate(bounds.x() + bitmap_offset_.x(),
bounds.y() + bitmap_offset_.y());
canvas_->scale(1.f / bitmap_scale_factor_, 1.f / bitmap_scale_factor_);
canvas_->drawBitmap(subset, 0, 0);
canvas_->restore();
}
CGContextRelease(cg_context_);
cg_context_ = 0;
use_device_bits_ = false;
bitmap_is_dummy_ = false;
}
CGContextRef GraphicsContextCanvas::CgContext() {
ReleaseIfNeeded(); // This flushes any prior bitmap use
SkIRect clip_bounds;
if (!canvas_->getDeviceClipBounds(&clip_bounds)) {
// If the clip is empty, then there is nothing to draw. The caller may
// attempt to draw (to-be-clipped) results, so ensure there is a dummy
// non-NULL CGContext to use.
bitmap_is_dummy_ = true;
clip_bounds = SkIRect::MakeXYWH(0, 0, 1, 1);
}
// remember the top/left, in case we need to compose this later
bitmap_offset_.set(clip_bounds.x(), clip_bounds.y());
// Now make clip_bounds be relative to the current layer/device
if (!bitmap_is_dummy_) {
canvas_->temporary_internal_describeTopLayer(nullptr, &clip_bounds);
}
SkPixmap device_pixels;
ToPixmap(canvas_, &device_pixels);
// Only draw directly if we have pixels, and we're only rect-clipped.
// If not, we allocate an offscreen and draw into that, relying on the
// compositing step to apply skia's clip.
use_device_bits_ =
device_pixels.addr() && canvas_->isClipRect() && !bitmap_is_dummy_;
WTF::RetainPtr<CGColorSpace> color_space(CGColorSpaceCreateDeviceRGB());
int display_height;
if (use_device_bits_) {
SkPixmap subset;
bool result = device_pixels.extractSubset(&subset, clip_bounds);
DCHECK(result);
if (!result)
return 0;
display_height = subset.height();
cg_context_ = CGBitmapContextCreate(
subset.writable_addr(), subset.width(), subset.height(), 8,
subset.rowBytes(), color_space.Get(),
kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
} else {
bool result = offscreen_.tryAllocN32Pixels(
SkScalarCeilToInt(bitmap_scale_factor_ * clip_bounds.width()),
SkScalarCeilToInt(bitmap_scale_factor_ * clip_bounds.height()));
DCHECK(result);
if (!result)
return 0;
offscreen_.eraseColor(0);
display_height = offscreen_.height();
cg_context_ = CGBitmapContextCreate(
offscreen_.getPixels(), offscreen_.width(), offscreen_.height(), 8,
offscreen_.rowBytes(), color_space.Get(),
kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
}
DCHECK(cg_context_);
SkMatrix matrix = canvas_->getTotalMatrix();
matrix.postTranslate(-SkIntToScalar(bitmap_offset_.x()),
-SkIntToScalar(bitmap_offset_.y()));
matrix.postScale(bitmap_scale_factor_, -bitmap_scale_factor_);
matrix.postTranslate(0, SkIntToScalar(display_height));
CGContextConcatCTM(cg_context_, skia::SkMatrixToCGAffineTransform(matrix));
return cg_context_;
}
bool GraphicsContextCanvas::HasEmptyClipRegion() const {
return canvas_->isClipEmpty();
}
} // namespace blink