Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(428)

Side by Side Diff: third_party/WebKit/Source/core/paint/PaintInvalidator.cpp

Issue 2792063002: Reland of Skip paint property update and visual rect update if no geometry change (Closed)
Patch Set: Rebase Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "core/paint/PaintInvalidator.h" 5 #include "core/paint/PaintInvalidator.h"
6 6
7 #include "core/editing/FrameSelection.h" 7 #include "core/editing/FrameSelection.h"
8 #include "core/frame/FrameView.h" 8 #include "core/frame/FrameView.h"
9 #include "core/frame/LocalFrame.h" 9 #include "core/frame/LocalFrame.h"
10 #include "core/frame/Settings.h" 10 #include "core/frame/Settings.h"
11 #include "core/layout/LayoutBlockFlow.h" 11 #include "core/layout/LayoutBlockFlow.h"
12 #include "core/layout/LayoutObject.h"
13 #include "core/layout/LayoutTable.h" 12 #include "core/layout/LayoutTable.h"
14 #include "core/layout/LayoutView.h" 13 #include "core/layout/LayoutView.h"
15 #include "core/layout/svg/SVGLayoutSupport.h" 14 #include "core/layout/svg/SVGLayoutSupport.h"
15 #include "core/paint/FindPaintOffsetAndVisualRectNeedingUpdate.h"
16 #include "core/paint/ObjectPaintProperties.h" 16 #include "core/paint/ObjectPaintProperties.h"
17 #include "core/paint/PaintLayer.h" 17 #include "core/paint/PaintLayer.h"
18 #include "core/paint/PaintLayerScrollableArea.h" 18 #include "core/paint/PaintLayerScrollableArea.h"
19 #include "core/paint/PaintPropertyTreeBuilder.h"
20 #include "platform/graphics/paint/GeometryMapper.h" 19 #include "platform/graphics/paint/GeometryMapper.h"
21 #include "wtf/Optional.h" 20 #include "wtf/Optional.h"
22 21
23 namespace blink { 22 namespace blink {
24 23
25 template <typename Rect> 24 template <typename Rect>
26 static LayoutRect slowMapToVisualRectInAncestorSpace( 25 static LayoutRect slowMapToVisualRectInAncestorSpace(
27 const LayoutObject& object, 26 const LayoutObject& object,
28 const LayoutBoxModelObject& ancestor, 27 const LayoutBoxModelObject& ancestor,
29 const Rect& rect) { 28 const Rect& rect) {
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
101 result = LayoutRect(rect); 100 result = LayoutRect(rect);
102 } else { 101 } else {
103 // For non-root SVG, the input rect is in local SVG coordinates in which 102 // For non-root SVG, the input rect is in local SVG coordinates in which
104 // paint offset doesn't apply. 103 // paint offset doesn't apply.
105 if (!isSVGChild) 104 if (!isSVGChild)
106 rect.moveBy(Point(object.paintOffset())); 105 rect.moveBy(Point(object.paintOffset()));
107 106
108 const auto* containerContentsProperties = 107 const auto* containerContentsProperties =
109 context.paintInvalidationContainer->contentsProperties(); 108 context.paintInvalidationContainer->contentsProperties();
110 109
111 if (context.m_treeBuilderContext.current.transform == 110 if (context.m_treeBuilderContext->current.transform ==
112 containerContentsProperties->transform() && 111 containerContentsProperties->transform() &&
113 context.m_treeBuilderContext.current.clip == 112 context.m_treeBuilderContext->current.clip ==
114 containerContentsProperties->clip()) { 113 containerContentsProperties->clip()) {
115 result = LayoutRect(rect); 114 result = LayoutRect(rect);
116 } else { 115 } else {
117 // Use enclosingIntRect to ensure the final visual rect will cover the 116 // Use enclosingIntRect to ensure the final visual rect will cover the
118 // rect in source coordinates no matter if the painting will use pixel 117 // rect in source coordinates no matter if the painting will use pixel
119 // snapping, when transforms are applied. If there is no transform, 118 // snapping, when transforms are applied. If there is no transform,
120 // enclosingIntRect is applied in the last step of paint invalidation 119 // enclosingIntRect is applied in the last step of paint invalidation
121 // (see CompositedLayerMapping::setContentsNeedDisplayInRect()). 120 // (see CompositedLayerMapping::setContentsNeedDisplayInRect()).
122 if (!isSVGChild && context.m_treeBuilderContext.current.transform != 121 if (!isSVGChild && context.m_treeBuilderContext->current.transform !=
123 containerContentsProperties->transform()) 122 containerContentsProperties->transform())
124 rect = Rect(enclosingIntRect(rect)); 123 rect = Rect(enclosingIntRect(rect));
125 124
126 PropertyTreeState currentTreeState( 125 PropertyTreeState currentTreeState(
127 context.m_treeBuilderContext.current.transform, 126 context.m_treeBuilderContext->current.transform,
128 context.m_treeBuilderContext.current.clip, nullptr); 127 context.m_treeBuilderContext->current.clip, nullptr);
129 128
130 FloatRect floatRect(rect); 129 FloatRect floatRect(rect);
131 GeometryMapper::sourceToDestinationVisualRect( 130 GeometryMapper::sourceToDestinationVisualRect(
132 currentTreeState, *containerContentsProperties, floatRect); 131 currentTreeState, *containerContentsProperties, floatRect);
133 result = LayoutRect(floatRect); 132 result = LayoutRect(floatRect);
134 } 133 }
135 134
136 // Convert the result to the container's contents space. 135 // Convert the result to the container's contents space.
137 result.moveBy(-context.paintInvalidationContainer->paintOffset()); 136 result.moveBy(-context.paintInvalidationContainer->paintOffset());
138 } 137 }
139 138
140 object.adjustVisualRectForRasterEffects(result); 139 object.adjustVisualRectForRasterEffects(result);
141 140
142 PaintLayer::mapRectInPaintInvalidationContainerToBacking( 141 PaintLayer::mapRectInPaintInvalidationContainerToBacking(
143 *context.paintInvalidationContainer, result); 142 *context.paintInvalidationContainer, result);
144 143
145 result.move(object.scrollAdjustmentForPaintInvalidation( 144 result.move(object.scrollAdjustmentForPaintInvalidation(
146 *context.paintInvalidationContainer)); 145 *context.paintInvalidationContainer));
147 146
148 return result; 147 return result;
149 } 148 }
150 149
151 void PaintInvalidatorContext::mapLocalRectToVisualRectInBacking( 150 void PaintInvalidatorContext::mapLocalRectToVisualRectInBacking(
152 const LayoutObject& object, 151 const LayoutObject& object,
153 LayoutRect& rect) const { 152 LayoutRect& rect) const {
153 DCHECK(needsVisualRectUpdate(object));
154 rect = PaintInvalidator::mapLocalRectToVisualRectInBacking<LayoutRect, 154 rect = PaintInvalidator::mapLocalRectToVisualRectInBacking<LayoutRect,
155 LayoutPoint>( 155 LayoutPoint>(
156 object, rect, *this); 156 object, rect, *this);
157 } 157 }
158 158
159 LayoutRect PaintInvalidator::computeVisualRectInBacking( 159 LayoutRect PaintInvalidator::computeVisualRectInBacking(
160 const LayoutObject& object, 160 const LayoutObject& object,
161 const PaintInvalidatorContext& context) { 161 const PaintInvalidatorContext& context) {
162 if (object.isSVGChild()) { 162 if (object.isSVGChild()) {
163 FloatRect localRect = SVGLayoutSupport::localVisualRect(object); 163 FloatRect localRect = SVGLayoutSupport::localVisualRect(object);
(...skipping 10 matching lines...) Expand all
174 // In SPv2, locationInBacking is in the space of their local transform node. 174 // In SPv2, locationInBacking is in the space of their local transform node.
175 if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) 175 if (RuntimeEnabledFeatures::slimmingPaintV2Enabled())
176 return object.paintOffset(); 176 return object.paintOffset();
177 177
178 LayoutPoint point; 178 LayoutPoint point;
179 if (object != context.paintInvalidationContainer) { 179 if (object != context.paintInvalidationContainer) {
180 point.moveBy(object.paintOffset()); 180 point.moveBy(object.paintOffset());
181 181
182 const auto* containerTransform = 182 const auto* containerTransform =
183 context.paintInvalidationContainer->contentsProperties()->transform(); 183 context.paintInvalidationContainer->contentsProperties()->transform();
184 if (context.m_treeBuilderContext.current.transform != containerTransform) { 184 if (context.m_treeBuilderContext->current.transform != containerTransform) {
185 FloatRect rect = FloatRect(FloatPoint(point), FloatSize()); 185 FloatRect rect = FloatRect(FloatPoint(point), FloatSize());
186 GeometryMapper::sourceToDestinationRect( 186 GeometryMapper::sourceToDestinationRect(
187 context.m_treeBuilderContext.current.transform, containerTransform, 187 context.m_treeBuilderContext->current.transform, containerTransform,
188 rect); 188 rect);
189 point = LayoutPoint(rect.location()); 189 point = LayoutPoint(rect.location());
190 } 190 }
191 191
192 // Convert the result to the container's contents space. 192 // Convert the result to the container's contents space.
193 point.moveBy(-context.paintInvalidationContainer->paintOffset()); 193 point.moveBy(-context.paintInvalidationContainer->paintOffset());
194 } 194 }
195 195
196 if (context.paintInvalidationContainer->layer()->groupedMapping()) { 196 if (context.paintInvalidationContainer->layer()->groupedMapping()) {
197 FloatPoint floatPoint(point); 197 FloatPoint floatPoint(point);
(...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after
323 context.forcedSubtreeInvalidationFlags |= 323 context.forcedSubtreeInvalidationFlags |=
324 PaintInvalidatorContext::ForcedSubtreeFullInvalidation; 324 PaintInvalidatorContext::ForcedSubtreeFullInvalidation;
325 } 325 }
326 326
327 if (object == context.paintInvalidationContainer) { 327 if (object == context.paintInvalidationContainer) {
328 // When we hit a new paint invalidation container, we don't need to 328 // When we hit a new paint invalidation container, we don't need to
329 // continue forcing a check for paint invalidation, since we're 329 // continue forcing a check for paint invalidation, since we're
330 // descending into a different invalidation container. (For instance if 330 // descending into a different invalidation container. (For instance if
331 // our parents were moved, the entire container will just move.) 331 // our parents were moved, the entire container will just move.)
332 if (object != context.paintInvalidationContainerForStackedContents) { 332 if (object != context.paintInvalidationContainerForStackedContents) {
333 // However, we need to keep the 333 // However, we need to keep ForcedSubtreeVisualRectUpdate and
334 // ForcedSubtreeFullInvalidationForStackedContents flag if the current 334 // ForcedSubtreeFullInvalidationForStackedContents flags if the current
335 // object isn't the paint invalidation container of stacked contents. 335 // object isn't the paint invalidation container of stacked contents.
336 context.forcedSubtreeInvalidationFlags &= PaintInvalidatorContext:: 336 context.forcedSubtreeInvalidationFlags &=
337 ForcedSubtreeFullInvalidationForStackedContents; 337 (PaintInvalidatorContext::ForcedSubtreeVisualRectUpdate |
338 PaintInvalidatorContext::
339 ForcedSubtreeFullInvalidationForStackedContents);
338 } else { 340 } else {
339 context.forcedSubtreeInvalidationFlags = 0; 341 context.forcedSubtreeInvalidationFlags = 0;
340 } 342 }
341 } 343 }
342 344
343 DCHECK(context.paintInvalidationContainer == 345 DCHECK(context.paintInvalidationContainer ==
344 object.containerForPaintInvalidation()); 346 object.containerForPaintInvalidation());
345 DCHECK(context.paintingLayer == object.paintingLayer()); 347 DCHECK(context.paintingLayer == object.paintingLayer());
346 } 348 }
347 349
350 void PaintInvalidator::updateVisualRectIfNeeded(
351 const LayoutObject& object,
352 PaintInvalidatorContext& context) {
353 context.oldVisualRect = object.visualRect();
354 context.oldLocation = ObjectPaintInvalidator(object).locationInBacking();
355
356 #if DCHECK_IS_ON()
357 FindObjectVisualRectNeedingUpdateScope finder(object, context);
358 #endif
359
360 if (!context.needsVisualRectUpdate(object)) {
361 context.newLocation = context.oldLocation;
362 return;
363 }
364
365 updateVisualRect(object, context);
366 }
367
348 void PaintInvalidator::updateVisualRect(const LayoutObject& object, 368 void PaintInvalidator::updateVisualRect(const LayoutObject& object,
349 PaintInvalidatorContext& context) { 369 PaintInvalidatorContext& context) {
370 // The paint offset should already be updated through
371 // PaintPropertyTreeBuilder::updatePropertiesForSelf.
372 DCHECK(context.m_treeBuilderContext);
373 DCHECK(context.m_treeBuilderContext->current.paintOffset ==
374 object.paintOffset());
375
350 Optional<ScopedUndoFrameViewContentClipAndScroll> 376 Optional<ScopedUndoFrameViewContentClipAndScroll>
351 undoFrameViewContentClipAndScroll; 377 undoFrameViewContentClipAndScroll;
352 378
353 if (!RuntimeEnabledFeatures::rootLayerScrollingEnabled() && 379 if (!RuntimeEnabledFeatures::rootLayerScrollingEnabled() &&
354 object.isLayoutView() && !object.isPaintInvalidationContainer()) { 380 object.isLayoutView() && !object.isPaintInvalidationContainer()) {
355 undoFrameViewContentClipAndScroll.emplace(*toLayoutView(object).frameView(), 381 undoFrameViewContentClipAndScroll.emplace(*toLayoutView(object).frameView(),
356 context.m_treeBuilderContext); 382 *context.m_treeBuilderContext);
357 } 383 }
358 384
359 // TODO(crbug.com/637313): Use GeometryMapper which now supports filter
360 // geometry effects, after skia optimizes filter's mapRect operation.
361 // TODO(crbug.com/648274): This is a workaround for multi-column contents.
362 if (object.hasFilterInducingProperty() || object.isLayoutFlowThread()) {
363 context.forcedSubtreeInvalidationFlags |=
364 PaintInvalidatorContext::ForcedSubtreeSlowPathRect;
365 }
366
367 ObjectPaintInvalidator objectPaintInvalidator(object);
368 context.oldVisualRect = object.visualRect();
369 context.oldLocation = objectPaintInvalidator.locationInBacking();
370
371 LayoutRect newVisualRect = computeVisualRectInBacking(object, context); 385 LayoutRect newVisualRect = computeVisualRectInBacking(object, context);
372 if (object.isBoxModelObject()) { 386 if (object.isBoxModelObject()) {
373 context.newLocation = computeLocationInBacking(object, context); 387 context.newLocation = computeLocationInBacking(object, context);
374
375 // Location of empty visual rect doesn't affect paint invalidation. Set it 388 // Location of empty visual rect doesn't affect paint invalidation. Set it
376 // to newLocation to avoid saving the previous location separately in 389 // to newLocation to avoid saving the previous location separately in
377 // ObjectPaintInvalidator. 390 // ObjectPaintInvalidator.
378 if (newVisualRect.isEmpty()) 391 if (newVisualRect.isEmpty())
379 newVisualRect.setLocation(context.newLocation); 392 newVisualRect.setLocation(context.newLocation);
380 } else { 393 } else {
381 // Use visual rect location for non-LayoutBoxModelObject because it suffices 394 // Use visual rect location for non-LayoutBoxModelObject because it suffices
382 // to check whether a visual rect changes for layout caused invalidation. 395 // to check whether a visual rect changes for layout caused invalidation.
383 context.newLocation = newVisualRect.location(); 396 context.newLocation = newVisualRect.location();
384 } 397 }
385 398
386 object.getMutableForPainting().setVisualRect(newVisualRect); 399 object.getMutableForPainting().setVisualRect(newVisualRect);
387 objectPaintInvalidator.setLocationInBacking(context.newLocation); 400 ObjectPaintInvalidator(object).setLocationInBacking(context.newLocation);
388 } 401 }
389 402
390 void PaintInvalidator::invalidatePaintIfNeeded( 403 void PaintInvalidator::invalidatePaintIfNeeded(
391 FrameView& frameView, 404 FrameView& frameView,
392 PaintInvalidatorContext& context) { 405 PaintInvalidatorContext& context) {
393 LayoutView* layoutView = frameView.layoutView(); 406 LayoutView* layoutView = frameView.layoutView();
394 CHECK(layoutView); 407 CHECK(layoutView);
395 408
396 context.paintInvalidationContainer = 409 context.paintInvalidationContainer =
397 context.paintInvalidationContainerForStackedContents = 410 context.paintInvalidationContainerForStackedContents =
398 &layoutView->containerForPaintInvalidation(); 411 &layoutView->containerForPaintInvalidation();
399 context.paintingLayer = layoutView->layer(); 412 context.paintingLayer = layoutView->layer();
400 413
401 if (!RuntimeEnabledFeatures::rootLayerScrollingEnabled()) { 414 if (!RuntimeEnabledFeatures::rootLayerScrollingEnabled()) {
402 ScopedUndoFrameViewContentClipAndScroll undo(frameView, 415 Optional<ScopedUndoFrameViewContentClipAndScroll> undo;
403 context.m_treeBuilderContext); 416 if (context.m_treeBuilderContext)
417 undo.emplace(frameView, *context.m_treeBuilderContext);
404 frameView.invalidatePaintOfScrollControlsIfNeeded(context); 418 frameView.invalidatePaintOfScrollControlsIfNeeded(context);
405 } 419 }
406 } 420 }
407 421
408 void PaintInvalidator::invalidatePaintIfNeeded( 422 void PaintInvalidator::invalidatePaintIfNeeded(
409 const LayoutObject& object, 423 const LayoutObject& object,
410 PaintInvalidatorContext& context) { 424 PaintInvalidatorContext& context) {
411 TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("blink.invalidation"), 425 TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("blink.invalidation"),
412 "PaintInvalidator::invalidatePaintIfNeeded()", "object", 426 "PaintInvalidator::invalidatePaintIfNeeded()", "object",
413 object.debugName().ascii()); 427 object.debugName().ascii());
414 428
415 object.getMutableForPainting().ensureIsReadyForPaintInvalidation(); 429 object.getMutableForPainting().ensureIsReadyForPaintInvalidation();
416 430
417 // The paint offset should already be updated through
418 // PaintPropertyTreeBuilder::updatePropertiesForSelf.
419 DCHECK(context.m_treeBuilderContext.current.paintOffset ==
420 object.paintOffset());
421
422 updatePaintingLayer(object, context); 431 updatePaintingLayer(object, context);
423 432
424 if (object.document().printing() && 433 if (object.document().printing() &&
425 !RuntimeEnabledFeatures::printBrowserEnabled()) 434 !RuntimeEnabledFeatures::printBrowserEnabled())
426 return; // Don't invalidate paints if we're printing. 435 return; // Don't invalidate paints if we're printing.
427 436
428 updatePaintInvalidationContainer(object, context); 437 // TODO(crbug.com/637313): Use GeometryMapper which now supports filter
429 438 // geometry effects, after skia optimizes filter's mapRect operation.
430 bool objectShouldCheckForPaintInvalidation = 439 // TODO(crbug.com/648274): This is a workaround for multi-column contents.
431 object.shouldCheckForPaintInvalidation(); 440 if (object.hasFilterInducingProperty() || object.isLayoutFlowThread()) {
432 if (!context.forcedSubtreeInvalidationFlags && 441 context.forcedSubtreeInvalidationFlags |=
433 !objectShouldCheckForPaintInvalidation) { 442 PaintInvalidatorContext::ForcedSubtreeSlowPathRect;
434 #if CHECK_VISUAL_RECT_UPDATE
435 updateVisualRect(object, context);
436 DCHECK(
437 (context.oldVisualRect.isEmpty() && context.newVisualRect.isEmpty()) ||
438 enclosingIntRect(context.oldVisualRect) ==
439 enclosingIntRect(context.newVisualRect))
440 << "Visual rect changed without needing paint invalidation:"
441 << " object=" << object.debugName()
442 << " old=" << context.oldVisualRect.toString()
443 << " new=" << context.newVisualRect.toString();
444 DCHECK(object.isText() || context.oldLocation == context.newLocation)
445 << "Location changed without needing paint invalidation:"
446 << " old=" << context.oldLocation.toString()
447 << " new=" << context.newLocation.toString();
448 #endif
449 return;
450 } 443 }
451 444
452 updateVisualRect(object, context); 445 updatePaintInvalidationContainer(object, context);
446 updateVisualRectIfNeeded(object, context);
453 447
454 if (!objectShouldCheckForPaintInvalidation && 448 if (!object.shouldCheckForPaintInvalidation() &&
455 context.forcedSubtreeInvalidationFlags == 449 !(context.forcedSubtreeInvalidationFlags &
456 PaintInvalidatorContext::ForcedSubtreeInvalidationRectUpdate) { 450 ~PaintInvalidatorContext::ForcedSubtreeVisualRectUpdate)) {
457 // We are done updating the visual rect. No other paint invalidation work to 451 // We are done updating anything needed. No other paint invalidation work to
458 // do for this object. 452 // do for this object.
459 return; 453 return;
460 } 454 }
461 455
462 if (object.isSVGHiddenContainer()) { 456 if (object.isSVGHiddenContainer()) {
463 context.forcedSubtreeInvalidationFlags |= 457 context.forcedSubtreeInvalidationFlags |=
464 PaintInvalidatorContext::ForcedSubtreeNoRasterInvalidation; 458 PaintInvalidatorContext::ForcedSubtreeNoRasterInvalidation;
465 } 459 }
466 460
467 PaintInvalidationReason reason = object.invalidatePaintIfNeeded(context); 461 PaintInvalidationReason reason = object.invalidatePaintIfNeeded(context);
(...skipping 18 matching lines...) Expand all
486 if (object.mayNeedPaintInvalidationSubtree()) { 480 if (object.mayNeedPaintInvalidationSubtree()) {
487 context.forcedSubtreeInvalidationFlags |= 481 context.forcedSubtreeInvalidationFlags |=
488 PaintInvalidatorContext::ForcedSubtreeInvalidationChecking; 482 PaintInvalidatorContext::ForcedSubtreeInvalidationChecking;
489 } 483 }
490 484
491 if (context.oldLocation != context.newLocation && 485 if (context.oldLocation != context.newLocation &&
492 !context.paintingLayer->subtreeIsInvisible()) { 486 !context.paintingLayer->subtreeIsInvisible()) {
493 context.forcedSubtreeInvalidationFlags |= 487 context.forcedSubtreeInvalidationFlags |=
494 PaintInvalidatorContext::ForcedSubtreeInvalidationChecking; 488 PaintInvalidatorContext::ForcedSubtreeInvalidationChecking;
495 } 489 }
490
491 if (context.forcedSubtreeInvalidationFlags &&
492 context.needsVisualRectUpdate(object)) {
493 // If any subtree flag is set, we also need to pass needsVisualRectUpdate
494 // requirement to the subtree.
495 context.forcedSubtreeInvalidationFlags |=
496 PaintInvalidatorContext::ForcedSubtreeVisualRectUpdate;
497 }
496 } 498 }
497 499
498 void PaintInvalidator::processPendingDelayedPaintInvalidations() { 500 void PaintInvalidator::processPendingDelayedPaintInvalidations() {
499 for (auto target : m_pendingDelayedPaintInvalidations) 501 for (auto target : m_pendingDelayedPaintInvalidations) {
500 target->getMutableForPainting().setShouldDoFullPaintInvalidation( 502 target->getMutableForPainting().setShouldDoFullPaintInvalidation(
501 PaintInvalidationDelayedFull); 503 PaintInvalidationDelayedFull);
504 }
502 } 505 }
503 506
504 } // namespace blink 507 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698