In the previous article, we demonstrated how a Second State FaaS function could handle text-based input and output. Through JSON support, we can encode arbitrary data types into text. However, for functions focused on media processing, binary-based input and output is far more efficient and more performant. In this article, we will show you how to use a Second State FaaS function to add watermark to an image.
The source code for the watermark example in this article is available on Github.
Watermark an image
The Rust function takes in a byte array for an image (in a &[u8]
), adds a watermark text to it, and then returns the watermarked image in a byte array (in a Vec<u8>
).
#[wasm_bindgen]
pub fn watermark (img_buf: &[u8]) -> Vec<u8> {
// Read the input image
let mut img = image::load_from_memory(img_buf).unwrap();
let (w,h) = img.dimensions();
let scale = Scale {
x: w as f32 /10.0,
y: h as f32 /10.0,
};
// Prepare fonts for the watermark
let font = Vec::from(include_bytes!("DejaVuSans.ttf") as &[u8]);
let font = Font::try_from_vec(font).unwrap();
// Draw the watermark on the input image
drawing::draw_text_mut(&mut img, image::Rgba([255u8, 255u8, 255u8, 255u8]), 0+(h/10),h/2, scale, &font, "Hello Second State");
// Write and return the watermarked image
let mut buf = vec![];
img.write_to(&mut buf, image::ImageOutputFormat::Png).unwrap();
return buf;
}
Before compiling, make sure that your Cargo.toml
file has declared the correct dependencies.
[dependencies]
image = { version = "0.23.0", default-features = false, features = ["jpeg", "png", "gif"] }
imageproc = "=0.21.0"
rusttype = "=0.9.2"
wasm-bindgen = "=0.2.61"
Build and deploy
You need to use the ssvmup tool to compile the Rust function into WebAssembly bytecode (i.e., the wasm file). Do the following and the compiled wasm file is in the pkg
directory.
$ ssvmup build
Upload the wasm file in the pkg
folder to the FaaS /api/executables
RPC service endpoint. Double check the .wasm
file name before you upload.
$ curl --location --request POST 'https://rpc.ssvm.secondstate.io:8081/api/executables' \
--header 'Content-Type: application/octet-stream' \
--header 'SSVM-Description: watermark' \
--data-binary '@pkg/hello_watermark_lib_bg.wasm'
Once the wasm file is deployed, the FaaS returns a wasm_id
for the deployment. You will use this wasm_id
to access the functions in the deployed wasm file later.
{"wasm_id":149,"wasm_sha256":"0xfb413547a8aba56d0349603a7989e269f3846245e51804932b3e02bc0be4b665","usage_key":"00000000-0000-0000-0000-000000000000","admin_key":"00xxxxxx-xxxx-xxxx-xxxx-4adc960fd2b8"}
Invoke the function via HTTP Post
You can now call the Rust function over the web. The RPC service endpoint is /api/run/wasm_id/function_name/bytes
where the wasm_id
is the ID for the wasm file we deployed, and the function_name
is the name of the function we want to invoke in the wasm file.
We are calling the
/bytes
endpoint here, because thewatermark
function of Rust source code returns bytes in aVec<u8>
vector.
The HTTP request body is passed to the function as the call argument. We put an image file cat.png
into the request body. The HTTP response body contains the returned bytes from the function. The response body is saved to the tmp.png
file by the curl command.
$ curl --location --request POST 'https://rpc.ssvm.secondstate.io:8081/api/run/149/watermark/bytes' \
--header 'Content-Type: application/octet-stream' \
--data-binary '@test/cat.png' --output tmp.png
Open the tmp.png
file and you will see the watermark!
Use a web hook as function input
An important use case of FaaS is to trigger the function from another service, such as a messaging queue. In this case, the calling service often passes in an URL pointing to the data, instead of submitting the data itself in a HTTP request. The Second State FaaS has built in support for web hooks as input. The FaaS itself fetches the data in the web hook and then passes the data as a byte array argument to the function. Here is how you pass an image from the Internet to our watermark function as input.
$ curl --location --request POST 'https://rpc.ssvm.secondstate.io:8081/api/run/149/watermark/bytes' \
--header 'SSVM_Fetch: https://www.secondstate.io/demo/dog.png' --output tmp.png
Open the tmp.png
file and you will see the watermark on the dog!
Web UI
On a static web page, you can use JavaScript to make an AJAX call to this FaaS function. The AJAX function posts an uploaded image file. The AJAX response is binary data for the watermarked image (bytes
). The JavaScript displays the response image on the page.
$.ajax({
url: "https://rpc.ssvm.secondstate.io:8081/api/run/149/watermark/bytes",
type: "post",
data : $('#input')[0].files[0],
contentType: "application/octet-stream",
processData: false,
xhrFields:{
responseType: 'blob'
},
success: function (data) {
const img_url = URL.createObjectURL(data);
$('#wm_img').prop('src', img_url);
}
});
What’s next
You can mix bytes and strings in function argument and return value. For example, you can pass in a &[u8]
as call argument and return a string. Or, pass in a string and return a Vec<u8>
. Just remember that, if the return value is Vec<u8>
, you will need to call the /function_name/bytes
endpoint.
However, what if we want to add a different text label as watermark? It is of course possible to wrap the watermark text and input image together in a JSON array and pass into the function, but that is slow and tedious. As a general use case, the FaaS should allow developers to pass in multiple arguments to a call. In this next article, we will discuss that.