Skip to content

Commit d2bffac

Browse files
committed
jpg output
1 parent 9e76e28 commit d2bffac

File tree

5 files changed

+79
-30
lines changed

5 files changed

+79
-30
lines changed

app/index.html

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,40 +10,52 @@
1010
<title>Image encoder</title>
1111

1212
<div id="input-container" class="container">
13+
<label>
14+
Select an image file and enter a secret to encode / decode
15+
</label>
1316
<div id="secret-container">
1417
<div id="drop-zone" style="display: none;">
1518
<p>Drag and drop an image file here.</p>
1619
<p>Supported formats: PNG, JPEG, BMP, ...</p>
1720
</div>
1821

19-
<label>Select an image file</label>
2022
<input type="file" id="fileInput" accept="image/*" required>
2123
<br>
2224
<label for="secretInput">Secret:</label>
2325
<input id="secretInput" placeholder="Enter your secret here" autocomplete="off">
2426

2527
<span style="margin-top: 0.25rem">
26-
<input type="checkbox" id="limitMaxSide" checked>
27-
<label for="limitMaxSide">Limit max side to </label>
28-
<input type="number" id="maxSide" value="1024" min="1" max="25565" step="1" style="width: 4em;">
29-
<label for="maxSide">px</label>
28+
<input type="checkbox" id="limitMaxSide" checked>
29+
<label for="limitMaxSide">Limit max side to </label>
30+
<input type="number" id="maxSide" value="1024" min="1" max="25565" step="1" style="width: 4em;">
31+
<label for="maxSide">px</label>
3032
</span>
3133
</div>
3234

3335
<div id="button-container">
3436
<button id="encodeButton">Encode</button>
3537
<button id="decodeButton">Decode</button>
38+
as
39+
<div style="display: flex">
40+
<select id="imTypeSelect">
41+
<option value="png">PNG</option>
42+
<option value="jpeg">JPEG</option>
43+
</select>
44+
</div>
3645
</div>
3746
</div>
3847

3948
<div id="output-container" class="container">
49+
<label id="hint"></label>
4050
<div id="error"></div>
41-
<div id="downloadBtnContainer" style="display: none">
42-
<button id="downloadBtn">Download Image</button>
51+
<div id="warning" style="display: none">
52+
Encode as JPEG will introduce noise to recovered image.
4353
</div>
44-
<label id="hint">Enter a secret and select an image file to encode / decode</label>
4554
<div id="inputImg"></div>
4655
<div id="output"></div>
56+
<div id="downloadBtnContainer" style="display: none">
57+
<button id="downloadBtn">Download Image</button>
58+
</div>
4759
</div>
4860

4961
<div id="footer">
@@ -58,4 +70,4 @@
5870
</body>
5971

6072
<script src="./script.js" type="module"></script>
61-
</html>
73+
</html>

app/script.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const decodeButton = document.getElementById('decodeButton');
1515
const downloadBtnContainer = document.getElementById('downloadBtnContainer');
1616
const downloadBtn = document.getElementById('downloadBtn');
1717
const errorDiv = document.getElementById('error');
18+
const imTypeSelect = document.getElementById('imTypeSelect');
1819
const worker = new Worker('./worker.js', { type: 'module' });
1920

2021
const urlParams = new URLSearchParams(window.location.search);
@@ -52,7 +53,7 @@ function resetOutput() {
5253
errorDiv.textContent = '';
5354
errorDiv.style.display = 'none';
5455
downloadBtnContainer.style.display = 'none';
55-
hintLabel.textContent = 'Enter a secret and select an image file to encode / decode';
56+
hintLabel.textContent = '';
5657
}
5758

5859
async function showImage(imBlob) {
@@ -71,7 +72,7 @@ async function showImage(imBlob) {
7172
const a = document.createElement('a');
7273
a.href = url;
7374
const timeName = new Date().toISOString().replace(/[:.]/g, '-');
74-
a.download = `img-${timeName}.png`;
75+
a.download = `img-${timeName}.${imTypeSelect.value}`;
7576
document.body.appendChild(a);
7677
a.click();
7778
document.body.removeChild(a);
@@ -87,7 +88,8 @@ async function encode_image() {
8788
type: 'encode',
8889
buffer: inputBlob,
8990
secret: secretInput.value,
90-
maxSide: limitMaxSideInput.checked ? parseInt(maxSideInput.value) : -1
91+
maxSide: limitMaxSideInput.checked ? parseInt(maxSideInput.value) : -1,
92+
outputAs: imTypeSelect.value
9193
});
9294
}
9395

@@ -100,13 +102,14 @@ async function decode_image() {
100102
type: 'decode',
101103
buffer: inputBlob,
102104
secret: secretInput.value,
103-
maxSide: limitMaxSideInput.checked ? parseInt(maxSideInput.value) : -1
105+
maxSide: limitMaxSideInput.checked ? parseInt(maxSideInput.value) : -1,
106+
outputAs: imTypeSelect.value
104107
});
105108
}
106109

