cvm-rs: Add jpegResizeEncode(), remove usage of sharp
sharp sucks.
This commit is contained in:
27
cvm-rs/Cargo.lock
generated
27
cvm-rs/Cargo.lock
generated
@@ -53,6 +53,12 @@ version = "2.9.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.23"
|
version = "1.2.23"
|
||||||
@@ -132,6 +138,8 @@ dependencies = [
|
|||||||
"napi-derive",
|
"napi-derive",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"resize",
|
||||||
|
"rgb",
|
||||||
"turbojpeg-sys",
|
"turbojpeg-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -331,6 +339,25 @@ version = "0.8.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "resize"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87a103d0b47e783f4579149402f7499397ab25540c7a57b2f70487a5d2d20ef0"
|
||||||
|
dependencies = [
|
||||||
|
"rayon",
|
||||||
|
"rgb",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rgb"
|
||||||
|
version = "0.8.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.24"
|
version = "0.1.24"
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ rayon = "1.10.0"
|
|||||||
napi = { version = "2.16.9", features = [ "async", "napi8", "error_anyhow" ] }
|
napi = { version = "2.16.9", features = [ "async", "napi8", "error_anyhow" ] }
|
||||||
napi-derive = "2.16.11"
|
napi-derive = "2.16.11"
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
|
resize = "0.8.8"
|
||||||
|
rgb = "0.8.50"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
napi-build = "2.1.3"
|
napi-build = "2.1.3"
|
||||||
|
|||||||
12
cvm-rs/index.d.ts
vendored
12
cvm-rs/index.d.ts
vendored
@@ -14,10 +14,20 @@ interface JpegInputArgs {
|
|||||||
// (i.e: new JpegEncoder(FORMAT_xxx)).
|
// (i.e: new JpegEncoder(FORMAT_xxx)).
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface JpegResizeInputArgs {
|
||||||
|
width: number; // source width
|
||||||
|
height: number; // source height
|
||||||
|
desiredWidth: number; // dest width
|
||||||
|
desiredHeight: number; // dest height
|
||||||
|
buffer: Buffer; // source raw pixel buffer
|
||||||
|
}
|
||||||
|
|
||||||
/// Performs JPEG encoding.
|
/// Performs JPEG encoding.
|
||||||
export function jpegEncode(input: JpegInputArgs): Promise<Buffer>;
|
export function jpegEncode(input: JpegInputArgs): Promise<Buffer>;
|
||||||
|
|
||||||
// TODO: Version that can downscale?
|
/// Performs JPEG encoding with resizing.
|
||||||
|
export function jpegResizeEncode(input: JpegResizeInputArgs): Promise<Buffer>;
|
||||||
|
|
||||||
|
|
||||||
/* remoting API?
|
/* remoting API?
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import { createRequire } from 'module';
|
import { createRequire } from 'module';
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
let {guacDecode, guacEncodeImpl, jpegEncode} = require('./index.node');
|
let {guacDecode, guacEncodeImpl, jpegEncode, jpegResizeEncode} = require('./index.node');
|
||||||
|
|
||||||
export { guacDecode, jpegEncode };
|
export { guacDecode, jpegEncode, jpegResizeEncode };
|
||||||
|
|
||||||
// shim for js->rust interop, because napi-rs kind of blows in this regard
|
// shim for js->rust interop, because napi-rs kind of blows in this regard
|
||||||
export function guacEncode(...args) {
|
export function guacEncode(...args) {
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ use rayon::{ThreadPool, ThreadPoolBuilder};
|
|||||||
|
|
||||||
use crate::jpeg_compressor::*;
|
use crate::jpeg_compressor::*;
|
||||||
|
|
||||||
|
|
||||||
|
use resize::Pixel::RGBA8;
|
||||||
|
use resize::Type::Triangle;
|
||||||
|
use rgb::FromSlice;
|
||||||
|
|
||||||
|
|
||||||
/// Gives a Rayon thread pool we use for parallelism
|
/// Gives a Rayon thread pool we use for parallelism
|
||||||
fn rayon_pool() -> &'static ThreadPool {
|
fn rayon_pool() -> &'static ThreadPool {
|
||||||
static RUNTIME: OnceCell<ThreadPool> = OnceCell::new();
|
static RUNTIME: OnceCell<ThreadPool> = OnceCell::new();
|
||||||
@@ -69,9 +75,76 @@ pub fn jpeg_encode(env: Env, input: JpegInputArgs) -> napi::Result<napi::JsObjec
|
|||||||
});
|
});
|
||||||
|
|
||||||
deferred_resolver.resolve(move |env| {
|
deferred_resolver.resolve(move |env| {
|
||||||
let buffer = env
|
let buffer = env.create_buffer_with_data(vec).expect(
|
||||||
.create_buffer_with_data(vec)
|
"Couldn't create node Buffer, things are probably very broken by this point",
|
||||||
.expect("Couldn't create node Buffer, things are probably very broken by this point");
|
);
|
||||||
|
// no longer need the input buffer
|
||||||
|
buf.unref(env)?;
|
||||||
|
Ok(buffer.into_raw())
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct JpegResizeInputArgs {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub desired_width: u32,
|
||||||
|
pub desired_height: u32,
|
||||||
|
pub buffer: napi::JsBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(js_name = "jpegResizeEncode")]
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn jpeg_resize_and_encode(
|
||||||
|
env: Env,
|
||||||
|
input: JpegResizeInputArgs,
|
||||||
|
) -> napi::Result<napi::JsObject> {
|
||||||
|
let (deferred_resolver, promise) = env.create_deferred::<napi::JsBuffer, _>()?;
|
||||||
|
let mut buf = input.buffer.into_ref()?;
|
||||||
|
|
||||||
|
// Spawn a task on the rayon pool that encodes the JPEG and fufills the promise
|
||||||
|
// once it is done encoding.
|
||||||
|
rayon_pool().spawn_fifo(move || {
|
||||||
|
let mut new_data: Vec<u8> =
|
||||||
|
vec![0; (input.desired_width * input.desired_height) as usize * 4];
|
||||||
|
|
||||||
|
let mut resizer = resize::new(
|
||||||
|
input.width as usize,
|
||||||
|
input.height as usize,
|
||||||
|
input.desired_width as usize,
|
||||||
|
input.desired_height as usize,
|
||||||
|
RGBA8,
|
||||||
|
Triangle,
|
||||||
|
)
|
||||||
|
.expect("Could not create resizer");
|
||||||
|
|
||||||
|
resizer
|
||||||
|
.resize(&buf.as_rgba(), new_data.as_rgba_mut())
|
||||||
|
.expect("Resize operation failed");
|
||||||
|
|
||||||
|
// then just jpeg encode. Ideally this would be shared :(
|
||||||
|
let image = Image {
|
||||||
|
buffer: &new_data,
|
||||||
|
width: input.desired_width as u32,
|
||||||
|
height: input.desired_height as u32,
|
||||||
|
stride: (input.desired_width as u64 * 4u64) as u32,
|
||||||
|
format: turbojpeg_sys::TJPF_TJPF_RGBA,
|
||||||
|
};
|
||||||
|
|
||||||
|
let vec = COMPRESSOR.with(|lazy| {
|
||||||
|
let mut b = lazy.borrow_mut();
|
||||||
|
b.set_quality(35);
|
||||||
|
b.set_subsamp(turbojpeg_sys::TJSAMP_TJSAMP_420);
|
||||||
|
b.compress_buffer(&image)
|
||||||
|
});
|
||||||
|
|
||||||
|
deferred_resolver.resolve(move |env| {
|
||||||
|
let buffer = env.create_buffer_with_data(vec).expect(
|
||||||
|
"Couldn't create node Buffer, things are probably very broken by this point",
|
||||||
|
);
|
||||||
// no longer need the input buffer
|
// no longer need the input buffer
|
||||||
buf.unref(env)?;
|
buf.unref(env)?;
|
||||||
Ok(buffer.into_raw())
|
Ok(buffer.into_raw())
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
"mnemonist": "^0.39.5",
|
"mnemonist": "^0.39.5",
|
||||||
"msgpackr": "^1.10.2",
|
"msgpackr": "^1.10.2",
|
||||||
"pino": "^9.3.1",
|
"pino": "^9.3.1",
|
||||||
"sharp": "^0.33.3",
|
|
||||||
"toml": "^3.0.0",
|
"toml": "^3.0.0",
|
||||||
"ws": "^8.17.1"
|
"ws": "^8.17.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Size, Rect } from './Utilities';
|
import { Size, Rect } from './Utilities';
|
||||||
import sharp from 'sharp';
|
|
||||||
import * as cvm from '@cvmts/cvm-rs';
|
import * as cvm from '@cvmts/cvm-rs';
|
||||||
|
|
||||||
// A good balance. TODO: Configurable?
|
// A good balance. TODO: Configurable?
|
||||||
@@ -10,17 +9,6 @@ const kThumbnailSize: Size = {
|
|||||||
height: 300
|
height: 300
|
||||||
};
|
};
|
||||||
|
|
||||||
// this returns appropiate Sharp options to deal with CVMTS raw framebuffers
|
|
||||||
// (which are RGBA bitmaps, essentially. We probably should abstract that out but
|
|
||||||
// that'd mean having to introduce that to rfb and oihwekjtgferklds;./tghnredsltg;erhds)
|
|
||||||
function GetRawSharpOptions(size: Size): sharp.CreateRaw {
|
|
||||||
return {
|
|
||||||
width: size.width,
|
|
||||||
height: size.height,
|
|
||||||
channels: 4
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class JPEGEncoder {
|
export class JPEGEncoder {
|
||||||
static SetQuality(quality: number) {
|
static SetQuality(quality: number) {
|
||||||
gJpegQuality = quality;
|
gJpegQuality = quality;
|
||||||
@@ -37,16 +25,12 @@ export class JPEGEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async EncodeThumbnail(buffer: Buffer, size: Size): Promise<Buffer> {
|
static async EncodeThumbnail(buffer: Buffer, size: Size): Promise<Buffer> {
|
||||||
let { data, info } = await sharp(buffer, { raw: GetRawSharpOptions(size) })
|
return cvm.jpegResizeEncode({
|
||||||
.resize(kThumbnailSize.width, kThumbnailSize.height, { fit: 'fill' })
|
width: size.width,
|
||||||
.raw()
|
height: size.height,
|
||||||
.toBuffer({ resolveWithObject: true });
|
desiredWidth: kThumbnailSize.width,
|
||||||
|
desiredHeight: kThumbnailSize.height,
|
||||||
return cvm.jpegEncode({
|
buffer: buffer
|
||||||
width: kThumbnailSize.width,
|
|
||||||
height: kThumbnailSize.height,
|
|
||||||
stride: kThumbnailSize.width,
|
|
||||||
buffer: data
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user