diff --git a/libraries/entities-renderer/src/RenderableCanvasEntityItem.cpp b/libraries/entities-renderer/src/RenderableCanvasEntityItem.cpp
index f881b38e94..01c514cc2a 100644
--- a/libraries/entities-renderer/src/RenderableCanvasEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableCanvasEntityItem.cpp
@@ -12,37 +12,33 @@ using namespace render;
 using namespace render::entities;
 
 CanvasEntityRenderer::CanvasEntityRenderer(const EntityItemPointer& entity) : Parent(entity) {
-    _testTimer.setInterval(16);
-    connect(&_testTimer, &QTimer::timeout, this, &CanvasEntityRenderer::onTimeout);
-    _testTimer.start();
+    auto canvas = std::dynamic_pointer_cast<CanvasEntityItem>(entity);
+    _width = canvas->getWidth();
+    _width = canvas->getHeight();
 }
 
 CanvasEntityRenderer::~CanvasEntityRenderer() { }
 
-void CanvasEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer &entity) {
+void CanvasEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
     _width = entity->getWidth();
     _height = entity->getHeight();
-}
 
-void CanvasEntityRenderer::onTimeout() {
-    gpu::Byte pixels[256 * 256 * 4];
+    qDebug() << "width: " << _width << ", height: " << _height;
 
-    // XOR placeholder texture
-    for (int x = 0; x < 256; x++) {
-        for (int y = 0; y < 256; y++) {
-            pixels[(y * 256 * 4) + (x * 4) + 0] = ((x + _ticks) ^ (y + _ticks));
-            pixels[(y * 256 * 4) + (x * 4) + 1] = ((x + _ticks) ^ (y + _ticks));
-            pixels[(y * 256 * 4) + (x * 4) + 2] = ((x + _ticks) ^ (y + _ticks));
-            pixels[(y * 256 * 4) + (x * 4) + 3] = 255;
+    if (entity->needsRenderUpdate()) {
+        // misaligned size, can't safely copy
+        if (entity->getImageData().length() != _width * _height * 4) {
+            entity->setNeedsRenderUpdate(false);
+            return;
         }
+
+        auto texture = gpu::Texture::createStrict(gpu::Element::COLOR_SRGBA_32, _width, _height);
+        texture->setStoredMipFormat(gpu::Element::COLOR_SRGBA_32);
+        texture->setAutoGenerateMips(false);
+        texture->assignStoredMip(0, _width * _height * 4, reinterpret_cast<const uint8_t*>(entity->getImageData().data()));
+        texture->setSource("CanvasEntityRenderer");
+        _texture = texture;
+
+        entity->setNeedsRenderUpdate(false);
     }
-
-    auto texture = gpu::Texture::createStrict(gpu::Element::COLOR_SRGBA_32, 256, 256);
-    texture->setStoredMipFormat(gpu::Element::COLOR_SRGBA_32);
-    texture->setAutoGenerateMips(false);
-    texture->assignStoredMip(0, 256 * 256 * 4, pixels);
-    texture->setSource("CanvasEntityRenderer");
-    _texture = texture;
-
-    _ticks += 1;
 }
diff --git a/libraries/entities-renderer/src/RenderableCanvasEntityItem.h b/libraries/entities-renderer/src/RenderableCanvasEntityItem.h
index 21a19df9b7..fe61195d5d 100644
--- a/libraries/entities-renderer/src/RenderableCanvasEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableCanvasEntityItem.h
@@ -32,16 +32,12 @@ protected:
     virtual bool wantsHandControllerPointerEvents() const override { return false; }
     virtual bool wantsKeyboardFocus() const override { return false; }
 
-    virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer &entity) override;
+    virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
 
 private:
     gpu::TexturePointer _texture;
 
-    int _ticks = 0;
-    QTimer _testTimer;
-    void onTimeout();
-
-    int _width = 4, _height = 4;
+    int _width, _height;
 };
 
 } }
diff --git a/libraries/entities/src/CanvasEntityItem.cpp.in b/libraries/entities/src/CanvasEntityItem.cpp.in
index 78510cbaf4..92a4ee2866 100644
--- a/libraries/entities/src/CanvasEntityItem.cpp.in
+++ b/libraries/entities/src/CanvasEntityItem.cpp.in
@@ -16,6 +16,9 @@
 EntityItemPointer CanvasEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
     std::shared_ptr<CanvasEntityItem> entity(new CanvasEntityItem(entityID), [](CanvasEntityItem* ptr) { ptr->deleteLater(); });
     entity->setProperties(properties);
+    size_t bufferSize = 4 * static_cast<size_t>(properties._width) + static_cast<size_t>(properties._height);
+    qDebug() << __FUNCTION__ << ": (4 * " << properties._width << " * " << properties._height << " = " << bufferSize;
+    entity->_imageData = QByteArray(bufferSize, (unsigned char)255);
     return entity;
 }
 
@@ -73,9 +76,17 @@ EntityPropertyFlags CanvasEntityItem::getEntityProperties(EncodeBitstreamParams&
 
 bool CanvasEntityItem::setSubClassProperties(const EntityItemProperties& properties) {
     bool somethingChanged = false;
+    uint16_t oldWidth = getWidth();
+    uint16_t oldHeight = getHeight();
 
 @Canvas_ENTITY_SET_FROM@
 
+    // reallocate and resize if the size properties are changed
+    if (oldWidth != getWidth() || oldHeight != getHeight()) {
+        size_t bufferSize = 4 * static_cast<size_t>(getWidth()) + static_cast<size_t>(getHeight());
+        _imageData = QByteArray(bufferSize, (unsigned char)255);
+    }
+
     return somethingChanged;
 }
 
@@ -86,3 +97,22 @@ EntityItemProperties CanvasEntityItem::getProperties(const EntityPropertyFlags&
 
     return properties;
 }
+
+QByteArray& CanvasEntityItem::getImageData() {
+    return _imageData;
+}
+
+void CanvasEntityItem::setImageData(const QByteArray& data) {
+    if (data.length() != _imageData.length()) {
+        return;
+    }
+
+    _imageData = data;
+    _needsRenderUpdate = true;
+}
+
+void CanvasEntityItem::setImageSubData(const QByteArray& data, uint32_t dx, uint32_t dy, uint32_t dw, uint32_t dh, uint32_t sx, uint32_t sy, uint32_t sw, uint32_t sh) {
+    qCWarning(entities) << "CanvasEntityItem::setImageSubData unimplemented!";
+
+    _needsRenderUpdate = false;
+}
diff --git a/libraries/entities/src/CanvasEntityItem.h.in b/libraries/entities/src/CanvasEntityItem.h.in
index 8a9a172d88..d389dad6d1 100644
--- a/libraries/entities/src/CanvasEntityItem.h.in
+++ b/libraries/entities/src/CanvasEntityItem.h.in
@@ -24,9 +24,14 @@ public:
     ALLOW_INSTANTIATION // This class can be instantiated
     ENTITY_PROPERTY_SUBCLASS_METHODS
 
+    QByteArray& getImageData();
+    void setImageData(const QByteArray& data);
+    void setImageSubData(const QByteArray& data, uint32_t dx, uint32_t dy, uint32_t dw, uint32_t dh, uint32_t sx, uint32_t sy, uint32_t sw, uint32_t sh);
+
 protected:
 @Canvas_ENTITY_PROPS@
 
+    QByteArray _imageData;
 };
 
 #endif // hifi_CanvasEntityItem_h
diff --git a/libraries/entities/src/EntityItemProperties.txt b/libraries/entities/src/EntityItemProperties.txt
index a36cc48da2..579a9eb94c 100644
--- a/libraries/entities/src/EntityItemProperties.txt
+++ b/libraries/entities/src/EntityItemProperties.txt
@@ -267,7 +267,7 @@ enum:SOUND_LOOP prop:loop type:bool default:true,
 enum:SOUND_POSITIONAL prop:positional type:bool default:true,
 enum:SOUND_LOCAL_ONLY prop:localOnly type:bool default:false,
 Canvas
-enum:CANVAS_WIDTH prop:width type:uint16_t default:300 basicProp,
-enum:CANVAS_HEIGHT prop:height type:uint16_t default:150 basicProp,
+enum:CANVAS_WIDTH prop:width type:uint16_t default:300 renderProp,
+enum:CANVAS_HEIGHT prop:height type:uint16_t default:150 renderProp,
 enum:CANVAS_BG_COLOR prop:bgColor type:u8vec3Color default:ENTITY_ITEM_DEFAULT_COLOR basicProp,
 enum:CANVAS_BG_ALPHA prop:bgAlpha type:float default:1.0f min:0.0f max:1.0f basicProp,
diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp
index 6bb0c0b69f..bf4173b18b 100644
--- a/libraries/entities/src/EntityScriptingInterface.cpp
+++ b/libraries/entities/src/EntityScriptingInterface.cpp
@@ -2690,3 +2690,27 @@ glm::vec3 EntityScriptingInterface::localToWorldDimensions(glm::vec3 localDimens
         return glm::vec3(0.0f);
     }
 }
+
+void EntityScriptingInterface::canvasSubmitImage(const QUuid& entityID, const QByteArray& imageData) {
+    EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID));
+    if (!entity) {
+        return;
+    }
+
+    if (entity->getType() == EntityTypes::Canvas) {
+        auto canvas = std::dynamic_pointer_cast<CanvasEntityItem>(entity);
+
+        if (imageData.length() != canvas->getImageData().length()) {
+            qCDebug(entities) << "canvasSubmitImage with different sized buffers on " << entityID << ": input size: " << imageData.length() << ", canvas size: " << canvas->getImageData().length();
+            qCDebug(entities) << "width: " << canvas->getWidth() << ", height: " << canvas->getHeight();
+        }
+
+        canvas->setImageData(imageData);
+    } else {
+        qCWarning(entities) << "canvasSubmitImage called on a non-canvas entity " << entityID;
+    }
+}
+
+void EntityScriptingInterface::canvasSubmitSubImage(const QUuid& entityID, const QByteArray& imageData, const QVector<uint32_t>& destRect, const QVector<uint32_t>& srcRect) {
+    qCWarning(entities) << "canvasSubmitSubImage unimplemented! called on " << entityID;
+}
diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h
index f916b97367..3bd4deefbc 100644
--- a/libraries/entities/src/EntityScriptingInterface.h
+++ b/libraries/entities/src/EntityScriptingInterface.h
@@ -2195,6 +2195,16 @@ public slots:
      */
     Q_INVOKABLE const EntityPropertyInfo getPropertyInfo(const QString& propertyName) const;
 
+    /*@jsdoc
+     * TODO
+     */
+    Q_INVOKABLE void canvasSubmitImage(const QUuid& entityID, const QByteArray& imageData);
+
+    /*@jsdoc
+     * TODO
+     */
+    Q_INVOKABLE void canvasSubmitSubImage(const QUuid& entityID, const QByteArray& imageData, const QVector<uint32_t>& destRect, const QVector<uint32_t>& srcRect);
+
 signals:
     /*@jsdoc
      * Triggered on the client that is the physics simulation owner during the collision of two entities. Note: Isn't triggered