107110
worker.onmessage = async (event) => {
108111
if (event.data.error) {
109-
await showError(`Error decoding: ${event.data.error}`);
112+
await showError(`Error: ${event.data.error}`);
110113
console.error(event.data.error);
111114
return;
112115
}
@@ -115,6 +118,15 @@ worker.onmessage = async (event) => {
115118

116119
encodeButton.addEventListener('click', encode_image);
117120
decodeButton.addEventListener('click', decode_image);
121+
imTypeSelect.addEventListener('change', () => {
122+
const warningDiv = document.getElementById('warning');
123+
if (imTypeSelect.value === 'jpeg') {
124+
warningDiv.style.display = 'block';
125+
}
126+
else {
127+
warningDiv.style.display = 'none';
128+
}
129+
});
118130
fileInput.addEventListener('change', ()=>{
119131
resetOutput();
120132
hintLabel.textContent += ' (Below is the selected image)';

app/style.css

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ div#output-container {
5050
}
5151

5252
div#button-container {
53-
margin-top: 0.5rem;
53+
margin-top: 1rem;
54+
display: flex;
55+
flex-direction: row;
56+
gap: 0.25rem;
5457
}
5558

5659
img {
@@ -61,4 +64,8 @@ img {
6164
div#error {
6265
color: red;
6366
font-weight: bold;
67+
}
68+
div#warning {
69+
color: orange;
70+
font-weight: bold;
6471
}

app/worker.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@ import init, { encode, decode } from './pkg/chaotic_enc.js';
22

33
await init();
44
self.onmessage = async function(event) {
5-
const { type, buffer, secret, maxSide } = event.data;
5+
const { type, buffer, secret, maxSide, outputAs } = event.data;
66

77
console.log('Worker received message:', event.data);
88

99
if (type === 'encode') {
10-
const encoded = encode(buffer, secret, maxSide);
10+
const encoded = encode(buffer, secret, maxSide, outputAs);
1111
self.postMessage({ type: 'encoded', buffer: encoded });
1212
}
1313

1414
if (type === 'decode') {
15-
const decoded = decode(buffer, secret, maxSide);
15+
const decoded = decode(buffer, secret, maxSide, outputAs);
1616
self.postMessage({ type: 'decoded', buffer: decoded });
1717
}
1818
}

src/lib.rs

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,12 @@ fn img2vec(im: &[u8], limit_max_side: Option<u32>) -> (Vec<u8>, ImageOptions) {
6565
})
6666
}
6767

68-
fn vec2pngblob(pixels: &Vec<u8>, im_opt: ImageOptions, limit_max_side: Option<u32>) -> Box<[u8]> {
68+
enum ImageType {
69+
Png,
70+
Jpeg,
71+
}
72+
73+
fn vec2imblob(pixels: &Vec<u8>, im_opt: ImageOptions, limit_max_side: Option<u32>, im_type: ImageType) -> Box<[u8]> {
6974
let ImageOptions { mut width, mut height , channels} = im_opt;
7075

7176
let mut img = image::RgbImage::new(width, height);
@@ -93,20 +98,25 @@ fn vec2pngblob(pixels: &Vec<u8>, im_opt: ImageOptions, limit_max_side: Option<u3
9398

9499
// Encode the image to PNG format
95100
let mut buf = Vec::new();
96-
let encoder = image::codecs::png::PngEncoder::new(&mut buf);
97-
encoder.write_image(
98-
&img,
99-
width,
100-
height,
101-
image::ExtendedColorType::Rgb8,
102-
).expect("Failed to encode image");
101+
match im_type {
102+
ImageType::Png => {
103+
image::codecs::png::PngEncoder::new(&mut buf)
104+
.write_image(&img, width, height, image::ExtendedColorType::Rgb8)
105+
.expect("Failed to encode PNG image");
106+
},
107+
ImageType::Jpeg => {
108+
image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 90)
109+
.write_image(&img, width, height, image::ExtendedColorType::Rgb8)
110+
.expect("Failed to encode JPEG image");
111+
},
112+
}
103113

104114
console_log!("Export image, dimensions: {}x{}", width, height);
105115
buf.into_boxed_slice()
106116
}
107117

108118
#[wasm_bindgen]
109-
pub fn encode(im: &[u8], secret: &str, max_side: i32) -> Box<[u8]> {
119+
pub fn encode(im: &[u8], secret: &str, max_side: i32, as_type: &str) -> Box<[u8]> {
110120
console_log!("Encoding image with secret: {}, max_side: {}", secret, max_side);
111121
let max_side = if max_side < 1 { None } else { Some(max_side as u32) };
112122

@@ -116,11 +126,15 @@ pub fn encode(im: &[u8], secret: &str, max_side: i32) -> Box<[u8]> {
116126

117127
let pixels = logistic_map::encode(&im_v, seed);
118128

119-
vec2pngblob(&pixels, im_opt, None)
129+
vec2imblob(&pixels, im_opt, None, match as_type {
130+
"png" => ImageType::Png,
131+
"jpeg" => ImageType::Jpeg,
132+
_ => panic!("Unsupported image type: {}", as_type),
133+
})
120134
}
121135

122136
#[wasm_bindgen]
123-
pub fn decode(im: &[u8], secret: &str, max_side: i32) -> Box<[u8]> {
137+
pub fn decode(im: &[u8], secret: &str, max_side: i32, as_type: &str) -> Box<[u8]> {
124138
console_log!("Decoding image with secret: {}, max_side: {}", secret, max_side);
125139
let max_side = if max_side < 1 { None } else { Some(max_side as u32) };
126140

@@ -130,5 +144,9 @@ pub fn decode(im: &[u8], secret: &str, max_side: i32) -> Box<[u8]> {
130144

131145
let pixels = logistic_map::decode(&im_v, seed);
132146

133-
vec2pngblob(&pixels, im_opt, max_side)
147+
vec2imblob(&pixels, im_opt, max_side, match as_type {
148+
"png" => ImageType::Png,
149+
"jpeg" => ImageType::Jpeg,
150+
_ => panic!("Unsupported image type: {}", as_type),
151+
})
134152
}

0 commit comments

Comments
 (0)