mirror of
https://github.com/penpot/penpot.git
synced 2026-08-07 13:29:07 +00:00
🐛 Fix client-side wasm export encoding jpeg and webp as png
This commit is contained in:
parent
bcc8399bcc
commit
0dbae06c45
@ -13,7 +13,10 @@
|
|||||||
|
|
||||||
(defn export-image-uri
|
(defn export-image-uri
|
||||||
[{:keys [type scale object-id]}]
|
[{:keys [type scale object-id]}]
|
||||||
(let [bytes (wasm.api/render-shape-pixels object-id scale)
|
;; The export type doubles as the encoder format, so the bytes always match
|
||||||
|
;; the mtype we wrap them in. `:svg` never reaches here — it needs vector
|
||||||
|
;; markup and `exports.assets/wasm-export-types` keeps it off this path.
|
||||||
|
(let [bytes (wasm.api/render-shape-pixels object-id scale type)
|
||||||
mtype (format->mtype type)
|
mtype (format->mtype type)
|
||||||
blob (wapi/create-blob bytes mtype)]
|
blob (wapi/create-blob bytes mtype)]
|
||||||
(wapi/create-uri blob)))
|
(wapi/create-uri blob)))
|
||||||
|
|||||||
@ -68,7 +68,9 @@
|
|||||||
{sel-w :width sel-h :height} (:selrect frame)
|
{sel-w :width sel-h :height} (:selrect frame)
|
||||||
max-size (mth/max (or ext-w sel-w) (or ext-h sel-h))
|
max-size (mth/max (or ext-w sel-w) (or ext-h sel-h))
|
||||||
scale (mth/max 1 (/ target-size max-size))
|
scale (mth/max 1 (/ target-size max-size))
|
||||||
png-bytes (wasm.api/render-shape-pixels frame-id scale)]
|
;; Thumbnails are always PNG: they need the alpha
|
||||||
|
;; channel and are consumed as a `image/png` data uri.
|
||||||
|
png-bytes (wasm.api/render-shape-pixels frame-id scale :png)]
|
||||||
(if (or (nil? png-bytes) (zero? (.-length png-bytes)))
|
(if (or (nil? png-bytes) (zero? (.-length png-bytes)))
|
||||||
(do
|
(do
|
||||||
(l/error :hint "render-shape-pixels returned empty" :frame-id (str frame-id))
|
(l/error :hint "render-shape-pixels returned empty" :frame-id (str frame-id))
|
||||||
|
|||||||
@ -2629,7 +2629,10 @@
|
|||||||
(reset! transition-image* snapshot)))))
|
(reset! transition-image* snapshot)))))
|
||||||
|
|
||||||
(defn render-shape-pixels
|
(defn render-shape-pixels
|
||||||
[shape-id scale]
|
"Renders a shape subtree to encoded image bytes. `format` is :png, :jpeg or
|
||||||
|
:webp; jpeg is flattened onto white on the Rust side, since it has no alpha
|
||||||
|
channel."
|
||||||
|
[shape-id scale format]
|
||||||
(let [buffer (uuid/get-u32 shape-id)
|
(let [buffer (uuid/get-u32 shape-id)
|
||||||
|
|
||||||
offset
|
offset
|
||||||
@ -2638,7 +2641,8 @@
|
|||||||
(aget buffer 1)
|
(aget buffer 1)
|
||||||
(aget buffer 2)
|
(aget buffer 2)
|
||||||
(aget buffer 3)
|
(aget buffer 3)
|
||||||
scale)
|
scale
|
||||||
|
(sr/translate-raster-format format))
|
||||||
|
|
||||||
heap (mem/get-heap-u8)
|
heap (mem/get-heap-u8)
|
||||||
heapu32 (mem/get-heap-u32)
|
heapu32 (mem/get-heap-u32)
|
||||||
|
|||||||
@ -976,6 +976,9 @@ pub extern "C" fn get_shape_extrect(a: u32, b: u32, c: u32, d: u32) -> Result<*m
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Raster image via the GPU surface. Returns `[len][width][height][bytes]`
|
||||||
|
/// (LE). `format` selects the encoder: 0 = PNG, 1 = JPEG, 2 = WEBP (see
|
||||||
|
/// `RasterFormat`).
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[wasm_error]
|
#[wasm_error]
|
||||||
pub extern "C" fn render_shape_pixels(
|
pub extern "C" fn render_shape_pixels(
|
||||||
@ -984,6 +987,7 @@ pub extern "C" fn render_shape_pixels(
|
|||||||
c: u32,
|
c: u32,
|
||||||
d: u32,
|
d: u32,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
|
format: u32,
|
||||||
) -> Result<*mut u8> {
|
) -> Result<*mut u8> {
|
||||||
let id = uuid_from_u32_quartet(a, b, c, d);
|
let id = uuid_from_u32_quartet(a, b, c, d);
|
||||||
|
|
||||||
@ -991,9 +995,11 @@ pub extern "C" fn render_shape_pixels(
|
|||||||
return Err(Error::CriticalError("Scale is not finite".to_string()));
|
return Err(Error::CriticalError("Scale is not finite".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let format = RasterFormat::from_u32(format)?;
|
||||||
|
|
||||||
with_state!(state, {
|
with_state!(state, {
|
||||||
let (data, width, height) =
|
let (data, width, height) =
|
||||||
state.render_shape_pixels(&id, scale, performance::get_time())?;
|
state.render_shape_pixels(&id, scale, performance::get_time(), format)?;
|
||||||
|
|
||||||
let len = data.len() as u32;
|
let len = data.len() as u32;
|
||||||
let mut buf = Vec::with_capacity(4 + data.len());
|
let mut buf = Vec::with_capacity(4 + data.len());
|
||||||
|
|||||||
@ -2429,6 +2429,7 @@ impl RenderState {
|
|||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
|
format: raster::RasterFormat,
|
||||||
) -> Result<(Vec<u8>, i32, i32)> {
|
) -> Result<(Vec<u8>, i32, i32)> {
|
||||||
let target_surface = SurfaceId::Export;
|
let target_surface = SurfaceId::Export;
|
||||||
|
|
||||||
@ -2459,7 +2460,7 @@ impl RenderState {
|
|||||||
|
|
||||||
self.surfaces
|
self.surfaces
|
||||||
.canvas(target_surface)
|
.canvas(target_surface)
|
||||||
.clear(skia::Color::TRANSPARENT);
|
.clear(format.clear_color());
|
||||||
|
|
||||||
if tree.len() != 0 {
|
if tree.len() != 0 {
|
||||||
let Some(shape) = tree.get(id) else {
|
let Some(shape) = tree.get(id) else {
|
||||||
@ -2476,6 +2477,14 @@ impl RenderState {
|
|||||||
self.render_area_with_margins = extrect;
|
self.render_area_with_margins = extrect;
|
||||||
self.surfaces.update_render_context(extrect, scale);
|
self.surfaces.update_render_context(extrect, scale);
|
||||||
|
|
||||||
|
// `resize_export_surface` swaps in a brand-new (zeroed, i.e.
|
||||||
|
// transparent) surface whenever the dimensions change, discarding
|
||||||
|
// the clear above — so an opaque backdrop has to be laid down again
|
||||||
|
// here, after the resize.
|
||||||
|
if let Some(background) = format.opaque_background() {
|
||||||
|
self.surfaces.canvas(target_surface).clear(background);
|
||||||
|
}
|
||||||
|
|
||||||
self.pending_nodes.push(NodeRenderState {
|
self.pending_nodes.push(NodeRenderState {
|
||||||
id: *id,
|
id: *id,
|
||||||
visited_children: false,
|
visited_children: false,
|
||||||
@ -2496,10 +2505,10 @@ impl RenderState {
|
|||||||
let data = image
|
let data = image
|
||||||
.encode(
|
.encode(
|
||||||
Some(&mut get_gpu_state().context),
|
Some(&mut get_gpu_state().context),
|
||||||
skia::EncodedImageFormat::PNG,
|
format.encoded(),
|
||||||
100,
|
format.quality(),
|
||||||
)
|
)
|
||||||
.expect("PNG encode failed");
|
.unwrap_or_else(|| panic!("{format:?} encode failed"));
|
||||||
let skia::ISize { width, height } = image.dimensions();
|
let skia::ISize { width, height } = image.dimensions();
|
||||||
|
|
||||||
// Restore the workspace render state.
|
// Restore the workspace render state.
|
||||||
|
|||||||
@ -96,8 +96,9 @@ impl State {
|
|||||||
id: &Uuid,
|
id: &Uuid,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
|
format: RasterFormat,
|
||||||
) -> Result<(Vec<u8>, i32, i32)> {
|
) -> Result<(Vec<u8>, i32, i32)> {
|
||||||
get_render_state().render_shape_pixels(id, &self.shapes, scale, timestamp)
|
get_render_state().render_shape_pixels(id, &self.shapes, scale, timestamp, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_shape_pdf(&mut self, id: &Uuid, scale: f32) -> Result<Vec<u8>> {
|
pub fn render_shape_pdf(&mut self, id: &Uuid, scale: f32) -> Result<Vec<u8>> {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user