Commit 3ed32dc7 authored by Kuo Jen Wei's avatar Kuo Jen Wei Committed by Commit Bot

CCA: Separate modes class into multiple files

Bug: 1113514
Test: tast run <DUT> "camera.CCA*"

Change-Id: I6134faa821936a7e130d319c6709cba414728e93
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2303331
Commit-Queue: Inker Kuo <inker@chromium.org>
Auto-Submit: Inker Kuo <inker@chromium.org>
Reviewed-by: default avatarShik Chen <shik@chromium.org>
Reviewed-by: default avatarWei Lee <wtlee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#802134}
parent 97215bbc
...@@ -26,6 +26,7 @@ group("chrome_camera_app") { ...@@ -26,6 +26,7 @@ group("chrome_camera_app") {
":chrome_camera_app_js_mojo", ":chrome_camera_app_js_mojo",
":chrome_camera_app_js_views", ":chrome_camera_app_js_views",
":chrome_camera_app_js_views_camera", ":chrome_camera_app_js_views_camera",
":chrome_camera_app_js_views_camera_mode",
":chrome_camera_app_mojo_generated", ":chrome_camera_app_mojo_generated",
":chrome_camera_app_sounds", ":chrome_camera_app_sounds",
":chrome_camera_app_views", ":chrome_camera_app_views",
...@@ -190,10 +191,8 @@ copy("chrome_camera_app_js_views") { ...@@ -190,10 +191,8 @@ copy("chrome_camera_app_js_views") {
copy("chrome_camera_app_js_views_camera") { copy("chrome_camera_app_js_views_camera") {
sources = [ sources = [
"src/js/views/camera/layout.js", "src/js/views/camera/layout.js",
"src/js/views/camera/modes.js",
"src/js/views/camera/options.js", "src/js/views/camera/options.js",
"src/js/views/camera/preview.js", "src/js/views/camera/preview.js",
"src/js/views/camera/recordtime.js",
"src/js/views/camera/review_result.js", "src/js/views/camera/review_result.js",
"src/js/views/camera/timertick.js", "src/js/views/camera/timertick.js",
] ]
...@@ -201,6 +200,20 @@ copy("chrome_camera_app_js_views_camera") { ...@@ -201,6 +200,20 @@ copy("chrome_camera_app_js_views_camera") {
outputs = [ "$out_camera_app_dir/js/views/camera/{{source_file_part}}" ] outputs = [ "$out_camera_app_dir/js/views/camera/{{source_file_part}}" ]
} }
copy("chrome_camera_app_js_views_camera_mode") {
sources = [
"src/js/views/camera/mode/index.js",
"src/js/views/camera/mode/mode_base.js",
"src/js/views/camera/mode/photo.js",
"src/js/views/camera/mode/portrait.js",
"src/js/views/camera/mode/record_time.js",
"src/js/views/camera/mode/square.js",
"src/js/views/camera/mode/video.js",
]
outputs = [ "$out_camera_app_dir/js/views/camera/mode/{{source_file_part}}" ]
}
copy("chrome_camera_app_sounds") { copy("chrome_camera_app_sounds") {
sources = [ sources = [
"src/sounds/record_end.ogg", "src/sounds/record_end.ogg",
......
...@@ -44,7 +44,13 @@ ...@@ -44,7 +44,13 @@
<structure name="IDR_CAMERA_MAIN_JS" file="src/js/main.js" type="chrome_html" /> <structure name="IDR_CAMERA_MAIN_JS" file="src/js/main.js" type="chrome_html" />
<structure name="IDR_CAMERA_MANIFEST" file="manifest.json" type="chrome_html" /> <structure name="IDR_CAMERA_MANIFEST" file="manifest.json" type="chrome_html" />
<structure name="IDR_CAMERA_METRICS_JS" file="src/js/metrics.js" type="chrome_html" /> <structure name="IDR_CAMERA_METRICS_JS" file="src/js/metrics.js" type="chrome_html" />
<structure name="IDR_CAMERA_MODES_JS" file="src/js/views/camera/modes.js" type="chrome_html" /> <structure name="IDR_CAMERA_MODE_MODE_BASE_JS" file="src/js/views/camera/mode/mode_base.js" type="chrome_html" />
<structure name="IDR_CAMERA_MODE_INDEX_JS" file="src/js/views/camera/mode/index.js" type="chrome_html" />
<structure name="IDR_CAMERA_MODE_PHOTO_JS" file="src/js/views/camera/mode/photo.js" type="chrome_html" />
<structure name="IDR_CAMERA_MODE_PORTRAIT_JS" file="src/js/views/camera/mode/portrait.js" type="chrome_html" />
<structure name="IDR_CAMERA_MODE_RECORD_TIME_JS" file="src/js/views/camera/mode/record_time.js" type="chrome_html" />
<structure name="IDR_CAMERA_MODE_SQUARE_JS" file="src/js/views/camera/mode/square.js" type="chrome_html" />
<structure name="IDR_CAMERA_MODE_VIDEO_JS" file="src/js/views/camera/mode/video.js" type="chrome_html" />
<structure name="IDR_CAMERA_MP4_VIDEO_PROCESSOR_JS" file="src/js/models/mp4_video_processor.js" type="chrome_html" /> <structure name="IDR_CAMERA_MP4_VIDEO_PROCESSOR_JS" file="src/js/models/mp4_video_processor.js" type="chrome_html" />
<structure name="IDR_CAMERA_NATIVE_FILE_SYSTEM_ENTRY_JS" file="src/js/models/native_file_system_entry.js" type="chrome_html" /> <structure name="IDR_CAMERA_NATIVE_FILE_SYSTEM_ENTRY_JS" file="src/js/models/native_file_system_entry.js" type="chrome_html" />
<structure name="IDR_CAMERA_NAV_JS" file="src/js/nav.js" type="chrome_html" /> <structure name="IDR_CAMERA_NAV_JS" file="src/js/nav.js" type="chrome_html" />
...@@ -53,7 +59,6 @@ ...@@ -53,7 +59,6 @@
<structure name="IDR_CAMERA_PERF_JS" file="src/js/perf.js" type="chrome_html" /> <structure name="IDR_CAMERA_PERF_JS" file="src/js/perf.js" type="chrome_html" />
<structure name="IDR_CAMERA_PREVIEW_JS" file="src/js/views/camera/preview.js" type="chrome_html" /> <structure name="IDR_CAMERA_PREVIEW_JS" file="src/js/views/camera/preview.js" type="chrome_html" />
<structure name="IDR_CAMERA_PWA_HTML" file="pwa.html" type="chrome_html" /> <structure name="IDR_CAMERA_PWA_HTML" file="pwa.html" type="chrome_html" />
<structure name="IDR_CAMERA_RECORDTIME_JS" file="src/js/views/camera/recordtime.js" type="chrome_html" />
<structure name="IDR_CAMERA_RESULT_SAVER_JS" file="src/js/models/result_saver.js" type="chrome_html" /> <structure name="IDR_CAMERA_RESULT_SAVER_JS" file="src/js/models/result_saver.js" type="chrome_html" />
<structure name="IDR_CAMERA_REVIEW_RESULT_JS" file="src/js/views/camera/review_result.js" type="chrome_html" /> <structure name="IDR_CAMERA_REVIEW_RESULT_JS" file="src/js/views/camera/review_result.js" type="chrome_html" />
<structure name="IDR_CAMERA_SETTINGS_JS" file="src/js/views/settings.js" type="chrome_html" /> <structure name="IDR_CAMERA_SETTINGS_JS" file="src/js/views/settings.js" type="chrome_html" />
......
...@@ -59,10 +59,15 @@ js_library("compile_resources") { ...@@ -59,10 +59,15 @@ js_library("compile_resources") {
"util.js", "util.js",
"views/camera.js", "views/camera.js",
"views/camera/layout.js", "views/camera/layout.js",
"views/camera/modes.js", "views/camera/mode/index.js",
"views/camera/mode/mode_base.js",
"views/camera/mode/photo.js",
"views/camera/mode/portrait.js",
"views/camera/mode/record_time.js",
"views/camera/mode/square.js",
"views/camera/mode/video.js",
"views/camera/options.js", "views/camera/options.js",
"views/camera/preview.js", "views/camera/preview.js",
"views/camera/recordtime.js",
"views/camera/review_result.js", "views/camera/review_result.js",
"views/camera/timertick.js", "views/camera/timertick.js",
"views/camera_intent.js", "views/camera_intent.js",
......
...@@ -29,12 +29,12 @@ import { ...@@ -29,12 +29,12 @@ import {
import * as util from '../util.js'; import * as util from '../util.js';
import {Layout} from './camera/layout.js'; import {Layout} from './camera/layout.js';
import { import { // eslint-disable-line no-unused-vars
Modes, Modes,
PhotoResult, // eslint-disable-line no-unused-vars PhotoHandler, // eslint-disable-line no-unused-vars
Video, Video,
VideoResult, // eslint-disable-line no-unused-vars VideoHandler, // eslint-disable-line no-unused-vars
} from './camera/modes.js'; } from './camera/mode/index.js';
import {Options} from './camera/options.js'; import {Options} from './camera/options.js';
import {Preview} from './camera/preview.js'; import {Preview} from './camera/preview.js';
import * as timertick from './camera/timertick.js'; import * as timertick from './camera/timertick.js';
...@@ -55,6 +55,8 @@ class CameraSuspendedError extends Error { ...@@ -55,6 +55,8 @@ class CameraSuspendedError extends Error {
/** /**
* Camera-view controller. * Camera-view controller.
* @implements {VideoHandler}
* @implements {PhotoHandler}
*/ */
export class Camera extends View { export class Camera extends View {
/** /**
...@@ -123,13 +125,6 @@ export class Camera extends View { ...@@ -123,13 +125,6 @@ export class Camera extends View {
*/ */
this.activeDeviceId_ = null; this.activeDeviceId_ = null;
const createVideoSaver = async () => resultSaver.startSaveVideo();
const playShutterEffect = () => {
sound.play('#sound-shutter');
util.animateOnce(this.preview_.video);
};
/** /**
* Modes for the camera. * Modes for the camera.
* @type {!Modes} * @type {!Modes}
...@@ -137,9 +132,7 @@ export class Camera extends View { ...@@ -137,9 +132,7 @@ export class Camera extends View {
*/ */
this.modes_ = new Modes( this.modes_ = new Modes(
this.defaultMode_, photoPreferrer, videoPreferrer, this.defaultMode_, photoPreferrer, videoPreferrer,
this.start.bind(this), this.doSavePhoto_.bind(this), createVideoSaver, this.start.bind(this), this, this);
this.doSaveVideo_.bind(this), playShutterEffect,
() => this.preview_.toImage());
/** /**
* @type {!Facing} * @type {!Facing}
...@@ -389,13 +382,9 @@ export class Camera extends View { ...@@ -389,13 +382,9 @@ export class Camera extends View {
} }
/** /**
* Handles captured photo result. * @override
* @param {!PhotoResult} result Captured photo result.
* @param {string} name Name of the photo result to be saved as.
* @return {!Promise} Promise for the operation.
* @protected
*/ */
async doSavePhoto_({resolution, blob, isVideoSnapshot = false}, name) { async handleResultPhoto({resolution, blob, isVideoSnapshot}, name) {
metrics.sendCaptureEvent({ metrics.sendCaptureEvent({
facing: this.facingMode_, facing: this.facingMode_,
resolution, resolution,
...@@ -411,12 +400,31 @@ export class Camera extends View { ...@@ -411,12 +400,31 @@ export class Camera extends View {
} }
/** /**
* Handles captured video result. * @override
* @param {!VideoResult} result Captured video result. */
* @return {!Promise} Promise for the operation. createVideoSaver() {
* @protected return this.resultSaver_.startSaveVideo();
}
/**
* @override
*/
playShutterEffect() {
sound.play('#sound-shutter');
util.animateOnce(this.preview_.video);
}
/**
* @override
*/
getPreviewFrame() {
return this.preview_.toImage();
}
/**
* @override
*/ */
async doSaveVideo_({resolution, duration, videoSaver, everPaused}) { async handleResultVideo({resolution, duration, videoSaver, everPaused}) {
metrics.sendCaptureEvent({ metrics.sendCaptureEvent({
facing: this.facingMode_, facing: this.facingMode_,
duration, duration,
......
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {
Facing, // eslint-disable-line no-unused-vars
Resolution, // eslint-disable-line no-unused-vars
} from '../../../type.js';
/**
* Base class for controlling capture sequence in different camera modes.
* @abstract
*/
export class ModeBase {
/**
* @param {!MediaStream} stream
* @param {!Facing} facing
* @param {?Resolution} captureResolution Capturing resolution width and
* height.
*/
constructor(stream, facing, captureResolution) {
/**
* Stream of current mode.
* @type {!MediaStream}
* @protected
*/
this.stream_ = stream;
/**
* Camera facing of current mode.
* @type {!Facing}
* @protected
*/
this.facing_ = facing;
/**
* Capture resolution. May be null on device not support of setting
* resolution.
* @type {?Resolution}
* @protected
*/
this.captureResolution_ = captureResolution;
/**
* Promise for ongoing capture operation.
* @type {?Promise}
* @private
*/
this.capture_ = null;
}
/**
* Initiates video/photo capture operation.
* @return {!Promise} Promise for ongoing capture operation.
*/
startCapture() {
if (this.capture_ === null) {
this.capture_ = this.start_().finally(() => this.capture_ = null);
}
return this.capture_;
}
/**
* Stops the ongoing capture operation.
* @return {!Promise} Promise for ongoing capture operation.
*/
async stopCapture() {
this.stop_();
return await this.capture_;
}
/**
* Adds an observer to save image metadata.
* @return {!Promise} Promise for the operation.
*/
async addMetadataObserver() {}
/**
* Remove the observer that saves metadata.
* @return {!Promise} Promise for the operation.
*/
async removeMetadataObserver() {}
/**
* Initiates video/photo capture operation under this mode.
* @return {!Promise}
* @protected
* @abstract
*/
async start_() {}
/**
* Stops the ongoing capture operation under this mode.
* @protected
*/
stop_() {}
}
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {Filenamer} from '../../../models/file_namer.js';
import * as filesystem from '../../../models/file_system.js';
import {DeviceOperator, parseMetadata} from '../../../mojo/device_operator.js';
import {CrosImageCapture} from '../../../mojo/image_capture.js';
import {PerfEvent} from '../../../perf.js';
import * as state from '../../../state.js';
import * as toast from '../../../toast.js';
import {
Facing, // eslint-disable-line no-unused-vars
Resolution,
} from '../../../type.js';
import * as util from '../../../util.js';
import {ModeBase} from './mode_base.js';
/**
* Contains photo taking result.
* @typedef {{
* resolution: !Resolution,
* blob: !Blob,
* isVideoSnapshot: (boolean|undefined),
* }}
*/
export let PhotoResult;
/**
* Provides external dependency functions used by photo mode and handles the
* captured result photo.
* @interface
*/
export class PhotoHandler {
/**
* Handles the result photo.
* @param {!PhotoResult} photo Captured photo result.
* @param {string} name Name of the photo result to be saved as.
* @return {!Promise}
* @abstract
*/
handleResultPhoto(photo, name) {}
/**
* Plays UI effect when taking photo.
*/
playShutterEffect() {}
}
/**
* Photo mode capture controller.
*/
export class Photo extends ModeBase {
/**
* @param {!MediaStream} stream
* @param {!Facing} facing
* @param {?Resolution} captureResolution
* @param {!PhotoHandler} handler
*/
constructor(stream, facing, captureResolution, handler) {
super(stream, facing, captureResolution);
/**
* @const {!PhotoHandler}
* @protected
*/
this.handler_ = handler;
/**
* CrosImageCapture object to capture still photos.
* @type {?CrosImageCapture}
* @protected
*/
this.crosImageCapture_ = null;
/**
* The observer id for saving metadata.
* @type {?number}
* @protected
*/
this.metadataObserverId_ = null;
/**
* Metadata names ready to be saved.
* @type {!Array<string>}
* @protected
*/
this.metadataNames_ = [];
}
/**
* @override
*/
async start_() {
if (this.crosImageCapture_ === null) {
this.crosImageCapture_ =
new CrosImageCapture(this.stream_.getVideoTracks()[0]);
}
await this.takePhoto_();
}
/**
* Takes and saves a photo.
* @return {!Promise}
* @private
*/
async takePhoto_() {
const imageName = (new Filenamer()).newImageName();
if (this.metadataObserverId_ !== null) {
this.metadataNames_.push(Filenamer.getMetadataName(imageName));
}
let photoSettings;
if (this.captureResolution_) {
photoSettings = /** @type {!PhotoSettings} */ ({
imageWidth: this.captureResolution_.width,
imageHeight: this.captureResolution_.height,
});
} else {
const caps = await this.crosImageCapture_.getPhotoCapabilities();
photoSettings = /** @type {!PhotoSettings} */ ({
imageWidth: caps.imageWidth.max,
imageHeight: caps.imageHeight.max,
});
}
state.set(PerfEvent.PHOTO_CAPTURE_SHUTTER, true);
try {
const results = await this.crosImageCapture_.takePhoto(photoSettings);
state.set(PerfEvent.PHOTO_CAPTURE_SHUTTER, false, {facing: this.facing_});
this.handler_.playShutterEffect();
state.set(PerfEvent.PHOTO_CAPTURE_POST_PROCESSING, true);
const blob = await results[0];
const image = await util.blobToImage(blob);
const resolution = new Resolution(image.width, image.height);
await this.handler_.handleResultPhoto({resolution, blob}, imageName);
state.set(
PerfEvent.PHOTO_CAPTURE_POST_PROCESSING, false,
{resolution, facing: this.facing_});
} catch (e) {
state.set(PerfEvent.PHOTO_CAPTURE_SHUTTER, false, {hasError: true});
state.set(
PerfEvent.PHOTO_CAPTURE_POST_PROCESSING, false, {hasError: true});
toast.show('error_msg_take_photo_failed');
throw e;
}
}
/**
* Adds an observer to save metadata.
* @return {!Promise} Promise for the operation.
*/
async addMetadataObserver() {
if (!this.stream_) {
return;
}
const deviceOperator = await DeviceOperator.getInstance();
if (!deviceOperator) {
return;
}
const cameraMetadataTagInverseLookup = {};
Object.entries(cros.mojom.CameraMetadataTag).forEach(([key, value]) => {
if (key === 'MIN_VALUE' || key === 'MAX_VALUE') {
return;
}
cameraMetadataTagInverseLookup[value] = key;
});
const callback = (metadata) => {
const parsedMetadata = {};
for (const entry of metadata.entries) {
const key = cameraMetadataTagInverseLookup[entry.tag];
if (key === undefined) {
// TODO(kaihsien): Add support for vendor tags.
continue;
}
const val = parseMetadata(entry);
parsedMetadata[key] = val;
}
filesystem.saveBlob(
new Blob(
[JSON.stringify(parsedMetadata, null, 2)],
{type: 'application/json'}),
this.metadataNames_.shift());
};
const deviceId = this.stream_.getVideoTracks()[0].getSettings().deviceId;
this.metadataObserverId_ = await deviceOperator.addMetadataObserver(
deviceId, callback, cros.mojom.StreamType.JPEG_OUTPUT);
}
/**
* Removes the observer that saves metadata.
* @return {!Promise} Promise for the operation.
*/
async removeMetadataObserver() {
if (!this.stream_ || this.metadataObserverId_ === null) {
return;
}
const deviceOperator = await DeviceOperator.getInstance();
if (!deviceOperator) {
return;
}
const deviceId = this.stream_.getVideoTracks()[0].getSettings().deviceId;
const isSuccess = await deviceOperator.removeMetadataObserver(
deviceId, this.metadataObserverId_);
if (!isSuccess) {
console.error(`Failed to remove metadata observer with id: ${
this.metadataObserverId_}`);
}
this.metadataObserverId_ = null;
}
}
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {Filenamer} from '../../../models/file_namer.js';
import {CrosImageCapture} from '../../../mojo/image_capture.js';
import {PerfEvent} from '../../../perf.js';
import * as state from '../../../state.js';
import * as toast from '../../../toast.js';
import {
Facing, // eslint-disable-line no-unused-vars
Resolution, // eslint-disable-line no-unused-vars
} from '../../../type.js';
import * as util from '../../../util.js';
import {
Photo,
PhotoHandler, // eslint-disable-line no-unused-vars
} from './photo.js';
/**
* Portrait mode capture controller.
*/
export class Portrait extends Photo {
/**
* @param {!MediaStream} stream
* @param {!Facing} facing
* @param {?Resolution} captureResolution
* @param {!PhotoHandler} handler
*/
constructor(stream, facing, captureResolution, handler) {
super(stream, facing, captureResolution, handler);
}
/**
* @override
*/
async start_() {
if (this.crosImageCapture_ === null) {
this.crosImageCapture_ =
new CrosImageCapture(this.stream_.getVideoTracks()[0]);
}
let photoSettings;
if (this.captureResolution_) {
photoSettings = /** @type {!PhotoSettings} */ ({
imageWidth: this.captureResolution_.width,
imageHeight: this.captureResolution_.height,
});
} else {
const caps = await this.crosImageCapture_.getPhotoCapabilities();
photoSettings = /** @type {!PhotoSettings} */ ({
imageWidth: caps.imageWidth.max,
imageHeight: caps.imageHeight.max,
});
}
const filenamer = new Filenamer();
const refImageName = filenamer.newBurstName(false);
const portraitImageName = filenamer.newBurstName(true);
if (this.metadataObserverId_ !== null) {
[refImageName, portraitImageName].forEach((/** string */ imageName) => {
this.metadataNames_.push(Filenamer.getMetadataName(imageName));
});
}
let /** ?Promise<!Blob> */ reference;
let /** ?Promise<!Blob> */ portrait;
try {
[reference, portrait] = await this.crosImageCapture_.takePhoto(
photoSettings, [cros.mojom.Effect.PORTRAIT_MODE]);
this.handler_.playShutterEffect();
} catch (e) {
toast.show('error_msg_take_photo_failed');
throw e;
}
state.set(PerfEvent.PORTRAIT_MODE_CAPTURE_POST_PROCESSING, true);
let hasError = false;
/**
* @param {!Promise<!Blob>} p
* @param {string} imageName
* @return {!Promise}
*/
const saveResult = async (p, imageName) => {
const isPortrait = Object.is(p, portrait);
let /** ?Blob */ blob = null;
try {
blob = await p;
} catch (e) {
hasError = true;
toast.show(
isPortrait ? 'error_msg_take_portrait_photo_failed' :
'error_msg_take_photo_failed');
throw e;
}
const {width, height} = await util.blobToImage(blob);
await this.handler_.handleResultPhoto(
{resolution: new Resolution(width, height), blob}, imageName);
};
const refSave = saveResult(reference, refImageName);
const portraitSave = saveResult(portrait, portraitImageName);
try {
await portraitSave;
} catch (e) {
hasError = true;
// Portrait image may failed due to absence of human faces.
// TODO(inker): Log non-intended error.
}
await refSave;
state.set(
PerfEvent.PORTRAIT_MODE_CAPTURE_POST_PROCESSING, false,
{hasError, facing: this.facing_});
}
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import {speak} from '../../toast.js'; import {speak} from '../../../toast.js';
/** /**
* Controller for the record-time of Camera view. * Controller for the record-time of Camera view.
......
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {
Facing, // eslint-disable-line no-unused-vars
Resolution, // eslint-disable-line no-unused-vars
} from '../../../type.js';
import * as util from '../../../util.js';
import {
Photo,
PhotoHandler, // eslint-disable-line no-unused-vars
} from './photo.js';
/**
* Crops out maximum possible centered square from the image blob.
* @param {!Blob} blob
* @return {!Promise<!Blob>} Promise with result cropped square image.
*/
async function cropSquare(blob) {
const img = await util.blobToImage(blob);
const side = Math.min(img.width, img.height);
const canvas = document.createElement('canvas');
canvas.width = side;
canvas.height = side;
const ctx = canvas.getContext('2d');
ctx.drawImage(
img, Math.floor((img.width - side) / 2),
Math.floor((img.height - side) / 2), side, side, 0, 0, side, side);
const croppedBlob = await new Promise((resolve) => {
canvas.toBlob(resolve, 'image/jpeg');
});
croppedBlob.resolution = blob.resolution;
return croppedBlob;
}
/**
* Cuts the returned photo into square and passed to underlying PhotoHandler.
* @implements {PhotoHandler}
*/
class SquarePhotoHandler {
/**
* @param {!PhotoHandler} handler
*/
constructor(handler) {
/**
* @const {!PhotoHandler}
*/
this.handler_ = handler;
}
/**
* @override
*/
async handleResultPhoto(result, ...args) {
// Since the image blob after square cut will lose its EXIF including
// orientation information. Corrects the orientation before the square
// cut.
result.blob = await new Promise(
(resolve, reject) => util.orientPhoto(result.blob, resolve, reject));
result.blob = await cropSquare(result.blob);
await this.handler_.handleResultPhoto(result, ...args);
}
/**
* @override
*/
playShutterEffect() {
this.handler_.playShutterEffect();
}
}
/**
* Square mode capture controller.
*/
export class Square extends Photo {
/**
* @param {!MediaStream} stream
* @param {!Facing} facing
* @param {?Resolution} captureResolution
* @param {!PhotoHandler} handler
*/
constructor(stream, facing, captureResolution, handler) {
super(stream, facing, captureResolution, new SquarePhotoHandler(handler));
}
}
...@@ -25,10 +25,8 @@ import * as toast from '../toast.js'; ...@@ -25,10 +25,8 @@ import * as toast from '../toast.js';
import * as util from '../util.js'; import * as util from '../util.js';
import {Camera} from './camera.js'; import {Camera} from './camera.js';
import { // eslint-disable-next-line no-unused-vars
PhotoResult, // eslint-disable-line no-unused-vars import {PhotoResult, VideoResult} from './camera/mode/index.js';
VideoResult, // eslint-disable-line no-unused-vars
} from './camera/modes.js';
import {ReviewResult} from './camera/review_result.js'; import {ReviewResult} from './camera/review_result.js';
/** /**
...@@ -109,7 +107,7 @@ export class CameraIntent extends Camera { ...@@ -109,7 +107,7 @@ export class CameraIntent extends Camera {
/** /**
* @override * @override
*/ */
async doSavePhoto_(result, name) { async handleResultPhoto(result, name) {
this.photoResult_ = result; this.photoResult_ = result;
try { try {
await this.resultSaver_.savePhoto(result.blob, name); await this.resultSaver_.savePhoto(result.blob, name);
...@@ -122,7 +120,7 @@ export class CameraIntent extends Camera { ...@@ -122,7 +120,7 @@ export class CameraIntent extends Camera {
/** /**
* @override * @override
*/ */
async doSaveVideo_(result) { async handleResultVideo(result) {
this.videoResult_ = result; this.videoResult_ = result;
try { try {
await this.resultSaver_.finishSaveVideo(result.videoSaver); await this.resultSaver_.finishSaveVideo(result.videoSaver);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment