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
35namespace WebCore {
36
37inline DataRef<NinePieceImage::Data>& NinePieceImage::defaultData()
38{
39 static NeverDestroyed<DataRef<Data>> data { Data::create() };
40 return data.get();
41}
42
43NinePieceImage::NinePieceImage()
44 : m_data(defaultData())
45{
46}
47
48NinePieceImage::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
53LayoutUnit 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
62LayoutBoxExtent 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
72LayoutBoxExtent 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
82void 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
99bool 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
109bool NinePieceImage::isEmptyPieceRect(ImagePiece piece, const Vector<FloatRect>& destinationRects, const Vector<FloatRect>& sourceRects)
110{
111 return destinationRects[piece].isEmpty() || sourceRects[piece].isEmpty();
112}
113
114Vector<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
138FloatSize 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
153FloatSize 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
178Vector<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
191void 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
226inline NinePieceImage::Data::Data()
227 : fill(false)
228 , horizontalRule(StretchImageRule)
229 , verticalRule(StretchImageRule)
230{
231}
232
233inline 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
244inline 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
256inline Ref<NinePieceImage::Data> NinePieceImage::Data::create()
257{
258 return adoptRef(*new Data);
259}
260
261inline 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
266Ref<NinePieceImage::Data> NinePieceImage::Data::copy() const
267{
268 return adoptRef(*new Data(*this));
269}
270
271bool 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
282TextStream& operator<<(TextStream& ts, const NinePieceImage& image)
283{
284 ts << "style-image " << image.image() << " slices " << image.imageSlices();
285 return ts;
286}
287
288}
289