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 | |