| /* |
| * Copyright (C) 2000 Lars Knoll (knoll@kde.org) |
| * (C) 2000 Antti Koivisto (koivisto@kde.org) |
| * (C) 2000 Dirk Mueller (mueller@kde.org) |
| * Copyright (C) 2003-2017 Apple Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #include "config.h" |
| #include "NinePieceImage.h" |
| |
| #include "GraphicsContext.h" |
| #include "ImageQualityController.h" |
| #include "LengthFunctions.h" |
| #include "RenderStyle.h" |
| #include <wtf/NeverDestroyed.h> |
| #include <wtf/PointerComparison.h> |
| #include <wtf/text/TextStream.h> |
| |
| namespace WebCore { |
| |
| inline DataRef<NinePieceImage::Data>& NinePieceImage::defaultData() |
| { |
| static NeverDestroyed<DataRef<Data>> data { Data::create() }; |
| return data.get(); |
| } |
| |
| inline DataRef<NinePieceImage::Data>& NinePieceImage::defaultMaskData() |
| { |
| static NeverDestroyed<DataRef<Data>> maskData { Data::create() }; |
| auto& data = maskData.get().access(); |
| data.imageSlices = LengthBox(0); |
| data.fill = true; |
| data.borderSlices = LengthBox(); |
| return maskData.get(); |
| } |
| |
| NinePieceImage::NinePieceImage(Type imageType) |
| : m_data(imageType == Type::Normal ? defaultData() : defaultMaskData()) |
| { |
| } |
| |
| NinePieceImage::NinePieceImage(RefPtr<StyleImage>&& image, LengthBox imageSlices, bool fill, LengthBox borderSlices, LengthBox outset, NinePieceImageRule horizontalRule, NinePieceImageRule verticalRule) |
| : m_data(Data::create(WTFMove(image), imageSlices, fill, borderSlices, outset, horizontalRule, verticalRule)) |
| { |
| } |
| |
| LayoutUnit NinePieceImage::computeSlice(Length length, LayoutUnit width, LayoutUnit slice, LayoutUnit extent) |
| { |
| if (length.isRelative()) |
| return LayoutUnit(length.value() * width); |
| if (length.isAuto()) |
| return slice; |
| return valueForLength(length, extent); |
| } |
| |
| LayoutBoxExtent NinePieceImage::computeSlices(const LayoutSize& size, const LengthBox& lengths, int scaleFactor) |
| { |
| return { |
| std::min(size.height(), valueForLength(lengths.top(), size.height())) * scaleFactor, |
| std::min(size.width(), valueForLength(lengths.right(), size.width())) * scaleFactor, |
| std::min(size.height(), valueForLength(lengths.bottom(), size.height())) * scaleFactor, |
| std::min(size.width(), valueForLength(lengths.left(), size.width())) * scaleFactor |
| }; |
| } |
| |
| LayoutBoxExtent NinePieceImage::computeSlices(const LayoutSize& size, const LengthBox& lengths, const FloatBoxExtent& widths, const LayoutBoxExtent& slices) |
| { |
| return { |
| computeSlice(lengths.top(), LayoutUnit(widths.top()), slices.top(), size.height()), |
| computeSlice(lengths.right(), LayoutUnit(widths.right()), slices.right(), size.width()), |
| computeSlice(lengths.bottom(), LayoutUnit(widths.bottom()), slices.bottom(), size.height()), |
| computeSlice(lengths.left(), LayoutUnit(widths.left()), slices.left(), size.width()) |
| }; |
| } |
| |
| void NinePieceImage::scaleSlicesIfNeeded(const LayoutSize& size, LayoutBoxExtent& slices, float deviceScaleFactor) |
| { |
| LayoutUnit width = std::max(LayoutUnit(1 / deviceScaleFactor), slices.left() + slices.right()); |
| LayoutUnit height = std::max(LayoutUnit(1 / deviceScaleFactor), slices.top() + slices.bottom()); |
| |
| float sliceScaleFactor = std::min((float)size.width() / width, (float)size.height() / height); |
| |
| if (sliceScaleFactor >= 1) |
| return; |
| |
| // All slices are reduced by multiplying them by sliceScaleFactor. |
| slices.top() *= sliceScaleFactor; |
| slices.right() *= sliceScaleFactor; |
| slices.bottom() *= sliceScaleFactor; |
| slices.left() *= sliceScaleFactor; |
| } |
| |
| bool NinePieceImage::isEmptyPieceRect(ImagePiece piece, const LayoutBoxExtent& slices) |
| { |
| if (piece == MiddlePiece) |
| return false; |
| |
| auto horizontalSide = imagePieceHorizontalSide(piece); |
| auto verticalSide = imagePieceVerticalSide(piece); |
| return !((!horizontalSide || slices.at(*horizontalSide)) && (!verticalSide || slices.at(*verticalSide))); |
| } |
| |
| bool NinePieceImage::isEmptyPieceRect(ImagePiece piece, const Vector<FloatRect>& destinationRects, const Vector<FloatRect>& sourceRects) |
| { |
| return destinationRects[piece].isEmpty() || sourceRects[piece].isEmpty(); |
| } |
| |
| Vector<FloatRect> NinePieceImage::computeNineRects(const FloatRect& outer, const LayoutBoxExtent& slices, float deviceScaleFactor) |
| { |
| FloatRect inner = outer; |
| inner.move(slices.left(), slices.top()); |
| inner.contract(slices.left() + slices.right(), slices.top() + slices.bottom()); |
| ASSERT(outer.contains(inner)); |
| |
| Vector<FloatRect> rects(MaxPiece); |
| |
| rects[TopLeftPiece] = snapRectToDevicePixels(LayoutUnit(outer.x()), LayoutUnit(outer.y()), slices.left(), slices.top(), deviceScaleFactor); |
| rects[BottomLeftPiece] = snapRectToDevicePixels(LayoutUnit(outer.x()), LayoutUnit(inner.maxY()), slices.left(), slices.bottom(), deviceScaleFactor); |
| rects[LeftPiece] = snapRectToDevicePixels(LayoutUnit(outer.x()), LayoutUnit(inner.y()), slices.left(), LayoutUnit(inner.height()), deviceScaleFactor); |
| |
| rects[TopRightPiece] = snapRectToDevicePixels(LayoutUnit(inner.maxX()), LayoutUnit(outer.y()), slices.right(), slices.top(), deviceScaleFactor); |
| rects[BottomRightPiece] = snapRectToDevicePixels(LayoutUnit(inner.maxX()), LayoutUnit(inner.maxY()), slices.right(), slices.bottom(), deviceScaleFactor); |
| rects[RightPiece] = snapRectToDevicePixels(LayoutUnit(inner.maxX()), LayoutUnit(inner.y()), slices.right(), LayoutUnit(inner.height()), deviceScaleFactor); |
| |
| rects[TopPiece] = snapRectToDevicePixels(LayoutUnit(inner.x()), LayoutUnit(outer.y()), LayoutUnit(inner.width()), slices.top(), deviceScaleFactor); |
| rects[BottomPiece] = snapRectToDevicePixels(LayoutUnit(inner.x()), LayoutUnit(inner.maxY()), LayoutUnit(inner.width()), slices.bottom(), deviceScaleFactor); |
| |
| rects[MiddlePiece] = snapRectToDevicePixels(LayoutUnit(inner.x()), LayoutUnit(inner.y()), LayoutUnit(inner.width()), LayoutUnit(inner.height()), deviceScaleFactor); |
| return rects; |
| } |
| |
| FloatSize NinePieceImage::computeSideTileScale(ImagePiece piece, const Vector<FloatRect>& destinationRects, const Vector<FloatRect>& sourceRects) |
| { |
| ASSERT(!isCornerPiece(piece) && !isMiddlePiece(piece)); |
| if (isEmptyPieceRect(piece, destinationRects, sourceRects)) |
| return FloatSize(1, 1); |
| |
| float scale; |
| if (isHorizontalPiece(piece)) |
| scale = destinationRects[piece].height() / sourceRects[piece].height(); |
| else |
| scale = destinationRects[piece].width() / sourceRects[piece].width(); |
| |
| return FloatSize(scale, scale); |
| } |
| |
| FloatSize NinePieceImage::computeMiddleTileScale(const Vector<FloatSize>& scales, const Vector<FloatRect>& destinationRects, const Vector<FloatRect>& sourceRects, NinePieceImageRule hRule, NinePieceImageRule vRule) |
| { |
| FloatSize scale(1, 1); |
| if (isEmptyPieceRect(MiddlePiece, destinationRects, sourceRects)) |
| return scale; |
| |
| // Unlike the side pieces, the middle piece can have "stretch" specified in one axis but not the other. |
| // In fact the side pieces don't even use the scale factor unless they have a rule other than "stretch". |
| if (hRule == NinePieceImageRule::Stretch) |
| scale.setWidth(destinationRects[MiddlePiece].width() / sourceRects[MiddlePiece].width()); |
| else if (!isEmptyPieceRect(TopPiece, destinationRects, sourceRects)) |
| scale.setWidth(scales[TopPiece].width()); |
| else if (!isEmptyPieceRect(BottomPiece, destinationRects, sourceRects)) |
| scale.setWidth(scales[BottomPiece].width()); |
| |
| if (vRule == NinePieceImageRule::Stretch) |
| scale.setHeight(destinationRects[MiddlePiece].height() / sourceRects[MiddlePiece].height()); |
| else if (!isEmptyPieceRect(LeftPiece, destinationRects, sourceRects)) |
| scale.setHeight(scales[LeftPiece].height()); |
| else if (!isEmptyPieceRect(RightPiece, destinationRects, sourceRects)) |
| scale.setHeight(scales[RightPiece].height()); |
| |
| return scale; |
| } |
| |
| Vector<FloatSize> NinePieceImage::computeTileScales(const Vector<FloatRect>& destinationRects, const Vector<FloatRect>& sourceRects, NinePieceImageRule hRule, NinePieceImageRule vRule) |
| { |
| Vector<FloatSize> scales(MaxPiece, FloatSize(1, 1)); |
| |
| scales[TopPiece] = computeSideTileScale(TopPiece, destinationRects, sourceRects); |
| scales[RightPiece] = computeSideTileScale(RightPiece, destinationRects, sourceRects); |
| scales[BottomPiece] = computeSideTileScale(BottomPiece, destinationRects, sourceRects); |
| scales[LeftPiece] = computeSideTileScale(LeftPiece, destinationRects, sourceRects); |
| |
| scales[MiddlePiece] = computeMiddleTileScale(scales, destinationRects, sourceRects, hRule, vRule); |
| return scales; |
| } |
| |
| void NinePieceImage::paint(GraphicsContext& graphicsContext, RenderElement* renderer, const RenderStyle& style, const LayoutRect& destination, const LayoutSize& source, float deviceScaleFactor, CompositeOperator op) const |
| { |
| StyleImage* styleImage = image(); |
| ASSERT(styleImage); |
| ASSERT(styleImage->isLoaded()); |
| |
| LayoutBoxExtent sourceSlices = computeSlices(source, imageSlices(), styleImage->imageScaleFactor()); |
| LayoutBoxExtent destinationSlices = computeSlices(destination.size(), borderSlices(), style.borderWidth(), sourceSlices); |
| |
| scaleSlicesIfNeeded(destination.size(), destinationSlices, deviceScaleFactor); |
| |
| Vector<FloatRect> destinationRects = computeNineRects(destination, destinationSlices, deviceScaleFactor); |
| Vector<FloatRect> sourceRects = computeNineRects(FloatRect(FloatPoint(), source), sourceSlices, deviceScaleFactor); |
| Vector<FloatSize> tileScales = computeTileScales(destinationRects, sourceRects, horizontalRule(), verticalRule()); |
| |
| RefPtr<Image> image = styleImage->image(renderer, source); |
| if (!image) |
| return; |
| |
| InterpolationQualityMaintainer interpolationMaintainer(graphicsContext, ImageQualityController::interpolationQualityFromStyle(style)); |
| for (ImagePiece piece = MinPiece; piece < MaxPiece; ++piece) { |
| if ((piece == MiddlePiece && !fill()) || isEmptyPieceRect(piece, destinationRects, sourceRects)) |
| continue; |
| |
| if (isCornerPiece(piece)) { |
| graphicsContext.drawImage(*image, destinationRects[piece], sourceRects[piece], op); |
| continue; |
| } |
| |
| Image::TileRule hRule = isHorizontalPiece(piece) ? static_cast<Image::TileRule>(horizontalRule()) : Image::StretchTile; |
| Image::TileRule vRule = isVerticalPiece(piece) ? static_cast<Image::TileRule>(verticalRule()) : Image::StretchTile; |
| graphicsContext.drawTiledImage(*image, destinationRects[piece], sourceRects[piece], tileScales[piece], hRule, vRule, op); |
| } |
| } |
| |
| inline NinePieceImage::Data::Data() = default; |
| |
| inline NinePieceImage::Data::Data(RefPtr<StyleImage>&& image, LengthBox imageSlices, bool fill, LengthBox borderSlices, LengthBox outset, NinePieceImageRule horizontalRule, NinePieceImageRule verticalRule) |
| : fill(fill) |
| , horizontalRule(horizontalRule) |
| , verticalRule(verticalRule) |
| , image(WTFMove(image)) |
| , imageSlices(imageSlices) |
| , borderSlices(borderSlices) |
| , outset(outset) |
| { |
| } |
| |
| inline NinePieceImage::Data::Data(const Data& other) |
| : RefCounted<Data>() |
| , fill(other.fill) |
| , horizontalRule(other.horizontalRule) |
| , verticalRule(other.verticalRule) |
| , image(other.image) |
| , imageSlices(other.imageSlices) |
| , borderSlices(other.borderSlices) |
| , outset(other.outset) |
| { |
| } |
| |
| inline Ref<NinePieceImage::Data> NinePieceImage::Data::create() |
| { |
| return adoptRef(*new Data); |
| } |
| |
| inline Ref<NinePieceImage::Data> NinePieceImage::Data::create(RefPtr<StyleImage>&& image, LengthBox imageSlices, bool fill, LengthBox borderSlices, LengthBox outset, NinePieceImageRule horizontalRule, NinePieceImageRule verticalRule) |
| { |
| return adoptRef(*new Data(WTFMove(image), imageSlices, fill, borderSlices, outset, horizontalRule, verticalRule)); |
| } |
| |
| Ref<NinePieceImage::Data> NinePieceImage::Data::copy() const |
| { |
| return adoptRef(*new Data(*this)); |
| } |
| |
| bool NinePieceImage::Data::operator==(const Data& other) const |
| { |
| return arePointingToEqualData(image, other.image) |
| && imageSlices == other.imageSlices |
| && fill == other.fill |
| && borderSlices == other.borderSlices |
| && outset == other.outset |
| && horizontalRule == other.horizontalRule |
| && verticalRule == other.verticalRule; |
| } |
| |
| TextStream& operator<<(TextStream& ts, const NinePieceImage& image) |
| { |
| ts << "style-image " << image.image() << " slices " << image.imageSlices(); |
| return ts; |
| } |
| |
| } |