Bug 1641886 [wpt PR 23851] - Support interpolating contain-intrinsic-size, a=testonly
[gecko.git] / gfx / 2d / DrawTargetTiled.cpp
blob3e4f904bfe230092067bfe01f499ccd0a93304f0
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "DrawTargetTiled.h"
8 #include "Logging.h"
9 #include "nsRegion.h"
10 #include "PathHelpers.h"
12 namespace mozilla {
13 namespace gfx {
15 DrawTargetTiled::DrawTargetTiled() = default;
17 bool DrawTargetTiled::Init(const TileSet& aTiles) {
18 if (!aTiles.mTileCount) {
19 return false;
22 mTiles.reserve(aTiles.mTileCount);
23 for (size_t i = 0; i < aTiles.mTileCount; ++i) {
24 mTiles.push_back(TileInternal(aTiles.mTiles[i]));
25 if (!aTiles.mTiles[i].mDrawTarget) {
26 return false;
28 if (mTiles[0].mDrawTarget->GetFormat() !=
29 mTiles.back().mDrawTarget->GetFormat() ||
30 mTiles[0].mDrawTarget->GetBackendType() !=
31 mTiles.back().mDrawTarget->GetBackendType()) {
32 return false;
34 uint32_t newXMost =
35 std::max(mRect.XMost(), mTiles[i].mTileOrigin.x +
36 mTiles[i].mDrawTarget->GetSize().width);
37 uint32_t newYMost =
38 std::max(mRect.YMost(), mTiles[i].mTileOrigin.y +
39 mTiles[i].mDrawTarget->GetSize().height);
40 if (i == 0) {
41 mRect.MoveTo(mTiles[0].mTileOrigin.x, mTiles[0].mTileOrigin.y);
42 } else {
43 mRect.MoveTo(std::min(mRect.X(), mTiles[i].mTileOrigin.x),
44 std::min(mRect.Y(), mTiles[i].mTileOrigin.y));
46 mRect.SetRightEdge(newXMost);
47 mRect.SetBottomEdge(newYMost);
48 mTiles[i].mDrawTarget->SetTransform(Matrix::Translation(
49 -mTiles[i].mTileOrigin.x, -mTiles[i].mTileOrigin.y));
51 mFormat = mTiles[0].mDrawTarget->GetFormat();
52 SetPermitSubpixelAA(IsOpaque(mFormat));
53 return true;
56 already_AddRefed<SourceSurface> DrawTargetTiled::Snapshot() {
57 return MakeAndAddRef<SnapshotTiled>(mTiles, mRect);
60 void DrawTargetTiled::DetachAllSnapshots() {}
62 // Skip the mClippedOut check since this is only used for Flush() which
63 // should happen even if we're clipped.
64 #define TILED_COMMAND(command) \
65 void DrawTargetTiled::command() { \
66 for (size_t i = 0; i < mTiles.size(); i++) { \
67 mTiles[i].mDrawTarget->command(); \
68 } \
70 #define TILED_COMMAND1(command, type1) \
71 void DrawTargetTiled::command(type1 arg1) { \
72 for (size_t i = 0; i < mTiles.size(); i++) { \
73 if (!mTiles[i].mClippedOut) mTiles[i].mDrawTarget->command(arg1); \
74 } \
76 #define TILED_COMMAND3(command, type1, type2, type3) \
77 void DrawTargetTiled::command(type1 arg1, type2 arg2, type3 arg3) { \
78 for (size_t i = 0; i < mTiles.size(); i++) { \
79 if (!mTiles[i].mClippedOut) \
80 mTiles[i].mDrawTarget->command(arg1, arg2, arg3); \
81 } \
83 #define TILED_COMMAND4(command, type1, type2, type3, type4) \
84 void DrawTargetTiled::command(type1 arg1, type2 arg2, type3 arg3, \
85 type4 arg4) { \
86 for (size_t i = 0; i < mTiles.size(); i++) { \
87 if (!mTiles[i].mClippedOut) \
88 mTiles[i].mDrawTarget->command(arg1, arg2, arg3, arg4); \
89 } \
91 #define TILED_COMMAND5(command, type1, type2, type3, type4, type5) \
92 void DrawTargetTiled::command(type1 arg1, type2 arg2, type3 arg3, \
93 type4 arg4, type5 arg5) { \
94 for (size_t i = 0; i < mTiles.size(); i++) { \
95 if (!mTiles[i].mClippedOut) \
96 mTiles[i].mDrawTarget->command(arg1, arg2, arg3, arg4, arg5); \
97 } \
100 TILED_COMMAND(Flush)
101 TILED_COMMAND4(DrawFilter, FilterNode*, const Rect&, const Point&,
102 const DrawOptions&)
103 TILED_COMMAND1(ClearRect, const Rect&)
104 TILED_COMMAND4(MaskSurface, const Pattern&, SourceSurface*, Point,
105 const DrawOptions&)
106 TILED_COMMAND4(FillGlyphs, ScaledFont*, const GlyphBuffer&, const Pattern&,
107 const DrawOptions&)
108 TILED_COMMAND3(Mask, const Pattern&, const Pattern&, const DrawOptions&)
110 void DrawTargetTiled::PushClip(const Path* aPath) {
111 if (!mClippedOutTilesStack.append(std::vector<bool>(mTiles.size()))) {
112 MOZ_CRASH("out of memory");
114 std::vector<bool>& clippedTiles = mClippedOutTilesStack.back();
116 Rect deviceRect = aPath->GetBounds(mTransform);
118 for (size_t i = 0; i < mTiles.size(); i++) {
119 if (!mTiles[i].mClippedOut) {
120 if (deviceRect.Intersects(
121 Rect(mTiles[i].mTileOrigin.x, mTiles[i].mTileOrigin.y,
122 mTiles[i].mDrawTarget->GetSize().width,
123 mTiles[i].mDrawTarget->GetSize().height))) {
124 mTiles[i].mDrawTarget->PushClip(aPath);
125 } else {
126 mTiles[i].mClippedOut = true;
127 clippedTiles[i] = true;
133 void DrawTargetTiled::PushClipRect(const Rect& aRect) {
134 if (!mClippedOutTilesStack.append(std::vector<bool>(mTiles.size()))) {
135 MOZ_CRASH("out of memory");
137 std::vector<bool>& clippedTiles = mClippedOutTilesStack.back();
139 Rect deviceRect = mTransform.TransformBounds(aRect);
141 for (size_t i = 0; i < mTiles.size(); i++) {
142 if (!mTiles[i].mClippedOut) {
143 if (deviceRect.Intersects(
144 Rect(mTiles[i].mTileOrigin.x, mTiles[i].mTileOrigin.y,
145 mTiles[i].mDrawTarget->GetSize().width,
146 mTiles[i].mDrawTarget->GetSize().height))) {
147 mTiles[i].mDrawTarget->PushClipRect(aRect);
148 } else {
149 mTiles[i].mClippedOut = true;
150 clippedTiles[i] = true;
156 void DrawTargetTiled::PopClip() {
157 std::vector<bool>& clippedTiles = mClippedOutTilesStack.back();
158 MOZ_ASSERT(clippedTiles.size() == mTiles.size());
159 for (size_t i = 0; i < mTiles.size(); i++) {
160 if (!mTiles[i].mClippedOut) {
161 mTiles[i].mDrawTarget->PopClip();
162 } else if (clippedTiles[i]) {
163 mTiles[i].mClippedOut = false;
167 mClippedOutTilesStack.popBack();
170 void DrawTargetTiled::CopySurface(SourceSurface* aSurface,
171 const IntRect& aSourceRect,
172 const IntPoint& aDestination) {
173 for (size_t i = 0; i < mTiles.size(); i++) {
174 IntPoint tileOrigin = mTiles[i].mTileOrigin;
175 IntSize tileSize = mTiles[i].mDrawTarget->GetSize();
176 if (!IntRect(aDestination, aSourceRect.Size())
177 .Intersects(IntRect(tileOrigin, tileSize))) {
178 continue;
180 // CopySurface ignores the transform, account for that here.
181 mTiles[i].mDrawTarget->CopySurface(aSurface, aSourceRect,
182 aDestination - tileOrigin);
186 void DrawTargetTiled::SetTransform(const Matrix& aTransform) {
187 for (size_t i = 0; i < mTiles.size(); i++) {
188 Matrix mat = aTransform;
189 mat.PostTranslate(Float(-mTiles[i].mTileOrigin.x),
190 Float(-mTiles[i].mTileOrigin.y));
191 mTiles[i].mDrawTarget->SetTransform(mat);
193 DrawTarget::SetTransform(aTransform);
196 void DrawTargetTiled::SetPermitSubpixelAA(bool aPermitSubpixelAA) {
197 DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA);
198 for (size_t i = 0; i < mTiles.size(); i++) {
199 mTiles[i].mDrawTarget->SetPermitSubpixelAA(aPermitSubpixelAA);
203 void DrawTargetTiled::DrawSurface(SourceSurface* aSurface, const Rect& aDest,
204 const Rect& aSource,
205 const DrawSurfaceOptions& aSurfaceOptions,
206 const DrawOptions& aDrawOptions) {
207 Rect deviceRect = mTransform.TransformBounds(aDest);
208 for (size_t i = 0; i < mTiles.size(); i++) {
209 if (!mTiles[i].mClippedOut &&
210 deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
211 mTiles[i].mTileOrigin.y,
212 mTiles[i].mDrawTarget->GetSize().width,
213 mTiles[i].mDrawTarget->GetSize().height))) {
214 mTiles[i].mDrawTarget->DrawSurface(aSurface, aDest, aSource,
215 aSurfaceOptions, aDrawOptions);
220 void DrawTargetTiled::FillRect(const Rect& aRect, const Pattern& aPattern,
221 const DrawOptions& aDrawOptions) {
222 Rect deviceRect = mTransform.TransformBounds(aRect);
223 for (size_t i = 0; i < mTiles.size(); i++) {
224 if (!mTiles[i].mClippedOut &&
225 deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
226 mTiles[i].mTileOrigin.y,
227 mTiles[i].mDrawTarget->GetSize().width,
228 mTiles[i].mDrawTarget->GetSize().height))) {
229 mTiles[i].mDrawTarget->FillRect(aRect, aPattern, aDrawOptions);
234 void DrawTargetTiled::Stroke(const Path* aPath, const Pattern& aPattern,
235 const StrokeOptions& aStrokeOptions,
236 const DrawOptions& aDrawOptions) {
237 // Approximate the stroke extents, since Path::GetStrokeExtents can be slow
238 Rect deviceRect = aPath->GetBounds(mTransform);
239 deviceRect.Inflate(MaxStrokeExtents(aStrokeOptions, mTransform));
240 for (size_t i = 0; i < mTiles.size(); i++) {
241 if (!mTiles[i].mClippedOut &&
242 deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
243 mTiles[i].mTileOrigin.y,
244 mTiles[i].mDrawTarget->GetSize().width,
245 mTiles[i].mDrawTarget->GetSize().height))) {
246 mTiles[i].mDrawTarget->Stroke(aPath, aPattern, aStrokeOptions,
247 aDrawOptions);
252 void DrawTargetTiled::StrokeRect(const Rect& aRect, const Pattern& aPattern,
253 const StrokeOptions& aStrokeOptions,
254 const DrawOptions& aDrawOptions) {
255 Rect deviceRect = mTransform.TransformBounds(aRect);
256 Margin strokeMargin = MaxStrokeExtents(aStrokeOptions, mTransform);
257 Rect outerRect = deviceRect;
258 outerRect.Inflate(strokeMargin);
259 Rect innerRect;
260 if (mTransform.IsRectilinear()) {
261 // If rects are mapped to rects, we can compute the inner rect
262 // of the stroked rect.
263 innerRect = deviceRect;
264 innerRect.Deflate(strokeMargin);
266 for (size_t i = 0; i < mTiles.size(); i++) {
267 if (mTiles[i].mClippedOut) {
268 continue;
270 Rect tileRect(mTiles[i].mTileOrigin.x, mTiles[i].mTileOrigin.y,
271 mTiles[i].mDrawTarget->GetSize().width,
272 mTiles[i].mDrawTarget->GetSize().height);
273 if (outerRect.Intersects(tileRect) && !innerRect.Contains(tileRect)) {
274 mTiles[i].mDrawTarget->StrokeRect(aRect, aPattern, aStrokeOptions,
275 aDrawOptions);
280 void DrawTargetTiled::StrokeLine(const Point& aStart, const Point& aEnd,
281 const Pattern& aPattern,
282 const StrokeOptions& aStrokeOptions,
283 const DrawOptions& aDrawOptions) {
284 Rect lineBounds = Rect(aStart, Size()).UnionEdges(Rect(aEnd, Size()));
285 Rect deviceRect = mTransform.TransformBounds(lineBounds);
286 deviceRect.Inflate(MaxStrokeExtents(aStrokeOptions, mTransform));
287 for (size_t i = 0; i < mTiles.size(); i++) {
288 if (!mTiles[i].mClippedOut &&
289 deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
290 mTiles[i].mTileOrigin.y,
291 mTiles[i].mDrawTarget->GetSize().width,
292 mTiles[i].mDrawTarget->GetSize().height))) {
293 mTiles[i].mDrawTarget->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions,
294 aDrawOptions);
299 void DrawTargetTiled::Fill(const Path* aPath, const Pattern& aPattern,
300 const DrawOptions& aDrawOptions) {
301 Rect deviceRect = aPath->GetBounds(mTransform);
302 for (size_t i = 0; i < mTiles.size(); i++) {
303 if (!mTiles[i].mClippedOut &&
304 deviceRect.Intersects(Rect(mTiles[i].mTileOrigin.x,
305 mTiles[i].mTileOrigin.y,
306 mTiles[i].mDrawTarget->GetSize().width,
307 mTiles[i].mDrawTarget->GetSize().height))) {
308 mTiles[i].mDrawTarget->Fill(aPath, aPattern, aDrawOptions);
313 void DrawTargetTiled::PushLayer(bool aOpaque, Float aOpacity,
314 SourceSurface* aMask,
315 const Matrix& aMaskTransform,
316 const IntRect& aBounds, bool aCopyBackground) {
317 // XXX - not sure this is what we want or whether we want to continue drawing
318 // to a larger intermediate surface, that would require tweaking the code in
319 // here a little though.
320 for (size_t i = 0; i < mTiles.size(); i++) {
321 if (!mTiles[i].mClippedOut) {
322 IntRect bounds = aBounds;
323 bounds.MoveBy(-mTiles[i].mTileOrigin);
324 mTiles[i].mDrawTarget->PushLayer(aOpaque, aOpacity, aMask, aMaskTransform,
325 bounds, aCopyBackground);
329 PushedLayer layer(GetPermitSubpixelAA());
330 mPushedLayers.push_back(layer);
331 SetPermitSubpixelAA(aOpaque);
334 void DrawTargetTiled::PushLayerWithBlend(bool aOpaque, Float aOpacity,
335 SourceSurface* aMask,
336 const Matrix& aMaskTransform,
337 const IntRect& aBounds,
338 bool aCopyBackground,
339 CompositionOp aOp) {
340 // XXX - not sure this is what we want or whether we want to continue drawing
341 // to a larger intermediate surface, that would require tweaking the code in
342 // here a little though.
343 for (size_t i = 0; i < mTiles.size(); i++) {
344 if (!mTiles[i].mClippedOut) {
345 IntRect bounds = aBounds;
346 bounds.MoveBy(-mTiles[i].mTileOrigin);
347 mTiles[i].mDrawTarget->PushLayerWithBlend(aOpaque, aOpacity, aMask,
348 aMaskTransform, bounds,
349 aCopyBackground, aOp);
353 PushedLayer layer(GetPermitSubpixelAA());
354 mPushedLayers.push_back(layer);
355 SetPermitSubpixelAA(aOpaque);
358 void DrawTargetTiled::PopLayer() {
359 // XXX - not sure this is what we want or whether we want to continue drawing
360 // to a larger intermediate surface, that would require tweaking the code in
361 // here a little though.
362 for (size_t i = 0; i < mTiles.size(); i++) {
363 if (!mTiles[i].mClippedOut) {
364 mTiles[i].mDrawTarget->PopLayer();
368 MOZ_ASSERT(mPushedLayers.size());
369 const PushedLayer& layer = mPushedLayers.back();
370 SetPermitSubpixelAA(layer.mOldPermitSubpixelAA);
371 mPushedLayers.pop_back();
374 RefPtr<DrawTarget> DrawTargetTiled::CreateClippedDrawTarget(
375 const Rect& aBounds, SurfaceFormat aFormat) {
376 Rect deviceRect = mTransform.TransformBounds(aBounds);
378 // Build up an approximation of the current clip rect by unioning
379 // the tiles that are not clipped
380 Rect clipRectApproximation;
381 for (size_t i = 0; i < mTiles.size(); i++) {
382 if (!mTiles[i].mClippedOut) {
383 clipRectApproximation = clipRectApproximation.Union(
384 Rect(mTiles[i].mTileOrigin.x, mTiles[i].mTileOrigin.y,
385 mTiles[i].mDrawTarget->GetSize().width,
386 mTiles[i].mDrawTarget->GetSize().height));
390 IntRect clipBounds;
391 if (!aBounds.IsEmpty()) {
392 clipBounds = IntRect::RoundOut(deviceRect.Intersect(clipRectApproximation));
393 } else {
394 clipBounds = IntRect::RoundOut(clipRectApproximation);
397 RefPtr<DrawTarget> result;
398 if (!clipBounds.IsEmpty()) {
399 RefPtr<DrawTarget> dt = CreateSimilarDrawTarget(
400 IntSize(clipBounds.width, clipBounds.height), aFormat);
401 result = gfx::Factory::CreateOffsetDrawTarget(
402 dt, IntPoint(clipBounds.x, clipBounds.y));
403 result->SetTransform(mTransform);
404 } else {
405 // Everything is clipped but we still want some kind of surface
406 result = CreateSimilarDrawTarget(IntSize(1, 1), aFormat);
408 return result;
411 void DrawTargetTiled::PadEdges(const IntRegion& aRegion) {
412 for (size_t i = 0; i < mTiles.size(); i++) {
413 if (mTiles[i].mClippedOut) {
414 continue;
417 auto tileRect =
418 RoundedOut(Rect(mTiles[i].mTileOrigin.x, mTiles[i].mTileOrigin.y,
419 mTiles[i].mDrawTarget->GetSize().width,
420 mTiles[i].mDrawTarget->GetSize().height));
422 // We only need to pad edges on tiles that intersect the edge of the region
423 if (aRegion.Intersects(tileRect) && !aRegion.Contains(tileRect)) {
424 IntRegion padRegion = aRegion;
425 padRegion.MoveBy(-mTiles[i].mTileOrigin);
426 padRegion.AndWith(IntRect(0, 0, mTiles[i].mDrawTarget->GetSize().width,
427 mTiles[i].mDrawTarget->GetSize().height));
428 mTiles[i].mDrawTarget->PadEdges(padRegion);
433 } // namespace gfx
434 } // namespace mozilla