| /* |
| * Copyright (C) 2012 Adobe Systems Incorporated. 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 THE COPYRIGHT HOLDERS AND 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 THE |
| * COPYRIGHT HOLDER OR 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 "third_party/blink/renderer/core/layout/shapes/shape.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/renderer/core/css/basic_shape_functions.h" |
| #include "third_party/blink/renderer/core/layout/shapes/box_shape.h" |
| #include "third_party/blink/renderer/core/layout/shapes/polygon_shape.h" |
| #include "third_party/blink/renderer/core/layout/shapes/raster_shape.h" |
| #include "third_party/blink/renderer/core/layout/shapes/rectangle_shape.h" |
| #include "third_party/blink/renderer/core/svg/graphics/svg_image.h" |
| #include "third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.h" |
| #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h" |
| #include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h" |
| #include "third_party/blink/renderer/platform/geometry/float_rounded_rect.h" |
| #include "third_party/blink/renderer/platform/geometry/float_size.h" |
| #include "third_party/blink/renderer/platform/geometry/length_functions.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_types.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_flags.h" |
| #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h" |
| #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h" |
| #include "third_party/blink/renderer/platform/wtf/math_extras.h" |
| #include "third_party/skia/include/core/SkSurface.h" |
| |
| namespace blink { |
| |
| static std::unique_ptr<Shape> CreateInsetShape(const FloatRoundedRect& bounds) { |
| DCHECK_GE(bounds.Rect().Width(), 0); |
| DCHECK_GE(bounds.Rect().Height(), 0); |
| return std::make_unique<BoxShape>(bounds); |
| } |
| |
| static std::unique_ptr<Shape> CreateCircleShape(const FloatPoint& center, |
| float radius) { |
| DCHECK_GE(radius, 0); |
| return std::make_unique<RectangleShape>( |
| FloatRect(center.X() - radius, center.Y() - radius, radius * 2, |
| radius * 2), |
| FloatSize(radius, radius)); |
| } |
| |
| static std::unique_ptr<Shape> CreateEllipseShape(const FloatPoint& center, |
| const FloatSize& radii) { |
| DCHECK_GE(radii.Width(), 0); |
| DCHECK_GE(radii.Height(), 0); |
| return std::make_unique<RectangleShape>( |
| FloatRect(center.X() - radii.Width(), center.Y() - radii.Height(), |
| radii.Width() * 2, radii.Height() * 2), |
| radii); |
| } |
| |
| static std::unique_ptr<Shape> CreatePolygonShape(Vector<FloatPoint> vertices, |
| WindRule fill_rule) { |
| return std::make_unique<PolygonShape>(std::move(vertices), fill_rule); |
| } |
| |
| static inline FloatRect PhysicalRectToLogical(const FloatRect& rect, |
| float logical_box_height, |
| WritingMode writing_mode) { |
| if (IsHorizontalWritingMode(writing_mode)) |
| return rect; |
| if (IsFlippedBlocksWritingMode(writing_mode)) |
| return FloatRect(rect.Y(), logical_box_height - rect.MaxX(), rect.Height(), |
| rect.Width()); |
| return rect.TransposedRect(); |
| } |
| |
| static inline FloatPoint PhysicalPointToLogical(const FloatPoint& point, |
| float logical_box_height, |
| WritingMode writing_mode) { |
| if (IsHorizontalWritingMode(writing_mode)) |
| return point; |
| if (IsFlippedBlocksWritingMode(writing_mode)) |
| return FloatPoint(point.Y(), logical_box_height - point.X()); |
| return point.TransposedPoint(); |
| } |
| |
| static inline FloatSize PhysicalSizeToLogical(const FloatSize& size, |
| WritingMode writing_mode) { |
| if (IsHorizontalWritingMode(writing_mode)) |
| return size; |
| return size.TransposedSize(); |
| } |
| |
| std::unique_ptr<Shape> Shape::CreateShape(const BasicShape* basic_shape, |
| const LayoutSize& logical_box_size, |
| WritingMode writing_mode, |
| float margin) { |
| DCHECK(basic_shape); |
| |
| bool horizontal_writing_mode = IsHorizontalWritingMode(writing_mode); |
| float box_width = horizontal_writing_mode |
| ? logical_box_size.Width().ToFloat() |
| : logical_box_size.Height().ToFloat(); |
| float box_height = horizontal_writing_mode |
| ? logical_box_size.Height().ToFloat() |
| : logical_box_size.Width().ToFloat(); |
| std::unique_ptr<Shape> shape; |
| |
| switch (basic_shape->GetType()) { |
| case BasicShape::kBasicShapeCircleType: { |
| const BasicShapeCircle* circle = To<BasicShapeCircle>(basic_shape); |
| FloatPoint center = |
| FloatPointForCenterCoordinate(circle->CenterX(), circle->CenterY(), |
| FloatSize(box_width, box_height)); |
| float radius = |
| circle->FloatValueForRadiusInBox(FloatSize(box_width, box_height)); |
| FloatPoint logical_center = PhysicalPointToLogical( |
| center, logical_box_size.Height().ToFloat(), writing_mode); |
| |
| shape = CreateCircleShape(logical_center, radius); |
| break; |
| } |
| |
| case BasicShape::kBasicShapeEllipseType: { |
| const BasicShapeEllipse* ellipse = To<BasicShapeEllipse>(basic_shape); |
| FloatPoint center = |
| FloatPointForCenterCoordinate(ellipse->CenterX(), ellipse->CenterY(), |
| FloatSize(box_width, box_height)); |
| float radius_x = ellipse->FloatValueForRadiusInBox(ellipse->RadiusX(), |
| center.X(), box_width); |
| float radius_y = ellipse->FloatValueForRadiusInBox( |
| ellipse->RadiusY(), center.Y(), box_height); |
| FloatPoint logical_center = PhysicalPointToLogical( |
| center, logical_box_size.Height().ToFloat(), writing_mode); |
| |
| shape = CreateEllipseShape(logical_center, FloatSize(radius_x, radius_y)); |
| break; |
| } |
| |
| case BasicShape::kBasicShapePolygonType: { |
| const BasicShapePolygon* polygon = To<BasicShapePolygon>(basic_shape); |
| const Vector<Length>& values = polygon->Values(); |
| wtf_size_t values_size = values.size(); |
| DCHECK(!(values_size % 2)); |
| Vector<FloatPoint> vertices(values_size / 2); |
| for (wtf_size_t i = 0; i < values_size; i += 2) { |
| FloatPoint vertex(FloatValueForLength(values.at(i), box_width), |
| FloatValueForLength(values.at(i + 1), box_height)); |
| vertices[i / 2] = PhysicalPointToLogical( |
| vertex, logical_box_size.Height().ToFloat(), writing_mode); |
| } |
| shape = CreatePolygonShape(std::move(vertices), polygon->GetWindRule()); |
| break; |
| } |
| |
| case BasicShape::kBasicShapeInsetType: { |
| const BasicShapeInset& inset = *To<BasicShapeInset>(basic_shape); |
| float left = FloatValueForLength(inset.Left(), box_width); |
| float top = FloatValueForLength(inset.Top(), box_height); |
| float right = FloatValueForLength(inset.Right(), box_width); |
| float bottom = FloatValueForLength(inset.Bottom(), box_height); |
| FloatRect rect(left, top, std::max<float>(box_width - left - right, 0), |
| std::max<float>(box_height - top - bottom, 0)); |
| FloatRect logical_rect = PhysicalRectToLogical( |
| rect, logical_box_size.Height().ToFloat(), writing_mode); |
| |
| FloatSize box_size(box_width, box_height); |
| FloatSize top_left_radius = PhysicalSizeToLogical( |
| FloatSizeForLengthSize(inset.TopLeftRadius(), box_size), |
| writing_mode); |
| FloatSize top_right_radius = PhysicalSizeToLogical( |
| FloatSizeForLengthSize(inset.TopRightRadius(), box_size), |
| writing_mode); |
| FloatSize bottom_left_radius = PhysicalSizeToLogical( |
| FloatSizeForLengthSize(inset.BottomLeftRadius(), box_size), |
| writing_mode); |
| FloatSize bottom_right_radius = PhysicalSizeToLogical( |
| FloatSizeForLengthSize(inset.BottomRightRadius(), box_size), |
| writing_mode); |
| FloatRoundedRect::Radii corner_radii(top_left_radius, top_right_radius, |
| bottom_left_radius, |
| bottom_right_radius); |
| |
| FloatRoundedRect final_rect(logical_rect, corner_radii); |
| final_rect.ConstrainRadii(); |
| |
| shape = CreateInsetShape(final_rect); |
| break; |
| } |
| |
| default: |
| NOTREACHED(); |
| } |
| |
| shape->writing_mode_ = writing_mode; |
| shape->margin_ = margin; |
| |
| return shape; |
| } |
| |
| std::unique_ptr<Shape> Shape::CreateEmptyRasterShape(WritingMode writing_mode, |
| float margin) { |
| std::unique_ptr<RasterShapeIntervals> intervals = |
| std::make_unique<RasterShapeIntervals>(0, 0); |
| std::unique_ptr<RasterShape> raster_shape = |
| std::make_unique<RasterShape>(std::move(intervals), IntSize()); |
| raster_shape->writing_mode_ = writing_mode; |
| raster_shape->margin_ = margin; |
| return std::move(raster_shape); |
| } |
| |
| static bool ExtractImageData(Image* image, |
| const IntSize& image_size, |
| ArrayBufferContents& contents, |
| RespectImageOrientationEnum respect_orientation) { |
| if (!image) |
| return false; |
| |
| // Compute the SkImageInfo for the output. |
| SkImageInfo dst_info = SkImageInfo::Make( |
| image_size.Width(), image_size.Height(), kN32_SkColorType, |
| kPremul_SkAlphaType, SkColorSpace::MakeSRGB()); |
| |
| // Populate |contents| with newly allocated and zero-initialized data, big |
| // enough for |dst_info|. |
| size_t dst_size_bytes = dst_info.computeMinByteSize(); |
| { |
| if (SkImageInfo::ByteSizeOverflowed(dst_size_bytes) || |
| dst_size_bytes > v8::TypedArray::kMaxLength) { |
| return false; |
| } |
| ArrayBufferContents result(dst_size_bytes, 1, |
| ArrayBufferContents::kNotShared, |
| ArrayBufferContents::kZeroInitialize); |
| if (result.DataLength() != dst_size_bytes) |
| return false; |
| result.Transfer(contents); |
| } |
| |
| // Set |surface| to draw directly to |contents|. |
| const SkSurfaceProps disable_lcd_props(0, kUnknown_SkPixelGeometry); |
| sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect( |
| dst_info, contents.Data(), dst_info.minRowBytes(), &disable_lcd_props); |
| if (!surface) |
| return false; |
| |
| // FIXME: This is not totally correct but it is needed to prevent shapes |
| // that loads SVG Images during paint invalidations to mark layoutObjects |
| // for layout, which is not allowed. See https://crbug.com/429346 |
| ImageObserverDisabler disabler(image); |
| PaintFlags flags; |
| FloatRect image_source_rect(FloatPoint(), FloatSize(image->Size())); |
| IntRect image_dest_rect(IntPoint(), image_size); |
| SkiaPaintCanvas canvas(surface->getCanvas()); |
| canvas.clear(SK_ColorTRANSPARENT); |
| image->Draw(&canvas, flags, FloatRect(image_dest_rect), image_source_rect, |
| SkSamplingOptions(), respect_orientation, |
| Image::kDoNotClampImageToSourceRect, Image::kSyncDecode); |
| return true; |
| } |
| |
| static std::unique_ptr<RasterShapeIntervals> ExtractIntervalsFromImageData( |
| ArrayBufferContents& contents, |
| float threshold, |
| const IntRect& image_rect, |
| const IntRect& margin_rect) { |
| DOMArrayBuffer* array_buffer = DOMArrayBuffer::Create(contents); |
| DOMUint8ClampedArray* pixel_array = |
| DOMUint8ClampedArray::Create(array_buffer, 0, array_buffer->ByteLength()); |
| |
| unsigned pixel_array_offset = 3; // Each pixel is four bytes: RGBA. |
| uint8_t alpha_pixel_threshold = threshold * 255; |
| |
| DCHECK_EQ(image_rect.Size().Area() * 4, pixel_array->length()); |
| |
| int min_buffer_y = std::max(0, margin_rect.Y() - image_rect.Y()); |
| int max_buffer_y = |
| std::min(image_rect.Height(), margin_rect.MaxY() - image_rect.Y()); |
| |
| std::unique_ptr<RasterShapeIntervals> intervals = |
| std::make_unique<RasterShapeIntervals>(margin_rect.Height(), |
| -margin_rect.Y()); |
| |
| for (int y = min_buffer_y; y < max_buffer_y; ++y) { |
| int start_x = -1; |
| for (int x = 0; x < image_rect.Width(); ++x, pixel_array_offset += 4) { |
| uint8_t alpha = pixel_array->Item(pixel_array_offset); |
| bool alpha_above_threshold = alpha > alpha_pixel_threshold; |
| if (start_x == -1 && alpha_above_threshold) { |
| start_x = x; |
| } else if (start_x != -1 && |
| (!alpha_above_threshold || x == image_rect.Width() - 1)) { |
| int end_x = alpha_above_threshold ? x + 1 : x; |
| intervals->IntervalAt(y + image_rect.Y()) |
| .Unite(IntShapeInterval(start_x + image_rect.X(), |
| end_x + image_rect.X())); |
| start_x = -1; |
| } |
| } |
| } |
| return intervals; |
| } |
| |
| static bool IsValidRasterShapeSize(const IntSize& size) { |
| // Some platforms don't limit MaxDecodedImageBytes. |
| constexpr size_t size32_max_bytes = 0xFFFFFFFF / 4; |
| static const size_t max_image_size_bytes = |
| std::min(size32_max_bytes, Platform::Current()->MaxDecodedImageBytes()); |
| return size.Area() * 4 < max_image_size_bytes; |
| } |
| |
| std::unique_ptr<Shape> Shape::CreateRasterShape( |
| Image* image, |
| float threshold, |
| const LayoutRect& image_r, |
| const LayoutRect& margin_r, |
| WritingMode writing_mode, |
| float margin, |
| RespectImageOrientationEnum respect_orientation) { |
| IntRect image_rect = PixelSnappedIntRect(image_r); |
| IntRect margin_rect = PixelSnappedIntRect(margin_r); |
| |
| if (!IsValidRasterShapeSize(margin_rect.Size()) || |
| !IsValidRasterShapeSize(image_rect.Size())) { |
| return CreateEmptyRasterShape(writing_mode, margin); |
| } |
| |
| ArrayBufferContents contents; |
| if (!ExtractImageData(image, image_rect.Size(), contents, |
| respect_orientation)) { |
| return CreateEmptyRasterShape(writing_mode, margin); |
| } |
| |
| std::unique_ptr<RasterShapeIntervals> intervals = |
| ExtractIntervalsFromImageData(contents, threshold, image_rect, |
| margin_rect); |
| std::unique_ptr<RasterShape> raster_shape = |
| std::make_unique<RasterShape>(std::move(intervals), margin_rect.Size()); |
| raster_shape->writing_mode_ = writing_mode; |
| raster_shape->margin_ = margin; |
| return std::move(raster_shape); |
| } |
| |
| std::unique_ptr<Shape> Shape::CreateLayoutBoxShape( |
| const FloatRoundedRect& rounded_rect, |
| WritingMode writing_mode, |
| float margin) { |
| FloatRect rect(0, 0, rounded_rect.Rect().Width(), |
| rounded_rect.Rect().Height()); |
| FloatRoundedRect bounds(rect, rounded_rect.GetRadii()); |
| std::unique_ptr<Shape> shape = CreateInsetShape(bounds); |
| shape->writing_mode_ = writing_mode; |
| shape->margin_ = margin; |
| |
| return shape; |
| } |
| |
| } // namespace blink |