Skip to content

Commit

Permalink
Merge pull request #293 from tomchm/feature-auto-take
Browse files Browse the repository at this point in the history
Add new Auto-Take feature.
  • Loading branch information
charlielee committed Apr 8, 2021
2 parents 6c62871 + b4517e7 commit 45b2a80
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 1 deletion.
Binary file modified docs/img/ba-0-11-0-screenshot-1.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/sidebar.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 9 additions & 1 deletion docs/interface/main-window.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,12 @@ This displays a grid over the preview area in **capture mode**. The grid is comm

#### Aspect ratio mask

This displays a mask over the preview area with the selected aspect ratio.
This displays a mask over the preview area with the selected aspect ratio.

### Auto-Capture

This feature is used to synchronize taking images with a DSLR whose video output is connected via a capture card. The connected DSLR must have "Image Review" or equivalent setting enabled. If Auto-Capture is enabled, it will wait for a black frame (indicating the DSLR is taking a photo), and then capture the next visible frame, which corresponds to the actual photo taken by the DSLR. The quality of this image is often significantly better than the live preview, and corresponds exactly to the full-quality image saved on the DSLR's memory card.

#### Brightness Threshold

This value determines the brightness threshold (0 - 255) between a black frame (DSLR is taking a photo) and visible frame. Note that sufficiently dark scenes are indistinguishable from the black frames a DSLR outputs while taking a photo, so pictures may be erroneously captured as the brightness level floats around the threshold value.
12 changes: 12 additions & 0 deletions src/animator.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ <h2>Select a camera source to begin!</h2>
<video id="preview" class="visible-capture" autoplay>Video stream not available.</video>
<!--The actual playback window -->
<canvas id="playback" class="visible-playback"></canvas>
<!-- Hidden preview canvas used for reading pixels of live image -->
<canvas id="hidden-preview" class="hidden"></canvas>
</div>

<!--Playback controls-->
Expand Down Expand Up @@ -169,6 +171,16 @@ <h2>
</div>
<ul id="overlay-list"></ul>
</div>

<!--AutoCapture-->
<div class="sidebar-item" id="auto-capture-header">
<div class="sidebar-header">
<h2>
<i class="fa fa-eye fa-fw"></i>Auto-Capture</h2>
</div>
<ul id="auto-capture-list">
</ul>
</div>
</aside>
</main>

