Use Binary Data as Function Input and Output

Sep 27, 2020 • 4 minutes to read

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 the watermark function of Rust source code returns bytes in a Vec<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!

Rust FaaS

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!

Rust FaaS

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.

Source code | Live demo

  $.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.

RustJavaScriptWebAssemblyNode.jsFaaSRust FaaSServerlessCloud computing
A high-performance, extensible, and hardware optimized WebAssembly Virtual Machine for automotive, cloud, AI, and blockchain applications