转换为 io::BufRead + io::Seek
28 | let mut bufreader = std::io::Cursor::new(raw.as_slice());
29 | let exif = exifreader.read_from_container(&mut bufreader).unwrap();
30 |
31 | for field in exif.fields() {
32 | if let Some(_) = field.tag.to_string().find("Tag(Exif") {
33 | continue;
34 | }
35 |
36 | if ["Make", "Model"].contains(&field.tag.to_string().as_str()) {
37 | exif_data.push(ExifData {
38 | tag: field.tag.to_string(),
39 | value: field
40 | .display_value()
41 | .to_string()
42 | .replace(
43 | |item: char| ["\"", ","].contains(&item.to_string().as_str()),
44 | "",
45 | )
46 | .trim()
47 | .to_string(),
48 | value_with_unit: field
49 | .display_value()
50 | .with_unit(&exif)
51 | .to_string()
52 | .replace('"', ""),
53 | });
54 | continue;
55 | }
56 |
57 | exif_data.push(ExifData {
58 | tag: field.tag.to_string(),
59 | value: field.display_value().to_string(),
60 | value_with_unit: field.display_value().with_unit(&exif).to_string(),
61 | });
62 | }
63 |
64 | JsValue::from_serde(&exif_data).unwrap()
65 | }
66 |
--------------------------------------------------------------------------------
/sw.reg.mgr.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | register({
3 | ver: 2,
4 | path: ".",
5 | name: "sw.js",
6 | onUpdate: () => {},
7 | onSuccess: () => {},
8 | });
9 |
10 | function register(config) {
11 | if ("serviceWorker" in navigator) {
12 | var swUrl = config.path + "/" + config.name + "?v=" + config.ver;
13 | navigator.serviceWorker
14 | .register(swUrl)
15 | .then((swReg) => {
16 | config.log && console.log("Service Worker 注册成功。", swReg);
17 | if (config && config.onRegster) {
18 | config.onRegister(swReg);
19 | }
20 |
21 | swReg.onupdatefound = () => {
22 | var installingWorker = swReg.installing;
23 | if (installingWorker == null) {
24 | return;
25 | }
26 | installingWorker.onstatechange = () => {
27 | if (installingWorker.state === "installed") {
28 | if (navigator.serviceWorker.controller) {
29 | config.log && console.log("Service Worker 已安装更新。");
30 | if (config && config.onUpdate) {
31 | config.onUpdate(swReg);
32 | }
33 | } else {
34 | config.log && console.log("Service Worker 已安装。");
35 | if (config && config.onSuccess) {
36 | config.onSuccess(swReg);
37 | }
38 | }
39 | }
40 | };
41 | };
42 | })
43 | .catch((err) => {
44 | if (config && config.onError) {
45 | config.onError(err);
46 | }
47 | console.error("serviceWorker注册期间发生错误:", error);
48 | });
49 | }
50 | }
51 |
52 | function unregister() {
53 | if ("serviceWorker" in navigator) {
54 | navigator.serviceWorker.ready.then((swReg) => {
55 | swReg.unregister((result) => {
56 | result && console.log("Service Worker 注销成功");
57 | });
58 | });
59 | }
60 | }
61 | })();
--------------------------------------------------------------------------------
/brand/canon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/brand/sony.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: rust
2 | sudo: false
3 |
4 | cache: cargo
5 |
6 | matrix:
7 | include:
8 |
9 | # Builds with wasm-pack.
10 | - rust: beta
11 | env: RUST_BACKTRACE=1
12 | addons:
13 | firefox: latest
14 | chrome: stable
15 | before_script:
16 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
17 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
18 | - cargo install-update -a
19 | - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
20 | script:
21 | - cargo generate --git . --name testing
22 | # Having a broken Cargo.toml (in that it has curlies in fields) anywhere
23 | # in any of our parent dirs is problematic.
24 | - mv Cargo.toml Cargo.toml.tmpl
25 | - cd testing
26 | - wasm-pack build
27 | - wasm-pack test --chrome --firefox --headless
28 |
29 | # Builds on nightly.
30 | - rust: nightly
31 | env: RUST_BACKTRACE=1
32 | before_script:
33 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
34 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
35 | - cargo install-update -a
36 | - rustup target add wasm32-unknown-unknown
37 | script:
38 | - cargo generate --git . --name testing
39 | - mv Cargo.toml Cargo.toml.tmpl
40 | - cd testing
41 | - cargo check
42 | - cargo check --target wasm32-unknown-unknown
43 | - cargo check --no-default-features
44 | - cargo check --target wasm32-unknown-unknown --no-default-features
45 | - cargo check --no-default-features --features console_error_panic_hook
46 | - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
47 | - cargo check --no-default-features --features "console_error_panic_hook wee_alloc"
48 | - cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc"
49 |
50 | # Builds on beta.
51 | - rust: beta
52 | env: RUST_BACKTRACE=1
53 | before_script:
54 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
55 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
56 | - cargo install-update -a
57 | - rustup target add wasm32-unknown-unknown
58 | script:
59 | - cargo generate --git . --name testing
60 | - mv Cargo.toml Cargo.toml.tmpl
61 | - cd testing
62 | - cargo check
63 | - cargo check --target wasm32-unknown-unknown
64 | - cargo check --no-default-features
65 | - cargo check --target wasm32-unknown-unknown --no-default-features
66 | - cargo check --no-default-features --features console_error_panic_hook
67 | - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
68 | # Note: no enabling the `wee_alloc` feature here because it requires
69 | # nightly for now.
70 |
--------------------------------------------------------------------------------
/brand/fujifilm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/brand/leica.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
28 |
--------------------------------------------------------------------------------
/brand/nikon corporation.svg:
--------------------------------------------------------------------------------
1 |
28 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import init, { get_exif } from "./pkg/gen_brand_photo_pictrue.js";
2 |
3 | const { useState, useRef } = React;
4 | const { Typography, Divider, Button, Form, Input, Select, Upload, message } =
5 | antd;
6 | const { PlusOutlined, VerticalAlignBottomOutlined } = icons;
7 |
8 | init();
9 |
10 | function App() {
11 | const [formValue, setFormValue] = useState({
12 | model: "XIAOMI 12S ULTRA",
13 | date: "2022.06.20 22:51:12",
14 | gps: `51°30'00"N 0°10'00"E`,
15 | device: "23mm f/1.0 1/320 ISO1495",
16 | brand: "leica",
17 | brand_url: "./brand/leica.svg",
18 | });
19 | const [imgUrl, setImgUrl] = useState("./simple.jpg");
20 | const brandList = [
21 | "Apple",
22 | "Canon",
23 | "Dji",
24 | "Fujifilm",
25 | "Huawei",
26 | "Leica",
27 | "Xiaomi",
28 | "Nikon Corporation",
29 | "Sony",
30 | "未收录",
31 | ];
32 | const formRef = useRef();
33 |
34 | const handleDownload = () => {
35 | const previewDom = document.getElementById("preview");
36 | const zoomRatio = 4;
37 |
38 | domtoimage
39 | .toJpeg(previewDom, {
40 | quality: 0.8,
41 | width: previewDom.clientWidth * zoomRatio,
42 | height: previewDom.clientHeight * zoomRatio,
43 | style: {
44 | transform: "scale(" + zoomRatio + ")",
45 | "transform-origin": "top left",
46 | },
47 | })
48 | .then((data) => {
49 | const binaryString = window.atob(data.split(",")[1]);
50 | const length = binaryString.length;
51 | const binaryArray = new Uint8Array(length);
52 |
53 | for (let i = 0; i < length; i++)
54 | binaryArray[i] = binaryString.charCodeAt(i);
55 |
56 | return new Blob([binaryArray], {
57 | type: "image/jpeg",
58 | });
59 | })
60 | .then((data) => window.URL.createObjectURL(data))
61 | .then((data) => {
62 | const link = document.createElement("a");
63 |
64 | link.download = Date.now() + ".jpg";
65 | link.href = data;
66 | document.body.appendChild(link);
67 | link.click();
68 | link.remove();
69 | });
70 | };
71 |
72 | const handleAdd = (file) => {
73 | const reader = new FileReader();
74 |
75 | reader.onloadend = (e) => {
76 | try {
77 | const result = get_exif(new Uint8Array(e.target.result));
78 | const resultObj = {};
79 |
80 | result.map((item) => (resultObj[item.tag] = item));
81 |
82 | let deviceObj = {
83 | focalLen:
84 | resultObj?.FocalLengthIn35mmFilm?.value_with_unit ??
85 | resultObj?.FocalLength?.value_with_unit,
86 | fNum: resultObj?.FNumber?.value_with_unit
87 | ?.split("/")
88 | ?.map((item, index) => (index == 1 ? (+item).toFixed(1) : item))
89 | ?.join("/"),
90 | exposureTime: resultObj?.ExposureTime?.value
91 | ?.split("/")
92 | ?.map((item) => ~~item)
93 | ?.join("/"),
94 | iso: resultObj?.PhotographicSensitivity?.value,
95 | };
96 | let formValue = {
97 | model: resultObj?.Model?.value,
98 | date: resultObj?.DateTimeOriginal?.value,
99 | gps: `${formatGPS(
100 | resultObj?.GPSLatitude?.value_with_unit
101 | )} ${formatGPS(resultObj?.GPSLongitude?.value_with_unit)}`,
102 | device: `${deviceObj.focalLen ? deviceObj.focalLen + " " : ""}${
103 | deviceObj.fNum ? deviceObj.fNum + " " : ""
104 | }${deviceObj.exposureTime ? deviceObj.exposureTime + " " : ""}${
105 | deviceObj.iso ? "ISO " + deviceObj.iso + " " : ""
106 | }`,
107 | brand: resultObj?.Make?.value.toLowerCase(),
108 | };
109 | formValue.brand_url = `./brand/${
110 | formValue.brand === "未收录"
111 | ? "unknow.svg"
112 | : (formValue?.brand?.toLowerCase() ?? "unknow") + ".svg"
113 | }`;
114 |
115 | formRef.current.setFieldsValue(formValue);
116 | setFormValue(formValue);
117 | setImgUrl(URL.createObjectURL(new Blob([file], { type: file.type })));
118 | } catch (error) {
119 | console.log({ error });
120 | message.error("无法识别照片特定数据,请换一张照片");
121 | }
122 | };
123 | reader.readAsArrayBuffer(file);
124 | };
125 |
126 | return (
127 | <>
128 | 生成徕卡水印照片
129 |
130 |
预览
131 |
132 |

133 |
134 |
135 |
{formValue.model}
136 |
{formValue.date}
137 |
138 |

139 |
140 |
141 |
142 |
143 |
{formValue.device}
144 |
{formValue.gps}
145 |
146 |
147 |
148 |
149 |
150 |
151 | 参数
152 |
169 |
170 |
171 |
172 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 | {/*
185 |
186 | */}
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 | {
199 | handleAdd(file);
200 | return false;
201 | }}
202 | fileList={[]}
203 | >
204 | } ghost>
205 | 新建照片
206 |
207 |
208 | }
211 | onClick={handleDownload}
212 | >
213 | 导出照片
214 |
215 |
216 | >
217 | );
218 | }
219 |
220 | ReactDOM.render(, document.getElementById("app"));
221 |
222 | function formatGPS(gps) {
223 | if (!gps) {
224 | return "";
225 | }
226 |
227 | const [degrees, minutes, seconds, dir] = gps
228 | .match(/(\d+\.?\d*)|([NSWE]$)/gim)
229 | .map((item) =>
230 | !Number.isNaN(Number(item)) ? (~~item < 10 ? "0" + ~~item : ~~item) : item
231 | );
232 |
233 | return `${degrees}°${minutes}'${seconds}"${dir}`;
234 | }
235 |
--------------------------------------------------------------------------------