Expand Down
146 changes: 146 additions & 0 deletions src/js/animator/core/AutoCapture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
(function() {
"use strict";
// Imports
const ToggleButton = require("../ui/ToggleButton");
const Features = require("./Features");

// HTML elements
const preview = document.querySelector("#preview");
const hiddenPreview = document.querySelector("#hidden-preview");
const autoCaptureList = document.querySelector("#auto-capture-list");

/**
* Class for automatically taking picture after detecting the connected DSLR video stream has taken a photo.
* This is useful when DSLRs have their video output connected to an HDMI capture card, such as Cam Link.
* While DSLRs are capturing a photo, their video stream becomes black. After the photo is taken, it is
* previewed on the video output for around 1-2 seconds before switching back to the live view. This transition
* can be detected, so the high-quality image preview can be captured off the video stream.
*/
class AutoCapture {

/**
* Initializes the AutoCapture sidebar menu, and class variables.
*/
static initialise() {
AutoCapture.autoCaptureDaemon = null; // Timer Interval for detecting brightness change
AutoCapture.enabled = true; // Is AutoCapture is enabled?
AutoCapture.isDark = false; // Is the current video frame dark (brightness below threshold)?
AutoCapture.brightnessThreshold = 1.2; // Brightness Threshold

// Add a list item to settings dialog
let autoCaptureItem = document.createElement("li");
autoCaptureList.appendChild(autoCaptureItem);

// Item title
let itemTitle = document.createElement("div");
let itemTitleText = document.createElement("div");

let itemToggleBtn = document.createElement("div"); // Item toggle button

let itemSettingsContainer = document.createElement("div");
let brightnessThresholdInput = document.createElement("input"); // Brightness Threshold input
let brightnessThresholdText = document.createElement("div");

// Set title
autoCaptureItem.appendChild(itemTitle);
itemTitle.classList.add("flex");
itemTitle.appendChild(itemTitleText);
itemTitleText.innerText = "Auto-Capture";

// Add toggle button
itemTitle.appendChild(itemToggleBtn);
itemToggleBtn.setAttribute("style", "text-align: right");
itemToggleBtn.classList.add("auto-capture-toggle-btn");
new ToggleButton(itemToggleBtn, function() {
if (AutoCapture.enabled) {
clearInterval(AutoCapture.autoCaptureDaemon);
AutoCapture.autoCaptureDaemon = null;
AutoCapture.enabled = false;
} else {
// Start daemon to check webcam brightness 30 times per second. TODO: Make the interval time configurable
AutoCapture.autoCaptureDaemon = setInterval(AutoCapture.checkBrightnessThreshold, 1000 / 30); // 30 FPS
AutoCapture.enabled = true;
}
itemSettingsContainer.classList.toggle("hidden");
});

// Create item settings container to hold configurable inputs
autoCaptureItem.appendChild(itemSettingsContainer);
itemSettingsContainer.classList.add("flex");
itemSettingsContainer.setAttribute("style", "align-items: center");

// Create brightness threshold text label and input
itemSettingsContainer.appendChild(brightnessThresholdText);
brightnessThresholdText.classList.add("flex");
brightnessThresholdText.setAttribute("style", "flex-basis: 80%");
brightnessThresholdText.innerText = "Brightness Threshold:";
itemSettingsContainer.appendChild(brightnessThresholdInput);
brightnessThresholdInput.style.margin = "0 0 0 0.5em";
brightnessThresholdInput.setAttribute("type", "number");
brightnessThresholdInput.setAttribute("min", "0");
brightnessThresholdInput.setAttribute("max", "255");
brightnessThresholdInput.setAttribute("step", "0.1");
brightnessThresholdInput.setAttribute("value", 1.2);

// Listen to the input being updated
brightnessThresholdInput.addEventListener("input", function(e) {
let val = e.target.value;
// Limit value to range [0-255]
if (val > 255) {
val = 255;
} else if (val < 0) {
val = 0;
}
e.target.value = val; // Set input back to corrected value.
AutoCapture.brightnessThreshold = val;
});
}

static checkBrightnessThreshold() {
let w = preview.videoWidth;
let h = preview.videoHeight;

if (w === 0 || h === 0) {
return; // Return if video stream not setup
}

hiddenPreview.width = w;
hiddenPreview.height = h;
let context = hiddenPreview.getContext('2d');

context.drawImage(preview, 0, 0, w, h);

let imgData = context.getImageData(0, 0, w, h)
let data = imgData.data;
let r, g, b, a, avg;
let colorSum = 0;
let totalPixels = 0;

for (let x = 0, len = data.length; x < len; x += 4) {
r = data[x];
g = data[x+1];
b = data[x+2];
a = data[x+3];

if (a > 0) {
avg = (r + b + g) / 3;
colorSum += avg;
totalPixels += 1;
}
}

let brightness = colorSum / totalPixels;

if (brightness > AutoCapture.brightnessThreshold) {
if (AutoCapture.isDark) { // Take picture if live image transitions from dark to light.
Features.takePicture();
}
AutoCapture.isDark = false;
} else {
AutoCapture.isDark = true;
}
}
}

module.exports = AutoCapture;
})();
2 changes: 2 additions & 0 deletions src/js/animator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
const PreviewOverlay = require("./js/animator/core/PreviewOverlay");
const Project = require("./js/animator/projects/Project");
const Shortcuts = require("./js/animator/core/Shortcuts");
const AutoCapture = require("./js/animator/core/AutoCapture");

// UI imports
const CaptureOptions = require("./js/animator/ui/CaptureOptions");
Expand Down Expand Up @@ -37,6 +38,7 @@
ExportVideo.setListeners();
FrameReelRow.setListeners();
PreviewOverlay.initialise();
AutoCapture.initialise();
global.projectInst.setListeners();
global.projectInst.checkExportFrameDir();

Expand Down

0 comments on commit 45b2a80

Please sign in to comment.