forked from BabylonJS/BabylonNative
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathImageCapture.cpp
More file actions
210 lines (180 loc) · 8.59 KB
/
ImageCapture.cpp
File metadata and controls
210 lines (180 loc) · 8.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#include "ImageCapture.h"
#include "CameraDevice.h"
#include "MediaStream.h"
#include <Babylon/JsRuntime.h>
#include <Babylon/JsRuntimeScheduler.h>
namespace Babylon::Plugins::Internal
{
namespace
{
std::string RedEyeReductionToString(RedEyeReduction redEyeReduction)
{
return
redEyeReduction == RedEyeReduction::Always ? "always" :
redEyeReduction == RedEyeReduction::Controllable ? "controllable" : "never";
}
std::string FillLightModeToString(FillLightMode fillLightMode)
{
return
fillLightMode == FillLightMode::Auto ? "auto" :
fillLightMode == FillLightMode::Flash ? "flash" : "off";
}
Napi::Array FillLightModesToNapi(const Napi::Env& env, const std::set<FillLightMode>& fillLightModes)
{
auto arrayJS = Napi::Array::New(env, fillLightModes.size());
uint32_t index = 0;
for (FillLightMode fillLightMode : fillLightModes)
{
arrayJS.Set(index, FillLightModeToString(fillLightMode));
index++;
}
return arrayJS;
}
Napi::Object PhotoCapabilitiesToNapi(const Napi::Env& env, const PhotoCapabilities& photoCapabilities)
{
auto imageWidthJS = Napi::Object::New(env);
imageWidthJS.Set("min", photoCapabilities.MinWidth);
imageWidthJS.Set("max", photoCapabilities.MaxWidth);
imageWidthJS.Set("step", 1);
auto imageHeightJS = Napi::Object::New(env);
imageHeightJS.Set("min", photoCapabilities.MinHeight);
imageHeightJS.Set("max", photoCapabilities.MaxHeight);
imageHeightJS.Set("step", 1);
auto photoCapabilitiesJS = Napi::Object::New(env);
photoCapabilitiesJS.Set("redEyeReduction", RedEyeReductionToString(photoCapabilities.RedEyeReduction));
photoCapabilitiesJS.Set("fillLightMode", FillLightModesToNapi(env, photoCapabilities.FillLightModes));
photoCapabilitiesJS.Set("imageWidth", imageWidthJS);
photoCapabilitiesJS.Set("imageHeight", imageHeightJS);
return photoCapabilitiesJS;
}
Napi::Object PhotoSettingsToNapi(const Napi::Env& env, const PhotoSettings& photoSettings)
{
auto photoSettingsJS = Napi::Object::New(env);
photoSettingsJS.Set("redEyeReduction", photoSettings.RedEyeReduction);
photoSettingsJS.Set("fillLightMode", FillLightModeToString(photoSettings.FillLightMode));
photoSettingsJS.Set("imageWidth", photoSettings.Width);
photoSettingsJS.Set("imageHeight", photoSettings.Height);
return photoSettingsJS;
}
};
class ImageCapture : public Napi::ObjectWrap<ImageCapture>
{
static constexpr auto JS_CLASS_NAME = "ImageCapture";
public:
static void Initialize(Napi::Env env)
{
Napi::HandleScope scope{env};
Napi::Function func = DefineClass(
env,
JS_CLASS_NAME,
{
InstanceMethod("getPhotoCapabilities", &ImageCapture::GetPhotoCapabilities),
InstanceMethod("getPhotoSettings", &ImageCapture::GetPhotoSettings),
InstanceMethod("takePhoto", &ImageCapture::TakePhoto),
InstanceMethod("grabFrame", &ImageCapture::GrabFrame),
});
env.Global().Set(JS_CLASS_NAME, func);
}
ImageCapture(const Napi::CallbackInfo& info)
: Napi::ObjectWrap<ImageCapture>{info}
, m_runtimeScheduler{JsRuntime::GetFromJavaScript(info.Env())}
, m_cameraDevice{MediaStream::Unwrap(info[0].As<Napi::Object>())->CameraDevice()}
, m_photoSettings{m_cameraDevice->DefaultPhotoSettings()}
{
}
private:
Napi::Value GetPhotoCapabilities(const Napi::CallbackInfo& info)
{
return PhotoCapabilitiesToNapi(info.Env(), m_cameraDevice->PhotoCapabilities());
}
Napi::Value GetPhotoSettings(const Napi::CallbackInfo& info)
{
return PhotoSettingsToNapi(info.Env(), m_photoSettings);
}
Napi::Value TakePhoto(const Napi::CallbackInfo& info)
{
// If the optional PhotoSettings are passed in, update the cached settings.
if (info.Length() > 0)
{
const auto photoSettingsJS = info[0].As<Napi::Object>();
if (photoSettingsJS.Has("redEyeReduction"))
{
m_photoSettings.RedEyeReduction = photoSettingsJS.Get("redEyeReduction").ToBoolean();
}
if (photoSettingsJS.Has("imageWidth"))
{
m_photoSettings.Width = photoSettingsJS.Get("imageWidth").ToNumber().Int32Value();
}
if (photoSettingsJS.Has("imageHeight"))
{
m_photoSettings.Height = photoSettingsJS.Get("imageHeight").ToNumber().Int32Value();
}
if (photoSettingsJS.Has("fillLightMode"))
{
const auto fillLightMode = photoSettingsJS.Get("fillLightMode").ToString().Utf8Value();
if (fillLightMode == "auto")
{
m_photoSettings.FillLightMode = FillLightMode::Auto;
}
else if (fillLightMode == "flash")
{
m_photoSettings.FillLightMode = FillLightMode::Flash;
}
else
{
m_photoSettings.FillLightMode = FillLightMode::Off;
}
}
}
//unreachable code is detected below in x86 Release build. Not obvious fix so Warning as error doesn't break the build. Disabling warning for now.
DISABLE_UNREACHABLE_CODE_WARNINGS
auto env = info.Env();
auto deferred = Napi::Promise::Deferred::New(env);
// Take a photo and synchronously (via inline_scheduler) make a copy of the data (since we know nothing about its lifetime) before
// transitioning back to the JavaScript thread to complete the promise.
m_cameraDevice->TakePhotoAsync(m_photoSettings).then(arcana::inline_scheduler, arcana::cancellation::none(), [](gsl::span<const uint8_t> result) {
std::vector<uint8_t> bytes{};
bytes.resize(result.size());
std::memcpy(bytes.data(), result.data(), result.size());
return bytes;
}).then(m_runtimeScheduler.Get(), arcana::cancellation::none(), [env, deferred](const arcana::expected<std::vector<uint8_t>, std::exception_ptr>& result) {
if (result.has_error())
{
deferred.Reject(Napi::Error::New(env, result.error()).Value());
return;
}
// Create a JS ArrayBuffer and copy the image into the buffer.
auto arrayBuffer = Napi::ArrayBuffer::New(env, result.value().size());
std::memcpy(arrayBuffer.Data(), result.value().data(), result.value().size());
// Ideally we'd have a real Blob polyfill, but we can also create a partial polyfill object inline here (enough to access the underlying ArrayBuffer).
auto arrayBufferDeferred = Napi::Promise::Deferred::New(env);
arrayBufferDeferred.Resolve(arrayBuffer);
auto arrayBufferFunction = Napi::Function::New(env, [arrayBufferDeferred](const Napi::CallbackInfo&) -> Napi::Value {
return arrayBufferDeferred.Promise();
}, "arrayBuffer");
auto blob = Napi::Object::New(env);
blob.Set("arrayBuffer", arrayBufferFunction);
blob.Set("size", arrayBuffer.ByteLength());
blob.Set("type", "image/jpeg");
deferred.Resolve(blob);
});
ENABLE_UNREACHABLE_CODE_WARNINGS
return deferred.Promise();
}
Napi::Value GrabFrame(const Napi::CallbackInfo& info)
{
// Maybe implement this in the future if needed.
throw Napi::Error::New(info.Env(), "Not implemented.");
}
JsRuntimeScheduler m_runtimeScheduler;
const std::shared_ptr<Plugins::CameraDevice> m_cameraDevice{};
PhotoSettings m_photoSettings{};
};
}
namespace Babylon::Plugins::ImageCapture
{
void Initialize(Napi::Env env)
{
Internal::ImageCapture::Initialize(env);
}
}