mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
Merge pull request #7782 from penpot/alotor-wasm-thumbnails
✨ Render WASM dashboard thumbnails
This commit is contained in:
@@ -96,7 +96,7 @@
|
|||||||
;; loading all pages into memory for find the frame set for thumbnail.
|
;; loading all pages into memory for find the frame set for thumbnail.
|
||||||
|
|
||||||
(defn get-file-data-for-thumbnail
|
(defn get-file-data-for-thumbnail
|
||||||
[{:keys [::db/conn] :as cfg} {:keys [data id] :as file}]
|
[{:keys [::db/conn] :as cfg} {:keys [data id] :as file} strip-frames-with-thumbnails]
|
||||||
(letfn [;; function responsible on finding the frame marked to be
|
(letfn [;; function responsible on finding the frame marked to be
|
||||||
;; used as thumbnail; the returned frame always have
|
;; used as thumbnail; the returned frame always have
|
||||||
;; the :page-id set to the page that it belongs.
|
;; the :page-id set to the page that it belongs.
|
||||||
@@ -173,7 +173,7 @@
|
|||||||
|
|
||||||
;; Assoc the available thumbnails and prune not visible shapes
|
;; Assoc the available thumbnails and prune not visible shapes
|
||||||
;; for avoid transfer unnecessary data.
|
;; for avoid transfer unnecessary data.
|
||||||
:always
|
strip-frames-with-thumbnails
|
||||||
(update :objects assoc-thumbnails page-id thumbs)))))
|
(update :objects assoc-thumbnails page-id thumbs)))))
|
||||||
|
|
||||||
(def ^:private
|
(def ^:private
|
||||||
@@ -186,7 +186,8 @@
|
|||||||
[:map {:title "PartialFile"}
|
[:map {:title "PartialFile"}
|
||||||
[:id ::sm/uuid]
|
[:id ::sm/uuid]
|
||||||
[:revn {:min 0} ::sm/int]
|
[:revn {:min 0} ::sm/int]
|
||||||
[:page [:map-of :keyword ::sm/any]]])
|
[:page [:map-of :keyword ::sm/any]]
|
||||||
|
[:strip-frames-with-thumbnails {:optional true} ::sm/boolean]])
|
||||||
|
|
||||||
(sv/defmethod ::get-file-data-for-thumbnail
|
(sv/defmethod ::get-file-data-for-thumbnail
|
||||||
"Retrieves the data for generate the thumbnail of the file. Used
|
"Retrieves the data for generate the thumbnail of the file. Used
|
||||||
@@ -195,7 +196,7 @@
|
|||||||
::doc/module :files
|
::doc/module :files
|
||||||
::sm/params schema:get-file-data-for-thumbnail
|
::sm/params schema:get-file-data-for-thumbnail
|
||||||
::sm/result schema:partial-file}
|
::sm/result schema:partial-file}
|
||||||
[cfg {:keys [::rpc/profile-id file-id] :as params}]
|
[cfg {:keys [::rpc/profile-id file-id strip-frames-with-thumbnails] :as params}]
|
||||||
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
||||||
(files/check-read-permissions! conn profile-id file-id)
|
(files/check-read-permissions! conn profile-id file-id)
|
||||||
|
|
||||||
@@ -205,14 +206,18 @@
|
|||||||
|
|
||||||
file (bfc/get-file cfg file-id
|
file (bfc/get-file cfg file-id
|
||||||
:realize? true
|
:realize? true
|
||||||
:read-only? true)]
|
:read-only? true)
|
||||||
|
|
||||||
|
strip-frames-with-thumbnails
|
||||||
|
(or (nil? strip-frames-with-thumbnails) ;; if not present, default to true
|
||||||
|
(true? strip-frames-with-thumbnails))]
|
||||||
|
|
||||||
(-> (cfeat/get-team-enabled-features cf/flags team)
|
(-> (cfeat/get-team-enabled-features cf/flags team)
|
||||||
(cfeat/check-file-features! (:features file)))
|
(cfeat/check-file-features! (:features file)))
|
||||||
|
|
||||||
{:file-id file-id
|
{:file-id file-id
|
||||||
:revn (:revn file)
|
:revn (:revn file)
|
||||||
:page (get-file-data-for-thumbnail cfg file)}))))
|
:page (get-file-data-for-thumbnail cfg file strip-frames-with-thumbnails)}))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; MUTATION COMMANDS
|
;; MUTATION COMMANDS
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
:source-map-detail-level :all}}}
|
:source-map-detail-level :all}}}
|
||||||
|
|
||||||
:worker
|
:worker
|
||||||
{:target :browser
|
{:target :esm
|
||||||
:output-dir "resources/public/js/worker/"
|
:output-dir "resources/public/js/worker/"
|
||||||
:asset-path "/js/worker"
|
:asset-path "/js/worker"
|
||||||
:devtools {:browser-inject :main
|
:devtools {:browser-inject :main
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
:height (:height vbox)
|
:height (:height vbox)
|
||||||
:fill color}])
|
:fill color}])
|
||||||
|
|
||||||
(defn- calculate-dimensions
|
(defn calculate-dimensions
|
||||||
[objects aspect-ratio]
|
[objects aspect-ratio]
|
||||||
(let [root-objects (ctst/get-root-objects objects)]
|
(let [root-objects (ctst/get-root-objects objects)]
|
||||||
(if (empty? root-objects)
|
(if (empty? root-objects)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
[app.main.data.notifications :as ntf]
|
[app.main.data.notifications :as ntf]
|
||||||
[app.main.data.project :as dpj]
|
[app.main.data.project :as dpj]
|
||||||
[app.main.data.team :as dtm]
|
[app.main.data.team :as dtm]
|
||||||
|
[app.main.features :as features]
|
||||||
[app.main.fonts :as fonts]
|
[app.main.fonts :as fonts]
|
||||||
[app.main.rasterizer :as thr]
|
[app.main.rasterizer :as thr]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
@@ -46,6 +47,8 @@
|
|||||||
|
|
||||||
(log/set-level! :debug)
|
(log/set-level! :debug)
|
||||||
|
|
||||||
|
(def thumbnail-width 252)
|
||||||
|
|
||||||
;; --- Grid Item Thumbnail
|
;; --- Grid Item Thumbnail
|
||||||
|
|
||||||
(defn- persist-thumbnail
|
(defn- persist-thumbnail
|
||||||
@@ -56,15 +59,22 @@
|
|||||||
|
|
||||||
(defn render-thumbnail
|
(defn render-thumbnail
|
||||||
[file-id revn]
|
[file-id revn]
|
||||||
|
(if (features/active-feature? @st/state "render-wasm/v1")
|
||||||
|
(->> (mw/ask! {:cmd :thumbnails/generate-for-file-wasm
|
||||||
|
:revn revn
|
||||||
|
:file-id file-id
|
||||||
|
:width thumbnail-width}))
|
||||||
(->> (mw/ask! {:cmd :thumbnails/generate-for-file
|
(->> (mw/ask! {:cmd :thumbnails/generate-for-file
|
||||||
:revn revn
|
:revn revn
|
||||||
:file-id file-id})
|
:file-id file-id
|
||||||
(rx/mapcat (fn [{:keys [fonts] :as result}]
|
:width thumbnail-width})
|
||||||
|
(rx/mapcat
|
||||||
|
(fn [{:keys [fonts] :as result}]
|
||||||
(->> (fonts/render-font-styles fonts)
|
(->> (fonts/render-font-styles fonts)
|
||||||
(rx/map (fn [styles]
|
(rx/map (fn [styles]
|
||||||
(assoc result
|
(-> result
|
||||||
:styles styles
|
(assoc :styles styles
|
||||||
:width 252))))))))
|
:width thumbnail-width))))))))))
|
||||||
|
|
||||||
(defn- ask-for-thumbnail
|
(defn- ask-for-thumbnail
|
||||||
"Creates some hooks to handle the files thumbnails cache"
|
"Creates some hooks to handle the files thumbnails cache"
|
||||||
|
|||||||
@@ -94,6 +94,23 @@
|
|||||||
(set! wasm/internal-frame-id nil)
|
(set! wasm/internal-frame-id nil)
|
||||||
(ug/dispatch! (ug/event "penpot:wasm:render"))))
|
(ug/dispatch! (ug/event "penpot:wasm:render"))))
|
||||||
|
|
||||||
|
(defn render-sync
|
||||||
|
[]
|
||||||
|
(when wasm/context-initialized?
|
||||||
|
(h/call wasm/internal-module "_render_sync")
|
||||||
|
(set! wasm/internal-frame-id nil)))
|
||||||
|
|
||||||
|
(defn render-sync-shape
|
||||||
|
[id]
|
||||||
|
(when wasm/context-initialized?
|
||||||
|
(let [buffer (uuid/get-u32 id)]
|
||||||
|
(h/call wasm/internal-module "_render_sync_shape"
|
||||||
|
(aget buffer 0)
|
||||||
|
(aget buffer 1)
|
||||||
|
(aget buffer 2)
|
||||||
|
(aget buffer 3))
|
||||||
|
(set! wasm/internal-frame-id nil))))
|
||||||
|
|
||||||
(def set-view-render
|
(def set-view-render
|
||||||
(fns/debounce
|
(fns/debounce
|
||||||
(fn [ts]
|
(fn [ts]
|
||||||
@@ -290,6 +307,13 @@
|
|||||||
(aset textures new-id texture)
|
(aset textures new-id texture)
|
||||||
new-id))
|
new-id))
|
||||||
|
|
||||||
|
(defn- retrieve-image
|
||||||
|
[url]
|
||||||
|
(rx/from
|
||||||
|
(-> (js/fetch url)
|
||||||
|
(p/then (fn [^js response] (.blob response)))
|
||||||
|
(p/then (fn [^js image] (js/createImageBitmap image))))))
|
||||||
|
|
||||||
(defn- fetch-image
|
(defn- fetch-image
|
||||||
"Loads an image and creates a WebGL texture from it, passing the texture ID to WASM.
|
"Loads an image and creates a WebGL texture from it, passing the texture ID to WASM.
|
||||||
This avoids decoding the image twice (once in browser, once in WASM)."
|
This avoids decoding the image twice (once in browser, once in WASM)."
|
||||||
@@ -297,25 +321,18 @@
|
|||||||
(let [url (cf/resolve-file-media {:id image-id} thumbnail?)]
|
(let [url (cf/resolve-file-media {:id image-id} thumbnail?)]
|
||||||
{:key url
|
{:key url
|
||||||
:thumbnail? thumbnail?
|
:thumbnail? thumbnail?
|
||||||
:callback #(->> (p/create
|
:callback
|
||||||
(fn [resolve reject]
|
(fn []
|
||||||
(let [img (js/Image.)
|
(->> (retrieve-image url)
|
||||||
on-load (fn []
|
(rx/map
|
||||||
(resolve img))
|
(fn [img]
|
||||||
on-error (fn [err]
|
|
||||||
(reject err))]
|
|
||||||
(set! (.-crossOrigin img) "anonymous")
|
|
||||||
(.addEventListener img "load" on-load)
|
|
||||||
(.addEventListener img "error" on-error)
|
|
||||||
(set! (.-src img) url))))
|
|
||||||
(rx/from)
|
|
||||||
(rx/map (fn [img]
|
|
||||||
(when-let [gl (get-webgl-context)]
|
(when-let [gl (get-webgl-context)]
|
||||||
(let [texture (create-webgl-texture-from-image gl img)
|
(let [texture (create-webgl-texture-from-image gl img)
|
||||||
texture-id (get-texture-id-for-gl-object texture)
|
texture-id (get-texture-id-for-gl-object texture)
|
||||||
width (.-width ^js img)
|
width (.-width ^js img)
|
||||||
height (.-height ^js img)
|
height (.-height ^js img)
|
||||||
;; Header: 32 bytes (2 UUIDs) + 4 bytes (thumbnail) + 4 bytes (texture ID) + 8 bytes (dimensions)
|
;; Header: 32 bytes (2 UUIDs) + 4 bytes (thumbnail)
|
||||||
|
;; + 4 bytes (texture ID) + 8 bytes (dimensions)
|
||||||
total-bytes 48
|
total-bytes 48
|
||||||
offset (mem/alloc->offset-32 total-bytes)
|
offset (mem/alloc->offset-32 total-bytes)
|
||||||
heap32 (mem/get-heap-u32)]
|
heap32 (mem/get-heap-u32)]
|
||||||
@@ -340,13 +357,14 @@
|
|||||||
|
|
||||||
(h/call wasm/internal-module "_store_image_from_texture")
|
(h/call wasm/internal-module "_store_image_from_texture")
|
||||||
true))))
|
true))))
|
||||||
(rx/catch (fn [cause]
|
(rx/catch
|
||||||
|
(fn [cause]
|
||||||
(log/error :hint "Could not fetch image"
|
(log/error :hint "Could not fetch image"
|
||||||
:image-id image-id
|
:image-id image-id
|
||||||
:thumbnail? thumbnail?
|
:thumbnail? thumbnail?
|
||||||
:url url
|
:url url
|
||||||
:cause cause)
|
:cause cause)
|
||||||
(rx/empty))))}))
|
(rx/empty)))))}))
|
||||||
|
|
||||||
(defn- get-fill-images
|
(defn- get-fill-images
|
||||||
[leaf]
|
[leaf]
|
||||||
@@ -961,7 +979,9 @@
|
|||||||
:dimensions (get-text-dimensions id)})))))
|
:dimensions (get-text-dimensions id)})))))
|
||||||
|
|
||||||
(defn process-pending
|
(defn process-pending
|
||||||
[shapes thumbnails full on-complete]
|
([shapes thumbnails full on-complete]
|
||||||
|
(process-pending shapes thumbnails full nil on-complete))
|
||||||
|
([shapes thumbnails full on-render on-complete]
|
||||||
(let [pending-thumbnails
|
(let [pending-thumbnails
|
||||||
(d/index-by :key :callback thumbnails)
|
(d/index-by :key :callback thumbnails)
|
||||||
|
|
||||||
@@ -978,9 +998,11 @@
|
|||||||
(rx/subs!
|
(rx/subs!
|
||||||
(fn [_]
|
(fn [_]
|
||||||
(update-text-layouts shapes)
|
(update-text-layouts shapes)
|
||||||
(request-render "pending-finished"))
|
(if on-render
|
||||||
|
(on-render)
|
||||||
|
(request-render "pending-finished")))
|
||||||
noop-fn
|
noop-fn
|
||||||
on-complete))))
|
on-complete)))))
|
||||||
|
|
||||||
(defn process-object
|
(defn process-object
|
||||||
[shape]
|
[shape]
|
||||||
@@ -988,7 +1010,9 @@
|
|||||||
(process-pending [shape] thumbnails full noop-fn)))
|
(process-pending [shape] thumbnails full noop-fn)))
|
||||||
|
|
||||||
(defn set-objects
|
(defn set-objects
|
||||||
[objects]
|
([objects]
|
||||||
|
(set-objects objects nil))
|
||||||
|
([objects render-callback]
|
||||||
(perf/begin-measure "set-objects")
|
(perf/begin-measure "set-objects")
|
||||||
(let [shapes (into [] (vals objects))
|
(let [shapes (into [] (vals objects))
|
||||||
total-shapes (count shapes)
|
total-shapes (count shapes)
|
||||||
@@ -1003,9 +1027,9 @@
|
|||||||
(into full-acc full)))
|
(into full-acc full)))
|
||||||
{:thumbnails thumbnails-acc :full full-acc}))]
|
{:thumbnails thumbnails-acc :full full-acc}))]
|
||||||
(perf/end-measure "set-objects")
|
(perf/end-measure "set-objects")
|
||||||
(process-pending shapes thumbnails full
|
(process-pending shapes thumbnails full render-callback
|
||||||
(fn []
|
(fn []
|
||||||
(ug/dispatch! (ug/event "penpot:wasm:set-objects"))))))
|
(ug/dispatch! (ug/event "penpot:wasm:set-objects")))))))
|
||||||
|
|
||||||
(defn clear-focus-mode
|
(defn clear-focus-mode
|
||||||
[]
|
[]
|
||||||
@@ -1132,14 +1156,16 @@
|
|||||||
(request-render "set-modifiers")))))
|
(request-render "set-modifiers")))))
|
||||||
|
|
||||||
(defn initialize-viewport
|
(defn initialize-viewport
|
||||||
[base-objects zoom vbox background]
|
([base-objects zoom vbox background]
|
||||||
|
(initialize-viewport base-objects zoom vbox background nil))
|
||||||
|
([base-objects zoom vbox background callback]
|
||||||
(let [rgba (sr-clr/hex->u32argb background 1)
|
(let [rgba (sr-clr/hex->u32argb background 1)
|
||||||
shapes (into [] (vals base-objects))
|
shapes (into [] (vals base-objects))
|
||||||
total-shapes (count shapes)]
|
total-shapes (count shapes)]
|
||||||
(h/call wasm/internal-module "_set_canvas_background" rgba)
|
(h/call wasm/internal-module "_set_canvas_background" rgba)
|
||||||
(h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox)))
|
(h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox)))
|
||||||
(h/call wasm/internal-module "_init_shapes_pool" total-shapes)
|
(h/call wasm/internal-module "_init_shapes_pool" total-shapes)
|
||||||
(set-objects base-objects)))
|
(set-objects base-objects callback))))
|
||||||
|
|
||||||
(def ^:private default-context-options
|
(def ^:private default-context-options
|
||||||
#js {:antialias false
|
#js {:antialias false
|
||||||
@@ -1160,8 +1186,10 @@
|
|||||||
|
|
||||||
(defn set-canvas-size
|
(defn set-canvas-size
|
||||||
[canvas]
|
[canvas]
|
||||||
(set! (.-width canvas) (* dpr (.-clientWidth ^js canvas)))
|
(let [width (or (.-clientWidth ^js canvas) (.-width ^js canvas))
|
||||||
(set! (.-height canvas) (* dpr (.-clientHeight ^js canvas))))
|
height (or (.-clientHeight ^js canvas) (.-height ^js canvas))]
|
||||||
|
(set! (.-width canvas) (* dpr width))
|
||||||
|
(set! (.-height canvas) (* dpr height))))
|
||||||
|
|
||||||
(defn- get-browser
|
(defn- get-browser
|
||||||
[]
|
[]
|
||||||
@@ -1274,14 +1302,13 @@
|
|||||||
(mem/free)
|
(mem/free)
|
||||||
content)))
|
content)))
|
||||||
|
|
||||||
(defonce module
|
|
||||||
(delay
|
(defn init-wasm-module
|
||||||
(if (exists? js/dynamicImport)
|
[module]
|
||||||
(let [uri (cf/resolve-static-asset "js/render_wasm.js")]
|
(let [default-fn (unchecked-get module "default")
|
||||||
(->> (js/dynamicImport (str uri))
|
serializers
|
||||||
(p/mcat (fn [module]
|
#js
|
||||||
(let [default (unchecked-get module "default")
|
{:blur-type (unchecked-get module "RawBlurType")
|
||||||
serializers #js{:blur-type (unchecked-get module "RawBlurType")
|
|
||||||
:blend-mode (unchecked-get module "RawBlendMode")
|
:blend-mode (unchecked-get module "RawBlendMode")
|
||||||
:bool-type (unchecked-get module "RawBoolType")
|
:bool-type (unchecked-get module "RawBoolType")
|
||||||
:font-style (unchecked-get module "RawFontStyle")
|
:font-style (unchecked-get module "RawFontStyle")
|
||||||
@@ -1314,11 +1341,20 @@
|
|||||||
:stroke-linejoin (unchecked-get module "RawStrokeLineJoin")
|
:stroke-linejoin (unchecked-get module "RawStrokeLineJoin")
|
||||||
:fill-rule (unchecked-get module "RawFillRule")}]
|
:fill-rule (unchecked-get module "RawFillRule")}]
|
||||||
(set! wasm/serializers serializers)
|
(set! wasm/serializers serializers)
|
||||||
(default))))
|
(default-fn)))
|
||||||
(p/fmap (fn [default]
|
|
||||||
|
(defonce module
|
||||||
|
(delay
|
||||||
|
(if (exists? js/dynamicImport)
|
||||||
|
(let [uri (cf/resolve-static-asset "js/render_wasm.js")]
|
||||||
|
(->> (js/dynamicImport (str uri))
|
||||||
|
(p/mcat init-wasm-module)
|
||||||
|
(p/fmap
|
||||||
|
(fn [default]
|
||||||
(set! wasm/internal-module default)
|
(set! wasm/internal-module default)
|
||||||
true))
|
true))
|
||||||
(p/merr (fn [cause]
|
(p/merr
|
||||||
|
(fn [cause]
|
||||||
(js/console.error cause)
|
(js/console.error cause)
|
||||||
(p/resolved false)))))
|
(p/resolved false)))))
|
||||||
(p/resolved false))))
|
(p/resolved false))))
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
(defn init
|
(defn init
|
||||||
"Return a initialized webworker instance."
|
"Return a initialized webworker instance."
|
||||||
[path on-error]
|
[path on-error]
|
||||||
(let [instance (js/Worker. path)
|
(let [instance (js/Worker. path #js {:type "module"})
|
||||||
bus (rx/subject)
|
bus (rx/subject)
|
||||||
worker (Worker. instance (rx/to-observable bus))
|
worker (Worker. instance (rx/to-observable bus))
|
||||||
|
|
||||||
|
|||||||
@@ -7,16 +7,24 @@
|
|||||||
(ns app.worker.thumbnails
|
(ns app.worker.thumbnails
|
||||||
(:require
|
(:require
|
||||||
["react-dom/server" :as rds]
|
["react-dom/server" :as rds]
|
||||||
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.geom.rect :as grc]
|
||||||
|
[app.common.geom.shapes.bounds :as gsb]
|
||||||
[app.common.logging :as log]
|
[app.common.logging :as log]
|
||||||
|
[app.common.types.color :as cc]
|
||||||
[app.common.uri :as u]
|
[app.common.uri :as u]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.main.fonts :as fonts]
|
[app.main.fonts :as fonts]
|
||||||
[app.main.render :as render]
|
[app.main.render :as render]
|
||||||
|
[app.render-wasm.api :as wasm.api]
|
||||||
|
[app.render-wasm.wasm :as wasm]
|
||||||
[app.util.http :as http]
|
[app.util.http :as http]
|
||||||
[app.worker.impl :as impl]
|
[app.worker.impl :as impl]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[okulary.core :as l]
|
[okulary.core :as l]
|
||||||
[rumext.v2 :as mf]))
|
[promesa.core :as p]
|
||||||
|
[rumext.v2 :as mf]
|
||||||
|
[shadow.esm :refer (dynamic-import)]))
|
||||||
|
|
||||||
(log/set-level! :trace)
|
(log/set-level! :trace)
|
||||||
|
|
||||||
@@ -42,11 +50,11 @@
|
|||||||
:http-body body})))
|
:http-body body})))
|
||||||
|
|
||||||
(defn- request-data-for-thumbnail
|
(defn- request-data-for-thumbnail
|
||||||
[file-id revn]
|
[file-id revn strip-frames-with-thumbnails]
|
||||||
(let [path "api/main/methods/get-file-data-for-thumbnail"
|
(let [path "api/main/methods/get-file-data-for-thumbnail"
|
||||||
params {:file-id file-id
|
params {:file-id file-id
|
||||||
:revn revn
|
:revn revn
|
||||||
:strip-frames-with-thumbnails true}
|
:strip-frames-with-thumbnails strip-frames-with-thumbnails}
|
||||||
request {:method :get
|
request {:method :get
|
||||||
:uri (u/join cf/public-uri path)
|
:uri (u/join cf/public-uri path)
|
||||||
:credentials "include"
|
:credentials "include"
|
||||||
@@ -86,5 +94,89 @@
|
|||||||
|
|
||||||
(defmethod impl/handler :thumbnails/generate-for-file
|
(defmethod impl/handler :thumbnails/generate-for-file
|
||||||
[{:keys [file-id revn] :as message} _]
|
[{:keys [file-id revn] :as message} _]
|
||||||
(->> (request-data-for-thumbnail file-id revn)
|
(->> (request-data-for-thumbnail file-id revn true)
|
||||||
(rx/map render-thumbnail)))
|
(rx/map render-thumbnail)))
|
||||||
|
|
||||||
|
(def init-wasm
|
||||||
|
(delay
|
||||||
|
(let [uri (cf/resolve-static-asset "js/render_wasm.js")]
|
||||||
|
(-> (dynamic-import (str uri))
|
||||||
|
(p/then #(wasm.api/init-wasm-module %))
|
||||||
|
(p/then #(set! wasm/internal-module %))))))
|
||||||
|
|
||||||
|
(mf/defc svg-wrapper
|
||||||
|
[{:keys [data-uri background width height]}]
|
||||||
|
[:svg {:version "1.1"
|
||||||
|
:xmlns "http://www.w3.org/2000/svg"
|
||||||
|
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||||
|
|
||||||
|
:style {:width "100%"
|
||||||
|
:height "100%"
|
||||||
|
:background background}
|
||||||
|
:fill "none"
|
||||||
|
:viewBox (dm/str "0 0 " width " " height)}
|
||||||
|
[:image {:xlinkHref data-uri
|
||||||
|
:width width
|
||||||
|
:height height}]])
|
||||||
|
|
||||||
|
(defn blob->uri
|
||||||
|
[blob]
|
||||||
|
(.readAsDataURL (js/FileReaderSync.) blob))
|
||||||
|
|
||||||
|
(def thumbnail-aspect-ratio (/ 2 3))
|
||||||
|
|
||||||
|
(defmethod impl/handler :thumbnails/generate-for-file-wasm
|
||||||
|
[{:keys [file-id revn width] :as message} _]
|
||||||
|
|
||||||
|
(->> (rx/from @init-wasm)
|
||||||
|
(rx/mapcat #(request-data-for-thumbnail file-id revn false))
|
||||||
|
(rx/mapcat
|
||||||
|
(fn [{:keys [page] :as file}]
|
||||||
|
(rx/create
|
||||||
|
(fn [subs]
|
||||||
|
(try
|
||||||
|
(let [background-color (or (:background page) cc/canvas)
|
||||||
|
height (* width thumbnail-aspect-ratio)
|
||||||
|
canvas (js/OffscreenCanvas. width height)
|
||||||
|
init? (wasm.api/init-canvas-context canvas)]
|
||||||
|
(if init?
|
||||||
|
(let [objects (:objects page)
|
||||||
|
frame (some->> page :thumbnail-frame-id (get objects))
|
||||||
|
vbox (if frame
|
||||||
|
(-> (gsb/get-object-bounds objects frame)
|
||||||
|
(grc/fix-aspect-ratio thumbnail-aspect-ratio))
|
||||||
|
(render/calculate-dimensions objects thumbnail-aspect-ratio))
|
||||||
|
zoom (/ width (:width vbox))]
|
||||||
|
|
||||||
|
(wasm.api/initialize-viewport
|
||||||
|
objects zoom vbox background-color
|
||||||
|
(fn []
|
||||||
|
(if frame
|
||||||
|
(wasm.api/render-sync-shape (:id frame))
|
||||||
|
(wasm.api/render-sync))
|
||||||
|
|
||||||
|
(-> (.convertToBlob canvas)
|
||||||
|
(p/then
|
||||||
|
(fn [blob]
|
||||||
|
(let [data
|
||||||
|
(rds/renderToStaticMarkup
|
||||||
|
(mf/element
|
||||||
|
svg-wrapper
|
||||||
|
#js {:data-uri (blob->uri blob)
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:background background-color}))]
|
||||||
|
(rx/push! subs {:data data :file-id file-id :revn revn}))))
|
||||||
|
(p/catch #(do (.error js/console %)
|
||||||
|
(rx/error! subs %)))
|
||||||
|
(p/finally #(rx/end! subs))))))
|
||||||
|
|
||||||
|
(do (rx/error! subs "Error loading webgl context")
|
||||||
|
(rx/end! subs)))
|
||||||
|
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(catch :default err
|
||||||
|
(.error js/console err)
|
||||||
|
(rx/error! subs err)
|
||||||
|
(rx/end! subs)))))))))
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
addToLibrary({
|
addToLibrary({
|
||||||
wapi_requestAnimationFrame: function wapi_requestAnimationFrame() {
|
wapi_requestAnimationFrame: function wapi_requestAnimationFrame() {
|
||||||
|
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
|
||||||
|
setTimeout(Module._process_animation_frame);
|
||||||
|
} else {
|
||||||
return window.requestAnimationFrame(Module._process_animation_frame);
|
return window.requestAnimationFrame(Module._process_animation_frame);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
wapi_cancelAnimationFrame: function wapi_cancelAnimationFrame(frameId) {
|
wapi_cancelAnimationFrame: function wapi_cancelAnimationFrame(frameId) {
|
||||||
|
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
|
||||||
|
clearTimeout(frameId);
|
||||||
|
} else {
|
||||||
return window.cancelAnimationFrame(frameId);
|
return window.cancelAnimationFrame(frameId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -149,6 +149,27 @@ pub extern "C" fn render(_: i32) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn render_sync() {
|
||||||
|
with_state_mut!(state, {
|
||||||
|
state.rebuild_tiles();
|
||||||
|
state
|
||||||
|
.render_sync(performance::get_time())
|
||||||
|
.expect("Error rendering");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn render_sync_shape(a: u32, b: u32, c: u32, d: u32) {
|
||||||
|
with_state_mut!(state, {
|
||||||
|
let id = uuid_from_u32_quartet(a, b, c, d);
|
||||||
|
state.rebuild_tiles_from(Some(&id));
|
||||||
|
state
|
||||||
|
.render_sync_shape(&id, performance::get_time())
|
||||||
|
.expect("Error rendering");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn render_from_cache(_: i32) {
|
pub extern "C" fn render_from_cache(_: i32) {
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
|
|||||||
@@ -867,7 +867,13 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_render_loop(&mut self, tree: ShapesPoolRef, timestamp: i32) -> Result<(), String> {
|
pub fn start_render_loop(
|
||||||
|
&mut self,
|
||||||
|
base_object: Option<&Uuid>,
|
||||||
|
tree: ShapesPoolRef,
|
||||||
|
timestamp: i32,
|
||||||
|
sync_render: bool,
|
||||||
|
) -> Result<(), String> {
|
||||||
let scale = self.get_scale();
|
let scale = self.get_scale();
|
||||||
self.tile_viewbox.update(self.viewbox, scale);
|
self.tile_viewbox.update(self.viewbox, scale);
|
||||||
|
|
||||||
@@ -917,20 +923,27 @@ impl RenderState {
|
|||||||
self.current_tile = None;
|
self.current_tile = None;
|
||||||
self.render_in_progress = true;
|
self.render_in_progress = true;
|
||||||
self.apply_drawing_to_render_canvas(None);
|
self.apply_drawing_to_render_canvas(None);
|
||||||
self.process_animation_frame(tree, timestamp)?;
|
|
||||||
|
if sync_render {
|
||||||
|
self.render_shape_tree_sync(base_object, tree, timestamp)?;
|
||||||
|
} else {
|
||||||
|
self.process_animation_frame(base_object, tree, timestamp)?;
|
||||||
|
}
|
||||||
|
|
||||||
performance::end_measure!("start_render_loop");
|
performance::end_measure!("start_render_loop");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_animation_frame(
|
pub fn process_animation_frame(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
base_object: Option<&Uuid>,
|
||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
performance::begin_measure!("process_animation_frame");
|
performance::begin_measure!("process_animation_frame");
|
||||||
if self.render_in_progress {
|
if self.render_in_progress {
|
||||||
if tree.len() != 0 {
|
if tree.len() != 0 {
|
||||||
self.render_shape_tree_partial(tree, timestamp)?;
|
self.render_shape_tree_partial(base_object, tree, timestamp, true)?;
|
||||||
} else {
|
} else {
|
||||||
println!("Empty tree");
|
println!("Empty tree");
|
||||||
}
|
}
|
||||||
@@ -947,6 +960,22 @@ impl RenderState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render_shape_tree_sync(
|
||||||
|
&mut self,
|
||||||
|
base_object: Option<&Uuid>,
|
||||||
|
tree: ShapesPoolRef,
|
||||||
|
timestamp: i32,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
if tree.len() != 0 {
|
||||||
|
self.render_shape_tree_partial(base_object, tree, timestamp, false)?;
|
||||||
|
} else {
|
||||||
|
println!("Empty tree");
|
||||||
|
}
|
||||||
|
self.flush_and_submit();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn should_stop_rendering(&self, iteration: i32, timestamp: i32) -> bool {
|
pub fn should_stop_rendering(&self, iteration: i32, timestamp: i32) -> bool {
|
||||||
iteration % NODE_BATCH_THRESHOLD == 0
|
iteration % NODE_BATCH_THRESHOLD == 0
|
||||||
@@ -1215,6 +1244,7 @@ impl RenderState {
|
|||||||
&mut self,
|
&mut self,
|
||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
|
allow_stop: bool,
|
||||||
) -> Result<(bool, bool), String> {
|
) -> Result<(bool, bool), String> {
|
||||||
let mut iteration = 0;
|
let mut iteration = 0;
|
||||||
let mut is_empty = true;
|
let mut is_empty = true;
|
||||||
@@ -1495,7 +1525,7 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We try to avoid doing too many calls to get_time
|
// We try to avoid doing too many calls to get_time
|
||||||
if self.should_stop_rendering(iteration, timestamp) {
|
if allow_stop && self.should_stop_rendering(iteration, timestamp) {
|
||||||
return Ok((is_empty, true));
|
return Ok((is_empty, true));
|
||||||
}
|
}
|
||||||
iteration += 1;
|
iteration += 1;
|
||||||
@@ -1505,8 +1535,10 @@ impl RenderState {
|
|||||||
|
|
||||||
pub fn render_shape_tree_partial(
|
pub fn render_shape_tree_partial(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
base_object: Option<&Uuid>,
|
||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
|
allow_stop: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut should_stop = false;
|
let mut should_stop = false;
|
||||||
while !should_stop {
|
while !should_stop {
|
||||||
@@ -1532,7 +1564,7 @@ impl RenderState {
|
|||||||
} else {
|
} else {
|
||||||
performance::begin_measure!("render_shape_tree::uncached");
|
performance::begin_measure!("render_shape_tree::uncached");
|
||||||
let (is_empty, early_return) =
|
let (is_empty, early_return) =
|
||||||
self.render_shape_tree_partial_uncached(tree, timestamp)?;
|
self.render_shape_tree_partial_uncached(tree, timestamp, allow_stop)?;
|
||||||
|
|
||||||
if early_return {
|
if early_return {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -1564,10 +1596,16 @@ impl RenderState {
|
|||||||
.canvas(SurfaceId::Current)
|
.canvas(SurfaceId::Current)
|
||||||
.clear(self.background_color);
|
.clear(self.background_color);
|
||||||
|
|
||||||
|
let root_ids = {
|
||||||
|
if let Some(shape_id) = base_object {
|
||||||
|
vec![*shape_id]
|
||||||
|
} else {
|
||||||
let Some(root) = tree.get(&Uuid::nil()) else {
|
let Some(root) = tree.get(&Uuid::nil()) else {
|
||||||
return Err(String::from("Root shape not found"));
|
return Err(String::from("Root shape not found"));
|
||||||
};
|
};
|
||||||
let root_ids = root.children_ids(false);
|
root.children_ids(false)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// If we finish processing every node rendering is complete
|
// If we finish processing every node rendering is complete
|
||||||
// let's check if there are more pending nodes
|
// let's check if there are more pending nodes
|
||||||
@@ -1711,13 +1749,19 @@ impl RenderState {
|
|||||||
performance::end_measure!("rebuild_tiles_shallow");
|
performance::end_measure!("rebuild_tiles_shallow");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rebuild_tiles(&mut self, tree: ShapesPoolRef) {
|
pub fn rebuild_tiles_from(&mut self, tree: ShapesPoolRef, base_id: Option<&Uuid>) {
|
||||||
performance::begin_measure!("rebuild_tiles");
|
performance::begin_measure!("rebuild_tiles");
|
||||||
|
|
||||||
self.tiles.invalidate();
|
self.tiles.invalidate();
|
||||||
|
|
||||||
let mut all_tiles = HashSet::<tiles::Tile>::new();
|
let mut all_tiles = HashSet::<tiles::Tile>::new();
|
||||||
let mut nodes = vec![Uuid::nil()];
|
let mut nodes = {
|
||||||
|
if let Some(base_id) = base_id {
|
||||||
|
vec![*base_id]
|
||||||
|
} else {
|
||||||
|
vec![Uuid::nil()]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
while let Some(shape_id) = nodes.pop() {
|
while let Some(shape_id) = nodes.pop() {
|
||||||
if let Some(shape) = tree.get(&shape_id) {
|
if let Some(shape) = tree.get(&shape_id) {
|
||||||
@@ -1737,7 +1781,6 @@ impl RenderState {
|
|||||||
for tile in all_tiles {
|
for tile in all_tiles {
|
||||||
self.remove_cached_tile(tile);
|
self.remove_cached_tile(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
performance::end_measure!("rebuild_tiles");
|
performance::end_measure!("rebuild_tiles");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,15 +63,27 @@ impl<'a> State<'a> {
|
|||||||
self.render_state.render_from_cache(&self.shapes);
|
self.render_state.render_from_cache(&self.shapes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render_sync(&mut self, timestamp: i32) -> Result<(), String> {
|
||||||
|
self.render_state
|
||||||
|
.start_render_loop(None, &self.shapes, timestamp, true)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_sync_shape(&mut self, id: &Uuid, timestamp: i32) -> Result<(), String> {
|
||||||
|
self.render_state
|
||||||
|
.start_render_loop(Some(id), &self.shapes, timestamp, true)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> {
|
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> {
|
||||||
self.render_state
|
self.render_state
|
||||||
.start_render_loop(&self.shapes, timestamp)?;
|
.start_render_loop(None, &self.shapes, timestamp, false)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> {
|
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> {
|
||||||
self.render_state
|
self.render_state
|
||||||
.process_animation_frame(&self.shapes, timestamp)?;
|
.process_animation_frame(None, &self.shapes, timestamp)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +174,11 @@ impl<'a> State<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn rebuild_tiles(&mut self) {
|
pub fn rebuild_tiles(&mut self) {
|
||||||
self.render_state.rebuild_tiles(&self.shapes);
|
self.render_state.rebuild_tiles_from(&self.shapes, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rebuild_tiles_from(&mut self, base_id: Option<&Uuid>) {
|
||||||
|
self.render_state.rebuild_tiles_from(&self.shapes, base_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rebuild_touched_tiles(&mut self) {
|
pub fn rebuild_touched_tiles(&mut self) {
|
||||||
|
|||||||
Reference in New Issue
Block a user