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

Side by Side Diff: Source/core/html/canvas/CanvasRenderingContext2D.cpp

Issue 907453003: Move overdraw tracking code from GraphicsContext to CanvasRenderingContext2D (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Created 5 years, 10 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 | Annotate | Revision Log
OLDNEW
1 /* 1 /*
2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights reserved. 2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights reserved.
3 * Copyright (C) 2008, 2010 Nokia Corporation and/or its subsidiary(-ies) 3 * Copyright (C) 2008, 2010 Nokia Corporation and/or its subsidiary(-ies)
4 * Copyright (C) 2007 Alp Toker <alp@atoker.com> 4 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
5 * Copyright (C) 2008 Eric Seidel <eric@webkit.org> 5 * Copyright (C) 2008 Eric Seidel <eric@webkit.org>
6 * Copyright (C) 2008 Dirk Schulze <krit@webkit.org> 6 * Copyright (C) 2008 Dirk Schulze <krit@webkit.org>
7 * Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved. 7 * Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved.
8 * Copyright (C) 2012, 2013 Intel Corporation. All rights reserved. 8 * Copyright (C) 2012, 2013 Intel Corporation. All rights reserved.
9 * Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved. 9 * Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
10 * 10 *
(...skipping 265 matching lines...) Expand 10 before | Expand all | Expand 10 after
276 , m_globalComposite(SkXfermode::kSrcOver_Mode) 276 , m_globalComposite(SkXfermode::kSrcOver_Mode)
277 , m_invertibleCTM(true) 277 , m_invertibleCTM(true)
278 , m_lineDashOffset(0) 278 , m_lineDashOffset(0)
279 , m_imageSmoothingEnabled(true) 279 , m_imageSmoothingEnabled(true)
280 , m_textAlign(StartTextAlign) 280 , m_textAlign(StartTextAlign)
281 , m_textBaseline(AlphabeticTextBaseline) 281 , m_textBaseline(AlphabeticTextBaseline)
282 , m_direction(DirectionInherit) 282 , m_direction(DirectionInherit)
283 , m_unparsedFont(defaultFont) 283 , m_unparsedFont(defaultFont)
284 , m_realizedFont(false) 284 , m_realizedFont(false)
285 , m_hasClip(false) 285 , m_hasClip(false)
286 , m_hasComplexClip(false)
286 { 287 {
287 } 288 }
288 289
289 CanvasRenderingContext2D::State::State(const State& other, ClipListCopyMode mode ) 290 CanvasRenderingContext2D::State::State(const State& other, ClipListCopyMode mode )
290 : CSSFontSelectorClient() 291 : CSSFontSelectorClient()
291 , m_unrealizedSaveCount(other.m_unrealizedSaveCount) 292 , m_unrealizedSaveCount(other.m_unrealizedSaveCount)
292 , m_unparsedStrokeColor(other.m_unparsedStrokeColor) 293 , m_unparsedStrokeColor(other.m_unparsedStrokeColor)
293 , m_unparsedFillColor(other.m_unparsedFillColor) 294 , m_unparsedFillColor(other.m_unparsedFillColor)
294 , m_strokeStyle(other.m_strokeStyle) 295 , m_strokeStyle(other.m_strokeStyle)
295 , m_fillStyle(other.m_fillStyle) 296 , m_fillStyle(other.m_fillStyle)
(...skipping 10 matching lines...) Expand all
306 , m_invertibleCTM(other.m_invertibleCTM) 307 , m_invertibleCTM(other.m_invertibleCTM)
307 , m_lineDashOffset(other.m_lineDashOffset) 308 , m_lineDashOffset(other.m_lineDashOffset)
308 , m_imageSmoothingEnabled(other.m_imageSmoothingEnabled) 309 , m_imageSmoothingEnabled(other.m_imageSmoothingEnabled)
309 , m_textAlign(other.m_textAlign) 310 , m_textAlign(other.m_textAlign)
310 , m_textBaseline(other.m_textBaseline) 311 , m_textBaseline(other.m_textBaseline)
311 , m_direction(other.m_direction) 312 , m_direction(other.m_direction)
312 , m_unparsedFont(other.m_unparsedFont) 313 , m_unparsedFont(other.m_unparsedFont)
313 , m_font(other.m_font) 314 , m_font(other.m_font)
314 , m_realizedFont(other.m_realizedFont) 315 , m_realizedFont(other.m_realizedFont)
315 , m_hasClip(other.m_hasClip) 316 , m_hasClip(other.m_hasClip)
317 , m_hasComplexClip(other.m_hasComplexClip)
316 { 318 {
317 if (mode == CopyClipList) { 319 if (mode == CopyClipList) {
318 m_clipList = other.m_clipList; 320 m_clipList = other.m_clipList;
319 } 321 }
320 if (m_realizedFont) 322 if (m_realizedFont)
321 static_cast<CSSFontSelector*>(m_font.fontSelector())->registerForInvalid ationCallbacks(this); 323 static_cast<CSSFontSelector*>(m_font.fontSelector())->registerForInvalid ationCallbacks(this);
322 } 324 }
323 325
324 CanvasRenderingContext2D::State& CanvasRenderingContext2D::State::operator=(cons t State& other) 326 CanvasRenderingContext2D::State& CanvasRenderingContext2D::State::operator=(cons t State& other)
325 { 327 {
(...skipping 22 matching lines...) Expand all
348 m_transform = other.m_transform; 350 m_transform = other.m_transform;
349 m_invertibleCTM = other.m_invertibleCTM; 351 m_invertibleCTM = other.m_invertibleCTM;
350 m_imageSmoothingEnabled = other.m_imageSmoothingEnabled; 352 m_imageSmoothingEnabled = other.m_imageSmoothingEnabled;
351 m_textAlign = other.m_textAlign; 353 m_textAlign = other.m_textAlign;
352 m_textBaseline = other.m_textBaseline; 354 m_textBaseline = other.m_textBaseline;
353 m_direction = other.m_direction; 355 m_direction = other.m_direction;
354 m_unparsedFont = other.m_unparsedFont; 356 m_unparsedFont = other.m_unparsedFont;
355 m_font = other.m_font; 357 m_font = other.m_font;
356 m_realizedFont = other.m_realizedFont; 358 m_realizedFont = other.m_realizedFont;
357 m_hasClip = other.m_hasClip; 359 m_hasClip = other.m_hasClip;
360 m_hasComplexClip = other.m_hasComplexClip;
358 m_clipList = other.m_clipList; 361 m_clipList = other.m_clipList;
359 362
360 if (m_realizedFont) 363 if (m_realizedFont)
361 static_cast<CSSFontSelector*>(m_font.fontSelector())->registerForInvalid ationCallbacks(this); 364 static_cast<CSSFontSelector*>(m_font.fontSelector())->registerForInvalid ationCallbacks(this);
362 365
363 return *this; 366 return *this;
364 } 367 }
365 368
366 CanvasRenderingContext2D::State::~State() 369 CanvasRenderingContext2D::State::~State()
367 { 370 {
(...skipping 747 matching lines...) Expand 10 before | Expand all | Expand 10 after
1115 1118
1116 SkPath skPath = path.skPath(); 1119 SkPath skPath = path.skPath();
1117 skPath.setFillType(parseWinding(windingRuleString)); 1120 skPath.setFillType(parseWinding(windingRuleString));
1118 ImageBuffer* buffer = canvas()->buffer(); 1121 ImageBuffer* buffer = canvas()->buffer();
1119 if (buffer && buffer->needsClipTracking()) { 1122 if (buffer && buffer->needsClipTracking()) {
1120 modifiableState().m_clipList.clipPath(skPath, m_clipAntialiasing, affine TransformToSkMatrix(state().m_transform)); 1123 modifiableState().m_clipList.clipPath(skPath, m_clipAntialiasing, affine TransformToSkMatrix(state().m_transform));
1121 } 1124 }
1122 1125
1123 c->clipPath(skPath, SkRegion::kIntersect_Op, m_clipAntialiasing == AntiAlias ed); 1126 c->clipPath(skPath, SkRegion::kIntersect_Op, m_clipAntialiasing == AntiAlias ed);
1124 if (!skPath.isRect(0)) 1127 if (!skPath.isRect(0))
1125 drawingContext()->setHasComplexClip(); 1128 modifiableState().m_hasComplexClip = true;
1126 modifiableState().m_hasClip = true; 1129 modifiableState().m_hasClip = true;
1127 } 1130 }
1128 1131
1129 void CanvasRenderingContext2D::clip(const String& windingRuleString) 1132 void CanvasRenderingContext2D::clip(const String& windingRuleString)
1130 { 1133 {
1131 clipInternal(m_path, windingRuleString); 1134 clipInternal(m_path, windingRuleString);
1132 } 1135 }
1133 1136
1134 void CanvasRenderingContext2D::clip(Path2D* domPath, const String& windingRuleSt ring) 1137 void CanvasRenderingContext2D::clip(Path2D* domPath, const String& windingRuleSt ring)
1135 { 1138 {
(...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after
1290 1293
1291 // from the HTML5 Canvas spec: 1294 // from the HTML5 Canvas spec:
1292 // If x0 = x1 and y0 = y1, then the linear gradient must paint nothing 1295 // If x0 = x1 and y0 = y1, then the linear gradient must paint nothing
1293 // If x0 = x1 and y0 = y1 and r0 = r1, then the radial gradient must paint n othing 1296 // If x0 = x1 and y0 = y1 and r0 = r1, then the radial gradient must paint n othing
1294 Gradient* gradient = c->fillGradient(); 1297 Gradient* gradient = c->fillGradient();
1295 if (gradient && gradient->isZeroSize()) 1298 if (gradient && gradient->isZeroSize())
1296 return; 1299 return;
1297 1300
1298 FloatRect rect(x, y, width, height); 1301 FloatRect rect(x, y, width, height);
1299 if (rectContainsTransformedRect(rect, clipBounds)) { 1302 if (rectContainsTransformedRect(rect, clipBounds)) {
1303 checkOverdraw(rect, &c->fillPaint(), NoImage, ClipFill);
1300 c->fillRect(rect); 1304 c->fillRect(rect);
1301 didDraw(clipBounds); 1305 didDraw(clipBounds);
1302 } else if (isFullCanvasCompositeMode(state().m_globalComposite)) { 1306 } else if (isFullCanvasCompositeMode(state().m_globalComposite)) {
1303 fullCanvasCompositedDraw(bind(&fillRectOnContext, c, rect)); 1307 fullCanvasCompositedDraw(bind(&fillRectOnContext, c, rect));
1304 didDraw(clipBounds); 1308 didDraw(clipBounds);
1305 } else if (state().m_globalComposite == SkXfermode::kSrc_Mode) { 1309 } else if (state().m_globalComposite == SkXfermode::kSrc_Mode) {
1306 clearCanvas(); 1310 clearCanvas();
1307 c->clearShadow(); 1311 c->clearShadow(); // Takes care of signaling the overdraw
1308 c->fillRect(rect); 1312 c->fillRect(rect);
1309 applyShadow(DrawShadowAndForeground); 1313 applyShadow(DrawShadowAndForeground);
1310 didDraw(clipBounds); 1314 didDraw(clipBounds);
1311 } else { 1315 } else {
1312 FloatRect dirtyRect; 1316 FloatRect dirtyRect;
1313 if (computeDirtyRect(rect, clipBounds, &dirtyRect)) { 1317 if (computeDirtyRect(rect, clipBounds, &dirtyRect)) {
1314 c->fillRect(rect); 1318 c->fillRect(rect);
1315 didDraw(dirtyRect); 1319 didDraw(dirtyRect);
1316 } 1320 }
1317 } 1321 }
(...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after
1511 1515
1512 // FIXME: crbug.com/447218 1516 // FIXME: crbug.com/447218
1513 // We make the destination canvas fall out of display list mode by calling 1517 // We make the destination canvas fall out of display list mode by calling
1514 // willAccessPixels. This is to prevent run-away memory consumption caused b y SkSurface 1518 // willAccessPixels. This is to prevent run-away memory consumption caused b y SkSurface
1515 // copyOnWrite when the source canvas is animated and consumed at a rate hig her than the 1519 // copyOnWrite when the source canvas is animated and consumed at a rate hig her than the
1516 // presentation frame rate of the destination canvas. 1520 // presentation frame rate of the destination canvas.
1517 if (imageSource->isCanvasElement()) 1521 if (imageSource->isCanvasElement())
1518 canvas()->buffer()->willAccessPixels(); 1522 canvas()->buffer()->willAccessPixels();
1519 1523
1520 if (rectContainsTransformedRect(dstRect, clipBounds)) { 1524 if (rectContainsTransformedRect(dstRect, clipBounds)) {
1525 checkOverdraw(dstRect, &c->fillPaint(), imageSource->isOpaque() ? Opaque Image : NonOpaqueImage, NormalFill);
dshwang 2015/02/06 19:10:37 Why drawImage uses NormalFill while fillRect use C
Justin Novosad 2015/02/06 19:37:31 You are right. And this was the only place NormalF
1521 drawImageOnContext(drawingCanvas(), c, imageSource, image.get(), srcRect , dstRect); 1526 drawImageOnContext(drawingCanvas(), c, imageSource, image.get(), srcRect , dstRect);
1522 didDraw(clipBounds); 1527 didDraw(clipBounds);
1523 } else if (isFullCanvasCompositeMode(state().m_globalComposite)) { 1528 } else if (isFullCanvasCompositeMode(state().m_globalComposite)) {
1524 fullCanvasCompositedDraw(bind(&drawImageOnContext, drawingCanvas(), c, i mageSource, image.get(), srcRect, dstRect)); 1529 fullCanvasCompositedDraw(bind(&drawImageOnContext, drawingCanvas(), c, i mageSource, image.get(), srcRect, dstRect));
1525 didDraw(clipBounds); 1530 didDraw(clipBounds);
1526 } else if (state().m_globalComposite == SkXfermode::kSrc_Mode) { 1531 } else if (state().m_globalComposite == SkXfermode::kSrc_Mode) {
1527 clearCanvas(); 1532 clearCanvas(); // takes care of signaling an overdraw
1528 drawImageOnContext(drawingCanvas(), c, imageSource, image.get(), srcRect , dstRect); 1533 drawImageOnContext(drawingCanvas(), c, imageSource, image.get(), srcRect , dstRect);
1529 didDraw(clipBounds); 1534 didDraw(clipBounds);
1530 } else { 1535 } else {
1531 FloatRect dirtyRect; 1536 FloatRect dirtyRect;
1532 if (computeDirtyRect(dstRect, clipBounds, &dirtyRect)) { 1537 if (computeDirtyRect(dstRect, clipBounds, &dirtyRect)) {
1533 drawImageOnContext(drawingCanvas(), c, imageSource, image.get(), src Rect, dstRect); 1538 drawImageOnContext(drawingCanvas(), c, imageSource, image.get(), src Rect, dstRect);
1534 didDraw(dirtyRect); 1539 didDraw(dirtyRect);
1535 } 1540 }
1536 } 1541 }
1537 1542
1538 validateStateStack(); 1543 validateStateStack();
1539 1544
1540 if (sourceImageStatus == ExternalSourceImageStatus && isAccelerated() && can vas()->buffer()) 1545 if (sourceImageStatus == ExternalSourceImageStatus && isAccelerated() && can vas()->buffer())
1541 canvas()->buffer()->flush(); 1546 canvas()->buffer()->flush();
1542 1547
1543 if (canvas()->originClean() && wouldTaintOrigin(imageSource)) 1548 if (canvas()->originClean() && wouldTaintOrigin(imageSource))
1544 canvas()->setOriginTainted(); 1549 canvas()->setOriginTainted();
1545 } 1550 }
1546 1551
1547 void CanvasRenderingContext2D::clearCanvas() 1552 void CanvasRenderingContext2D::clearCanvas()
1548 { 1553 {
1549 FloatRect canvasRect(0, 0, canvas()->width(), canvas()->height()); 1554 FloatRect canvasRect(0, 0, canvas()->width(), canvas()->height());
1550 SkCanvas* c = drawingCanvas(); 1555 SkCanvas* c = drawingCanvas();
1551 if (!c) 1556 if (!c)
1552 return; 1557 return;
1553 1558
1559 checkOverdraw(canvasRect, 0, NoImage, ClipFill);
1554 c->clear(m_hasAlpha ? SK_ColorTRANSPARENT : SK_ColorBLACK); 1560 c->clear(m_hasAlpha ? SK_ColorTRANSPARENT : SK_ColorBLACK);
1555 } 1561 }
1556 1562
1557 bool CanvasRenderingContext2D::rectContainsTransformedRect(const FloatRect& rect , const FloatRect& transformedRect) const 1563 bool CanvasRenderingContext2D::rectContainsTransformedRect(const FloatRect& rect , const FloatRect& transformedRect) const
1558 { 1564 {
1559 FloatQuad quad(rect); 1565 FloatQuad quad(rect);
1560 FloatQuad transformedQuad(transformedRect); 1566 FloatQuad transformedQuad(transformedRect);
1561 return state().m_transform.mapQuad(quad).containsQuad(transformedQuad); 1567 return state().m_transform.mapQuad(quad).containsQuad(transformedQuad);
1562 } 1568 }
1563 1569
(...skipping 220 matching lines...) Expand 10 before | Expand all | Expand 10 after
1784 clipRect.intersect(IntRect(0, 0, data->width(), data->height())); 1790 clipRect.intersect(IntRect(0, 0, data->width(), data->height()));
1785 IntSize destOffset(static_cast<int>(dx), static_cast<int>(dy)); 1791 IntSize destOffset(static_cast<int>(dx), static_cast<int>(dy));
1786 IntRect destRect = enclosingIntRect(clipRect); 1792 IntRect destRect = enclosingIntRect(clipRect);
1787 destRect.move(destOffset); 1793 destRect.move(destOffset);
1788 destRect.intersect(IntRect(IntPoint(), buffer->size())); 1794 destRect.intersect(IntRect(IntPoint(), buffer->size()));
1789 if (destRect.isEmpty()) 1795 if (destRect.isEmpty())
1790 return; 1796 return;
1791 IntRect sourceRect(destRect); 1797 IntRect sourceRect(destRect);
1792 sourceRect.move(-destOffset); 1798 sourceRect.move(-destOffset);
1793 1799
1800 checkOverdraw(destRect, 0, NoImage, UntransformedUnclippedFill);
1801
1794 buffer->putByteArray(Unmultiplied, data->data()->data(), IntSize(data->width (), data->height()), sourceRect, IntPoint(destOffset)); 1802 buffer->putByteArray(Unmultiplied, data->data()->data(), IntSize(data->width (), data->height()), sourceRect, IntPoint(destOffset));
1795 1803
1796 didDraw(destRect); 1804 didDraw(destRect);
1797 } 1805 }
1798 1806
1799 String CanvasRenderingContext2D::font() const 1807 String CanvasRenderingContext2D::font() const
1800 { 1808 {
1801 if (!state().m_realizedFont) 1809 if (!state().m_realizedFont)
1802 return defaultFont; 1810 return defaultFont;
1803 1811
(...skipping 571 matching lines...) Expand 10 before | Expand all | Expand 10 after
2375 } 2383 }
2376 2384
2377 unsigned CanvasRenderingContext2D::hitRegionsCount() const 2385 unsigned CanvasRenderingContext2D::hitRegionsCount() const
2378 { 2386 {
2379 if (m_hitRegionManager) 2387 if (m_hitRegionManager)
2380 return m_hitRegionManager->getHitRegionsCount(); 2388 return m_hitRegionManager->getHitRegionsCount();
2381 2389
2382 return 0; 2390 return 0;
2383 } 2391 }
2384 2392
2393 void CanvasRenderingContext2D::checkOverdraw(const SkRect& rect, const SkPaint* paint, ImageType imageType, DrawType drawType)
2394 {
2395 SkCanvas* c = drawingCanvas();
2396 if (!c || !canvas()->buffer()->isRecording())
2397 return;
2398
2399 SkRect deviceRect;
2400 if (drawType == UntransformedUnclippedFill) {
2401 deviceRect = rect;
2402 } else if (drawType == ClipFill) {
2403 if (state().m_hasComplexClip)
2404 return;
2405
2406 SkIRect skIBounds;
2407 if (!c->getClipDeviceBounds(&skIBounds))
2408 return;
2409 deviceRect = SkRect::Make(skIBounds);
2410 } else {
2411 // FIXME: Early out if rotate/skew. To be thorough, we could compute
2412 // an enclsed rect, but that would probably be overkill.
dshwang 2015/02/06 19:10:37 s/enclsed/enclosed/
Justin Novosad 2015/02/06 19:37:31 Acknowledged.
2413 const SkMatrix& ctm = c->getTotalMatrix();
2414 if (!ctm.rectStaysRect())
2415 return;
2416
2417 if (state().m_hasComplexClip)
2418 return;
2419
2420 ctm.mapRect(&deviceRect, rect);
2421 SkIRect skIBounds;
2422 if (!c->getClipDeviceBounds(&skIBounds))
2423 return;
2424 SkRect skBounds = SkRect::Make(skIBounds);
2425 if (!deviceRect.intersect(skBounds))
2426 return;
2427 }
2428
2429 const SkImageInfo& imageInfo = c->imageInfo();
2430 if (!deviceRect.contains(SkRect::MakeWH(imageInfo.width(), imageInfo.height( ))))
2431 return;
2432
2433 bool isSourceOver = true;
2434 unsigned alpha = 0xFF;
2435 if (paint) {
2436 if (paint->getLooper() || paint->getImageFilter() || paint->getMaskFilte r())
2437 return;
2438
2439 SkXfermode* xfermode = paint->getXfermode();
2440 if (xfermode) {
2441 SkXfermode::Mode mode;
2442 if (xfermode->asMode(&mode)) {
2443 isSourceOver = mode == SkXfermode::kSrcOver_Mode;
2444 if (!isSourceOver && mode != SkXfermode::kSrc_Mode && mode != Sk Xfermode::kClear_Mode)
2445 return; // The code below only knows how to handle Src, SrcO ver, and Clear
2446 } else {
2447 // unknown xfermode
dshwang 2015/02/06 19:10:37 ASSERT_NOT_REACHED?
Justin Novosad 2015/02/06 19:37:31 Acknowledged.
2448 return;
2449 }
2450 }
2451
2452 if (isSourceOver) {
2453 // With source over, we need to certify that alpha == 0xFF for all p ixels
2454 SkColorFilter* colorFilter = paint->getColorFilter();
2455 if (colorFilter && !(colorFilter->getFlags() & SkColorFilter::kAlpha Unchanged_Flag))
2456 return;
2457
2458 SkShader* shader = paint->getShader();
2459 if (shader) {
2460 // Shader overrides bitmap and paint color, so we can end here
2461 if (shader->isOpaque())
dshwang 2015/02/06 19:10:37 don't we need to consider alpha even if shader is
Justin Novosad 2015/02/06 19:37:31 Did some digging in the code. In skia-land, the sh
2462 canvas()->buffer()->willOverwriteCanvas();
2463 return;
2464 }
2465 }
2466
2467 alpha = paint->getAlpha();
2468 }
2469
2470 if (isSourceOver) {
2471 // With source over, we need to certify that alpha == 0xFF for all pixel s
2472 if (imageType == NonOpaqueImage)
2473 return;
2474 if (imageType == NoImage && alpha < 0xFF)
2475 return;
dshwang 2015/02/06 19:10:37 Can we overdraw when imageType == OpaqueImage && a
Justin Novosad 2015/02/06 19:37:31 Yep, Made the same mistake here as with shaders ab
2476 }
2477
2478 canvas()->buffer()->willOverwriteCanvas();
2479 }
2480
2385 } // namespace blink 2481 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698