-
Notifications
You must be signed in to change notification settings - Fork 100
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from alabulei1/alabulei1-add-poster
add poster
- Loading branch information
Showing
20 changed files
with
411 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[package] | ||
name = "watermark" | ||
version = "0.1.0" | ||
authors = ["ubuntu"] | ||
edition = "2018" | ||
|
||
[lib] | ||
name = "watermark_lib" | ||
path = "src/lib.rs" | ||
crate-type =["cdylib"] | ||
|
||
[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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# The image watermark example | ||
|
||
In this example, we demonstrate how to create and run a Rust function in the Second State Rust FaaS. | ||
|
||
## Prerequisites | ||
|
||
If you have not done so already, follow these simple instructions to install [Rust](https://www.rust-lang.org/tools/install) and [ssvmup](https://www.secondstate.io/articles/ssvmup/). | ||
|
||
## Build the WASM bytecode | ||
|
||
``` | ||
ssvmup build | ||
``` | ||
|
||
## FaaS | ||
|
||
Upload the wasm file in the `pkg` folder to the FaaS. 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/watermark_lib_bg.wasm' | ||
``` | ||
|
||
Returns | ||
|
||
``` | ||
{"wasm_id":148,"wasm_sha256":"0x0a3227cd8d76c32f4788ca8d020091f89c41f4abc7a3c3b1c10490d439a22b1b","SSVM_Usage_Key":"00000000-0000-0000-0000-000000000000","SSVM_Admin_Key":"aaaa-bbbb-cccc-dddd-0000"} | ||
``` | ||
|
||
Note: You can update this binary with the `SSVM_Admin_Key`. | ||
|
||
``` | ||
$ curl --location --request PUT 'https://rpc.ssvm.secondstate.io:8081/api/update_wasm_binary/148' \ | ||
--header 'Content-Type: application/octet-stream' \ | ||
--header 'SSVM_Admin_Key: 7dxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx0c41' \ | ||
--data-binary '@pkg/watermark_lib_bg.wasm' | ||
``` | ||
|
||
## Setup the watermark text | ||
|
||
Add watermark to a local PNG image. | ||
|
||
``` | ||
$ curl --location --request POST 'https://rpc.ssvm.secondstate.io:8081/api/multipart/run/148/watermark/bytes' \ | ||
--header 'Content-Type: multipart/form-data' \ | ||
--form 'input_1=Meow Human!' \ | ||
--form 'input_2=@test/cat.png' \ | ||
--output tmp.png | ||
``` | ||
|
||
Make a pre-fetched FaaS call to add watermark to an Internet image. | ||
|
||
``` | ||
$ curl --location --request POST 'https://rpc.ssvm.secondstate.io:8081/api/multipart/run/148/watermark/bytes' \ | ||
--header 'Content-Type: multipart/form-data' \ | ||
--form 'input_1=Woof Human!' \ | ||
--form 'fetch_input_2=https://www.secondstate.io/demo/dog.png' \ | ||
--output tmp.png | ||
``` | ||
|
||
## Serverless web app | ||
|
||
Open web page [html/index.html](html/index.html) in any browser. See a [static demo](https://second-state.github.io/wasm-learning/faas/watermark/html/index.html). |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
<title>Watermark</title> | ||
|
||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> | ||
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script> | ||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script> | ||
|
||
<script> | ||
function callService() { | ||
setTimeout(function(){ | ||
$('#process').prop('disabled', true); | ||
},0); | ||
|
||
var formData = new FormData(); | ||
formData.append('input_1', $('#input_1').val()); | ||
formData.append('input_2', $('#input_2')[0].files[0]); | ||
// console.log($('#input_2')[0].files[0]); | ||
|
||
$.ajax({ | ||
url: "https://rpc.ssvm.secondstate.io:8081/api/multipart/run/148/watermark/bytes", | ||
type: "post", | ||
data : formData, | ||
contentType: false, | ||
processData: false, | ||
xhrFields:{ | ||
responseType: 'blob' | ||
}, | ||
success: function (data) { | ||
const img_url = URL.createObjectURL(data); | ||
$('#wm_img').prop('src', img_url); | ||
$('#process').prop('disabled', false); | ||
}, | ||
error: function(){ | ||
alert("Cannot get data"); | ||
$('#process').prop('disabled', false); | ||
} | ||
}); | ||
|
||
return false; | ||
} | ||
</script> | ||
</head> | ||
|
||
<body> | ||
<div class="container"> | ||
<div style="text-align:center;margin:25px"> | ||
<a href="https://www.secondstate.io/"><img style="border:0;" src="https://www.secondstate.io/assets/img/logo.png"></a> | ||
<div style="font-size:90%;color:gray;margin:20px"><a href="https://www.secondstate.io/faas/">Fast, safe, portable and serverless Rust functions as services</a></div> | ||
</div> | ||
|
||
<h1>Watermark</h1> | ||
<p class="lead">Add a custom watermark to an image. <a href="https://www.secondstate.io/articles/mixing-text-and-binary-data-in-call-arguments/">See code tutorial</a></p> | ||
|
||
<form id="draw" enctype="multipart/form-data"> | ||
<div class="form-group"> | ||
<label for="input_1">The watermark text</label> | ||
<input type="text" class="form-control" id="input_1" name="input_1" value=""/> | ||
</div> | ||
|
||
<div class="form-group"> | ||
<label for="input_2">Please upload an image file (<a href="dog.png">example</a>)</label> | ||
<input type="file" class="form-control-file" id="input_2" name="input_2"> | ||
</div> | ||
|
||
<button class="btn btn-primary mb-2" id="process" name="process" value="1" onclick="return callService();">Add Watermark</button> | ||
</form> | ||
|
||
<div class="jumbotron"> | ||
<img id="wm_img" class="rounded mx-auto d-block"/> | ||
</div> | ||
|
||
</div> <!-- /container --> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
<title>Watermark</title> | ||
|
||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> | ||
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script> | ||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script> | ||
|
||
<script> | ||
function callService() { | ||
setTimeout(function(){ | ||
$('#process').prop('disabled', true); | ||
},0); | ||
|
||
$.ajax({ | ||
url: "https://rpc.ssvm.secondstate.io:8081/api/run/148/watermark/bytes", | ||
type: "post", | ||
beforeSend: function(xhr) { | ||
xhr.setRequestHeader("SSVM_Fetch", $('#img_url').val()); | ||
}, | ||
xhrFields:{ | ||
responseType: 'blob' | ||
}, | ||
success: function (data) { | ||
// alert(JSON.stringify(data)); | ||
// var binaryData = []; | ||
// binaryData.push(data); | ||
// const img_url = window.URL.createObjectURL(new Blob(binaryData, {type: "image/png"})) | ||
const img_url = URL.createObjectURL(data); | ||
$('#wm_img').prop('src', img_url); | ||
$('#process').prop('disabled', false); | ||
}, | ||
error: function(){ | ||
alert("Cannot get data"); | ||
$('#process').prop('disabled', false); | ||
} | ||
}); | ||
|
||
return false; | ||
} | ||
</script> | ||
</head> | ||
|
||
<body> | ||
<div class="container"> | ||
<div style="text-align:center;margin:25px"> | ||
<a href="https://www.secondstate.io/"><img style="border:0;" src="https://www.secondstate.io/assets/img/logo.png"></a> | ||
<div style="font-size:90%;color:gray;margin:20px"><a href="https://www.secondstate.io/faas/">Fast, safe, portable and serverless Rust functions as services</a></div> | ||
</div> | ||
|
||
<h1>Watermark</h1> | ||
<p class="lead">Add a watermark to an Internet image</p> | ||
|
||
<form id="draw"> | ||
<div class="form-group"> | ||
<label for="img_url">Put an image URL below</label> | ||
<input type="text" class="form-control" id="img_url" name="img_url" value="https://www.secondstate.io/demo/dog.png"/> | ||
</div> | ||
|
||
<button class="btn btn-primary mb-2" id="process" name="process" value="1" onclick="return callService();">Add Watermark</button> | ||
</form> | ||
|
||
<div class="jumbotron"> | ||
<img id="wm_img" class="rounded mx-auto d-block"/> | ||
</div> | ||
|
||
</div> <!-- /container --> | ||
</body> | ||
</html> |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
use image::{ | ||
GenericImageView, | ||
DynamicImage, | ||
Rgba, | ||
ImageResult, | ||
}; | ||
|
||
#[derive(Debug)] | ||
pub struct Point { | ||
pub x: u32, | ||
pub y: u32, | ||
} | ||
|
||
pub struct ImageCrop { | ||
pub original: DynamicImage, | ||
} | ||
|
||
impl ImageCrop { | ||
pub fn new(img: DynamicImage) -> ImageResult<ImageCrop> { | ||
Ok(ImageCrop { | ||
original: img, | ||
}) | ||
} | ||
|
||
pub fn calculate_corners(&self) -> (Point, Point) { | ||
(self.top_left_corner(), self.bottom_right_corner()) | ||
} | ||
|
||
fn is_white(pixel: Rgba<u8>) -> bool { | ||
pixel[0] != 255 && | ||
pixel[1] != 255 && | ||
pixel[2] != 255 | ||
} | ||
|
||
fn top_left_corner(&self) -> Point { | ||
Point { | ||
x: self.top_left_corner_x(), | ||
y: self.top_left_corner_y(), | ||
} | ||
} | ||
|
||
fn top_left_corner_x(&self) -> u32 { | ||
for x in 0..(self.original.dimensions().0) { | ||
for y in 0..(self.original.dimensions().1) { | ||
let pixel = self.original.get_pixel(x, y); | ||
if Self::is_white(pixel) { | ||
return x; | ||
} | ||
} | ||
} | ||
unreachable!(); | ||
} | ||
|
||
fn top_left_corner_y(&self) -> u32 { | ||
for y in 0..(self.original.dimensions().1) { | ||
for x in 0..(self.original.dimensions().0) { | ||
let pixel = self.original.get_pixel(x, y); | ||
if Self::is_white(pixel) { | ||
return y; | ||
} | ||
} | ||
} | ||
unreachable!(); | ||
} | ||
|
||
fn bottom_right_corner(&self) -> Point { | ||
Point { | ||
x: self.bottom_right_corner_x(), | ||
y: self.bottom_right_corner_y(), | ||
} | ||
} | ||
|
||
fn bottom_right_corner_x(&self) -> u32 { | ||
let mut x = self.original.dimensions().0 as i32 - 1; | ||
// Using while loop as currently there is no reliable built-in | ||
// way to use custom negative steps when specifying range | ||
while x >= 0 { | ||
let mut y = self.original.dimensions().1 as i32 - 1; | ||
while y >= 0 { | ||
let pixel = self.original.get_pixel(x as u32, y as u32); | ||
if Self::is_white(pixel) { | ||
return x as u32 + 1; | ||
} | ||
y -= 1; | ||
} | ||
x -= 1; | ||
} | ||
unreachable!(); | ||
} | ||
|
||
fn bottom_right_corner_y(&self) -> u32 { | ||
let mut y = self.original.dimensions().1 as i32 - 1; | ||
// Using while loop as currently there is no reliable built-in | ||
// way to use custom negative steps when specifying range | ||
while y >= 0 { | ||
let mut x = self.original.dimensions().0 as i32 - 1; | ||
while x >= 0 { | ||
let pixel = self.original.get_pixel(x as u32, y as u32); | ||
if Self::is_white(pixel) { | ||
return y as u32 + 1; | ||
} | ||
x -= 1; | ||
} | ||
y -= 1; | ||
} | ||
unreachable!(); | ||
} | ||
} |
Oops, something went wrong.