From 21a9d3fcd4cf75b79cbf5b131d0c7a84a37a5652 Mon Sep 17 00:00:00 2001 From: Samuel Marks <807580+SamuelMarks@users.noreply.github.com> Date: Sat, 25 Mar 2023 16:01:42 -0400 Subject: [PATCH 1/2] [JeelizExposureController.ts] TypeScript rewrite ; [package{-lock}.json] Add `npm` support ; [tsconfig.json] Init ; [README.md] Whitespace fixes and typo-fix ; [.gitignore] Init --- JeelizExposureController.ts | 401 ++++++++++++++++++++++++++++++++++++ README.md | 27 ++- package-lock.json | 30 +++ package.json | 22 ++ tsconfig.json | 109 ++++++++++ 5 files changed, 574 insertions(+), 15 deletions(-) create mode 100644 JeelizExposureController.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tsconfig.json diff --git a/JeelizExposureController.ts b/JeelizExposureController.ts new file mode 100644 index 0000000..a85cb47 --- /dev/null +++ b/JeelizExposureController.ts @@ -0,0 +1,401 @@ +/** +* JeelizExposureController - https://github.com/jeeliz/jeelizExposureController +* +* MIT License +* +* Copyright 2018 Jeeliz ( https://jeeliz.com ) +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + + +// internal states: +const _states: { idle: number; busy: number; error: number; loading: number; notLoaded: number } = { + error: -2, + notLoaded: -1, + loading: 0, + idle: 1, + busy: 2 +}; +let _state: number = _states.notLoaded; + +let _gl: WebGL2RenderingContext; +let _glVideoCropTexture: WebGLTexture | null; +let _glCropShp: WebGLProgram; +let _glCropShpUniformCropArea: WebGLUniformLocation; +let _glCopyShp: WebGLProgram; +let _subsampleSize: number = 16; +let _imageCapture: ImageCapture | undefined +let _video: HTMLMediaElement & {srcObject: MediaStream}; +let _cameraCapabilities: MediaTrackCapabilities; +let _cameraSettings: MediaTrackSettings; +let _cameraExposureNormalized: number; +let _exposureCompensationKey: string; + +const _readBufferPixel: Uint8Array = new Uint8Array(4); + +interface IArea { + w: number; + x: number; + y: number; + h: number; +} + +const _fullArea: IArea = { + x: 0, + y: 0, + w: 1, + h: 1 +}; + + +// private functions: +//BEGIN WEBGL HELPERS + +function create_glTexture(): void { + _glVideoCropTexture = _gl.createTexture(); + _gl.bindTexture(_gl.TEXTURE_2D, _glVideoCropTexture); + _gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.RGBA, _subsampleSize, _subsampleSize, 0, _gl.RGBA, _gl.UNSIGNED_BYTE, null as ArrayBufferView | null); + _gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MAG_FILTER, _gl.LINEAR); + _gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, _gl.NEAREST_MIPMAP_LINEAR); +} + +function build_shp(vertexSource: string, fragmentSource: string): WebGLProgram { + const GLSLprefix: string = "precision lowp float;\n"; + + // compile vertex shader: + const glVertexShader = _gl.createShader(_gl.VERTEX_SHADER); + _gl.shaderSource(glVertexShader, GLSLprefix + vertexSource); + _gl.compileShader(glVertexShader); + + // compile fragment shader: + const glFragmentShader = _gl.createShader(_gl.FRAGMENT_SHADER); + _gl.shaderSource(glFragmentShader, GLSLprefix + fragmentSource); + _gl.compileShader(glFragmentShader); + + // build shader program: + const glShp = _gl.createProgram(); + _gl.attachShader(glShp, glVertexShader); + _gl.attachShader(glShp, glFragmentShader); + _gl.linkProgram(glShp); + + const glAttPosition = _gl.getAttribLocation(glShp, "aat_position"); + _gl.enableVertexAttribArray(glAttPosition); + return glShp; +} + +function set_shpUniformSource(glShp: WebGLProgram): void { + const glUniformSource = _gl.getUniformLocation(glShp, "uun_source"); + _gl.useProgram(glShp); + _gl.uniform1i(glUniformSource, 0); +} + +function create_glShps(): void { + const CopyFragmentSource = "varying vec2 vUV;\n\t\t\tuniform sampler2D uun_source;\n\t\t\tvoid main(void){\n\t\t\t\tgl_FragColor = texture2D(uun_source, vUV);\n\t\t\t}"; + + // image crop Shp: + _glCropShp = build_shp("attribute vec2 aat_position;\n\t\t\tuniform vec4 uun_cropArea;\n\t\t\tvarying vec2 vUV;\n\t\t\tvoid main(void){\n\t\t\t\tgl_Position = vec4(aat_position,0.,1.);\n\t\t\t\tvUV = vec2(0.5,0.5) + 0.5 * (uun_cropArea.xy+aat_position*uun_cropArea.zw);\n\t\t\t}", CopyFragmentSource); + + // link variables: + _glCropShpUniformCropArea = _gl.getUniformLocation(_glCropShp, "uun_cropArea"); + set_shpUniformSource(_glCropShp); + + // image copy shp: + _glCopyShp = build_shp("attribute vec2 aat_position;\n\t\t\tvarying vec2 vUV;\n\t\t\tvoid main(void){\n\t\t\t\tgl_Position = vec4(aat_position,0.,1.);\n\t\t\t\tvUV = vec2(0.5,0.5) + 0.5 * aat_position;\n\t\t\t}", CopyFragmentSource); + set_shpUniformSource(_glCopyShp); +} //end create_glShps() +//END WEBGL HELPERS + +//BEGIN IMAGECAPTUREAPI HELPERS +function update_cameraSettings(): void { + _cameraSettings = _imageCapture!["track"]["getSettings"](); +} + +function get_cameraExposureRange(): [number, number] { + const mn: number = _cameraCapabilities[_exposureCompensationKey]["min"]; + const mx: number = _cameraCapabilities[_exposureCompensationKey]["max"]; + return [mn, mx]; +} + +function update_cameraExposureNormalized(): void { + if (!is_cameraExposureModeManual()) { + _cameraExposureNormalized = 0.2; + return; + } + const mnMx = get_cameraExposureRange(); //[min, max] + const exposure = _cameraSettings[_exposureCompensationKey]; + _cameraExposureNormalized = (exposure - mnMx[0]) / (mnMx[1] - mnMx[0]); +} + +function set_cameraSetting(setting: string, value: MeteringMode, callback: (val: boolean) => void): void { + if (_cameraSettings[setting] === value) { // no need to change camera settings :) + callback(true); + return; + } + + const appliedSetting: {[key: string]: typeof value} = {}; + appliedSetting[setting] = value; + + _imageCapture!["track"]["applyConstraints"]({ + "advanced": [appliedSetting] + }).then((): void => { + update_cameraSettings(); + callback(_cameraSettings[setting] === value); // check that the value has really changed + }).catch((error: Error): void => { + console.log("ERROR in JeelizExposureController - cannot apply", setting, "=", value, ": ", error); + update_cameraSettings(); + callback(false); + }); +} + +function set_cameraExposureMode(mode: "AUTO" | "MANUAL", callback: (val: boolean) => void): void { + if (mode === "AUTO" && _cameraCapabilities["exposureMode"].indexOf("continuous") === -1) { + callback(false); + return; + } + + const modeCap: MeteringMode = (mode === "AUTO") ? "continuous" : "manual"; + if (mode === "AUTO") { + _cameraExposureNormalized = 0.2; + } + set_cameraSetting("exposureMode", modeCap, callback); +} + +function set_cameraExposure(exposureNormalized: number, callback: (val: boolean) => void) { + if (_cameraSettings["exposureMode"] !== "manual") { + console.log("WARNING in JeelizExposureController - set_cameraExposure(): cannot set exposure in continuous mode"); + callback(false); + return; + } + + const mnMx: [number, number] = get_cameraExposureRange(); // [min, max] + const step: number = _cameraCapabilities[_exposureCompensationKey]["step"]; + + // scale: + let exposure: number = mnMx[0] + exposureNormalized * (mnMx[1] - mnMx[0]); + // round to step: + exposure = step * Math.round(exposure / step); + + set_cameraSetting(_exposureCompensationKey, exposure, callback); +} + +function is_cameraExposureModeManual(): boolean { + return _cameraSettings["exposureMode"] === "manual"; +} + +//END IMAGECAPTUREAPI HELPERS + + +// public methods: +export const that = { + /* + spec with properties: + * GL + * subsampleSize: size of the subsample area. should be PoT. default: 32 + * video + * callbackReady: function to launch when the library is ready. + The callback function is launched with an argument, the error code. + */ + "init": function (spec): boolean { + // check we can start: + if (!that["test_compatibility"]()) { + spec["callbackReady"]("IMAGECAPTUREAPI_NOTFOUND"); + return false; + } + if (_state !== _states.notLoaded) { + spec["callbackReady"]("ALREADY_INITIALIZED"); + return false; + } + if (typeof (spec["subsampleSize"]) !== "undefined") { + _subsampleSize = spec["subsampleSize"]; + } + _state = _states.loading; + _video = spec["videoElement"]; + _gl = spec["GL"]; + + + // get the video track and init imageCapture: + let track: MediaStreamTrack; + try { + const mediaStream = _video["srcObject"]; + track = mediaStream.getVideoTracks()[0]; + } + catch (e) { + spec["callbackReady"]("NO_VIDEOTRACK"); + return false; + } + try { + _imageCapture = new ImageCapture(track); + } + catch (e) { + spec["callbackReady"]("INVALID_VIDEOTRACK"); + return false; + } + + try { + _cameraCapabilities = _imageCapture["track"]["getCapabilities"](); + } + catch (e) { + spec["callbackReady"]("NO_CAMERACAPABILITIES"); + return false; + } + + console.log("DEBUG in JeelizExposureController - _cameraCapabilities = ", _cameraCapabilities); + + if (!_cameraCapabilities["exposureMode"] || _cameraCapabilities["exposureMode"].indexOf("manual") === -1) { + spec["callbackReady"]("INVALID_CAMERACAPABILITIES"); + return false; + } + + if (!_cameraCapabilities["exposureCompensation"] && !_cameraCapabilities["exposureTime"]) { + spec["callbackReady"]("INVALID_CAMERACAPABILITIES"); + return false; + } + if (_cameraCapabilities["exposureCompensation"]) { + _exposureCompensationKey = "exposureCompensation"; + } + else if (_cameraCapabilities["exposureTime"]) { + _exposureCompensationKey = "exposureTime"; + } + + try { + _cameraSettings = _imageCapture["track"]["getSettings"](); + } + catch (e) { + spec["callbackReady"]("NO_CAMERASETTINGS"); + return false; + } + + if (!_cameraSettings["exposureMode"] || !_cameraSettings[_exposureCompensationKey]) { + spec["callbackReady"]("INVALID_CAMERASETTINGS"); + return false; + } + + // create WebGL objects + create_glTexture(); + create_glShps(); + + update_cameraExposureNormalized(); + + // for debug: + //window.debugIc=_imageCapture, window.debugCap=_cameraCapabilities, window.debugSet=_cameraSettings; + + _state = _states.idle; + spec["callbackReady"](false); + return true; + }, + + /* + glTexture: texture with the video + area with properties: + * x: hzt position of the center of the area, in [-1,1] from left to right + * y: vt position of the center of the area, in [-1,1] from bottom to top + * w: width of the area, in [0,1] (1-> full width) + * h: height of the area, in [0,1] (1-> full height) + adjustedLightness: target lightness in [0,1]. Advised: 0.5 + epsilon: target precision. Advised: 0.05 + relaxationFactor: factor of relaxation, in [0,1]. Advised: 0.1 + callback: function to launch as soon as the lightness is adjusted + */ + "adjust": function (glTexture: WebGLTexture | null, area: IArea, + adjustedLightness: number, epsilon: number, relaxationFactor: number, + callback: (val: boolean) => void) { + if (_state !== _states.idle) { + callback(false); + return; + } + + _state = _states.busy; + + // render cropped glTexture on _glVideoCropTexture: + _gl.useProgram(_glCropShp); + _gl.framebufferTexture2D(_gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, _glVideoCropTexture, 0); + _gl.activeTexture(_gl.TEXTURE0); + _gl.bindTexture(_gl.TEXTURE_2D, glTexture); + _gl.viewport(0, 0, _subsampleSize, _subsampleSize); + _gl.uniform4f(_glCropShpUniformCropArea, area.x, area.y, area.w, area.h); + _gl.drawElements(_gl.TRIANGLES, 3, _gl.UNSIGNED_SHORT, 0); //fill viewport + + // display _glVideoCropTexture on a 1x1 pixels viewport and read the result: + _gl.bindFramebuffer(_gl.FRAMEBUFFER, null as WebGLFramebuffer | null); + _gl.useProgram(_glCopyShp); + _gl.bindTexture(_gl.TEXTURE_2D, _glVideoCropTexture); + _gl.generateMipmap(_gl.TEXTURE_2D); + _gl.viewport(0, 0, 1, 1); + _gl.drawElements(_gl.TRIANGLES, 3, _gl.UNSIGNED_SHORT, 0); + + // read rendering: + _gl.readPixels(0, 0, 1, 1, _gl.RGBA, _gl.UNSIGNED_BYTE, _readBufferPixel); + const lightness: number = (_readBufferPixel[0] + _readBufferPixel[1] + _readBufferPixel[2]) / (255 * 3); + //console.log(lightness); + const dLightness: number = lightness - adjustedLightness; // error + if (Math.abs(dLightness) < epsilon) { + _state = _states.idle; + callback(true); + //setTimeout(callback.bind(null, true), 1000); + return; + } + + // if dLightness>0, we should lower the exposure //and conversely + _cameraExposureNormalized -= dLightness * relaxationFactor; + + // clamp the _cameraExposureNormalized: + _cameraExposureNormalized = Math.max(_cameraExposureNormalized, 0.01); + _cameraExposureNormalized = Math.min(_cameraExposureNormalized, 1); + + //console.log(_cameraExposureNormalized, lightness); + + that["set_manualExposure"](_cameraExposureNormalized, function (isSuccess) { + _state = _states.idle; + callback(false); + //setTimeout(callback.bind(null, false), 1000); + }); + + //for debug: draw input texture in fullscreen: + //_gl.viewport(0,0,_gl['canvas']['width'],_gl['canvas']['height']); + //_gl.drawElements(_gl.TRIANGLES, 3, _gl.UNSIGNED_SHORT, 0); + }, + + // same than 'adjust' except that the adjustment area is the whole glTexture. + // the glTexture needs to be in GL.NEAREST_MIPMAP_LINEAR for GL.MIN_FILTER + "adjust_full": function (glTexture, adjustedLightness, epsilon, relaxationFactor, callback) { + that["adjust"](glTexture, _fullArea, adjustedLightness, epsilon, relaxationFactor, callback); + }, + + "toggle_auto": (callback: (val: boolean) => void) => { + set_cameraExposureMode("AUTO", callback); + }, + + "set_manualExposure": (exposure: number, callback: (val: boolean) => void): void => { + if (is_cameraExposureModeManual()) { + set_cameraExposure(exposure, callback); + return; + } + set_cameraExposureMode("MANUAL", (isSuccess: boolean) => { + if (!isSuccess) { + callback(false); + return; + } + set_cameraExposure(exposure, callback); + }); + }, + + "test_compatibility": (): boolean => !!(window["ImageCapture"]) + +}; //end that diff --git a/README.md b/README.md index 20934c4..c4299b0 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,25 @@ - # Jeeliz Exposure controller - - For some applications, we would like to set the camera exposure so that a specific area of the video has a set average color lightness. Thanks to [ImageCapture API](https://developer.mozilla.org/en-US/docs/Web/API/ImageCapture), it is now possible to adjust the camera driver settings directly in the web browser (like exposure, saturation, brightness, ...). - - But is is not supported by all browsers yet. You can check the compatibility table [here](https://developer.mozilla.org/en-US/docs/Web/API/ImageCapture#Browser_compatibility). +Jeeliz Exposure controller +========================== +For some applications, we would like to set the camera exposure so that a specific area of the video has a set average color lightness. Thanks to [ImageCapture API](https://developer.mozilla.org/en-US/docs/Web/API/ImageCapture), it is now possible to adjust the camera driver settings directly in the web browser (like exposure, saturation, brightness, ...). +But it is not supported by all browsers yet. You can check the compatibility table [here](https://developer.mozilla.org/en-US/docs/Web/API/ImageCapture#Browser_compatibility). ## Architecture -* `/demos/`: demonstrations -* `/helpers/`: helpers to get the video feed through *WebRTC* or to initialize the WebGL context, -* `./JeelizExposureController.js`: main script of the library - + * `/demos/`: demonstrations + * `/helpers/`: helpers to get the video feed through *WebRTC* or to initialize the WebGL context, + * `./JeelizExposureController.js`: main script of the library ## Demonstrations Included in this repository: -* `/demos/basic`: the exposure is tuned according to an area at the center of the video. It should be served with an HTTPS static server. + * `/demos/basic`: the exposure is tuned according to an area at the center of the video. It should be served with an HTTPS static server. Other demonstrations: -* *Camera auto exposure adjuster*: The user's face is detected and tracked, then the exposure of the camera is adjusted according to the face lightness. It is useful when there is a strong backlight or if the ambient luminosity is low. The demonstration is available on [Jeeliz GlanceTracker Github repository](https://github.com/jeeliz/jeelizGlanceTracker). You can check the live demo here: [jeeliz.com/demos/glanceTracker/demos/cameraExposureAdjuster/](https://jeeliz.com/demos/glanceTracker/demos/cameraExposureAdjuster/). + * *Camera auto exposure adjuster*: The user's face is detected and tracked, then the exposure of the camera is adjusted according to the face lightness. It is useful when there is a strong backlight or if the ambient luminosity is low. The demonstration is available on [Jeeliz GlanceTracker Github repository](https://github.com/jeeliz/jeelizGlanceTracker). You can check the live demo here: [jeeliz.com/demos/glanceTracker/demos/cameraExposureAdjuster/](https://jeeliz.com/demos/glanceTracker/demos/cameraExposureAdjuster/). If it does not work on your computer, this is a video screenshot: @@ -31,14 +29,13 @@ If it does not work on your computer, this is a video screenshot: You can subscribe to the [Jeeliz Youtube channel](https://www.youtube.com/channel/UC3XmXH1T3d1XFyOhrRiiUeA) or to the [@Jeeliz_AR Twitter account](https://twitter.com/Jeeliz_AR) to be kept informed of our cutting edge developments. - ## Compatibility To run this library, you need: -* A browser implementing *Image Capture API*. The compatibility tables are here: [developer.mozilla.org/en-US/docs/Web/API/ImageCapture](https://developer.mozilla.org/en-US/docs/Web/API/ImageCapture#Browser_compatibility), + * A browser implementing *Image Capture API*. The compatibility tables are here: [developer.mozilla.org/en-US/docs/Web/API/ImageCapture](https://developer.mozilla.org/en-US/docs/Web/API/ImageCapture#browser_compatibility), -* A webcam which driver allows to adjust the exposure (not a Mac). + * A webcam which driver allows to adjust the exposure (not a Mac). This API is still very experimental and its specification may change before it becomes implemented on Firefox and Edge, then Safari. @@ -60,4 +57,4 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI * [Jeeliz website](https://jeeliz.com) * [Doc about Image Capture API on developers.google.com](https://developers.google.com/web/updates/2016/12/imagecapture) - * [Specifications on MDN](https://developer.mozilla.org/en-US/docs/Web/API/ImageCapture) \ No newline at end of file + * [Specifications on MDN](https://developer.mozilla.org/en-US/docs/Web/API/ImageCapture) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b6cb7c2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,30 @@ +{ + "name": "jeelizexposurecontroller", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "jeelizexposurecontroller", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@types/w3c-image-capture": "^1.0.7" + }, + "devDependencies": {} + }, + "node_modules/@types/w3c-image-capture": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/w3c-image-capture/-/w3c-image-capture-1.0.7.tgz", + "integrity": "sha512-BNmjJT+yjwVzB6N5kf1/m9YZMF//e3JAcII+TIuhRkALn7UD9xZX0R1azVGqqyYuQKbQ6UskC+zSQctnsk9zHg==", + "dependencies": { + "@types/webrtc": "*" + } + }, + "node_modules/@types/webrtc": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/webrtc/-/webrtc-0.0.33.tgz", + "integrity": "sha512-xjN6BelzkY3lzXjIjXGqJVDS6XDleEsvp1bVIyNccXCcMoTH3wvUXFew4/qflwJdNqjmq98Zc5VcALV+XBKBvg==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e994576 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "jeelizexposurecontroller", + "version": "1.0.0", + "description": "set the camera exposure so that a specific area of the video has a set average color lightness", + "main": "JeelizExposureController.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/jeeliz/jeelizExposureController.git" + }, + "author": "", + "license": "MIT", + "bugs": { + "url": "https://github.com/jeeliz/jeelizExposureController/issues" + }, + "homepage": "https://github.com/jeeliz/jeelizExposureController#readme", + "dependencies": { + "@types/w3c-image-capture": "^1.0.7" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e075f97 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} From 32009752f394c38ec97b04ae5fea9fdbd3658138 Mon Sep 17 00:00:00 2001 From: Samuel Marks <807580+SamuelMarks@users.noreply.github.com> Date: Sat, 25 Mar 2023 16:46:33 -0400 Subject: [PATCH 2/2] [JeelizExposureController.ts] Use arrow functions ; add missing type annotations (down to 1 type issue in this file) --- JeelizExposureController.ts | 67 +++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/JeelizExposureController.ts b/JeelizExposureController.ts index a85cb47..ba49e8f 100644 --- a/JeelizExposureController.ts +++ b/JeelizExposureController.ts @@ -68,15 +68,15 @@ const _fullArea: IArea = { // private functions: //BEGIN WEBGL HELPERS -function create_glTexture(): void { +const create_glTexture = (): void => { _glVideoCropTexture = _gl.createTexture(); _gl.bindTexture(_gl.TEXTURE_2D, _glVideoCropTexture); _gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.RGBA, _subsampleSize, _subsampleSize, 0, _gl.RGBA, _gl.UNSIGNED_BYTE, null as ArrayBufferView | null); _gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MAG_FILTER, _gl.LINEAR); _gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, _gl.NEAREST_MIPMAP_LINEAR); -} +}; -function build_shp(vertexSource: string, fragmentSource: string): WebGLProgram { +const build_shp = (vertexSource: string, fragmentSource: string): WebGLProgram => { const GLSLprefix: string = "precision lowp float;\n"; // compile vertex shader: @@ -98,15 +98,15 @@ function build_shp(vertexSource: string, fragmentSource: string): WebGLProgram { const glAttPosition = _gl.getAttribLocation(glShp, "aat_position"); _gl.enableVertexAttribArray(glAttPosition); return glShp; -} +}; -function set_shpUniformSource(glShp: WebGLProgram): void { +const set_shpUniformSource = (glShp: WebGLProgram): void => { const glUniformSource = _gl.getUniformLocation(glShp, "uun_source"); _gl.useProgram(glShp); _gl.uniform1i(glUniformSource, 0); -} +}; -function create_glShps(): void { +const create_glShps = (): void => { const CopyFragmentSource = "varying vec2 vUV;\n\t\t\tuniform sampler2D uun_source;\n\t\t\tvoid main(void){\n\t\t\t\tgl_FragColor = texture2D(uun_source, vUV);\n\t\t\t}"; // image crop Shp: @@ -119,21 +119,23 @@ function create_glShps(): void { // image copy shp: _glCopyShp = build_shp("attribute vec2 aat_position;\n\t\t\tvarying vec2 vUV;\n\t\t\tvoid main(void){\n\t\t\t\tgl_Position = vec4(aat_position,0.,1.);\n\t\t\t\tvUV = vec2(0.5,0.5) + 0.5 * aat_position;\n\t\t\t}", CopyFragmentSource); set_shpUniformSource(_glCopyShp); -} //end create_glShps() +}; //end create_glShps() //END WEBGL HELPERS //BEGIN IMAGECAPTUREAPI HELPERS -function update_cameraSettings(): void { +const update_cameraSettings = (): void => { _cameraSettings = _imageCapture!["track"]["getSettings"](); -} +}; -function get_cameraExposureRange(): [number, number] { +const get_cameraExposureRange = (): [number, number] => { const mn: number = _cameraCapabilities[_exposureCompensationKey]["min"]; const mx: number = _cameraCapabilities[_exposureCompensationKey]["max"]; return [mn, mx]; -} +}; + +const is_cameraExposureModeManual = (): boolean => _cameraSettings["exposureMode"] === "manual"; -function update_cameraExposureNormalized(): void { +const update_cameraExposureNormalized = (): void => { if (!is_cameraExposureModeManual()) { _cameraExposureNormalized = 0.2; return; @@ -141,9 +143,9 @@ function update_cameraExposureNormalized(): void { const mnMx = get_cameraExposureRange(); //[min, max] const exposure = _cameraSettings[_exposureCompensationKey]; _cameraExposureNormalized = (exposure - mnMx[0]) / (mnMx[1] - mnMx[0]); -} +}; -function set_cameraSetting(setting: string, value: MeteringMode, callback: (val: boolean) => void): void { +const set_cameraSetting = (setting: string, value: MeteringMode, callback: (val: boolean) => void): void => { if (_cameraSettings[setting] === value) { // no need to change camera settings :) callback(true); return; @@ -162,9 +164,9 @@ function set_cameraSetting(setting: string, value: MeteringMode, callback: (val: update_cameraSettings(); callback(false); }); -} +}; -function set_cameraExposureMode(mode: "AUTO" | "MANUAL", callback: (val: boolean) => void): void { +const set_cameraExposureMode = (mode: "AUTO" | "MANUAL", callback: (val: boolean) => void): void => { if (mode === "AUTO" && _cameraCapabilities["exposureMode"].indexOf("continuous") === -1) { callback(false); return; @@ -175,9 +177,9 @@ function set_cameraExposureMode(mode: "AUTO" | "MANUAL", callback: (val: boolean _cameraExposureNormalized = 0.2; } set_cameraSetting("exposureMode", modeCap, callback); -} +}; -function set_cameraExposure(exposureNormalized: number, callback: (val: boolean) => void) { +const set_cameraExposure = (exposureNormalized: number, callback: (val: boolean) => void): void => { if (_cameraSettings["exposureMode"] !== "manual") { console.log("WARNING in JeelizExposureController - set_cameraExposure(): cannot set exposure in continuous mode"); callback(false); @@ -193,11 +195,7 @@ function set_cameraExposure(exposureNormalized: number, callback: (val: boolean) exposure = step * Math.round(exposure / step); set_cameraSetting(_exposureCompensationKey, exposure, callback); -} - -function is_cameraExposureModeManual(): boolean { - return _cameraSettings["exposureMode"] === "manual"; -} +}; //END IMAGECAPTUREAPI HELPERS @@ -212,7 +210,11 @@ export const that = { * callbackReady: function to launch when the library is ready. The callback function is launched with an argument, the error code. */ - "init": function (spec): boolean { + "init": (spec: { + "callbackReady": (errCode: boolean|string) => void, + "videoElement": typeof _video, + "subsampleSize": undefined | number + }): boolean => { // check we can start: if (!that["test_compatibility"]()) { spec["callbackReady"]("IMAGECAPTUREAPI_NOTFOUND"); @@ -223,7 +225,7 @@ export const that = { return false; } if (typeof (spec["subsampleSize"]) !== "undefined") { - _subsampleSize = spec["subsampleSize"]; + _subsampleSize = spec["subsampleSize"] as number; } _state = _states.loading; _video = spec["videoElement"]; @@ -313,9 +315,9 @@ export const that = { relaxationFactor: factor of relaxation, in [0,1]. Advised: 0.1 callback: function to launch as soon as the lightness is adjusted */ - "adjust": function (glTexture: WebGLTexture | null, area: IArea, - adjustedLightness: number, epsilon: number, relaxationFactor: number, - callback: (val: boolean) => void) { + "adjust": (glTexture: WebGLTexture | null, area: IArea, + adjustedLightness: number, epsilon: number, relaxationFactor: number, + callback: (val: boolean) => void): void => { if (_state !== _states.idle) { callback(false); return; @@ -361,7 +363,7 @@ export const that = { //console.log(_cameraExposureNormalized, lightness); - that["set_manualExposure"](_cameraExposureNormalized, function (isSuccess) { + that["set_manualExposure"](_cameraExposureNormalized, (isSuccess: boolean) => { _state = _states.idle; callback(false); //setTimeout(callback.bind(null, false), 1000); @@ -374,11 +376,12 @@ export const that = { // same than 'adjust' except that the adjustment area is the whole glTexture. // the glTexture needs to be in GL.NEAREST_MIPMAP_LINEAR for GL.MIN_FILTER - "adjust_full": function (glTexture, adjustedLightness, epsilon, relaxationFactor, callback) { + "adjust_full": (glTexture: WebGLTexture | null, adjustedLightness: number, epsilon: number, + relaxationFactor: number, callback: (val: boolean) => void): void => { that["adjust"](glTexture, _fullArea, adjustedLightness, epsilon, relaxationFactor, callback); }, - "toggle_auto": (callback: (val: boolean) => void) => { + "toggle_auto": (callback: (val: boolean) => void): void => { set_cameraExposureMode("AUTO", callback); },