| 1 | /* |
| 2 | * Copyright (C) 2000 Lars Knoll (knoll@kde.org) |
| 3 | * (C) 2000 Antti Koivisto (koivisto@kde.org) |
| 4 | * (C) 2000 Dirk Mueller (mueller@kde.org) |
| 5 | * Copyright (C) 2003-2017 Apple Inc. All rights reserved. |
| 6 | * |
| 7 | * This library is free software; you can redistribute it and/or |
| 8 | * modify it under the terms of the GNU Library General Public |
| 9 | * License as published by the Free Software Foundation; either |
| 10 | * version 2 of the License, or (at your option) any later version. |
| 11 | * |
| 12 | * This library is distributed in the hope that it will be useful, |
| 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 15 | * Library General Public License for more details. |
| 16 | * |
| 17 | * You should have received a copy of the GNU Library General Public License |
| 18 | * along with this library; see the file COPYING.LIB. If not, write to |
| 19 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 20 | * Boston, MA 02110-1301, USA. |
| 21 | * |
| 22 | */ |
| 23 | |
| 24 | #include "config.h" |
| 25 | #include "NinePieceImage.h" |
| 26 | |
| 27 | #include "GraphicsContext.h" |
| 28 | #include "ImageQualityController.h" |
| 29 | #include "LengthFunctions.h" |
| 30 | #include "RenderStyle.h" |
| 31 | #include <wtf/NeverDestroyed.h> |
| 32 | #include <wtf/PointerComparison.h> |
| 33 | #include <wtf/text/TextStream.h> |
| 34 | |
| 35 | namespace WebCore { |
| 36 | |
| 37 | inline DataRef<NinePieceImage::Data>& NinePieceImage::defaultData() |
| 38 | { |
| 39 | static NeverDestroyed<DataRef<Data>> data { Data::create() }; |
| 40 | return data.get(); |
| 41 | } |
| 42 | |
| 43 | NinePieceImage::NinePieceImage() |
| 44 | : m_data(defaultData()) |
| 45 | { |
| 46 | } |
| 47 | |
| 48 | NinePieceImage::NinePieceImage(RefPtr<StyleImage>&& image, LengthBox imageSlices, bool fill, LengthBox borderSlices, LengthBox outset, ENinePieceImageRule horizontalRule, ENinePieceImageRule verticalRule) |
| 49 | : m_data(Data::create(WTFMove(image), imageSlices, fill, borderSlices, outset, horizontalRule, verticalRule)) |
| 50 | { |
| 51 | } |
| 52 | |
| 53 | LayoutUnit NinePieceImage::computeSlice(Length length, LayoutUnit width, LayoutUnit slice, LayoutUnit extent) |
| 54 | { |
| 55 | if (length.isRelative()) |
| 56 | return length.value() * width; |
| 57 | if (length.isAuto()) |
| 58 | return slice; |
| 59 | return valueForLength(length, extent); |
| 60 | } |
| 61 | |
| 62 | LayoutBoxExtent NinePieceImage::computeSlices(const LayoutSize& size, const LengthBox& lengths, int scaleFactor) |
| 63 | { |
| 64 | return { |
| 65 | std::min(size.height(), valueForLength(lengths.top(), size.height())) * scaleFactor, |
| 66 | std::min(size.width(), valueForLength(lengths.right(), size.width())) * scaleFactor, |
| 67 | std::min(size.height(), valueForLength(lengths.bottom(), size.height())) * scaleFactor, |
| 68 | std::min(size.width(), valueForLength(lengths.left(), size.width())) * scaleFactor |
| 69 | }; |
| 70 | } |
| 71 | |
| 72 | LayoutBoxExtent NinePieceImage::computeSlices(const LayoutSize& size, const LengthBox& lengths, const FloatBoxExtent& widths, const LayoutBoxExtent& slices) |
| 73 | { |
| 74 | return { |
| 75 | computeSlice(lengths.top(), widths.top(), slices.top(), size.height()), |
| 76 | computeSlice(lengths.right(), widths.right(), slices.right(), size.width()), |
| 77 | computeSlice(lengths.bottom(), widths.bottom(), slices.bottom(), size.height()), |
| 78 | computeSlice(lengths.left(), widths.left(), slices.left(), size.width()) |
| 79 | }; |
| 80 | } |
| 81 | |
| 82 | void NinePieceImage::scaleSlicesIfNeeded(const LayoutSize& size, LayoutBoxExtent& slices, float deviceScaleFactor) |
| 83 | { |
| 84 | LayoutUnit width = std::max<LayoutUnit>(1 / deviceScaleFactor, slices.left() + slices.right()); |
| 85 | LayoutUnit height = std::max<LayoutUnit>(1 / deviceScaleFactor, slices.top() + slices.bottom()); |
| 86 | |
| 87 | float sliceScaleFactor = std::min((float)size.width() / width, (float)size.height() / height); |
| 88 | |
| 89 | if (sliceScaleFactor >= 1) |
| 90 | return; |
| 91 | |
| 92 | // All slices are reduced by multiplying them by sliceScaleFactor. |
| 93 | slices.top() *= sliceScaleFactor; |
| 94 | slices.right() *= sliceScaleFactor; |
| 95 | slices.bottom() *= sliceScaleFactor; |
| 96 | slices.left() *= sliceScaleFactor; |
| 97 | } |
| 98 | |
| 99 | bool NinePieceImage::isEmptyPieceRect(ImagePiece piece, const LayoutBoxExtent& slices) |
| 100 | { |
| 101 | if (piece == MiddlePiece) |
| 102 | return false; |
| 103 | |
| 104 | auto horizontalSide = imagePieceHorizontalSide(piece); |
| 105 | auto verticalSide = imagePieceVerticalSide(piece); |
| 106 | return !((!horizontalSide || slices.at(*horizontalSide)) && (!verticalSide || slices.at(*verticalSide))); |
| 107 | } |
| 108 | |
| 109 | bool NinePieceImage::isEmptyPieceRect(ImagePiece piece, const Vector<FloatRect>& destinationRects, const Vector<FloatRect>& sourceRects) |
| 110 | { |
| 111 | return destinationRects[piece].isEmpty() || sourceRects[piece].isEmpty(); |
| 112 | } |
| 113 | |
| 114 | Vector<FloatRect> NinePieceImage::computeNineRects(const FloatRect& outer, const LayoutBoxExtent& slices, float deviceScaleFactor) |
| 115 | { |
| 116 | FloatRect inner = outer; |
| 117 | inner.move(slices.left(), slices.top()); |
| 118 | inner.contract(slices.left() + slices.right(), slices.top() + slices.bottom()); |
| 119 | ASSERT(outer.contains(inner)); |
| 120 | |
| 121 | Vector<FloatRect> rects(MaxPiece); |
| 122 | |
| 123 | rects[TopLeftPiece] = snapRectToDevicePixels(outer.x(), outer.y(), slices.left(), slices.top(), deviceScaleFactor); |
| 124 | rects[BottomLeftPiece] = snapRectToDevicePixels(outer.x(), inner.maxY(), slices.left(), slices.bottom(), deviceScaleFactor); |
| 125 | rects[LeftPiece] = snapRectToDevicePixels(outer.x(), inner.y(), slices.left(), inner.height(), deviceScaleFactor); |
| 126 | |
| 127 | rects[TopRightPiece] = snapRectToDevicePixels(inner.maxX(), outer.y(), slices.right(), slices.top(), deviceScaleFactor); |
| 128 | rects[BottomRightPiece] = snapRectToDevicePixels(inner.maxX(), inner.maxY(), slices.right(), slices.bottom(), deviceScaleFactor); |
| 129 | rects[RightPiece] = snapRectToDevicePixels(inner.maxX(), inner.y(), slices.right(), inner.height(), deviceScaleFactor); |
| 130 | |
| 131 | rects[TopPiece] = snapRectToDevicePixels(inner.x(), outer.y(), inner.width(), slices.top(), deviceScaleFactor); |
| 132 | rects[BottomPiece] = snapRectToDevicePixels(inner.x(), inner.maxY(), inner.width(), slices.bottom(), deviceScaleFactor); |
| 133 | |
| 134 | rects[MiddlePiece] = snapRectToDevicePixels(inner.x(), inner.y(), inner.width(), inner.height(), deviceScaleFactor); |
| 135 | return rects; |
| 136 | } |
| 137 | |
| 138 | FloatSize NinePieceImage::computeSideTileScale(ImagePiece piece, const Vector<FloatRect>& destinationRects, const Vector<FloatRect>& sourceRects) |
| 139 | { |
| 140 | ASSERT(!isCornerPiece(piece) && !isMiddlePiece(piece)); |
| 141 | if (isEmptyPieceRect(piece, destinationRects, sourceRects)) |
| 142 | return FloatSize(1, 1); |
| 143 | |
| 144 | float scale; |
| 145 | if (isHorizontalPiece(piece)) |
| 146 | scale = destinationRects[piece].height() / sourceRects[piece].height(); |
| 147 | else |
| 148 | scale = destinationRects[piece].width() / sourceRects[piece].width(); |
| 149 | |
| 150 | return FloatSize(scale, scale); |
| 151 | } |
| 152 | |
| 153 | FloatSize NinePieceImage::computeMiddleTileScale(const Vector<FloatSize>& scales, const Vector<FloatRect>& destinationRects, const Vector<FloatRect>& sourceRects, ENinePieceImageRule hRule, ENinePieceImageRule vRule) |
| 154 | { |
| 155 | FloatSize scale(1, 1); |
| 156 | if (isEmptyPieceRect(MiddlePiece, destinationRects, sourceRects)) |
| 157 | return scale; |
| 158 | |
| 159 | // Unlike the side pieces, the middle piece can have "stretch" specified in one axis but not the other. |
| 160 | // In fact the side pieces don't even use the scale factor unless they have a rule other than "stretch". |
| 161 | if (hRule == StretchImageRule) |
| 162 | scale.setWidth(destinationRects[MiddlePiece].width() / sourceRects[MiddlePiece].width()); |
| 163 | else if (!isEmptyPieceRect(TopPiece, destinationRects, sourceRects)) |
| 164 | scale.setWidth(scales[TopPiece].width()); |
| 165 | else if (!isEmptyPieceRect(BottomPiece, destinationRects, sourceRects)) |
| 166 | scale.setWidth(scales[BottomPiece].width()); |
| 167 | |
| 168 | if (vRule == StretchImageRule) |
| 169 | scale.setHeight(destinationRects[MiddlePiece].height() / sourceRects[MiddlePiece].height()); |
| 170 | else if (!isEmptyPieceRect(LeftPiece, destinationRects, sourceRects)) |
| 171 | scale.setHeight(scales[LeftPiece].height()); |
| 172 | else if (!isEmptyPieceRect(RightPiece, destinationRects, sourceRects)) |
| 173 | scale.setHeight(scales[RightPiece].height()); |
| 174 | |
| 175 | return scale; |
| 176 | } |
| 177 | |
| 178 | Vector<FloatSize> NinePieceImage::computeTileScales(const Vector<FloatRect>& destinationRects, const Vector<FloatRect>& sourceRects, ENinePieceImageRule hRule, ENinePieceImageRule vRule) |
| 179 | { |
| 180 | Vector<FloatSize> scales(MaxPiece, FloatSize(1, 1)); |
| 181 | |
| 182 | scales[TopPiece] = computeSideTileScale(TopPiece, destinationRects, sourceRects); |
| 183 | scales[RightPiece] = computeSideTileScale(RightPiece, destinationRects, sourceRects); |
| 184 | scales[BottomPiece] = computeSideTileScale(BottomPiece, destinationRects, sourceRects); |
| 185 | scales[LeftPiece] = computeSideTileScale(LeftPiece, destinationRects, sourceRects); |
| 186 | |
| 187 | scales[MiddlePiece] = computeMiddleTileScale(scales, destinationRects, sourceRects, hRule, vRule); |
| 188 | return scales; |
| 189 | } |
| 190 | |
| 191 | void NinePieceImage::paint(GraphicsContext& graphicsContext, RenderElement* renderer, const RenderStyle& style, const LayoutRect& destination, const LayoutSize& source, float deviceScaleFactor, CompositeOperator op) const |
| 192 | { |
| 193 | StyleImage* styleImage = image(); |
| 194 | ASSERT(styleImage); |
| 195 | ASSERT(styleImage->isLoaded()); |
| 196 | |
| 197 | LayoutBoxExtent sourceSlices = computeSlices(source, imageSlices(), styleImage->imageScaleFactor()); |
| 198 | LayoutBoxExtent destinationSlices = computeSlices(destination.size(), borderSlices(), style.borderWidth(), sourceSlices); |
| 199 | |
| 200 | scaleSlicesIfNeeded(destination.size(), destinationSlices, deviceScaleFactor); |
| 201 | |
| 202 | Vector<FloatRect> destinationRects = computeNineRects(destination, destinationSlices, deviceScaleFactor); |
| 203 | Vector<FloatRect> sourceRects = computeNineRects(FloatRect(FloatPoint(), source), sourceSlices, deviceScaleFactor); |
| 204 | Vector<FloatSize> tileScales = computeTileScales(destinationRects, sourceRects, horizontalRule(), verticalRule()); |
| 205 | |
| 206 | RefPtr<Image> image = styleImage->image(renderer, source); |
| 207 | if (!image) |
| 208 | return; |
| 209 | |
| 210 | InterpolationQualityMaintainer interpolationMaintainer(graphicsContext, ImageQualityController::interpolationQualityFromStyle(style)); |
| 211 | for (ImagePiece piece = MinPiece; piece < MaxPiece; ++piece) { |
| 212 | if ((piece == MiddlePiece && !fill()) || isEmptyPieceRect(piece, destinationRects, sourceRects)) |
| 213 | continue; |
| 214 | |
| 215 | if (isCornerPiece(piece)) { |
| 216 | graphicsContext.drawImage(*image, destinationRects[piece], sourceRects[piece], op); |
| 217 | continue; |
| 218 | } |
| 219 | |
| 220 | Image::TileRule hRule = isHorizontalPiece(piece) ? static_cast<Image::TileRule>(horizontalRule()) : Image::StretchTile; |
| 221 | Image::TileRule vRule = isVerticalPiece(piece) ? static_cast<Image::TileRule>(verticalRule()) : Image::StretchTile; |
| 222 | graphicsContext.drawTiledImage(*image, destinationRects[piece], sourceRects[piece], tileScales[piece], hRule, vRule, op); |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | inline NinePieceImage::Data::Data() |
| 227 | : fill(false) |
| 228 | , horizontalRule(StretchImageRule) |
| 229 | , verticalRule(StretchImageRule) |
| 230 | { |
| 231 | } |
| 232 | |
| 233 | inline NinePieceImage::Data::Data(RefPtr<StyleImage>&& image, LengthBox imageSlices, bool fill, LengthBox borderSlices, LengthBox outset, ENinePieceImageRule horizontalRule, ENinePieceImageRule verticalRule) |
| 234 | : fill(fill) |
| 235 | , horizontalRule(horizontalRule) |
| 236 | , verticalRule(verticalRule) |
| 237 | , image(WTFMove(image)) |
| 238 | , imageSlices(imageSlices) |
| 239 | , borderSlices(borderSlices) |
| 240 | , outset(outset) |
| 241 | { |
| 242 | } |
| 243 | |
| 244 | inline NinePieceImage::Data::Data(const Data& other) |
| 245 | : RefCounted<Data>() |
| 246 | , fill(other.fill) |
| 247 | , horizontalRule(other.horizontalRule) |
| 248 | , verticalRule(other.verticalRule) |
| 249 | , image(other.image) |
| 250 | , imageSlices(other.imageSlices) |
| 251 | , borderSlices(other.borderSlices) |
| 252 | , outset(other.outset) |
| 253 | { |
| 254 | } |
| 255 | |
| 256 | inline Ref<NinePieceImage::Data> NinePieceImage::Data::create() |
| 257 | { |
| 258 | return adoptRef(*new Data); |
| 259 | } |
| 260 | |
| 261 | inline Ref<NinePieceImage::Data> NinePieceImage::Data::create(RefPtr<StyleImage>&& image, LengthBox imageSlices, bool fill, LengthBox borderSlices, LengthBox outset, ENinePieceImageRule horizontalRule, ENinePieceImageRule verticalRule) |
| 262 | { |
| 263 | return adoptRef(*new Data(WTFMove(image), imageSlices, fill, borderSlices, outset, horizontalRule, verticalRule)); |
| 264 | } |
| 265 | |
| 266 | Ref<NinePieceImage::Data> NinePieceImage::Data::copy() const |
| 267 | { |
| 268 | return adoptRef(*new Data(*this)); |
| 269 | } |
| 270 | |
| 271 | bool NinePieceImage::Data::operator==(const Data& other) const |
| 272 | { |
| 273 | return arePointingToEqualData(image, other.image) |
| 274 | && imageSlices == other.imageSlices |
| 275 | && fill == other.fill |
| 276 | && borderSlices == other.borderSlices |
| 277 | && outset == other.outset |
| 278 | && horizontalRule == other.horizontalRule |
| 279 | && verticalRule == other.verticalRule; |
| 280 | } |
| 281 | |
| 282 | TextStream& operator<<(TextStream& ts, const NinePieceImage& image) |
| 283 | { |
| 284 | ts << "style-image " << image.image() << " slices " << image.imageSlices(); |
| 285 | return ts; |
| 286 | } |
| 287 | |
| 288 | } |
| 289 | |