| /* |
| * Copyright (C) 2003, 2004, 2005, 2006, 2009 Apple Inc. All rights reserved. |
| * Copyright (C) 2013 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "platform/graphics/GraphicsContext.h" |
| |
| #include <memory> |
| #include "platform/geometry/FloatRect.h" |
| #include "platform/geometry/FloatRoundedRect.h" |
| #include "platform/geometry/IntRect.h" |
| #include "platform/graphics/GraphicsContextStateSaver.h" |
| #include "platform/graphics/ImageBuffer.h" |
| #include "platform/graphics/InterpolationSpace.h" |
| #include "platform/graphics/Path.h" |
| #include "platform/graphics/paint/PaintController.h" |
| #include "platform/graphics/paint/PaintRecord.h" |
| #include "platform/graphics/paint/PaintRecorder.h" |
| #include "platform/instrumentation/tracing/TraceEvent.h" |
| #include "platform/weborigin/KURL.h" |
| #include "platform/wtf/Assertions.h" |
| #include "platform/wtf/MathExtras.h" |
| #include "skia/ext/platform_canvas.h" |
| #include "third_party/skia/include/core/SkAnnotation.h" |
| #include "third_party/skia/include/core/SkColorFilter.h" |
| #include "third_party/skia/include/core/SkData.h" |
| #include "third_party/skia/include/core/SkRRect.h" |
| #include "third_party/skia/include/core/SkRefCnt.h" |
| #include "third_party/skia/include/effects/SkLumaColorFilter.h" |
| #include "third_party/skia/include/effects/SkPictureImageFilter.h" |
| #include "third_party/skia/include/pathops/SkPathOps.h" |
| #include "third_party/skia/include/utils/SkNullCanvas.h" |
| |
| namespace blink { |
| |
| GraphicsContext::GraphicsContext(PaintController& paint_controller, |
| DisabledMode disable_context_or_painting, |
| SkMetaData* meta_data) |
| : canvas_(nullptr), |
| paint_controller_(paint_controller), |
| paint_state_stack_(), |
| paint_state_index_(0), |
| #if DCHECK_IS_ON() |
| layer_count_(0), |
| disable_destruction_checks_(false), |
| in_drawing_recorder_(false), |
| #endif |
| disabled_state_(disable_context_or_painting), |
| device_scale_factor_(1.0f), |
| printing_(false), |
| has_meta_data_(!!meta_data) { |
| if (meta_data) |
| meta_data_ = *meta_data; |
| |
| // FIXME: Do some tests to determine how many states are typically used, and |
| // allocate several here. |
| paint_state_stack_.push_back(GraphicsContextState::Create()); |
| paint_state_ = paint_state_stack_.back().get(); |
| |
| if (ContextDisabled()) { |
| DEFINE_STATIC_LOCAL(SkCanvas*, null_sk_canvas, |
| (SkMakeNullCanvas().release())); |
| DEFINE_STATIC_LOCAL(SkiaPaintCanvas, null_canvas, (null_sk_canvas)); |
| canvas_ = &null_canvas; |
| } |
| } |
| |
| GraphicsContext::~GraphicsContext() { |
| #if DCHECK_IS_ON() |
| if (!disable_destruction_checks_) { |
| DCHECK(!paint_state_index_); |
| DCHECK(!paint_state_->SaveCount()); |
| DCHECK(!layer_count_); |
| DCHECK(!SaveCount()); |
| } |
| #endif |
| } |
| |
| void GraphicsContext::Save() { |
| if (ContextDisabled()) |
| return; |
| |
| paint_state_->IncrementSaveCount(); |
| |
| DCHECK(canvas_); |
| canvas_->save(); |
| } |
| |
| void GraphicsContext::Restore() { |
| if (ContextDisabled()) |
| return; |
| |
| if (!paint_state_index_ && !paint_state_->SaveCount()) { |
| DLOG(ERROR) << "ERROR void GraphicsContext::restore() stack is empty"; |
| return; |
| } |
| |
| if (paint_state_->SaveCount()) { |
| paint_state_->DecrementSaveCount(); |
| } else { |
| paint_state_index_--; |
| paint_state_ = paint_state_stack_[paint_state_index_].get(); |
| } |
| |
| DCHECK(canvas_); |
| canvas_->restore(); |
| } |
| |
| #if DCHECK_IS_ON() |
| unsigned GraphicsContext::SaveCount() const { |
| // Each m_paintStateStack entry implies an additional save op |
| // (on top of its own saveCount), except for the first frame. |
| unsigned count = paint_state_index_; |
| DCHECK_GE(paint_state_stack_.size(), paint_state_index_); |
| for (unsigned i = 0; i <= paint_state_index_; ++i) |
| count += paint_state_stack_[i]->SaveCount(); |
| |
| return count; |
| } |
| #endif |
| |
| void GraphicsContext::SaveLayer(const SkRect* bounds, const PaintFlags* flags) { |
| if (ContextDisabled()) |
| return; |
| |
| DCHECK(canvas_); |
| canvas_->saveLayer(bounds, flags); |
| } |
| |
| void GraphicsContext::RestoreLayer() { |
| if (ContextDisabled()) |
| return; |
| |
| DCHECK(canvas_); |
| |
| canvas_->restore(); |
| } |
| |
| #if DCHECK_IS_ON() |
| void GraphicsContext::SetInDrawingRecorder(bool val) { |
| // Nested drawing recorers are not allowed. |
| DCHECK(!val || !in_drawing_recorder_); |
| in_drawing_recorder_ = val; |
| } |
| #endif |
| |
| void GraphicsContext::SetShadow( |
| const FloatSize& offset, |
| float blur, |
| const Color& color, |
| DrawLooperBuilder::ShadowTransformMode shadow_transform_mode, |
| DrawLooperBuilder::ShadowAlphaMode shadow_alpha_mode, |
| ShadowMode shadow_mode) { |
| if (ContextDisabled()) |
| return; |
| |
| DrawLooperBuilder draw_looper_builder; |
| if (!color.Alpha()) { |
| // When shadow-only but there is no shadow, we use an empty draw looper |
| // to disable rendering of the source primitive. When not shadow-only, we |
| // clear the looper. |
| SetDrawLooper(shadow_mode != kDrawShadowOnly |
| ? nullptr |
| : draw_looper_builder.DetachDrawLooper()); |
| return; |
| } |
| |
| draw_looper_builder.AddShadow(offset, blur, color, shadow_transform_mode, |
| shadow_alpha_mode); |
| if (shadow_mode == kDrawShadowAndForeground) { |
| draw_looper_builder.AddUnmodifiedContent(); |
| } |
| SetDrawLooper(draw_looper_builder.DetachDrawLooper()); |
| } |
| |
| void GraphicsContext::SetDrawLooper(sk_sp<SkDrawLooper> draw_looper) { |
| if (ContextDisabled()) |
| return; |
| |
| MutableState()->SetDrawLooper(std::move(draw_looper)); |
| } |
| |
| SkColorFilter* GraphicsContext::GetColorFilter() const { |
| return ImmutableState()->GetColorFilter(); |
| } |
| |
| void GraphicsContext::SetColorFilter(ColorFilter color_filter) { |
| GraphicsContextState* state_to_set = MutableState(); |
| |
| // We only support one active color filter at the moment. If (when) this |
| // becomes a problem, we should switch to using color filter chains (Skia work |
| // in progress). |
| DCHECK(!state_to_set->GetColorFilter()); |
| state_to_set->SetColorFilter( |
| WebCoreColorFilterToSkiaColorFilter(color_filter)); |
| } |
| |
| void GraphicsContext::Concat(const SkMatrix& matrix) { |
| if (ContextDisabled()) |
| return; |
| |
| DCHECK(canvas_); |
| |
| canvas_->concat(matrix); |
| } |
| |
| void GraphicsContext::BeginLayer(float opacity, |
| SkBlendMode xfermode, |
| const FloatRect* bounds, |
| ColorFilter color_filter, |
| sk_sp<SkImageFilter> image_filter) { |
| if (ContextDisabled()) |
| return; |
| |
| PaintFlags layer_flags; |
| layer_flags.setAlpha(static_cast<unsigned char>(opacity * 255)); |
| layer_flags.setBlendMode(xfermode); |
| layer_flags.setColorFilter(WebCoreColorFilterToSkiaColorFilter(color_filter)); |
| layer_flags.setImageFilter(std::move(image_filter)); |
| |
| if (bounds) { |
| SkRect sk_bounds = *bounds; |
| SaveLayer(&sk_bounds, &layer_flags); |
| } else { |
| SaveLayer(nullptr, &layer_flags); |
| } |
| |
| #if DCHECK_IS_ON() |
| ++layer_count_; |
| #endif |
| } |
| |
| void GraphicsContext::EndLayer() { |
| if (ContextDisabled()) |
| return; |
| |
| RestoreLayer(); |
| |
| #if DCHECK_IS_ON() |
| DCHECK_GT(layer_count_--, 0); |
| #endif |
| } |
| |
| void GraphicsContext::BeginRecording(const FloatRect& bounds) { |
| if (ContextDisabled()) |
| return; |
| |
| DCHECK(!canvas_); |
| canvas_ = paint_recorder_.beginRecording(bounds); |
| if (has_meta_data_) |
| canvas_->getMetaData() = meta_data_; |
| } |
| |
| namespace { |
| |
| sk_sp<PaintRecord> CreateEmptyPaintRecord() { |
| PaintRecorder recorder; |
| recorder.beginRecording(SkRect::MakeEmpty()); |
| return recorder.finishRecordingAsPicture(); |
| } |
| |
| } // anonymous namespace |
| |
| sk_sp<PaintRecord> GraphicsContext::EndRecording() { |
| if (ContextDisabled()) { |
| // Clients expect endRecording() to always return a non-null paint record. |
| // Cache an empty one to minimize overhead when disabled. |
| DEFINE_STATIC_LOCAL(sk_sp<PaintRecord>, empty_paint_record, |
| (CreateEmptyPaintRecord())); |
| return empty_paint_record; |
| } |
| |
| sk_sp<PaintRecord> record = paint_recorder_.finishRecordingAsPicture(); |
| canvas_ = nullptr; |
| DCHECK(record); |
| return record; |
| } |
| |
| void GraphicsContext::DrawRecord(sk_sp<const PaintRecord> record) { |
| if (ContextDisabled() || !record || !record->size()) |
| return; |
| |
| DCHECK(canvas_); |
| canvas_->drawPicture(std::move(record)); |
| } |
| |
| void GraphicsContext::CompositeRecord(sk_sp<PaintRecord> record, |
| const FloatRect& dest, |
| const FloatRect& src, |
| SkBlendMode op) { |
| if (ContextDisabled() || !record) |
| return; |
| DCHECK(canvas_); |
| |
| PaintFlags flags; |
| flags.setBlendMode(op); |
| canvas_->save(); |
| SkRect source_bounds = src; |
| SkRect sk_bounds = dest; |
| SkMatrix transform; |
| transform.setRectToRect(source_bounds, sk_bounds, SkMatrix::kFill_ScaleToFit); |
| canvas_->concat(transform); |
| flags.setImageFilter(SkPictureImageFilter::MakeForLocalSpace( |
| ToSkPicture(record, source_bounds), source_bounds, |
| static_cast<SkFilterQuality>(ImageInterpolationQuality()))); |
| canvas_->saveLayer(&source_bounds, &flags); |
| canvas_->restore(); |
| canvas_->restore(); |
| } |
| |
| namespace { |
| |
| int AdjustedFocusRingOffset(int offset) { |
| #if OS(MACOSX) |
| return offset + 2; |
| #else |
| return 0; |
| #endif |
| } |
| |
| } // anonymous ns |
| |
| int GraphicsContext::FocusRingOutsetExtent(int offset, int width) { |
| // Unlike normal outlines (whole width is outside of the offset), focus |
| // rings are drawn with the center of the path aligned with the offset, so |
| // only half of the width is outside of the offset. |
| return AdjustedFocusRingOffset(offset) + (width + 1) / 2; |
| } |
| |
| void GraphicsContext::DrawFocusRingPath(const SkPath& path, |
| const Color& color, |
| float width) { |
| DrawPlatformFocusRing(path, canvas_, color.Rgb(), width); |
| } |
| |
| void GraphicsContext::DrawFocusRingRect(const SkRect& rect, |
| const Color& color, |
| float width) { |
| DrawPlatformFocusRing(rect, canvas_, color.Rgb(), width); |
| } |
| |
| void GraphicsContext::DrawFocusRing(const Path& focus_ring_path, |
| float width, |
| int offset, |
| const Color& color) { |
| // FIXME: Implement support for offset. |
| if (ContextDisabled()) |
| return; |
| |
| DrawFocusRingPath(focus_ring_path.GetSkPath(), color, width); |
| } |
| |
| void GraphicsContext::DrawFocusRing(const Vector<IntRect>& rects, |
| float width, |
| int offset, |
| const Color& color) { |
| if (ContextDisabled()) |
| return; |
| |
| unsigned rect_count = rects.size(); |
| if (!rect_count) |
| return; |
| |
| SkRegion focus_ring_region; |
| offset = AdjustedFocusRingOffset(offset); |
| for (unsigned i = 0; i < rect_count; i++) { |
| SkIRect r = rects[i]; |
| if (r.isEmpty()) |
| continue; |
| r.outset(offset, offset); |
| focus_ring_region.op(r, SkRegion::kUnion_Op); |
| } |
| |
| if (focus_ring_region.isEmpty()) |
| return; |
| |
| if (focus_ring_region.isRect()) { |
| DrawFocusRingRect(SkRect::Make(focus_ring_region.getBounds()), color, |
| width); |
| } else { |
| SkPath path; |
| if (focus_ring_region.getBoundaryPath(&path)) |
| DrawFocusRingPath(path, color, width); |
| } |
| } |
| |
| static inline FloatRect AreaCastingShadowInHole( |
| const FloatRect& hole_rect, |
| float shadow_blur, |
| float shadow_spread, |
| const FloatSize& shadow_offset) { |
| FloatRect bounds(hole_rect); |
| |
| bounds.Inflate(shadow_blur); |
| |
| if (shadow_spread < 0) |
| bounds.Inflate(-shadow_spread); |
| |
| FloatRect offset_bounds = bounds; |
| offset_bounds.Move(-shadow_offset); |
| return UnionRect(bounds, offset_bounds); |
| } |
| |
| void GraphicsContext::DrawInnerShadow(const FloatRoundedRect& rect, |
| const Color& shadow_color, |
| const FloatSize& shadow_offset, |
| float shadow_blur, |
| float shadow_spread, |
| Edges clipped_edges) { |
| if (ContextDisabled()) |
| return; |
| |
| FloatRect hole_rect(rect.Rect()); |
| hole_rect.Inflate(-shadow_spread); |
| |
| if (hole_rect.IsEmpty()) { |
| FillRoundedRect(rect, shadow_color); |
| return; |
| } |
| |
| if (clipped_edges & kLeftEdge) { |
| hole_rect.Move(-std::max(shadow_offset.Width(), 0.0f) - shadow_blur, 0); |
| hole_rect.SetWidth(hole_rect.Width() + |
| std::max(shadow_offset.Width(), 0.0f) + shadow_blur); |
| } |
| if (clipped_edges & kTopEdge) { |
| hole_rect.Move(0, -std::max(shadow_offset.Height(), 0.0f) - shadow_blur); |
| hole_rect.SetHeight(hole_rect.Height() + |
| std::max(shadow_offset.Height(), 0.0f) + shadow_blur); |
| } |
| if (clipped_edges & kRightEdge) |
| hole_rect.SetWidth(hole_rect.Width() - |
| std::min(shadow_offset.Width(), 0.0f) + shadow_blur); |
| if (clipped_edges & kBottomEdge) |
| hole_rect.SetHeight(hole_rect.Height() - |
| std::min(shadow_offset.Height(), 0.0f) + shadow_blur); |
| |
| Color fill_color(shadow_color.Red(), shadow_color.Green(), |
| shadow_color.Blue(), 255); |
| |
| FloatRect outer_rect = AreaCastingShadowInHole(rect.Rect(), shadow_blur, |
| shadow_spread, shadow_offset); |
| FloatRoundedRect rounded_hole(hole_rect, rect.GetRadii()); |
| |
| GraphicsContextStateSaver state_saver(*this); |
| if (rect.IsRounded()) { |
| ClipRoundedRect(rect); |
| if (shadow_spread < 0) |
| rounded_hole.ExpandRadii(-shadow_spread); |
| else |
| rounded_hole.ShrinkRadii(shadow_spread); |
| } else { |
| Clip(rect.Rect()); |
| } |
| |
| DrawLooperBuilder draw_looper_builder; |
| draw_looper_builder.AddShadow(FloatSize(shadow_offset), shadow_blur, |
| shadow_color, |
| DrawLooperBuilder::kShadowRespectsTransforms, |
| DrawLooperBuilder::kShadowIgnoresAlpha); |
| SetDrawLooper(draw_looper_builder.DetachDrawLooper()); |
| FillRectWithRoundedHole(outer_rect, rounded_hole, fill_color); |
| } |
| |
| void GraphicsContext::DrawLine(const IntPoint& point1, const IntPoint& point2) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| StrokeStyle pen_style = GetStrokeStyle(); |
| if (pen_style == kNoStroke) |
| return; |
| |
| FloatPoint p1 = point1; |
| FloatPoint p2 = point2; |
| bool is_vertical_line = (p1.X() == p2.X()); |
| int width = roundf(StrokeThickness()); |
| |
| // We know these are vertical or horizontal lines, so the length will just |
| // be the sum of the displacement component vectors give or take 1 - |
| // probably worth the speed up of no square root, which also won't be exact. |
| FloatSize disp = p2 - p1; |
| int length = SkScalarRoundToInt(disp.Width() + disp.Height()); |
| PaintFlags flags(ImmutableState()->StrokeFlags(length)); |
| |
| if (StrokeData::StrokeIsDashed(width, GetStrokeStyle())) { |
| // Do a rect fill of our endpoints. This ensures we always have the |
| // appearance of being a border. We then draw the actual dotted/dashed |
| // line. |
| SkRect r1, r2; |
| r1.set(p1.X(), p1.Y(), p1.X() + width, p1.Y() + width); |
| r2.set(p2.X(), p2.Y(), p2.X() + width, p2.Y() + width); |
| |
| if (is_vertical_line) { |
| r1.offset(-width / 2, 0); |
| r2.offset(-width / 2, -width); |
| } else { |
| r1.offset(0, -width / 2); |
| r2.offset(-width, -width / 2); |
| } |
| PaintFlags fill_flags; |
| fill_flags.setColor(flags.getColor()); |
| DrawRect(r1, fill_flags); |
| DrawRect(r2, fill_flags); |
| } else if (GetStrokeStyle() == kDottedStroke) { |
| // We draw thick dotted lines with 0 length dash strokes and round endcaps, |
| // producing circles. The endcaps extend beyond the line's endpoints, |
| // so move the start and end in. |
| if (is_vertical_line) { |
| p1.SetY(p1.Y() + width / 2.f); |
| p2.SetY(p2.Y() - width / 2.f); |
| } else { |
| p1.SetX(p1.X() + width / 2.f); |
| p2.SetX(p2.X() - width / 2.f); |
| } |
| } |
| |
| AdjustLineToPixelBoundaries(p1, p2, width, pen_style); |
| canvas_->drawLine(p1.X(), p1.Y(), p2.X(), p2.Y(), flags); |
| } |
| |
| void GraphicsContext::DrawLineForText(const FloatPoint& pt, float width) { |
| if (ContextDisabled()) |
| return; |
| |
| if (width <= 0) |
| return; |
| |
| PaintFlags flags; |
| switch (GetStrokeStyle()) { |
| case kNoStroke: |
| case kSolidStroke: |
| case kDoubleStroke: { |
| int thickness = SkMax32(static_cast<int>(StrokeThickness()), 1); |
| SkRect r; |
| r.fLeft = WebCoreFloatToSkScalar(pt.X()); |
| // Avoid anti-aliasing lines. Currently, these are always horizontal. |
| // Round to nearest pixel to match text and other content. |
| r.fTop = WebCoreFloatToSkScalar(floorf(pt.Y() + 0.5f)); |
| r.fRight = r.fLeft + WebCoreFloatToSkScalar(width); |
| r.fBottom = r.fTop + SkIntToScalar(thickness); |
| flags = ImmutableState()->FillFlags(); |
| // Text lines are drawn using the stroke color. |
| flags.setColor(StrokeColor().Rgb()); |
| DrawRect(r, flags); |
| return; |
| } |
| case kDottedStroke: |
| case kDashedStroke: { |
| int y = floorf(pt.Y() + std::max<float>(StrokeThickness() / 2.0f, 0.5f)); |
| DrawLine(IntPoint(pt.X(), y), IntPoint(pt.X() + width, y)); |
| return; |
| } |
| case kWavyStroke: |
| default: |
| break; |
| } |
| |
| NOTREACHED(); |
| } |
| |
| // Draws a filled rectangle with a stroked border. |
| void GraphicsContext::DrawRect(const IntRect& rect) { |
| if (ContextDisabled()) |
| return; |
| |
| DCHECK(!rect.IsEmpty()); |
| if (rect.IsEmpty()) |
| return; |
| |
| SkRect sk_rect = rect; |
| if (ImmutableState()->FillColor().Alpha()) |
| DrawRect(sk_rect, ImmutableState()->FillFlags()); |
| |
| if (ImmutableState()->GetStrokeData().Style() != kNoStroke && |
| ImmutableState()->StrokeColor().Alpha()) { |
| // Stroke a width: 1 inset border |
| PaintFlags flags(ImmutableState()->FillFlags()); |
| flags.setColor(StrokeColor().Rgb()); |
| flags.setStyle(PaintFlags::kStroke_Style); |
| flags.setStrokeWidth(1); |
| |
| sk_rect.inset(0.5f, 0.5f); |
| DrawRect(sk_rect, flags); |
| } |
| } |
| |
| template <typename TextPaintInfo> |
| void GraphicsContext::DrawTextInternal(const Font& font, |
| const TextPaintInfo& text_info, |
| const FloatPoint& point, |
| const PaintFlags& flags) { |
| if (ContextDisabled()) |
| return; |
| |
| if (font.DrawText(canvas_, text_info, point, device_scale_factor_, flags)) |
| paint_controller_.SetTextPainted(); |
| } |
| |
| void GraphicsContext::DrawText(const Font& font, |
| const TextRunPaintInfo& text_info, |
| const FloatPoint& point, |
| const PaintFlags& flags) { |
| DrawTextInternal(font, text_info, point, flags); |
| } |
| |
| void GraphicsContext::DrawText(const Font& font, |
| const TextFragmentPaintInfo& text_info, |
| const FloatPoint& point, |
| const PaintFlags& flags) { |
| DrawTextInternal(font, text_info, point, flags); |
| } |
| |
| template <typename DrawTextFunc> |
| void GraphicsContext::DrawTextPasses(const DrawTextFunc& draw_text) { |
| TextDrawingModeFlags mode_flags = TextDrawingMode(); |
| |
| if (mode_flags & kTextModeFill) { |
| draw_text(ImmutableState()->FillFlags()); |
| } |
| |
| if ((mode_flags & kTextModeStroke) && GetStrokeStyle() != kNoStroke && |
| StrokeThickness() > 0) { |
| PaintFlags stroke_flags(ImmutableState()->StrokeFlags()); |
| if (mode_flags & kTextModeFill) { |
| // shadow was already applied during fill pass |
| stroke_flags.setLooper(0); |
| } |
| draw_text(stroke_flags); |
| } |
| } |
| |
| template <typename TextPaintInfo> |
| void GraphicsContext::DrawTextInternal(const Font& font, |
| const TextPaintInfo& text_info, |
| const FloatPoint& point) { |
| if (ContextDisabled()) |
| return; |
| |
| DrawTextPasses([&font, &text_info, &point, this](const PaintFlags& flags) { |
| if (font.DrawText(canvas_, text_info, point, device_scale_factor_, flags)) |
| paint_controller_.SetTextPainted(); |
| }); |
| } |
| |
| void GraphicsContext::DrawText(const Font& font, |
| const TextRunPaintInfo& text_info, |
| const FloatPoint& point) { |
| DrawTextInternal(font, text_info, point); |
| } |
| |
| void GraphicsContext::DrawText(const Font& font, |
| const TextFragmentPaintInfo& text_info, |
| const FloatPoint& point) { |
| DrawTextInternal(font, text_info, point); |
| } |
| |
| template <typename TextPaintInfo> |
| void GraphicsContext::DrawEmphasisMarksInternal(const Font& font, |
| const TextPaintInfo& text_info, |
| const AtomicString& mark, |
| const FloatPoint& point) { |
| if (ContextDisabled()) |
| return; |
| |
| DrawTextPasses( |
| [&font, &text_info, &mark, &point, this](const PaintFlags& flags) { |
| font.DrawEmphasisMarks(canvas_, text_info, mark, point, |
| device_scale_factor_, flags); |
| }); |
| } |
| |
| void GraphicsContext::DrawEmphasisMarks(const Font& font, |
| const TextRunPaintInfo& text_info, |
| const AtomicString& mark, |
| const FloatPoint& point) { |
| DrawEmphasisMarksInternal(font, text_info, mark, point); |
| } |
| |
| void GraphicsContext::DrawEmphasisMarks(const Font& font, |
| const TextFragmentPaintInfo& text_info, |
| const AtomicString& mark, |
| const FloatPoint& point) { |
| DrawEmphasisMarksInternal(font, text_info, mark, point); |
| } |
| |
| void GraphicsContext::DrawBidiText( |
| const Font& font, |
| const TextRunPaintInfo& run_info, |
| const FloatPoint& point, |
| Font::CustomFontNotReadyAction custom_font_not_ready_action) { |
| if (ContextDisabled()) |
| return; |
| |
| DrawTextPasses([&font, &run_info, &point, custom_font_not_ready_action, |
| this](const PaintFlags& flags) { |
| if (font.DrawBidiText(canvas_, run_info, point, |
| custom_font_not_ready_action, device_scale_factor_, |
| flags)) |
| paint_controller_.SetTextPainted(); |
| }); |
| } |
| |
| void GraphicsContext::DrawHighlightForText(const Font& font, |
| const TextRun& run, |
| const FloatPoint& point, |
| int h, |
| const Color& background_color, |
| int from, |
| int to) { |
| if (ContextDisabled()) |
| return; |
| |
| FillRect(font.SelectionRectForText(run, point, h, from, to), |
| background_color); |
| } |
| |
| void GraphicsContext::DrawImage( |
| Image* image, |
| const FloatRect& dest, |
| const FloatRect* src_ptr, |
| SkBlendMode op, |
| RespectImageOrientationEnum should_respect_image_orientation) { |
| if (ContextDisabled() || !image) |
| return; |
| |
| const FloatRect src = src_ptr ? *src_ptr : image->Rect(); |
| |
| PaintFlags image_flags = ImmutableState()->FillFlags(); |
| image_flags.setBlendMode(op); |
| image_flags.setColor(SK_ColorBLACK); |
| image_flags.setFilterQuality(ComputeFilterQuality(image, dest, src)); |
| image_flags.setAntiAlias(ShouldAntialias()); |
| image->Draw(canvas_, image_flags, dest, src, should_respect_image_orientation, |
| Image::kClampImageToSourceRect); |
| paint_controller_.SetImagePainted(); |
| } |
| |
| void GraphicsContext::DrawImageRRect( |
| Image* image, |
| const FloatRoundedRect& dest, |
| const FloatRect& src_rect, |
| SkBlendMode op, |
| RespectImageOrientationEnum respect_orientation) { |
| if (ContextDisabled() || !image) |
| return; |
| |
| if (!dest.IsRounded()) { |
| DrawImage(image, dest.Rect(), &src_rect, op, respect_orientation); |
| return; |
| } |
| |
| DCHECK(dest.IsRenderable()); |
| |
| const FloatRect visible_src = Intersection(src_rect, image->Rect()); |
| if (dest.IsEmpty() || visible_src.IsEmpty()) |
| return; |
| |
| PaintFlags image_flags = ImmutableState()->FillFlags(); |
| image_flags.setBlendMode(op); |
| image_flags.setColor(SK_ColorBLACK); |
| image_flags.setFilterQuality( |
| ComputeFilterQuality(image, dest.Rect(), src_rect)); |
| image_flags.setAntiAlias(ShouldAntialias()); |
| |
| bool use_shader = (visible_src == src_rect) && |
| (respect_orientation == kDoNotRespectImageOrientation); |
| if (use_shader) { |
| const SkMatrix local_matrix = SkMatrix::MakeRectToRect( |
| visible_src, dest.Rect(), SkMatrix::kFill_ScaleToFit); |
| use_shader = image->ApplyShader(image_flags, local_matrix); |
| } |
| |
| if (use_shader) { |
| // Shader-based fast path. |
| canvas_->drawRRect(dest, image_flags); |
| } else { |
| // Clip-based fallback. |
| PaintCanvasAutoRestore auto_restore(canvas_, true); |
| canvas_->clipRRect(dest, image_flags.isAntiAlias()); |
| image->Draw(canvas_, image_flags, dest.Rect(), src_rect, |
| respect_orientation, Image::kClampImageToSourceRect); |
| } |
| |
| paint_controller_.SetImagePainted(); |
| } |
| |
| SkFilterQuality GraphicsContext::ComputeFilterQuality( |
| Image* image, |
| const FloatRect& dest, |
| const FloatRect& src) const { |
| InterpolationQuality resampling; |
| if (Printing()) { |
| resampling = kInterpolationNone; |
| } else if (image->CurrentFrameIsLazyDecoded()) { |
| resampling = kInterpolationHigh; |
| } else { |
| resampling = ComputeInterpolationQuality( |
| SkScalarToFloat(src.Width()), SkScalarToFloat(src.Height()), |
| SkScalarToFloat(dest.Width()), SkScalarToFloat(dest.Height()), |
| image->CurrentFrameIsComplete()); |
| |
| if (resampling == kInterpolationNone) { |
| // FIXME: This is to not break tests (it results in the filter bitmap flag |
| // being set to true). We need to decide if we respect InterpolationNone |
| // being returned from computeInterpolationQuality. |
| resampling = kInterpolationLow; |
| } |
| } |
| return static_cast<SkFilterQuality>( |
| LimitInterpolationQuality(*this, resampling)); |
| } |
| |
| void GraphicsContext::DrawTiledImage(Image* image, |
| const FloatRect& dest_rect, |
| const FloatPoint& src_point, |
| const FloatSize& tile_size, |
| SkBlendMode op, |
| const FloatSize& repeat_spacing) { |
| if (ContextDisabled() || !image) |
| return; |
| image->DrawTiledBackground(*this, dest_rect, src_point, tile_size, op, |
| repeat_spacing); |
| paint_controller_.SetImagePainted(); |
| } |
| |
| void GraphicsContext::DrawTiledImage(Image* image, |
| const FloatRect& dest, |
| const FloatRect& src_rect, |
| const FloatSize& tile_scale_factor, |
| Image::TileRule h_rule, |
| Image::TileRule v_rule, |
| SkBlendMode op) { |
| if (ContextDisabled() || !image) |
| return; |
| |
| if (h_rule == Image::kStretchTile && v_rule == Image::kStretchTile) { |
| // Just do a scale. |
| DrawImage(image, dest, &src_rect, op); |
| return; |
| } |
| |
| image->DrawTiledBorder(*this, dest, src_rect, tile_scale_factor, h_rule, |
| v_rule, op); |
| paint_controller_.SetImagePainted(); |
| } |
| |
| void GraphicsContext::DrawOval(const SkRect& oval, const PaintFlags& flags) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| canvas_->drawOval(oval, flags); |
| } |
| |
| void GraphicsContext::DrawPath(const SkPath& path, const PaintFlags& flags) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| canvas_->drawPath(path, flags); |
| } |
| |
| void GraphicsContext::DrawRect(const SkRect& rect, const PaintFlags& flags) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| canvas_->drawRect(rect, flags); |
| } |
| |
| void GraphicsContext::DrawRRect(const SkRRect& rrect, const PaintFlags& flags) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| canvas_->drawRRect(rrect, flags); |
| } |
| |
| void GraphicsContext::FillPath(const Path& path_to_fill) { |
| if (ContextDisabled() || path_to_fill.IsEmpty()) |
| return; |
| |
| DrawPath(path_to_fill.GetSkPath(), ImmutableState()->FillFlags()); |
| } |
| |
| void GraphicsContext::FillRect(const FloatRect& rect) { |
| if (ContextDisabled()) |
| return; |
| |
| DrawRect(rect, ImmutableState()->FillFlags()); |
| } |
| |
| void GraphicsContext::FillRect(const FloatRect& rect, |
| const Color& color, |
| SkBlendMode xfer_mode) { |
| if (ContextDisabled()) |
| return; |
| |
| PaintFlags flags = ImmutableState()->FillFlags(); |
| flags.setColor(color.Rgb()); |
| flags.setBlendMode(xfer_mode); |
| |
| DrawRect(rect, flags); |
| } |
| |
| void GraphicsContext::FillRoundedRect(const FloatRoundedRect& rrect, |
| const Color& color) { |
| if (ContextDisabled()) |
| return; |
| |
| if (!rrect.IsRounded() || !rrect.IsRenderable()) { |
| FillRect(rrect.Rect(), color); |
| return; |
| } |
| |
| if (color == FillColor()) { |
| DrawRRect(rrect, ImmutableState()->FillFlags()); |
| return; |
| } |
| |
| PaintFlags flags = ImmutableState()->FillFlags(); |
| flags.setColor(color.Rgb()); |
| |
| DrawRRect(rrect, flags); |
| } |
| |
| namespace { |
| |
| bool IsSimpleDRRect(const FloatRoundedRect& outer, |
| const FloatRoundedRect& inner) { |
| // A DRRect is "simple" (i.e. can be drawn as a rrect stroke) if |
| // 1) all sides have the same width |
| const FloatSize stroke_size = |
| inner.Rect().MinXMinYCorner() - outer.Rect().MinXMinYCorner(); |
| if (!WebCoreFloatNearlyEqual(stroke_size.AspectRatio(), 1) || |
| !WebCoreFloatNearlyEqual(stroke_size.Width(), |
| outer.Rect().MaxX() - inner.Rect().MaxX()) || |
| !WebCoreFloatNearlyEqual(stroke_size.Height(), |
| outer.Rect().MaxY() - inner.Rect().MaxY())) |
| return false; |
| |
| // and |
| // 2) the inner radii are not constrained |
| const FloatRoundedRect::Radii& o_radii = outer.GetRadii(); |
| const FloatRoundedRect::Radii& i_radii = inner.GetRadii(); |
| if (!WebCoreFloatNearlyEqual(o_radii.TopLeft().Width() - stroke_size.Width(), |
| i_radii.TopLeft().Width()) || |
| !WebCoreFloatNearlyEqual( |
| o_radii.TopLeft().Height() - stroke_size.Height(), |
| i_radii.TopLeft().Height()) || |
| !WebCoreFloatNearlyEqual(o_radii.TopRight().Width() - stroke_size.Width(), |
| i_radii.TopRight().Width()) || |
| !WebCoreFloatNearlyEqual( |
| o_radii.TopRight().Height() - stroke_size.Height(), |
| i_radii.TopRight().Height()) || |
| !WebCoreFloatNearlyEqual( |
| o_radii.BottomRight().Width() - stroke_size.Width(), |
| i_radii.BottomRight().Width()) || |
| !WebCoreFloatNearlyEqual( |
| o_radii.BottomRight().Height() - stroke_size.Height(), |
| i_radii.BottomRight().Height()) || |
| !WebCoreFloatNearlyEqual( |
| o_radii.BottomLeft().Width() - stroke_size.Width(), |
| i_radii.BottomLeft().Width()) || |
| !WebCoreFloatNearlyEqual( |
| o_radii.BottomLeft().Height() - stroke_size.Height(), |
| i_radii.BottomLeft().Height())) |
| return false; |
| |
| // We also ignore DRRects with a very thick relative stroke (shapes which are |
| // mostly filled by the stroke): Skia's stroke outline can diverge |
| // significantly from the outer/inner contours in some edge cases, so we fall |
| // back to drawDRRect() instead. |
| const float kMaxStrokeToSizeRatio = 0.75f; |
| if (2 * stroke_size.Width() / outer.Rect().Width() > kMaxStrokeToSizeRatio || |
| 2 * stroke_size.Height() / outer.Rect().Height() > kMaxStrokeToSizeRatio) |
| return false; |
| |
| return true; |
| } |
| |
| } // anonymous namespace |
| |
| void GraphicsContext::FillDRRect(const FloatRoundedRect& outer, |
| const FloatRoundedRect& inner, |
| const Color& color) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| if (!IsSimpleDRRect(outer, inner)) { |
| if (color == FillColor()) { |
| canvas_->drawDRRect(outer, inner, ImmutableState()->FillFlags()); |
| } else { |
| PaintFlags flags(ImmutableState()->FillFlags()); |
| flags.setColor(color.Rgb()); |
| canvas_->drawDRRect(outer, inner, flags); |
| } |
| |
| return; |
| } |
| |
| // We can draw this as a stroked rrect. |
| float stroke_width = inner.Rect().X() - outer.Rect().X(); |
| SkRRect stroke_r_rect = outer; |
| stroke_r_rect.inset(stroke_width / 2, stroke_width / 2); |
| |
| PaintFlags stroke_flags(ImmutableState()->FillFlags()); |
| stroke_flags.setColor(color.Rgb()); |
| stroke_flags.setStyle(PaintFlags::kStroke_Style); |
| stroke_flags.setStrokeWidth(stroke_width); |
| |
| canvas_->drawRRect(stroke_r_rect, stroke_flags); |
| } |
| |
| void GraphicsContext::FillEllipse(const FloatRect& ellipse) { |
| if (ContextDisabled()) |
| return; |
| |
| DrawOval(ellipse, ImmutableState()->FillFlags()); |
| } |
| |
| void GraphicsContext::StrokePath(const Path& path_to_stroke) { |
| if (ContextDisabled() || path_to_stroke.IsEmpty()) |
| return; |
| |
| DrawPath(path_to_stroke.GetSkPath(), ImmutableState()->StrokeFlags()); |
| } |
| |
| void GraphicsContext::StrokeRect(const FloatRect& rect, float line_width) { |
| if (ContextDisabled()) |
| return; |
| |
| PaintFlags flags(ImmutableState()->StrokeFlags()); |
| flags.setStrokeWidth(WebCoreFloatToSkScalar(line_width)); |
| // Reset the dash effect to account for the width |
| ImmutableState()->GetStrokeData().SetupPaintDashPathEffect(&flags, 0); |
| // strokerect has special rules for CSS when the rect is degenerate: |
| // if width==0 && height==0, do nothing |
| // if width==0 || height==0, then just draw line for the other dimension |
| SkRect r(rect); |
| bool valid_w = r.width() > 0; |
| bool valid_h = r.height() > 0; |
| if (valid_w && valid_h) { |
| DrawRect(r, flags); |
| } else if (valid_w || valid_h) { |
| // we are expected to respect the lineJoin, so we can't just call |
| // drawLine -- we have to create a path that doubles back on itself. |
| SkPath path; |
| path.moveTo(r.fLeft, r.fTop); |
| path.lineTo(r.fRight, r.fBottom); |
| path.close(); |
| DrawPath(path, flags); |
| } |
| } |
| |
| void GraphicsContext::StrokeEllipse(const FloatRect& ellipse) { |
| if (ContextDisabled()) |
| return; |
| |
| DrawOval(ellipse, ImmutableState()->StrokeFlags()); |
| } |
| |
| void GraphicsContext::ClipRoundedRect(const FloatRoundedRect& rrect, |
| SkClipOp clip_op, |
| AntiAliasingMode should_antialias) { |
| if (ContextDisabled()) |
| return; |
| |
| if (!rrect.IsRounded()) { |
| ClipRect(rrect.Rect(), should_antialias, clip_op); |
| return; |
| } |
| |
| ClipRRect(rrect, should_antialias, clip_op); |
| } |
| |
| void GraphicsContext::ClipOut(const Path& path_to_clip) { |
| if (ContextDisabled()) |
| return; |
| |
| // Use const_cast and temporarily toggle the inverse fill type instead of |
| // copying the path. |
| SkPath& path = const_cast<SkPath&>(path_to_clip.GetSkPath()); |
| path.toggleInverseFillType(); |
| ClipPath(path, kAntiAliased); |
| path.toggleInverseFillType(); |
| } |
| |
| void GraphicsContext::ClipOutRoundedRect(const FloatRoundedRect& rect) { |
| if (ContextDisabled()) |
| return; |
| |
| ClipRoundedRect(rect, SkClipOp::kDifference); |
| } |
| |
| void GraphicsContext::ClipRect(const SkRect& rect, |
| AntiAliasingMode aa, |
| SkClipOp op) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| canvas_->clipRect(rect, op, aa == kAntiAliased); |
| } |
| |
| void GraphicsContext::ClipPath(const SkPath& path, |
| AntiAliasingMode aa, |
| SkClipOp op) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| canvas_->clipPath(path, op, aa == kAntiAliased); |
| } |
| |
| void GraphicsContext::ClipRRect(const SkRRect& rect, |
| AntiAliasingMode aa, |
| SkClipOp op) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| canvas_->clipRRect(rect, op, aa == kAntiAliased); |
| } |
| |
| void GraphicsContext::Rotate(float angle_in_radians) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| canvas_->rotate( |
| WebCoreFloatToSkScalar(angle_in_radians * (180.0f / 3.14159265f))); |
| } |
| |
| void GraphicsContext::Translate(float x, float y) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| if (!x && !y) |
| return; |
| |
| canvas_->translate(WebCoreFloatToSkScalar(x), WebCoreFloatToSkScalar(y)); |
| } |
| |
| void GraphicsContext::Scale(float x, float y) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| canvas_->scale(WebCoreFloatToSkScalar(x), WebCoreFloatToSkScalar(y)); |
| } |
| |
| void GraphicsContext::SetURLForRect(const KURL& link, |
| const IntRect& dest_rect) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| sk_sp<SkData> url(SkData::MakeWithCString(link.GetString().Utf8().data())); |
| canvas_->Annotate(PaintCanvas::AnnotationType::URL, dest_rect, |
| std::move(url)); |
| } |
| |
| void GraphicsContext::SetURLFragmentForRect(const String& dest_name, |
| const IntRect& rect) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| sk_sp<SkData> sk_dest_name(SkData::MakeWithCString(dest_name.Utf8().data())); |
| canvas_->Annotate(PaintCanvas::AnnotationType::LINK_TO_DESTINATION, rect, |
| std::move(sk_dest_name)); |
| } |
| |
| void GraphicsContext::SetURLDestinationLocation(const String& name, |
| const IntPoint& location) { |
| if (ContextDisabled()) |
| return; |
| DCHECK(canvas_); |
| |
| SkRect rect = SkRect::MakeXYWH(location.X(), location.Y(), 0, 0); |
| sk_sp<SkData> sk_name(SkData::MakeWithCString(name.Utf8().data())); |
| canvas_->Annotate(PaintCanvas::AnnotationType::NAMED_DESTINATION, rect, |
| std::move(sk_name)); |
| } |
| |
| void GraphicsContext::ConcatCTM(const AffineTransform& affine) { |
| Concat(AffineTransformToSkMatrix(affine)); |
| } |
| |
| void GraphicsContext::FillRectWithRoundedHole( |
| const FloatRect& rect, |
| const FloatRoundedRect& rounded_hole_rect, |
| const Color& color) { |
| if (ContextDisabled()) |
| return; |
| |
| PaintFlags flags(ImmutableState()->FillFlags()); |
| flags.setColor(color.Rgb()); |
| canvas_->drawDRRect(SkRRect::MakeRect(rect), rounded_hole_rect, flags); |
| } |
| |
| void GraphicsContext::AdjustLineToPixelBoundaries(FloatPoint& p1, |
| FloatPoint& p2, |
| float stroke_width, |
| StrokeStyle pen_style) { |
| // For odd widths, we add in 0.5 to the appropriate x/y so that the float |
| // arithmetic works out. For example, with a border width of 3, WebKit will |
| // pass us (y1+y2)/2, e.g., (50+53)/2 = 103/2 = 51 when we want 51.5. It is |
| // always true that an even width gave us a perfect position, but an odd width |
| // gave us a position that is off by exactly 0.5. |
| if (StrokeData::StrokeIsDashed(stroke_width, pen_style)) { |
| if (p1.X() == p2.X()) { |
| p1.SetY(p1.Y() + stroke_width); |
| p2.SetY(p2.Y() - stroke_width); |
| } else { |
| p1.SetX(p1.X() + stroke_width); |
| p2.SetX(p2.X() - stroke_width); |
| } |
| } |
| |
| if (static_cast<int>(stroke_width) % 2) { // odd |
| if (p1.X() == p2.X()) { |
| // We're a vertical line. Adjust our x. |
| p1.SetX(p1.X() + 0.5f); |
| p2.SetX(p2.X() + 0.5f); |
| } else { |
| // We're a horizontal line. Adjust our y. |
| p1.SetY(p1.Y() + 0.5f); |
| p2.SetY(p2.Y() + 0.5f); |
| } |
| } |
| } |
| |
| sk_sp<SkColorFilter> GraphicsContext::WebCoreColorFilterToSkiaColorFilter( |
| ColorFilter color_filter) { |
| switch (color_filter) { |
| case kColorFilterLuminanceToAlpha: |
| return SkLumaColorFilter::Make(); |
| case kColorFilterLinearRGBToSRGB: |
| return InterpolationSpaceUtilities::CreateInterpolationSpaceFilter( |
| kInterpolationSpaceLinear, kInterpolationSpaceSRGB); |
| case kColorFilterSRGBToLinearRGB: |
| return InterpolationSpaceUtilities::CreateInterpolationSpaceFilter( |
| kInterpolationSpaceSRGB, kInterpolationSpaceLinear); |
| case kColorFilterNone: |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| return nullptr; |
| } |
| |
| } // namespace blink |