radames commited on
Commit
44a1803
1 Parent(s): 3a668a7

add snap with exif data

Browse files

fix firefox distorted webcam

Files changed (2) hide show
  1. img2img/index.html +51 -8
  2. txt2img/index.html +42 -4
img2img/index.html CHANGED
@@ -7,9 +7,11 @@
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
  <script
9
  src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script>
 
10
  <script src="https://cdn.tailwindcss.com"></script>
11
  <script type="module">
12
-
 
13
  const seedEl = document.querySelector("#seed");
14
  const promptEl = document.querySelector("#prompt");
15
  const guidanceEl = document.querySelector("#guidance-scale");
@@ -20,6 +22,7 @@
20
  const imageEl = document.querySelector("#player");
21
  const queueSizeEl = document.querySelector("#queue_size");
22
  const errorEl = document.querySelector("#error");
 
23
 
24
  function LCMLive(webcamVideo, liveImage, seedEl, promptEl, guidanceEl, strengthEl) {
25
  let websocket;
@@ -65,9 +68,13 @@
65
  }
66
 
67
  async function videoTimeUpdateHandler() {
68
- const canvas = new OffscreenCanvas(webcamVideo.videoWidth, webcamVideo.videoHeight);
 
 
 
69
  const ctx = canvas.getContext("2d");
70
- ctx.drawImage(webcamVideo, 0, 0, canvas.width, canvas.height);
 
71
  const blob = await canvas.convertToBlob({ type: "image/jpeg", quality: 1 });
72
  websocket.send(blob);
73
  websocket.send(JSON.stringify({
@@ -82,7 +89,7 @@
82
  liveImage.src = `/stream/${userId}`;
83
  const constraints = {
84
  audio: false,
85
- video: { width: 512, height: 512 },
86
  };
87
  navigator.mediaDevices
88
  .getUserMedia(constraints)
@@ -128,12 +135,43 @@
128
  errorEl.hidden = true;
129
  }, 2000);
130
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
 
133
  const lcmLive = LCMLive(videoEl, imageEl, seedEl, promptEl, guidanceEl, strengthEl);
134
  startBtn.addEventListener("click", async () => {
135
  try {
136
  startBtn.disabled = true;
 
137
  const res = await lcmLive.start();
138
  startBtn.disabled = false;
139
  if (res.status === "timeout")
@@ -150,6 +188,7 @@
150
  window.addEventListener("beforeunload", () => {
151
  lcmLive.stop();
152
  });
 
153
  setInterval(() =>
154
  fetch("/queue_size")
155
  .then((res) => res.json())
@@ -227,21 +266,25 @@
227
  </div>
228
  </details>
229
  </div>
230
- <div>
231
  <button id="start"
232
- class="bg-gray-700 hover:bg-gray-800 text-white font-normal py-2 w-16 rounded disabled:bg-gray-300 disabled:cursor-not-allowed">
233
  Start
234
  </button>
235
  <button id="stop"
236
- class="bg-gray-700 hover:bg-gray-800 text-white font-normal py-2 w-16 rounded disabled:bg-gray-300 disabled:cursor-not-allowed">
237
  Stop
238
  </button>
 
 
 
 
239
  </div>
240
  <div class="relative rounded-lg border border-slate-300 overflow-hidden">
241
  <img id="player" class="w-full aspect-square rounded-lg "
242
  src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">
243
  <div class="absolute top-0 left-0 w-1/4 aspect-square">
244
- <video id="webcam" class="w-full aspect-square relative z-10" playsinline autoplay muted loop></video>
245
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 448" width="100"
246
  class="w-full p-4 absolute top-0 opacity-20 z-0">
247
  <path fill="currentColor"
 
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
  <script
9
  src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/piexif.min.js"></script>
11
  <script src="https://cdn.tailwindcss.com"></script>
12
  <script type="module">
13
+ const WIDTH = 768;
14
+ const HEIGHT = 768;
15
  const seedEl = document.querySelector("#seed");
16
  const promptEl = document.querySelector("#prompt");
17
  const guidanceEl = document.querySelector("#guidance-scale");
 
22
  const imageEl = document.querySelector("#player");
23
  const queueSizeEl = document.querySelector("#queue_size");
24
  const errorEl = document.querySelector("#error");
25
+ const snapBtn = document.querySelector("#snap");
26
 
27
  function LCMLive(webcamVideo, liveImage, seedEl, promptEl, guidanceEl, strengthEl) {
28
  let websocket;
 
68
  }
69
 
70
  async function videoTimeUpdateHandler() {
71
+ const canvas = new OffscreenCanvas(WIDTH, HEIGHT);
72
+ const videoW = webcamVideo.videoWidth;
73
+ const videoH = webcamVideo.videoHeight;
74
+
75
  const ctx = canvas.getContext("2d");
76
+ // grap square from center
77
+ ctx.drawImage(webcamVideo, videoW / 2 - WIDTH / 2, videoH / 2 - HEIGHT / 2, WIDTH, HEIGHT, 0, 0, canvas.width, canvas.height);
78
  const blob = await canvas.convertToBlob({ type: "image/jpeg", quality: 1 });
79
  websocket.send(blob);
80
  websocket.send(JSON.stringify({
 
89
  liveImage.src = `/stream/${userId}`;
90
  const constraints = {
91
  audio: false,
92
+ video: { width: WIDTH, height: HEIGHT },
93
  };
94
  navigator.mediaDevices
95
  .getUserMedia(constraints)
 
135
  errorEl.hidden = true;
136
  }, 2000);
137
  }
138
+ function snapImage() {
139
+ try {
140
+ const zeroth = {};
141
+ const exif = {};
142
+ const gps = {};
143
+ zeroth[piexif.ImageIFD.Make] = "LCM Image-to-Image";
144
+ zeroth[piexif.ImageIFD.ImageDescription] = `prompt: ${promptEl.value} | seed: ${seedEl.value} | guidance_scale: ${guidanceEl.value} | strength: ${strengthEl.value}`;
145
+ zeroth[piexif.ImageIFD.Software] = "https://github.com/radames/Real-Time-Latent-Consistency-Model";
146
+
147
+ exif[piexif.ExifIFD.DateTimeOriginal] = new Date().toISOString();
148
+
149
+ const exifObj = { "0th": zeroth, "Exif": exif, "GPS": gps };
150
+ const exifBytes = piexif.dump(exifObj);
151
+
152
+ const canvas = document.createElement("canvas");
153
+ canvas.width = imageEl.naturalWidth;
154
+ canvas.height = imageEl.naturalHeight;
155
+ const ctx = canvas.getContext("2d");
156
+ ctx.drawImage(imageEl, 0, 0);
157
+ const dataURL = canvas.toDataURL("image/jpeg");
158
+ const withExif = piexif.insert(exifBytes, dataURL);
159
+
160
+ const a = document.createElement("a");
161
+ a.href = withExif;
162
+ a.download = `lcm_txt_2_img${Date.now()}.png`;
163
+ a.click();
164
+ } catch (err) {
165
+ console.log(err);
166
+ }
167
+ }
168
 
169
 
170
  const lcmLive = LCMLive(videoEl, imageEl, seedEl, promptEl, guidanceEl, strengthEl);
171
  startBtn.addEventListener("click", async () => {
172
  try {
173
  startBtn.disabled = true;
174
+ snapBtn.disabled = false;
175
  const res = await lcmLive.start();
176
  startBtn.disabled = false;
177
  if (res.status === "timeout")
 
188
  window.addEventListener("beforeunload", () => {
189
  lcmLive.stop();
190
  });
191
+ snapBtn.addEventListener("click", snapImage);
192
  setInterval(() =>
193
  fetch("/queue_size")
194
  .then((res) => res.json())
 
266
  </div>
267
  </details>
268
  </div>
269
+ <div class="flex gap-3">
270
  <button id="start"
271
+ class="bg-gray-700 hover:bg-gray-800 text-white font-normal p-2 rounded disabled:bg-gray-300 disabled:cursor-not-allowed">
272
  Start
273
  </button>
274
  <button id="stop"
275
+ class="bg-gray-700 hover:bg-gray-800 text-white font-normal p-2 rounded disabled:bg-gray-300 disabled:cursor-not-allowed">
276
  Stop
277
  </button>
278
+ <button id="snap" disabled
279
+ class="bg-gray-700 hover:bg-gray-800 text-white font-normal p-2 rounded disabled:bg-gray-300 disabled:cursor-not-allowed ml-auto">
280
+ Snapshot
281
+ </button>
282
  </div>
283
  <div class="relative rounded-lg border border-slate-300 overflow-hidden">
284
  <img id="player" class="w-full aspect-square rounded-lg "
285
  src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">
286
  <div class="absolute top-0 left-0 w-1/4 aspect-square">
287
+ <video id="webcam" class="w-full aspect-square relative z-10 object-cover" playsinline autoplay muted loop></video>
288
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 448" width="100"
289
  class="w-full p-4 absolute top-0 opacity-20 z-0">
290
  <path fill="currentColor"
txt2img/index.html CHANGED
@@ -7,6 +7,7 @@
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
  <script
9
  src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script>
 
10
  <script src="https://cdn.tailwindcss.com"></script>
11
  <script type="module">
12
 
@@ -19,6 +20,7 @@
19
  const imageEl = document.querySelector("#player");
20
  const queueSizeEl = document.querySelector("#queue_size");
21
  const errorEl = document.querySelector("#error");
 
22
 
23
  function LCMLive(promptEl, liveImage, seedEl, guidanceEl) {
24
  let websocket;
@@ -108,12 +110,43 @@
108
  errorEl.hidden = true;
109
  }, 2000);
110
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
 
113
  const lcmLive = LCMLive(promptEl, imageEl, seedEl, guidanceEl);
114
  startBtn.addEventListener("click", async () => {
115
  try {
116
  startBtn.disabled = true;
 
117
  const res = await lcmLive.start();
118
  startBtn.disabled = false;
119
  if (res.status === "timeout")
@@ -130,6 +163,7 @@
130
  window.addEventListener("beforeunload", () => {
131
  lcmLive.stop();
132
  });
 
133
  setInterval(() =>
134
  fetch("/queue_size")
135
  .then((res) => res.json())
@@ -202,18 +236,22 @@
202
  </div>
203
  </details>
204
  </div>
205
- <div>
206
  <button id="start"
207
- class="bg-gray-700 hover:bg-gray-800 text-white font-normal py-2 w-16 rounded disabled:bg-gray-300 disabled:cursor-not-allowed">
208
  Start
209
  </button>
210
  <button id="stop"
211
- class="bg-gray-700 hover:bg-gray-800 text-white font-normal py-2 w-16 rounded disabled:bg-gray-300 disabled:cursor-not-allowed">
212
  Stop
213
  </button>
 
 
 
 
214
  </div>
215
  <div class="relative rounded-lg border border-slate-300 overflow-hidden">
216
- <img id="player" class="w-full aspect-square rounded-lg "
217
  src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">
218
  </div>
219
  </main>
 
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
  <script
9
  src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/piexif.min.js"></script>
11
  <script src="https://cdn.tailwindcss.com"></script>
12
  <script type="module">
13
 
 
20
  const imageEl = document.querySelector("#player");
21
  const queueSizeEl = document.querySelector("#queue_size");
22
  const errorEl = document.querySelector("#error");
23
+ const snapBtn = document.querySelector("#snap");
24
 
25
  function LCMLive(promptEl, liveImage, seedEl, guidanceEl) {
26
  let websocket;
 
110
  errorEl.hidden = true;
111
  }, 2000);
112
  }
113
+ function snapImage() {
114
+ try {
115
+ const zeroth = {};
116
+ const exif = {};
117
+ const gps = {};
118
+ zeroth[piexif.ImageIFD.Make] = "LCM Text-to-Image";
119
+ zeroth[piexif.ImageIFD.ImageDescription] = `prompt: ${promptEl.value} | seed: ${seedEl.value} | guidance: ${guidanceEl.value}`;
120
+ zeroth[piexif.ImageIFD.Software] = "https://github.com/radames/Real-Time-Latent-Consistency-Model";
121
+
122
+ exif[piexif.ExifIFD.DateTimeOriginal] = new Date().toISOString();
123
+
124
+ const exifObj = { "0th": zeroth, "Exif": exif, "GPS": gps };
125
+ const exifBytes = piexif.dump(exifObj);
126
+
127
+ const canvas = document.createElement("canvas");
128
+ canvas.width = imageEl.naturalWidth;
129
+ canvas.height = imageEl.naturalHeight;
130
+ const ctx = canvas.getContext("2d");
131
+ ctx.drawImage(imageEl, 0, 0);
132
+ const dataURL = canvas.toDataURL("image/jpeg");
133
+ const withExif = piexif.insert(exifBytes, dataURL);
134
+
135
+ const a = document.createElement("a");
136
+ a.href = withExif;
137
+ a.download = `lcm_txt_2_img${Date.now()}.png`;
138
+ a.click();
139
+ } catch (err) {
140
+ console.log(err);
141
+ }
142
+ }
143
 
144
 
145
  const lcmLive = LCMLive(promptEl, imageEl, seedEl, guidanceEl);
146
  startBtn.addEventListener("click", async () => {
147
  try {
148
  startBtn.disabled = true;
149
+ snapBtn.disabled = false;
150
  const res = await lcmLive.start();
151
  startBtn.disabled = false;
152
  if (res.status === "timeout")
 
163
  window.addEventListener("beforeunload", () => {
164
  lcmLive.stop();
165
  });
166
+ snapBtn.addEventListener("click", snapImage);
167
  setInterval(() =>
168
  fetch("/queue_size")
169
  .then((res) => res.json())
 
236
  </div>
237
  </details>
238
  </div>
239
+ <div class="flex gap-3">
240
  <button id="start"
241
+ class="bg-gray-700 hover:bg-gray-800 text-white font-normal p-2 rounded disabled:bg-gray-300 disabled:cursor-not-allowed">
242
  Start
243
  </button>
244
  <button id="stop"
245
+ class="bg-gray-700 hover:bg-gray-800 text-white font-normal p-2 rounded disabled:bg-gray-300 disabled:cursor-not-allowed">
246
  Stop
247
  </button>
248
+ <button id="snap" disabled
249
+ class="bg-gray-700 hover:bg-gray-800 text-white font-normal p-2 rounded disabled:bg-gray-300 disabled:cursor-not-allowed ml-auto">
250
+ Snapshot
251
+ </button>
252
  </div>
253
  <div class="relative rounded-lg border border-slate-300 overflow-hidden">
254
+ <img id="player" class="w-full aspect-square rounded-lg"
255
  src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">
256
  </div>
257
  </main>