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

Side by Side Diff: android_webview/native/aw_contents.cc

Issue 11823027: [Android WebView] Implement the capture picture API. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: changes in native/Java AwContents to support SW rendering without platform functions (tests). Created 7 years, 11 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 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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 "android_webview/native/aw_contents.h" 5 #include "android_webview/native/aw_contents.h"
6 6
7 #include <android/bitmap.h>
7 #include <sys/system_properties.h> 8 #include <sys/system_properties.h>
8 9
9 #include "android_webview/browser/aw_browser_main_parts.h" 10 #include "android_webview/browser/aw_browser_main_parts.h"
10 #include "android_webview/browser/net_disk_cache_remover.h" 11 #include "android_webview/browser/net_disk_cache_remover.h"
11 #include "android_webview/browser/renderer_host/aw_render_view_host_ext.h" 12 #include "android_webview/browser/renderer_host/aw_render_view_host_ext.h"
12 #include "android_webview/browser/renderer_host/aw_resource_dispatcher_host_dele gate.h" 13 #include "android_webview/browser/renderer_host/aw_resource_dispatcher_host_dele gate.h"
13 #include "android_webview/common/aw_hit_test_data.h" 14 #include "android_webview/common/aw_hit_test_data.h"
14 #include "android_webview/common/renderer_picture_map.h" 15 #include "android_webview/common/renderer_picture_map.h"
15 #include "android_webview/native/aw_browser_dependency_factory.h" 16 #include "android_webview/native/aw_browser_dependency_factory.h"
16 #include "android_webview/native/aw_contents_io_thread_client_impl.h" 17 #include "android_webview/native/aw_contents_io_thread_client_impl.h"
(...skipping 13 matching lines...) Expand all
30 #include "content/components/navigation_interception/intercept_navigation_delega te.h" 31 #include "content/components/navigation_interception/intercept_navigation_delega te.h"
31 #include "content/public/browser/android/content_view_core.h" 32 #include "content/public/browser/android/content_view_core.h"
32 #include "content/public/browser/browser_thread.h" 33 #include "content/public/browser/browser_thread.h"
33 #include "content/public/browser/cert_store.h" 34 #include "content/public/browser/cert_store.h"
34 #include "content/public/browser/navigation_entry.h" 35 #include "content/public/browser/navigation_entry.h"
35 #include "content/public/browser/render_process_host.h" 36 #include "content/public/browser/render_process_host.h"
36 #include "content/public/browser/web_contents.h" 37 #include "content/public/browser/web_contents.h"
37 #include "content/public/common/ssl_status.h" 38 #include "content/public/common/ssl_status.h"
38 #include "jni/AwContents_jni.h" 39 #include "jni/AwContents_jni.h"
39 #include "net/base/x509_certificate.h" 40 #include "net/base/x509_certificate.h"
40 #include "skia/ext/refptr.h"
41 #include "third_party/skia/include/core/SkBitmap.h" 41 #include "third_party/skia/include/core/SkBitmap.h"
42 #include "third_party/skia/include/core/SkCanvas.h" 42 #include "third_party/skia/include/core/SkCanvas.h"
43 #include "third_party/skia/include/core/SkDevice.h" 43 #include "third_party/skia/include/core/SkDevice.h"
44 #include "third_party/skia/include/core/SkPicture.h" 44 #include "third_party/skia/include/core/SkGraphics.h"
45 #include "ui/gfx/transform.h" 45 #include "ui/gfx/transform.h"
46 #include "ui/gl/gl_bindings.h" 46 #include "ui/gl/gl_bindings.h"
47 47
48 // TODO(leandrogracia): remove when crbug.com/164140 is closed. 48 // TODO(leandrogracia): remove when crbug.com/164140 is closed.
49 // Borrowed from gl2ext.h. Cannot be included due to conflicts with 49 // Borrowed from gl2ext.h. Cannot be included due to conflicts with
50 // gl_bindings.h and the EGL library methods (eglGetCurrentContext). 50 // gl_bindings.h and the EGL library methods (eglGetCurrentContext).
51 #ifndef GL_TEXTURE_EXTERNAL_OES 51 #ifndef GL_TEXTURE_EXTERNAL_OES
52 #define GL_TEXTURE_EXTERNAL_OES 0x8D65 52 #define GL_TEXTURE_EXTERNAL_OES 0x8D65
53 #endif 53 #endif
54 54
(...skipping 17 matching lines...) Expand all
72 extern "C" { 72 extern "C" {
73 static AwDrawGLFunction DrawGLFunction; 73 static AwDrawGLFunction DrawGLFunction;
74 static void DrawGLFunction(int view_context, 74 static void DrawGLFunction(int view_context,
75 AwDrawGLInfo* draw_info, 75 AwDrawGLInfo* draw_info,
76 void* spare) { 76 void* spare) {
77 // |view_context| is the value that was returned from the java 77 // |view_context| is the value that was returned from the java
78 // AwContents.onPrepareDrawGL; this cast must match the code there. 78 // AwContents.onPrepareDrawGL; this cast must match the code there.
79 reinterpret_cast<android_webview::AwContents*>(view_context)->DrawGL( 79 reinterpret_cast<android_webview::AwContents*>(view_context)->DrawGL(
80 draw_info); 80 draw_info);
81 } 81 }
82
83 typedef base::Callback<bool(SkCanvas*)> RenderMethod;
84
85 static bool RasterizeIntoBitmap(JNIEnv* env,
86 jobject jbitmap,
87 int scroll_x,
88 int scroll_y,
89 const RenderMethod& renderer) {
90 DCHECK(jbitmap);
91
92 AndroidBitmapInfo bitmap_info;
93 if (AndroidBitmap_getInfo(env, jbitmap, &bitmap_info) < 0) {
94 LOG(WARNING) << "Error getting java bitmap info.";
95 return false;
96 }
97
98 void* pixels = NULL;
99 if (AndroidBitmap_lockPixels(env, jbitmap, &pixels) < 0) {
100 LOG(WARNING) << "Error locking java bitmap pixels.";
101 return false;
102 }
103
104 bool succeeded = false;
105 {
106 SkBitmap bitmap;
107 bitmap.setConfig(SkBitmap::kARGB_8888_Config,
108 bitmap_info.width,
109 bitmap_info.height,
110 bitmap_info.stride);
111 bitmap.setPixels(pixels);
112
113 SkDevice device(bitmap);
114 SkCanvas canvas(&device);
115 canvas.translate(-scroll_x, -scroll_y);
116 succeeded = renderer.Run(&canvas);
117 }
118
119 if (AndroidBitmap_unlockPixels(env, jbitmap) < 0) {
120 LOG(WARNING) << "Error unlocking java bitmap pixels.";
121 return false;
122 }
123
124 return succeeded;
125 }
82 } 126 }
83 127
84 namespace android_webview { 128 namespace android_webview {
85 129
86 namespace { 130 namespace {
87 131
88 AwDrawSWFunctionTable* g_draw_sw_functions = NULL; 132 AwDrawSWFunctionTable* g_draw_sw_functions = NULL;
133 bool g_is_skia_version_compatible = false;
89 134
90 const void* kAwContentsUserDataKey = &kAwContentsUserDataKey; 135 const void* kAwContentsUserDataKey = &kAwContentsUserDataKey;
91 136
92 class AwContentsUserData : public base::SupportsUserData::Data { 137 class AwContentsUserData : public base::SupportsUserData::Data {
93 public: 138 public:
94 AwContentsUserData(AwContents* ptr) : contents_(ptr) {} 139 AwContentsUserData(AwContents* ptr) : contents_(ptr) {}
95 140
96 static AwContents* GetContents(WebContents* web_contents) { 141 static AwContents* GetContents(WebContents* web_contents) {
97 if (!web_contents) 142 if (!web_contents)
98 return NULL; 143 return NULL;
(...skipping 15 matching lines...) Expand all
114 159
115 AwContents::AwContents(JNIEnv* env, 160 AwContents::AwContents(JNIEnv* env,
116 jobject obj, 161 jobject obj,
117 jobject web_contents_delegate) 162 jobject web_contents_delegate)
118 : java_ref_(env, obj), 163 : java_ref_(env, obj),
119 web_contents_delegate_( 164 web_contents_delegate_(
120 new AwWebContentsDelegate(env, web_contents_delegate)), 165 new AwWebContentsDelegate(env, web_contents_delegate)),
121 view_visible_(false), 166 view_visible_(false),
122 compositor_visible_(false), 167 compositor_visible_(false),
123 is_composite_pending_(false), 168 is_composite_pending_(false),
169 on_new_picture_mode_(kOnNewPictureDisabled),
124 last_frame_context_(NULL) { 170 last_frame_context_(NULL) {
125 RendererPictureMap::CreateInstance(); 171 RendererPictureMap::CreateInstance();
126 android_webview::AwBrowserDependencyFactory* dependency_factory = 172 android_webview::AwBrowserDependencyFactory* dependency_factory =
127 android_webview::AwBrowserDependencyFactory::GetInstance(); 173 android_webview::AwBrowserDependencyFactory::GetInstance();
128 174
129 // TODO(joth): rather than create and set the WebContents here, expose the 175 // TODO(joth): rather than create and set the WebContents here, expose the
130 // factory method to java side and have that orchestrate the construction 176 // factory method to java side and have that orchestrate the construction
131 // order. 177 // order.
132 SetWebContents(dependency_factory->CreateWebContents()); 178 SetWebContents(dependency_factory->CreateWebContents());
133 } 179 }
(...skipping 237 matching lines...) Expand 10 before | Expand all | Expand 10 after
371 glDisable(GL_SCISSOR_TEST); 417 glDisable(GL_SCISSOR_TEST);
372 418
373 glScissor(scissor_box[0], scissor_box[1], scissor_box[2], 419 glScissor(scissor_box[0], scissor_box[1], scissor_box[2],
374 scissor_box[3]); 420 scissor_box[3]);
375 421
376 glUseProgram(current_program); 422 glUseProgram(current_program);
377 } 423 }
378 // --------------------------------------------------------------------------- 424 // ---------------------------------------------------------------------------
379 } 425 }
380 426
381 bool AwContents::DrawSW(JNIEnv* env, jobject obj, jobject java_canvas) { 427 bool AwContents::DrawSW(JNIEnv* env,
382 skia::RefPtr<SkPicture> picture = 428 jobject obj,
383 RendererPictureMap::GetInstance()->GetRendererPicture( 429 jobject java_canvas,
384 web_contents_->GetRoutingID()); 430 jint clip_x,
385 if (!picture) 431 jint clip_y,
386 return false; 432 jint clip_w,
433 jint clip_h) {
434 TRACE_EVENT0("AwContents", "AwContents::DrawSW");
387 435
388 AwPixelInfo* pixels; 436 AwPixelInfo* pixels;
437
438 // Render into an auxiliary bitmap if pixel info is not available.
389 if (!g_draw_sw_functions || 439 if (!g_draw_sw_functions ||
390 (pixels = g_draw_sw_functions->access_pixels(env, java_canvas)) == NULL) { 440 (pixels = g_draw_sw_functions->access_pixels(env, java_canvas)) == NULL) {
391 // TODO(joth): Fall back to slow path rendering via temporary bitmap. 441 ScopedJavaLocalRef<jobject> jbitmap(Java_AwContents_createBitmap(
392 return false; 442 env, clip_w, clip_h));
443 if (!jbitmap.obj())
444 return false;
445
446 if (!RasterizeIntoBitmap(env, jbitmap.obj(), clip_x, clip_y,
447 base::Bind(&AwContents::RenderSW, base::Unretained(this))))
448 return false;
449
450 Java_AwContents_drawBitmapIntoCanvas(env, jbitmap.obj(), java_canvas);
451 return true;
393 } 452 }
394 453
454 // Draw in a SkCanvas built over the pixel information.
455 bool succeeded = false;
395 { 456 {
396 SkBitmap bitmap; 457 SkBitmap bitmap;
397 bitmap.setConfig(static_cast<SkBitmap::Config>(pixels->config), 458 bitmap.setConfig(static_cast<SkBitmap::Config>(pixels->config),
398 pixels->width, 459 pixels->width,
399 pixels->height, 460 pixels->height,
400 pixels->row_bytes); 461 pixels->row_bytes);
401 bitmap.setPixels(pixels->pixels); 462 bitmap.setPixels(pixels->pixels);
402 SkDevice device(bitmap); 463 SkDevice device(bitmap);
403 SkCanvas canvas(&device); 464 SkCanvas canvas(&device);
404 SkMatrix matrix; 465 SkMatrix matrix;
405 for (int i = 0; i < 9; i++) 466 for (int i = 0; i < 9; i++)
406 matrix.set(i, pixels->matrix[i]); 467 matrix.set(i, pixels->matrix[i]);
407 canvas.setMatrix(matrix); 468 canvas.setMatrix(matrix);
408 469
409 SkRegion clip; 470 SkRegion clip;
410 if (pixels->clip_region_size) { 471 if (pixels->clip_region_size) {
411 size_t bytes_read = clip.readFromMemory(pixels->clip_region); 472 size_t bytes_read = clip.readFromMemory(pixels->clip_region);
412 DCHECK_EQ(pixels->clip_region_size, bytes_read); 473 DCHECK_EQ(pixels->clip_region_size, bytes_read);
413 canvas.setClipRegion(clip); 474 canvas.setClipRegion(clip);
414 } else { 475 } else {
415 clip.setRect(SkIRect::MakeWH(pixels->width, pixels->height)); 476 clip.setRect(SkIRect::MakeWH(pixels->width, pixels->height));
416 } 477 }
417 478
418 picture->draw(&canvas); 479 succeeded = RenderSW(&canvas);
419 } 480 }
420 481
421 g_draw_sw_functions->release_pixels(pixels); 482 g_draw_sw_functions->release_pixels(pixels);
422 return true; 483 return succeeded;
423 } 484 }
424 485
425 jint AwContents::GetWebContents(JNIEnv* env, jobject obj) { 486 jint AwContents::GetWebContents(JNIEnv* env, jobject obj) {
426 return reinterpret_cast<jint>(web_contents_.get()); 487 return reinterpret_cast<jint>(web_contents_.get());
427 } 488 }
428 489
429 void AwContents::DidInitializeContentViewCore(JNIEnv* env, jobject obj, 490 void AwContents::DidInitializeContentViewCore(JNIEnv* env, jobject obj,
430 jint content_view_core) { 491 jint content_view_core) {
431 ContentViewCore* core = reinterpret_cast<ContentViewCore*>(content_view_core); 492 ContentViewCore* core = reinterpret_cast<ContentViewCore*>(content_view_core);
432 DCHECK(core == ContentViewCore::FromWebContents(web_contents_.get())); 493 DCHECK(core == ContentViewCore::FromWebContents(web_contents_.get()));
(...skipping 22 matching lines...) Expand all
455 } 516 }
456 517
457 void AwContents::Destroy(JNIEnv* env, jobject obj) { 518 void AwContents::Destroy(JNIEnv* env, jobject obj) {
458 delete this; 519 delete this;
459 } 520 }
460 521
461 // static 522 // static
462 void SetAwDrawSWFunctionTable(JNIEnv* env, jclass, jint function_table) { 523 void SetAwDrawSWFunctionTable(JNIEnv* env, jclass, jint function_table) {
463 g_draw_sw_functions = 524 g_draw_sw_functions =
464 reinterpret_cast<AwDrawSWFunctionTable*>(function_table); 525 reinterpret_cast<AwDrawSWFunctionTable*>(function_table);
526 // TODO(leandrogracia): uncomment once the glue layer implements this method.
527 //g_is_skia_version_compatible =
528 // g_draw_sw_functions->is_skia_version_compatible(&SkGraphics::GetVersion);
529 if (!g_is_skia_version_compatible)
530 LOG(WARNING) << "Skia native versions are not compatible.";
joth 2013/01/22 00:05:41 LOG_IF(!g_is_skia_version_compatible, WARNING) <<
Leandro Graciá Gil 2013/01/22 02:11:13 Didn't know that one. Done. (Btw, it's LOG_IF(seve
465 } 531 }
466 532
467 // static 533 // static
468 jint GetAwDrawGLFunction(JNIEnv* env, jclass) { 534 jint GetAwDrawGLFunction(JNIEnv* env, jclass) {
469 return reinterpret_cast<jint>(&DrawGLFunction); 535 return reinterpret_cast<jint>(&DrawGLFunction);
470 } 536 }
471 537
472 namespace { 538 namespace {
473 // |message| is passed as base::Owned, so it will automatically be deleted 539 // |message| is passed as base::Owned, so it will automatically be deleted
474 // when the callback goes out of scope. 540 // when the callback goes out of scope.
(...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after
689 is_composite_pending_ = true; 755 is_composite_pending_ = true;
690 Invalidate(); 756 Invalidate();
691 } 757 }
692 758
693 void AwContents::Invalidate() { 759 void AwContents::Invalidate() {
694 JNIEnv* env = AttachCurrentThread(); 760 JNIEnv* env = AttachCurrentThread();
695 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); 761 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
696 if (obj.is_null()) 762 if (obj.is_null())
697 return; 763 return;
698 764
699 Java_AwContents_invalidate(env, obj.obj()); 765 if (view_visible_)
766 Java_AwContents_invalidate(env, obj.obj());
767
768 // When not in invalidation-only mode onNewPicture will be triggered
769 // from the OnPictureUpdated callback.
770 if (on_new_picture_mode_ == kOnNewPictureInvalidationOnly)
771 Java_AwContents_onNewPicture(env, obj.obj(), NULL);
700 } 772 }
701 773
702 void AwContents::SetCompositorVisibility(bool visible) { 774 void AwContents::SetCompositorVisibility(bool visible) {
703 if (compositor_visible_ != visible) { 775 if (compositor_visible_ != visible) {
704 compositor_visible_ = visible; 776 compositor_visible_ = visible;
705 compositor_->SetVisible(compositor_visible_); 777 compositor_->SetVisible(compositor_visible_);
706 } 778 }
707 } 779 }
708 780
709 void AwContents::OnSwapBuffersCompleted() { 781 void AwContents::OnSwapBuffersCompleted() {
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after
841 } 913 }
842 914
843 void AwContents::FocusFirstNode(JNIEnv* env, jobject obj) { 915 void AwContents::FocusFirstNode(JNIEnv* env, jobject obj) {
844 web_contents_->FocusThroughTabTraversal(false); 916 web_contents_->FocusThroughTabTraversal(false);
845 } 917 }
846 918
847 jint AwContents::ReleasePopupWebContents(JNIEnv* env, jobject obj) { 919 jint AwContents::ReleasePopupWebContents(JNIEnv* env, jobject obj) {
848 return reinterpret_cast<jint>(pending_contents_.release()); 920 return reinterpret_cast<jint>(pending_contents_.release());
849 } 921 }
850 922
923 ScopedJavaLocalRef<jobject> AwContents::CapturePicture(JNIEnv* env,
924 jobject obj) {
925 skia::RefPtr<SkPicture> picture = GetLastCapturedPicture();
926 if (!picture || !g_draw_sw_functions)
927 return ScopedJavaLocalRef<jobject>();
928
929 if (g_is_skia_version_compatible)
930 return ScopedJavaLocalRef<jobject>(env,
931 g_draw_sw_functions->create_picture(env, picture->clone()));
932
933 // If Skia versions are not compatible, workaround it by rasterizing the
934 // picture into a bitmap and drawing it into a new Java picture.
935 ScopedJavaLocalRef<jobject> jbitmap(Java_AwContents_createBitmap(
936 env, picture->width(), picture->height()));
937 if (!jbitmap.obj())
938 return ScopedJavaLocalRef<jobject>();
939
940 if (!RasterizeIntoBitmap(env, jbitmap.obj(), 0, 0,
941 base::Bind(&AwContents::RenderPicture, base::Unretained(this))))
942 return ScopedJavaLocalRef<jobject>();
943
944 return Java_AwContents_recordRasterizedBitmap(env, jbitmap.obj());
945 }
946
947 bool AwContents::RenderSW(SkCanvas* canvas) {
948 // TODO(leandrogracia): once Ubercompositor is ready and we support software
949 // rendering mode, we should avoid this as much as we can, ideally always.
950 // This includes finding a proper replacement for onDraw calls in hardware
951 // mode with software canvases. http://crbug.com/170086.
952 return RenderPicture(canvas);
953 }
954
955 bool AwContents::RenderPicture(SkCanvas* canvas) {
956 skia::RefPtr<SkPicture> picture = GetLastCapturedPicture();
957 if (!picture)
958 return false;
959
960 picture->draw(canvas);
961 return true;
962 }
963
964 void AwContents::EnableOnNewPicture(JNIEnv* env,
965 jobject obj,
966 jboolean enabled,
967 jboolean invalidation_only) {
968 if (enabled) {
969 on_new_picture_mode_ = invalidation_only ? kOnNewPictureInvalidationOnly :
970 kOnNewPictureEnabled;
971 } else {
972 on_new_picture_mode_ = kOnNewPictureDisabled;
973 }
974
975 // If onNewPicture is triggered only on invalidation do not capture
976 // pictures on every new frame.
977 if (on_new_picture_mode_ == kOnNewPictureInvalidationOnly)
978 enabled = false;
979
980 // TODO(leandrogracia): uncomment when sw rendering uses Ubercompositor.
joth 2013/01/22 00:05:41 I'd rather we leave "TODO: uncomment..." for thing
Leandro Graciá Gil 2013/01/22 02:11:13 Done.
981 // Until then we need the callback enabled for SW mode invalidation.
982 // render_view_host_ext_->EnableCapturePictureCallback(enabled);
983 }
984
851 void AwContents::OnPictureUpdated(int process_id, int render_view_id) { 985 void AwContents::OnPictureUpdated(int process_id, int render_view_id) {
852 CHECK_EQ(web_contents_->GetRenderProcessHost()->GetID(), process_id); 986 CHECK_EQ(web_contents_->GetRenderProcessHost()->GetID(), process_id);
853 if (render_view_id != web_contents_->GetRoutingID()) 987 if (render_view_id != web_contents_->GetRoutingID())
854 return; 988 return;
855 989
990 // TODO(leandrogracia): this can be made unconditional once software rendering
991 // uses Ubercompositor. Until then this path is required for SW invalidations.
992 if (on_new_picture_mode_ == kOnNewPictureEnabled) {
993 JNIEnv* env = AttachCurrentThread();
994 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
995 if (!obj.is_null()) {
996 ScopedJavaLocalRef<jobject> picture = CapturePicture(env, obj.obj());
997 Java_AwContents_onNewPicture(env, obj.obj(), picture.obj());
998 }
999 }
1000
856 // TODO(leandrogracia): delete when sw rendering uses Ubercompositor. 1001 // TODO(leandrogracia): delete when sw rendering uses Ubercompositor.
857 // Invalidation should be provided by the compositor only. 1002 // Invalidation should be provided by the compositor only.
858 Invalidate(); 1003 Invalidate();
859 } 1004 }
860 1005
1006 skia::RefPtr<SkPicture> AwContents::GetLastCapturedPicture() {
1007 // Use the latest available picture if the listener callback is enabled.
1008 skia::RefPtr<SkPicture> picture;
1009 if (on_new_picture_mode_ == kOnNewPictureEnabled)
1010 picture = RendererPictureMap::GetInstance()->GetRendererPicture(
1011 web_contents_->GetRoutingID());
1012
1013 // If not available or not in listener mode get it synchronously.
1014 if (!picture) {
1015 render_view_host_ext_->CapturePictureSync();
1016 picture = RendererPictureMap::GetInstance()->GetRendererPicture(
1017 web_contents_->GetRoutingID());
1018 }
1019
1020 return picture;
1021 }
1022
861 } // namespace android_webview 1023 } // namespace android_webview
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698