Implement capture

This commit is contained in:
Ingvar Stepanyan
2021-07-14 11:40:17 +00:00
parent e5b22b02bb
commit 6613d89d7d
2 changed files with 63 additions and 44 deletions

94
api.cpp
View File

@@ -3,6 +3,7 @@
#include <emscripten/val.h> #include <emscripten/val.h>
#include <gphoto2/gphoto2.h> #include <gphoto2/gphoto2.h>
#include <cstring>
#include <iostream> #include <iostream>
using emscripten::val; using emscripten::val;
@@ -55,6 +56,8 @@ auto gpp_rethrow(Func func) {
const thread_local val Uint8Array = val::global("Uint8Array"); const thread_local val Uint8Array = val::global("Uint8Array");
const thread_local val Blob = val::global("Blob"); const thread_local val Blob = val::global("Blob");
const thread_local val File = val::global("File");
const thread_local val arrayOf = val::global("Array")["of"];
class Context { class Context {
public: public:
@@ -114,61 +117,44 @@ class Context {
val capturePreviewAsBlob() { val capturePreviewAsBlob() {
return gpp_rethrow([=]() { return gpp_rethrow([=]() {
class Output { auto &file = get_file();
public:
Output() : totalSize(0), chunks(val::array()) {}
void push(const uint8_t *data, unsigned long size) { gpp_try(gp_camera_capture_preview(camera.get(), &file, context.get()));
chunks.call<void>(
"push",
Uint8Array.new_(emscripten::typed_memory_view(size, data)));
totalSize += size;
}
uint64_t getTotalSize() { return totalSize; } auto params = blob_chunks_and_opts(file);
return Blob.new_(std::move(params.first), std::move(params.second));
});
}
val getChunks() { return chunks; } val captureImageAsFile() {
return gpp_rethrow([=]() {
CameraFilePath camera_file_path;
strcpy(camera_file_path.folder, "/");
strcpy(camera_file_path.name, "web-gphoto2");
private: gpp_try(gp_camera_capture(camera.get(), GP_CAPTURE_IMAGE,
uint64_t totalSize; &camera_file_path, context.get()));
val chunks;
};
static CameraFileHandler handler = { auto &file = get_file();
.size = gpp_try(gp_camera_file_get(camera.get(), camera_file_path.folder,
[](void *priv, uint64_t *size) { camera_file_path.name, GP_FILE_TYPE_NORMAL,
*size = static_cast<Output *>(priv)->getTotalSize(); &file, context.get()));
return GP_OK; gpp_try(gp_camera_file_delete(camera.get(), camera_file_path.folder,
}, camera_file_path.name, context.get()));
.read = [](void *priv, unsigned char *data,
uint64_t *len) { return GP_ERROR_NOT_SUPPORTED; },
.write =
[](void *priv, unsigned char *data, uint64_t *len) {
static_cast<Output *>(priv)->push(data, *len);
return GP_OK;
}};
Output output; gpp_try(gp_file_set_name(&file, "image."));
gpp_try(gp_file_adjust_name_for_mime_type(&file));
auto name = val(GPP_CALL(const char *, gp_file_get_name(&file, _)));
gpp_unique_ptr<CameraFile, gp_file_unref> file(GPP_CALL( auto params = blob_chunks_and_opts(file);
CameraFile *, gp_file_new_from_handler(_, &handler, &output))); return File.new_(std::move(params.first), std::move(name), std::move(params.second));
gpp_try(
gp_camera_capture_preview(camera.get(), file.get(), context.get()));
auto mime_type =
GPP_CALL(const char *, gp_file_get_mime_type(file.get(), _));
val blob_opts = val::object();
blob_opts.set("type", mime_type);
return Blob.new_(output.getChunks(), std::move(blob_opts));
}); });
} }
private: private:
gpp_unique_ptr<Camera, gp_camera_unref> camera; gpp_unique_ptr<Camera, gp_camera_unref> camera;
gpp_unique_ptr<GPContext, gp_context_unref> context; gpp_unique_ptr<GPContext, gp_context_unref> context;
gpp_unique_ptr<CameraFile, gp_file_unref> file;
static std::pair<val, val> walk_config(CameraWidget *widget) { static std::pair<val, val> walk_config(CameraWidget *widget) {
val result = val::object(); val result = val::object();
@@ -246,6 +232,27 @@ class Context {
return {name, result}; return {name, result};
} }
CameraFile &get_file() {
if (file.get() == nullptr) {
file.reset(GPP_CALL(CameraFile *, gp_file_new(_)));
}
return *file;
}
std::pair<val, val> blob_chunks_and_opts(CameraFile &file) {
auto mime_type = GPP_CALL(const char *, gp_file_get_mime_type(&file, _));
const char *data;
unsigned long size;
gpp_try(gp_file_get_data_and_size(&file, &data, &size));
val blob_opts = val::object();
blob_opts.set("type", mime_type);
return {arrayOf(Uint8Array.new_(emscripten::typed_memory_view(size, data))),
std::move(blob_opts)};
}
}; };
EMSCRIPTEN_BINDINGS(gphoto2_js_api) { EMSCRIPTEN_BINDINGS(gphoto2_js_api) {
@@ -253,5 +260,6 @@ EMSCRIPTEN_BINDINGS(gphoto2_js_api) {
.constructor<>() .constructor<>()
.function("configToJS", &Context::configToJS) .function("configToJS", &Context::configToJS)
.function("setConfigValue", &Context::setConfigValue) .function("setConfigValue", &Context::setConfigValue)
.function("capturePreviewAsBlob", &Context::capturePreviewAsBlob); .function("capturePreviewAsBlob", &Context::capturePreviewAsBlob)
.function("captureImageAsFile", &Context::captureImageAsFile);
} }

13
ui.html
View File

@@ -2,6 +2,7 @@
<link <link
rel="stylesheet" rel="stylesheet"
href="https://unpkg.com/purecss@2.0.6/build/pure-min.css" href="https://unpkg.com/purecss@2.0.6/build/pure-min.css"
crossorigin="anonymous"
/> />
<style> <style>
#config label { #config label {
@@ -110,7 +111,7 @@
choices.map(choice => choices.map(choice =>
h( h(
'option', 'option',
{ value: choice, disabled: attrs.readonly && value !== choice }, { key: choice, value: choice, disabled: attrs.readonly && value !== choice },
choice choice
) )
) )
@@ -182,6 +183,15 @@
}); });
} }
handleCapture = async () => {
let file = await scheduleOp(() => context.captureImageAsFile());
let a = document.createElement('a');
a.download = file.name;
a.href = URL.createObjectURL(file);
a.click();
URL.revokeObjectURL(a.href);
}
render(props, state) { render(props, state) {
return typeof config === 'string' return typeof config === 'string'
? config ? config
@@ -193,6 +203,7 @@
onfocusout: this.resumeUpdates, onfocusout: this.resumeUpdates,
onchange: this.handleChange onchange: this.handleChange
}, },
h('input', { type: 'button', value: 'Capture image', onclick: this.handleCapture }),
h(Config, state) h(Config, state)
); );
} }