SLProject  4.2.000
A platform independent 3D computer graphics framework for desktop OS, Android, iOS and online in web browsers
WebCamera.cpp
Go to the documentation of this file.
1 /**
2  * \file WebCamera.cpp
3  * \brief Interface to access the camera through the browser.
4  * \date October 2022
5  * \authors Marino von Wattenwyl
6  * \copyright http://opensource.org/licenses/GPL-3.0
7  * \remarks Please use clangformat to format the code. See more code style on
8  * https://github.com/cpvrlab/SLProject4/wiki/SLProject-Coding-Style
9  */
10 
11 #include <WebCamera.h>
12 #include <emscripten.h>
13 
14 //-----------------------------------------------------------------------------
15 //! Acquires a video stream
16 /*!
17  * Requests a video stream from the browser and assigns it to the HTML video
18  * object as soon as it is ready. It must be called before accessing any of
19  * the other member functions. The function does not block, so the stream may
20  * not be ready yet after it returns.
21  * \param facing Preferred facing mode on mobile
22  */
24 {
25  // clang-format off
26  EM_ASM({
27  console.log("[WebCamera] Requesting stream...");
28 
29  let facingMode;
30  if ($0 == 0) facingMode = "user";
31  else if ($0 == 1) facingMode = "environment";
32 
33  navigator.mediaDevices.getUserMedia({ "video": { "facingMode": facingMode } })
34  .then(stream => {
35  console.log("[WebCamera] Stream acquired");
36 
37  let video = document.querySelector("#capture-video");
38  video.srcObject = stream;
39  })
40  .catch(error => {
41  console.log("[WebCamera] Failed to acquire stream");
42  console.log(error);
43  });
44  }, facing);
45  // clang-format on
46 
47  _isOpened = true;
48 }
49 //-----------------------------------------------------------------------------
50 //! Returns whether the video stream has been acquired
51 /*!
52  * The video stream is not immediately available when calling WebCamera::open.
53  * This function can be used to determine if the user has allowed camera
54  * access and if it is ready for reading.
55  * \return True if the stream has been acquired
56  */
58 {
59  return _image.cols != 0 && _image.rows != 0;
60 }
61 //-----------------------------------------------------------------------------
62 //! Reads the current frame
63 /*!
64  * If the video stream is ready, copies the current frame from the video HTML
65  * element into a CVMat in BGR format. Returns an empty image otherwise.
66  * \return The CVMat containing the image data in BGR format
67  */
69 {
70  CVSize2i size = getSize();
71 
72  // If the width or the height is zero, the video is not ready
73  if (size.width == 0 || size.height == 0)
74  return CVMat(0, 0, CV_8UC3);
75 
76  // Recreate the image if the size has changed
77  if (size.width != _image.cols || size.height != _image.rows)
78  {
79  _image = CVMat(size.height,
80  size.width,
81  CV_8UC4);
82  _waitingForResize = false;
83  }
84 
85  // clang-format off
86  EM_ASM({
87  let video = document.querySelector("#capture-video");
88  let canvas = document.querySelector("#capture-canvas");
89 
90  // the last parameter canvas_will_read_frequently is set to true
91  // https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-will-read-frequently
92  let ctx = canvas.getContext("2d", true, false, "srgb", true);
93 
94  let width = video.videoWidth;
95  let height = video.videoHeight;
96 
97  if (width == 0 || height == 0)
98  return;
99 
100  canvas.width = width;
101  canvas.height = height;
102  ctx.drawImage(video, 0, 0, width, height);
103  let imageData = ctx.getImageData(0, 0, width, height);
104 
105  writeArrayToMemory(imageData.data, $0);
106  }, _image.data);
107  // clang-format on
108 
109  if (_imageBGR.size != _image.size)
110  _imageBGR = CVMat(_image.rows, _image.cols, CV_8UC3);
111 
112  cv::cvtColor(_image, _imageBGR, cv::COLOR_RGBA2BGR);
113 
114  return _imageBGR;
115 }
116 //-----------------------------------------------------------------------------
117 //! Gets the size of the video input
118 /*!
119  * The size is determined by getting the width and height of the HTML video
120  * element.
121  * \return The size of the video input
122  */
124 {
125  int32_t width;
126  int32_t height;
127 
128  // clang-format off
129  EM_ASM({
130  let video = document.querySelector("#capture-video");
131  let width = video.videoWidth;
132  let height = video.videoHeight;
133 
134  setValue($0, video.videoWidth, "i32");
135  setValue($1, video.videoHeight, "i32");
136  }, &width, &height);
137  // clang-format on
138 
139  return CVSize2i(width, height);
140 }
141 //-----------------------------------------------------------------------------
142 //! Requests a video size from the browser
143 /*!
144  * The function tries to resize the video size. It is not guaranteed that this
145  * will have any effect or that the size after resizing will be equal to the
146  * size provided because the browser may not support it.
147  * \param size Preferred size of the video
148  */
150 {
151  // Return if the stream is still loading
152  if (!isReady())
153  return;
154 
155  // Return if we are already waiting for the resize
156  if (_waitingForResize)
157  return;
158 
159  // Return if the new size is equal to the old size
160  if (size.width == _image.cols && size.height == _image.rows)
161  return;
162 
163  _waitingForResize = true;
164 
165  // clang-format off
166  EM_ASM({
167  let video = document.querySelector("#capture-video");
168  let stream = video.srcObject;
169 
170  if (stream === null)
171  return;
172 
173  // We can't use object literals because that breaks EM_ASM for some reason
174  let constraints = {};
175  constraints["width"] = $0;
176  constraints["height"] = $1;
177 
178  stream.getVideoTracks().forEach(track => {
179  track.applyConstraints(constraints);
180  });
181 
182  console.log("[WebCamera] Applied resolution " + $0 + "x" + $1);
183  }, size.width, size.height);
184  // clang-format on
185 }
186 //-----------------------------------------------------------------------------
187 //! Closes the video stream
188 /*!
189  * After calling this function, the camera must be reopened before accessing
190  * any of the other member functions.
191  */
193 {
194  // clang-format off
195  EM_ASM({
196  let video = document.querySelector("#capture-video");
197  let stream = video.srcObject;
198 
199  if (stream === null) {
200  console.log("[WebCamera] Stream is already closed");
201  }
202 
203  stream.getVideoTracks().forEach(track => {
204  if (track.readyState == "live") {
205  track.stop();
206  stream.removeTrack(track);
207  }
208  });
209 
210  console.log("[WebCamera] Stream closed");
211  });
212  // clang-format on
213 }
214 //-----------------------------------------------------------------------------
cv::Size2i CVSize2i
Definition: CVTypedefs.h:56
cv::Mat CVMat
Definition: CVTypedefs.h:38
Interface to access the camera through the browser.
WebCameraFacing
Facing modes for the camera.
Definition: WebCamera.h:19
CVMat _imageBGR
Definition: WebCamera.h:41
void close()
Closes the video stream.
Definition: WebCamera.cpp:192
bool isReady()
Returns whether the video stream has been acquired.
Definition: WebCamera.cpp:57
void open(WebCameraFacing facing)
Acquires a video stream.
Definition: WebCamera.cpp:23
CVMat read()
Reads the current frame.
Definition: WebCamera.cpp:68
bool _waitingForResize
Definition: WebCamera.h:42
CVMat _image
Definition: WebCamera.h:40
void setSize(CVSize2i size)
Requests a video size from the browser.
Definition: WebCamera.cpp:149
bool _isOpened
Definition: WebCamera.h:39
CVSize2i getSize()
Gets the size of the video input.
Definition: WebCamera.cpp:123