├── .babelrc ├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── TODO.md ├── assets ├── build │ ├── icon.icns │ └── icon.ico ├── electron │ ├── example.fspy │ ├── icon.png │ └── icon.svg └── master │ ├── icon.svg │ ├── import_test.blend │ ├── lettersandnumbers.svg │ └── mockups.key ├── doc └── Using Vanishing Points for Camera Calibration.pdf ├── download-stats.js ├── logo.png ├── package.json ├── project_file_format.md ├── projector_notes.md ├── screenshot.jpg ├── src ├── cli │ └── cli.ts ├── gui │ ├── App.tsx │ ├── actions │ │ └── index.ts │ ├── components │ │ ├── common │ │ │ ├── button.tsx │ │ │ ├── camera-preset-form.tsx │ │ │ ├── camera-presets-dropdown.tsx │ │ │ ├── dropdown.tsx │ │ │ ├── numeric-input-field.tsx │ │ │ └── panel-spacer.tsx │ │ ├── control-points-panel │ │ │ ├── control-point.tsx │ │ │ ├── control-points-panel.tsx │ │ │ ├── control-polyline.tsx │ │ │ ├── glyph-paths.tsx │ │ │ ├── horizon-control.tsx │ │ │ ├── magnifying-glass.tsx │ │ │ ├── origin-control.tsx │ │ │ ├── overlay-3d-panel.tsx │ │ │ ├── principal-point-control.tsx │ │ │ ├── reference-distance-anchor-control.tsx │ │ │ ├── reference-distance-control.tsx │ │ │ └── vanishing-point-control.tsx │ │ ├── result-panel │ │ │ ├── bullet-list.tsx │ │ │ ├── result-panel.tsx │ │ │ └── table-row.tsx │ │ ├── settings-panel │ │ │ ├── axis-dropdown.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── overlay-3d-guide-dropdown.tsx │ │ │ ├── reference-distance-axis-dropdown.tsx │ │ │ ├── reference-distance-form.tsx │ │ │ ├── reference-distance-unit-dropdown.tsx │ │ │ └── settings-panel.tsx │ │ └── splash-screen.tsx │ ├── constants.ts │ ├── containers │ │ ├── control-points-container.tsx │ │ ├── result-container.tsx │ │ └── settings-container.tsx │ ├── defaults │ │ ├── calibration-settings.ts │ │ ├── control-points-state.ts │ │ ├── global-settings.ts │ │ ├── image-state.ts │ │ ├── result-display-settings.ts │ │ ├── solver-result.ts │ │ └── ui-state.ts │ ├── index.css │ ├── index.html │ ├── index.tsx │ ├── io │ │ ├── project-file.ts │ │ ├── saved-state.ts │ │ └── util.ts │ ├── ipc-messages.ts │ ├── reducers │ │ ├── calibration-settings-1-vp.ts │ │ ├── calibration-settings-2-vp.ts │ │ ├── calibration-settings-base.ts │ │ ├── control-points-1-vp.ts │ │ ├── control-points-2-vp.ts │ │ ├── control-points-base.ts │ │ ├── global-settings.ts │ │ ├── image-state.ts │ │ ├── result-display-settings.ts │ │ ├── root.ts │ │ ├── solver-result.ts │ │ └── ui-state.ts │ ├── solver │ │ ├── aabb-ops.ts │ │ ├── aabb.ts │ │ ├── camera-presets.ts │ │ ├── coordinates-util.ts │ │ ├── math-util.ts │ │ ├── point-2d.ts │ │ ├── solver-result.ts │ │ ├── solver.ts │ │ ├── transform.ts │ │ └── vector-3d.ts │ ├── store │ │ ├── app-middleware.ts │ │ └── store.ts │ ├── strings │ │ └── strings.ts │ ├── style │ │ └── palette.ts │ └── types │ │ ├── calibration-settings.ts │ │ ├── control-points-state.ts │ │ ├── global-settings.ts │ │ ├── image-state.ts │ │ ├── result-display-settings.ts │ │ ├── store-state.ts │ │ └── ui-state.ts └── main │ ├── app-menu-manager.ts │ ├── index.ts │ └── ipc-messages.ts ├── test_data ├── 1 vp control test.fspy ├── box.jpg ├── canon5d_16mm.fspy ├── canoneos60d_24mm.fspy ├── defaults.fspy ├── iphone6plus.fspy ├── missing horizon.fspy ├── pp problem landscape.fspy ├── pp problem.fspy ├── quad-problem-2 copy.fspy ├── quad-problem-2.fspy ├── quad-problem-3.fspy ├── quad-problem-4.fspy ├── quad-problem.fspy ├── ref_distance_in_yards.fspy └── reference distance problem.fspy ├── tests ├── gui │ └── gui_tests.ts └── main │ └── main_tests.ts ├── tools ├── letters_and_numbers_paths.py └── png2icns.sh ├── tsconfig.json ├── tslint.json ├── webpack.config.js ├── webpack.tests.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015-node", "react"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | /dist 4 | 5 | # dependencies 6 | /node_modules 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | ghtoken 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | *junit.xml 28 | 29 | __tests__ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "files.eol": "\n" 4 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "args": [ 9 | "run", 10 | "build" 11 | ], 12 | "group": { 13 | "kind": "build", 14 | "isDefault": true 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## What is this? 2 | 3 | fSpy is an open source, cross platform app for still image camera matching. See [fspy.io](https://fspy.io) for more info. The source code is available under the GPL license. 4 | 5 | ![fSpy screenshot](screenshot.jpg) 6 | 7 | ## Backstory 8 | 9 | Once upon a time I wrote BLAM, a [Blender](https://blender.org) add-on for still image camera calibration that, despite its clunky UI, has gained some popularity in the Blender community. fSpy is an attempt to bring BLAM's functionality to a wider audience in the form of a stand alone app. 10 | 11 | ## Using the computed camera parameters in other applications 12 | 13 | In theory, camera parameters computed by fSpy could be used in any application that has a notion of a 3D camera and provides some way of setting the camera parameters. If you're a Blender user, have a look at the [offical fSpy importer add-on](https://github.com/stuffmatic/fSpy-Blender). If you're using an application without a dedicated importer, you may still be able to manually copy the camera parameters from fSpy. 14 | 15 | Interested in writing an importer for your favorite application? Then the [fSpy project file format spec](https://github.com/stuffmatic/fSpy/blob/develop/project_file_format.md) is a good starting point. 16 | 17 | 18 | ## Building and running 19 | 20 | The following instructions are for developers. If you just want to run the app, [download the latest executable for your platform](https://github.com/stuffmatic/fSpy/releases). 21 | 22 | fSpy is written in [Typescript](https://www.typescriptlang.org) using [Electron](https://electronjs.org), [React](https://reactjs.org) and [Redux](https://redux.js.org). [Visual Studio Code](https://code.visualstudio.com) is recommended for a pleasant editing experience. 23 | 24 | To install necessary dependencies, run 25 | 26 | ``` 27 | yarn 28 | ``` 29 | 30 | The `src` folder contains two subfolders `main` and `gui`, containing code for the [Electron main and renderer processes](https://electronjs.org/docs/tutorial/application-architecture) respectively. 31 | 32 | Here's how to run the app in development mode 33 | 34 | 1. Run `yarn dev-server` in a separate terminal tab to start the dev server 35 | 2. Run `yarn build-dev` to build both the main and GUI code. This build step is needed to generate main process code used to start up the app. 36 | 3. Run `yarn electron-dev` in a separate terminal tab to start an Electron instance which uses the dev server to provide automatic reloading on GUI code changes. 37 | 38 | ⚠️ The current build process is not ideal. For example, it lacks support for live reloading on main process code changes. Changes to main process code require a manual rebuild, i.e steps 2-3, in order to show up in the app. 39 | 40 | 41 | ## Creating binaries for distribution 42 | 43 | To create executables for distribution, run 44 | 45 | ``` 46 | yarn dist 47 | ``` 48 | 49 | which invokes [Electron builder](https://github.com/electron-userland/electron-builder). 50 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # 1.0.3 2 | 3 | * euler angles 4 | * overwrite warning does not use name with appended .fspy 5 | 6 | # Fix 7 | * cmd-s should trigger save-as for new project 8 | * add appropriate entries to the macos fSpy menu 9 | * Den kraschar när jag vill starta nytt projekt - Linux 10 | 11 | # build 12 | * silence test warning 13 | 14 | # linux 15 | * crisper icon (use nativeimage?) 16 | * name: fSpy instead of fspy 17 | 18 | # future 19 | * use transform() to scale bg image? 20 | * view menu -> view tool panels 21 | * help menu with link to site 22 | * include camera preset in solver result? 23 | * actions name space 24 | * about window? 25 | * revert to saved 26 | * full screen mode for projector use 27 | * tooltips 28 | * warn when reference distance handles go past the measuring axis vp 29 | * disallow selecting parallel vp axes? 30 | * I imported a camera via the python script and all worked well except it doesn’t use the sensor size I set in fSpy. 31 | The field of view is right, though. It uses 32mm instead of 35mm full frame but adjusted the focal length accordingly. It would be nice if the script also adjusted the sensor size to match the one in fSpy. 32 | * Can it be made possible to place the principal point outside the image area? -------------------------------------------------------------------------------- /assets/build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/assets/build/icon.icns -------------------------------------------------------------------------------- /assets/build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/assets/build/icon.ico -------------------------------------------------------------------------------- /assets/electron/example.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/assets/electron/example.fspy -------------------------------------------------------------------------------- /assets/electron/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/assets/electron/icon.png -------------------------------------------------------------------------------- /assets/electron/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 28 | 32 | 36 | 40 | 41 | 67 | 73 | 74 | 76 | 77 | 79 | image/svg+xml 80 | 82 | 83 | 84 | 85 | 86 | 91 | 97 | 105 | 112 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /assets/master/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 28 | 32 | 36 | 40 | 41 | 67 | 73 | 74 | 76 | 77 | 79 | image/svg+xml 80 | 82 | 83 | 84 | 85 | 86 | 91 | 97 | 105 | 112 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /assets/master/import_test.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/assets/master/import_test.blend -------------------------------------------------------------------------------- /assets/master/mockups.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/assets/master/mockups.key -------------------------------------------------------------------------------- /doc/Using Vanishing Points for Camera Calibration.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/doc/Using Vanishing Points for Camera Calibration.pdf -------------------------------------------------------------------------------- /download-stats.js: -------------------------------------------------------------------------------- 1 | const https = require('https'); 2 | 3 | const options = { 4 | hostname: 'api.github.com', 5 | port: 443, 6 | path: '/repos/stuffmatic/fspy/releases', 7 | method: 'GET', 8 | headers: { 'User-Agent': 'fSpy stats script' } 9 | }; 10 | 11 | https.get(options, (resp) => { 12 | let data = '' 13 | 14 | resp.on('data', (chunk) => { 15 | data += chunk; 16 | }); 17 | 18 | resp.on('end', () => { 19 | let releases = JSON.parse(data) 20 | for (let i = 0; i < releases.length; i++) { 21 | let release = releases[i] 22 | console.log(release.tag_name) 23 | let assets = release.assets 24 | let totalCount = 0 25 | for (let j = 0; j < assets.length; j++) { 26 | let asset = assets[j] 27 | console.log(' ' + asset.name + ', ' + asset.download_count + ' downloads') 28 | totalCount += asset.download_count 29 | } 30 | console.log(' -----') 31 | console.log(' Total: ' + totalCount) 32 | console.log('') 33 | } 34 | 35 | }); 36 | 37 | }).on("error", (err) => { 38 | console.log("Error: " + err.message); 39 | }); -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fspy", 3 | "version": "1.1.0-beta.3", 4 | "main": "build/main.js", 5 | "homepage": "https://fspy.io", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stuffmatic/fSpy.git" 9 | }, 10 | "author": { 11 | "name": "Per Gantelius", 12 | "email": "per@stuffmatic.com", 13 | "url": "https://stuffmatic.com" 14 | }, 15 | "description": "An open source, cross platform app for still image camera matching", 16 | "license": "GPL-3.0", 17 | "devDependencies": { 18 | "@types/electron-window-state": "^2.0.33", 19 | "@types/minimist": "^1.2.0", 20 | "@types/jest": "^23.0.0", 21 | "@types/react": "^16.3.16", 22 | "@types/react-dom": "^16.0.5", 23 | "@types/react-measure": "^2.0.2", 24 | "@types/react-redux": "^6.0.1", 25 | "babel-core": "^6.26.3", 26 | "babel-loader": "^7.1.4", 27 | "babel-preset-es2015": "^6.24.1", 28 | "babel-preset-es2015-node": "^6.1.1", 29 | "babel-preset-react": "^6.24.1", 30 | "babel-preset-stage-2": "^6.24.1", 31 | "css-loader": "^0.28.11", 32 | "electron": "^8.2.1", 33 | "electron-builder": "^22.4.0", 34 | "html-webpack-plugin": "^3.2.0", 35 | "jest": "^23.1.0", 36 | "jest-junit": "^5.0.0", 37 | "standard": "^11.0.1", 38 | "standard-loader": "^6.0.1", 39 | "style-loader": "^0.21.0", 40 | "trash-cli": "^1.4.0", 41 | "ts-loader": "^4.3.1", 42 | "tslint": "^5.10.0", 43 | "tslint-config-standard": "^7.0.0", 44 | "tslint-loader": "^3.6.0", 45 | "typescript": "^2.9.1", 46 | "webpack": "^4.10.2", 47 | "webpack-cli": "^3.0.1", 48 | "webpack-dev-server": "^3.1.4" 49 | }, 50 | "scripts": { 51 | "prebuild-dev": "trash build", 52 | "build-dev": "webpack --config webpack.config.js --mode development", 53 | "prebuild-dist": "trash build", 54 | "build-dist": "webpack --config webpack.config.js --mode production", 55 | "predist": "yarn run build-dist", 56 | "predist-preview": "yarn run build-dist", 57 | "dist-preview": "electron-builder --dir", 58 | "prebuild-test": "trash __tests__", 59 | "build-test": "webpack --config webpack.tests.config.js --mode development", 60 | "pretest": "yarn run build-test", 61 | "dev-server": "webpack-dev-server --config webpack.config.js --content-base build/ --mode development", 62 | "electron-dev": "DEV=true electron ./build/main.js", 63 | "dist": "electron-builder -mwl", 64 | "test": "jest", 65 | "prepublish-release": "yarn build-dist", 66 | "publish-release": "electron-builder -mwl --publish always" 67 | }, 68 | "dependencies": { 69 | "minimist": "^1.2.0", 70 | "electron-window-state": "^4.1.1", 71 | "konva": "^2.1.3", 72 | "react": "^16.4.0", 73 | "react-dom": "^16.4.0", 74 | "react-konva": "^1.7.4", 75 | "react-measure": "^2.0.2", 76 | "react-redux": "^5.0.7", 77 | "redux": "^4.0.0", 78 | "redux-thunk": "^2.3.0" 79 | }, 80 | "build": { 81 | "publish": { 82 | "provider": "github", 83 | "repo": "fSpy", 84 | "publishAutoUpdate": false 85 | }, 86 | "productName": "fSpy", 87 | "appId": "com.stuffmatic.fspy", 88 | "mac": { 89 | "target": "dmg", 90 | "category": "public.app-category.utilities" 91 | }, 92 | "linux": { 93 | "target": [ 94 | "AppImage" 95 | ] 96 | }, 97 | "win": { 98 | "target": [ 99 | { 100 | "target": "nsis", 101 | "arch": "x64" 102 | }, 103 | { 104 | "target": "nsis", 105 | "arch": "ia32" 106 | }, 107 | { 108 | "target": "zip", 109 | "arch": "x64" 110 | }, 111 | { 112 | "target": "zip", 113 | "arch": "ia32" 114 | } 115 | ] 116 | }, 117 | "files": [ 118 | "build/**/*" 119 | ], 120 | "extraResources": [ 121 | { 122 | "from": "assets/electron", 123 | "to": "", 124 | "filter": [ 125 | "example.fspy" 126 | ] 127 | }, 128 | { 129 | "from": "assets/build", 130 | "to": "", 131 | "filter": [ 132 | "icon.ico" 133 | ] 134 | }, 135 | { 136 | "from": "assets/electron", 137 | "to": "", 138 | "filter": [ 139 | "icon.png" 140 | ] 141 | }, 142 | { 143 | "from": "assets/electron", 144 | "to": "", 145 | "filter": [ 146 | "icon.svg" 147 | ] 148 | } 149 | ], 150 | "directories": { 151 | "buildResources": "assets/build" 152 | }, 153 | "fileAssociations": [ 154 | { 155 | "ext": "fspy", 156 | "name": "fSpy project", 157 | "description": "fSpy project file", 158 | "role": "Editor" 159 | } 160 | ] 161 | }, 162 | "jest": { 163 | "testRegex": "/__tests__/.*\\.jsx?", 164 | "testResultsProcessor": "jest-junit" 165 | }, 166 | "jest-junit": { 167 | "suiteName": "jest tests", 168 | "output": "./TEST-jest_junit.xml", 169 | "classNameTemplate": "{classname}-{title}", 170 | "titleTemplate": "{classname}-{title}", 171 | "usePathForSuiteName": "true" 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /project_file_format.md: -------------------------------------------------------------------------------- 1 | # fSpy project file format 2 | 3 | ## Binary file structure 4 | 5 | An fSpy project file consists of the following parts in the following order, where `uint32` is an unsigned 4 byte little endian integer: 6 | 7 | * __`file_id`__ - 4 x `uint8` 8 | 9 | `0x66`, `0x73`, `0x70`, `0x79` (`f`, `s`, `p`, `y` in ASCII) 10 | * __`project_file_version`__ - `uint32` 11 | 12 | `1` 13 | * __`state_size`__ - `uint32` 14 | 15 | The size in bytes of the project state JSON data 16 | * __`image_size`__ - `uint32` 17 | 18 | The size in bytes of the project image data. May be 0. 19 | * __Project state data__ 20 | 21 | A `state_size` byte JSON string describing the project state 22 | * __Image data__ 23 | 24 | `image_size` bytes of binary image data. 25 | 26 | ## Project state data 27 | 28 | The project state data describes the state of the entire fSpy project. Camera parameters are stored under the `cameraParameters` attribute. Note that the camera parameters can be null for example if the vanishing points are invalid. 29 | 30 | ## Image data 31 | 32 | The image data is a binary blob of image data. The fSpy code is not aware of the format of the data. As long as Electron can load it, it's considered valid. -------------------------------------------------------------------------------- /projector_notes.md: -------------------------------------------------------------------------------- 1 | # Projector calibration using fSpy 2 | 3 | ⚠️ This is a work-in-progress document outlining the steps to perform projector calibration using fSpy and Blender. Note that this functionality is experimental and pretty much untested at the time of writing and requires a [recent beta build of fSpy](https://github.com/stuffmatic/fSpy/releases). 4 | 5 | ## Performing calibration in fSpy 6 | 7 | 1. Create an image with the same resolution as the projector 8 | 2. Open the image in fSpy 9 | 3. Point the projector so that its viewport is projected onto an object with suitable perpendicular edges, e.g a table or a box. 10 | 4. Enter full screen mode (view -> enter full screen mode) 11 | 5. Position the fSpy control points as usual 12 | 6. Exit full screen mode (view -> leave full screen mode) 13 | 7. Save the fSpy project file. 14 | 15 | ## Blender import 16 | 17 | 1. Import the project file into Blender using the [importer add-on](https://github.com/stuffmatic/fSpy-Blender). This creates a new and properly calibrated camera. 18 | 2. In order to create 3D geometry based on the calibrated camera, it's vital that the Blender camera's viewport fits the screen perfectly. This can be achieved with the following steps (that should probably be automated in the future) 19 | * Enter full screen mode in Blender (window -> enter full screen) 20 | * Set the viewpoint to be the calibrated camera (numpad 0) 21 | * Make the 3D view cover the entire screen by positioning the mouse pointer in the 3D view and pressing ctrl-alt-space (pressing ctrl-alt-space again goes back to normal view). 22 | * Press F3, search for and select "View camera center" (pressing home should also work). This makes the camera viewport fit the screen. 23 | * For a more immersive experience, disable overlays and gizmos (button in the upper right corner of the 3D view) 24 | 3. By now, the Blender viewport and camera should match the scene the projector is projectin onto. 25 | 26 | ### Notes: 27 | 28 | 1. When calibrating a projector whith any lens shift it's important to set the Principal Point to "From 3rd vanishing point", otherwise the calibration will not be correct. 29 | 2. There is currently a bug in Blender, when in Render View AND Fullscreen mode AND in Maximize Area (ctrl-alt-space) with **Cycles**: the rendered image is shifted and scaled (see [this issue](https://developer.blender.org/T70800) in the bug tracker). This is **not** a bug whith fSpy's calibration, if you render the image and project it, it will be properly calibrated. 30 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/screenshot.jpg -------------------------------------------------------------------------------- /src/cli/cli.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import minimist from 'minimist' 20 | import { writeFileSync, readFileSync, existsSync } from 'fs' 21 | import SavedState from '../gui/io/saved-state' 22 | import { CalibrationMode } from '../gui/types/global-settings' 23 | import Solver from '../gui/solver/solver' 24 | import { ImageState } from '../gui/types/image-state' 25 | import { SolverResult } from '../gui/solver/solver-result' 26 | 27 | export class CLI { 28 | 29 | static printUsage() { 30 | console.log('fSpy CLI') 31 | console.log('') 32 | console.log('The fSpy CLI is used to compute camera parameters without') 33 | console.log('using the GUI. The image data is not used for this process.') 34 | console.log('Only the image dimensions are required.') 35 | console.log('') 36 | console.log('To show this help text, pass -h or --help as the only option') 37 | console.log('') 38 | console.log('Options (all required)') 39 | console.log(' -w') 40 | console.log(' The width of the input image.') 41 | console.log(' -h') 42 | console.log(' The height of the input image.') 43 | console.log(' -s') 44 | console.log(' Path to a JSON file with project state data, the shape') 45 | console.log(' of which is described by the SavedState interface.') 46 | console.log(' -o') 47 | console.log(' A path to write the solver result to. The shape of') 48 | console.log(' the data is described by the SolverResult interface.') 49 | } 50 | 51 | static run(argv: string[]) { 52 | const args = minimist(argv) 53 | 54 | let hasValidOptions = true 55 | const imageWidthString = args.w 56 | hasValidOptions = hasValidOptions && imageWidthString != undefined 57 | const imageHeightString = args.h 58 | hasValidOptions = hasValidOptions && imageHeightString != undefined 59 | const statePath = args.s 60 | hasValidOptions = hasValidOptions && statePath != undefined 61 | const outputPath = args.o 62 | hasValidOptions = hasValidOptions && outputPath != undefined 63 | 64 | if (!hasValidOptions) { 65 | this.printUsage() 66 | return 67 | } 68 | 69 | const imageWidth = parseFloat(imageWidthString) 70 | const imageHeight = parseFloat(imageHeightString) 71 | 72 | if (isNaN(imageWidth) || isNaN(imageHeight)) { 73 | console.log('Error: got invalid image dimensions') 74 | return 75 | } 76 | 77 | if (!existsSync(statePath)) { 78 | console.log('Error: project state file ' + statePath + ' does not exist') 79 | return 80 | } 81 | 82 | const imageState: ImageState = { 83 | width: imageWidth, 84 | height: imageHeight, 85 | url: null, 86 | data: null 87 | } 88 | 89 | const projectState: SavedState = JSON.parse(readFileSync(statePath).toString()) 90 | const is1VPMode = projectState.globalSettings.calibrationMode == CalibrationMode.OneVanishingPoint 91 | let solverResult: SolverResult 92 | if (is1VPMode) { 93 | solverResult = Solver.solve1VP( 94 | projectState.calibrationSettingsBase, 95 | projectState.calibrationSettings1VP, 96 | projectState.controlPointsStateBase, 97 | projectState.controlPointsState1VP, 98 | imageState 99 | ) 100 | } else { 101 | solverResult = Solver.solve2VP( 102 | projectState.calibrationSettingsBase, 103 | projectState.calibrationSettings2VP, 104 | projectState.controlPointsStateBase, 105 | projectState.controlPointsState2VP, 106 | imageState 107 | ) 108 | } 109 | 110 | writeFileSync(outputPath, JSON.stringify(solverResult, null, 2)) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/gui/components/common/button.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | 21 | interface ButtonProps { 22 | title: string 23 | width?: string 24 | height?: string 25 | isSelected?: boolean 26 | onClick(): void 27 | } 28 | 29 | export default function Button(props: ButtonProps) { 30 | 31 | let style: any = { 32 | width: '135px' 33 | } 34 | 35 | if (props.width) { 36 | style.width = props.width 37 | } 38 | 39 | if (props.height) { 40 | style.height = props.height 41 | } 42 | 43 | return ( 44 | 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /src/gui/components/common/camera-preset-form.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import CameraPresetsDropdown from './../common/camera-presets-dropdown' 21 | import NumericInputField from './../common/numeric-input-field' 22 | import PanelSpacer from './../common/panel-spacer' 23 | import { CameraData } from '../../types/calibration-settings' 24 | import strings from '../../strings/strings' 25 | import { cameraPresets } from '../../solver/camera-presets' 26 | 27 | export interface CameraPresetFormProps { 28 | absoluteFocalLength: number 29 | cameraData: CameraData 30 | onCameraPresetChange(cameraPreset: string | null): void 31 | onSensorSizeChange(width: number | undefined, height: number | undefined): void 32 | } 33 | 34 | export default class CameraPresetForm extends React.PureComponent { 35 | render() { 36 | let sensorWidth = this.props.cameraData.customSensorWidth 37 | let sensorHeight = this.props.cameraData.customSensorHeight 38 | let presetId = this.props.cameraData.presetId 39 | if (presetId != null) { 40 | const preset = cameraPresets[presetId] 41 | if (preset) { 42 | sensorWidth = preset.sensorWidth 43 | sensorHeight = preset.sensorHeight 44 | } 45 | } 46 | 47 | return ( 48 |
49 | 53 | 54 |
55 | Sensor { this.props.onSensorSizeChange(value, undefined) }} /> 59 | x 60 | { this.props.onSensorSizeChange(undefined, value) }} /> {strings.unitMm} 64 |
65 | 66 | 67 | { this.props.children } 68 |
69 | ) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/gui/components/common/camera-presets-dropdown.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import { cameraPresets } from '../../solver/camera-presets' 21 | import { CameraData } from '../../types/calibration-settings' 22 | import strings from '../../strings/strings' 23 | import Dropdown from './dropdown' 24 | 25 | export interface CameraPresetsDropdownProps { 26 | cameraData: CameraData 27 | onPresetChanged(presetId: string | null): void 28 | } 29 | 30 | export default function CameraPresetsDropdown(props: CameraPresetsDropdownProps) { 31 | 32 | let ids: (string | null)[] = [] 33 | for (let id in cameraPresets) { 34 | ids.push(id) 35 | } 36 | ids.sort() 37 | ids.unshift(null) 38 | 39 | return ( 40 |
41 | { 45 | return { 46 | value: id, 47 | id: id === null ? 'null' : id, 48 | title: id === null ? strings.customCameraPresetName : cameraPresets[id].displayName 49 | } 50 | } 51 | ) 52 | } 53 | selectedOptionId={props.cameraData.presetId === null ? 'null' : props.cameraData.presetId} 54 | onOptionSelected={props.onPresetChanged} 55 | /> 56 |
57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /src/gui/components/common/numeric-input-field.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import { Palette } from '../../style/palette' 21 | 22 | interface NumericInputFieldProps { 23 | precision?: number 24 | isDisabled?: boolean 25 | valueNotAvailable?: boolean 26 | value: number 27 | onSubmit(value: number): void 28 | } 29 | 30 | interface NumericInputFieldState { 31 | isEditing: Boolean 32 | editedValue: string 33 | editedValueIsValid: Boolean 34 | } 35 | 36 | export default class NumericInputField extends React.Component { 37 | 38 | constructor(props: NumericInputFieldProps) { 39 | super(props) 40 | this.state = { 41 | isEditing: false, 42 | editedValue: props.value.toString(), 43 | editedValueIsValid: true 44 | } 45 | } 46 | 47 | handleChange(event: any) { 48 | this.setState({ 49 | ...this.state, 50 | editedValue: event.target.value, 51 | editedValueIsValid: this.numericValue(event.target.value) !== undefined 52 | }) 53 | } 54 | 55 | handleSubmit(event: any) { 56 | event.preventDefault() 57 | this.finishEditing() 58 | event.target.blur() 59 | } 60 | 61 | handleFocus(_: any) { 62 | this.beginEditing() 63 | } 64 | 65 | handleBlur(_: any) { 66 | this.cancelEditing() 67 | } 68 | 69 | render() { 70 | let inputStyle: any = { 71 | height: '22px', 72 | outline: 'none', 73 | width: '60px', 74 | paddingLeft: '6px', 75 | border: '1px solid ' + Palette.gray 76 | } 77 | 78 | if (this.props.isDisabled) { 79 | inputStyle = { 80 | ...inputStyle, 81 | userSelect: 'none', 82 | cursor: 'default', 83 | color: Palette.disabledTextColor 84 | } 85 | } 86 | 87 | if (this.state.isEditing) { 88 | if (this.state.editedValueIsValid) { 89 | inputStyle = { 90 | ...inputStyle, 91 | border: '1px solid ' + Palette.green 92 | } 93 | } else { 94 | inputStyle = { 95 | ...inputStyle, 96 | border: '1px solid ' + Palette.red 97 | } 98 | } 99 | } 100 | 101 | let displayValue = this.props.precision ? this.props.value.toFixed(this.props.precision) : this.props.value 102 | if (this.props.valueNotAvailable) { 103 | displayValue = 'n/a' 104 | } 105 | 106 | return ( 107 | { 113 | if (!this.props.isDisabled) { 114 | this.handleChange(event) 115 | } 116 | }} 117 | onFocus={(event: any) => { 118 | if (!this.props.isDisabled) { 119 | this.handleFocus(event) 120 | } 121 | }} 122 | onBlur={(event: any) => { 123 | if (!this.props.isDisabled) { 124 | this.handleBlur(event) 125 | } 126 | }} 127 | onKeyDown={(event: any) => { 128 | if (!this.props.isDisabled) { 129 | if (event.key == 'Escape') { 130 | this.cancelEditing() 131 | event.target.blur() 132 | } else if (event.key == 'Enter') { 133 | if (this.state.editedValueIsValid) { 134 | this.handleSubmit(event) 135 | } 136 | } 137 | } 138 | }} 139 | /> 140 | ) 141 | } 142 | 143 | private beginEditing() { 144 | this.setState({ 145 | ...this.state, 146 | isEditing: true, 147 | editedValue: this.props.value.toString(), 148 | editedValueIsValid: true 149 | }) 150 | } 151 | 152 | private finishEditing() { 153 | this.setState({ 154 | ...this.state, 155 | isEditing: false 156 | }) 157 | let numericValue = this.numericValue(this.state.editedValue) 158 | this.props.onSubmit(numericValue === undefined ? 0 : numericValue) 159 | } 160 | 161 | private cancelEditing() { 162 | this.setState({ 163 | ...this.state, 164 | isEditing: false 165 | }) 166 | } 167 | 168 | private numericValue(stringValue: string): number | undefined { 169 | let value = parseFloat(stringValue) 170 | return isNaN(value) ? undefined : value 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/gui/components/common/panel-spacer.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | 21 | export default function PanelSpacer() { 22 | return ( 23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /src/gui/components/control-points-panel/control-point.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import Point2D from '../../solver/point-2d' 21 | import { Circle, Group } from 'react-konva' 22 | import ControlPolyline from './control-polyline' 23 | import MathUtil from '../../solver/math-util' 24 | 25 | interface ControlPointProps { 26 | hidden?: boolean 27 | absolutePosition: Point2D 28 | fill?: string | undefined 29 | stroke?: string | undefined 30 | isDragDisabled?: boolean 31 | lineNormal?: Point2D // TODO: less cryptic API to draw line control points 32 | onControlPointDrag(absolutePosition: Point2D): void 33 | } 34 | 35 | interface ControlPointState { 36 | isDragging: boolean 37 | dragPosition: Point2D 38 | previousDragPosition: Point2D 39 | dragDamping: number 40 | } 41 | 42 | export default class ControlPoint extends React.Component { 43 | 44 | readonly HIT_RADIUS = 8 45 | readonly RADIUS = 3 46 | readonly SHIFT_DRAG_DAMING = 0.1 47 | 48 | constructor(props: ControlPointProps) { 49 | super(props) 50 | 51 | this.state = { 52 | isDragging: false, 53 | dragPosition: { x: 0, y: 0 }, 54 | previousDragPosition: { x: 0, y: 0 }, 55 | dragDamping: 1 56 | } 57 | } 58 | 59 | componentDidMount() { 60 | document.addEventListener('keydown', this.handleKeyDown) 61 | document.addEventListener('keyup', this.handleKeyUp) 62 | } 63 | 64 | componentWillUnmount() { 65 | document.removeEventListener('keydown', this.handleKeyDown) 66 | document.removeEventListener('keyup', this.handleKeyUp) 67 | } 68 | 69 | handleKeyDown = (event: KeyboardEvent) => { 70 | if (event.key == 'Shift') { 71 | this.setState({ 72 | ...this.state, 73 | dragDamping: this.SHIFT_DRAG_DAMING 74 | }) 75 | } 76 | } 77 | 78 | handleKeyUp = (event: KeyboardEvent) => { 79 | if (event.key == 'Shift') { 80 | this.setState({ 81 | ...this.state, 82 | dragDamping: 1 83 | }) 84 | } 85 | } 86 | 87 | render() { 88 | if (this.props.hidden) { 89 | return null 90 | } 91 | 92 | return ( 93 | 94 | { 100 | this.setState({ 101 | ...this.state, 102 | isDragging: true, 103 | dragPosition: { x: event.target.x(), y: event.target.y() }, 104 | previousDragPosition: { x: event.target.x(), y: event.target.y() } 105 | }) 106 | this.onDragPositionChanged() 107 | }} 108 | onDragMove={(event: any) => { 109 | // Compute the drag delta 110 | const dx = event.target.x() - this.state.previousDragPosition.x 111 | const dy = event.target.y() - this.state.previousDragPosition.y 112 | // Compute the new drag position by adding the delta multiplied 113 | // by the damping factor 114 | const newDragPosition = { 115 | x: this.state.dragPosition.x + this.state.dragDamping * dx, 116 | y: this.state.dragPosition.y + this.state.dragDamping * dy 117 | } 118 | this.setState({ 119 | ...this.state, 120 | dragPosition: newDragPosition, 121 | previousDragPosition: { x: event.target.x(), y: event.target.y() } 122 | }) 123 | this.onDragPositionChanged() 124 | }} 125 | onDragEnd={(event: any) => { 126 | this.setState({ 127 | ...this.state, 128 | previousDragPosition: { x: event.target.x(), y: event.target.y() }, 129 | isDragging: false 130 | }) 131 | this.onDragPositionChanged() 132 | }} 133 | /> 134 | {this.renderVisualRepresentation()} 135 | 136 | ) 137 | } 138 | 139 | private renderVisualRepresentation() { 140 | let normal = this.props.lineNormal 141 | if (normal) { 142 | normal = MathUtil.normalized(normal) 143 | let width = 6 144 | return ( 145 | 158 | ) 159 | } else { 160 | return ( 161 | 170 | ) 171 | } 172 | } 173 | 174 | private onDragPositionChanged() { 175 | this.props.onControlPointDrag(this.state.dragPosition) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/gui/components/control-points-panel/control-polyline.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import { Circle, Group, Line } from 'react-konva' 21 | import Point2D from '../../solver/point-2d' 22 | import { Palette } from '../../style/palette' 23 | import { numberGlyph } from './glyph-paths' 24 | 25 | interface ControlPolylineProps { 26 | points: Point2D[] 27 | number?: number 28 | color?: string 29 | dimmed?: boolean 30 | dashed?: boolean 31 | } 32 | 33 | export default class ControlPolyline extends React.PureComponent { 34 | render() { 35 | 36 | let coords: number[] = [] 37 | let xMean = 0 38 | let yMean = 0 39 | for (let point of this.props.points) { 40 | xMean += (point.x / this.props.points.length) 41 | yMean += (point.y / this.props.points.length) 42 | coords.push(point.x) 43 | coords.push(point.y) 44 | } 45 | 46 | return ( 47 | 48 | 55 | {this.renderNumberCircle(xMean, yMean)} 56 | {this.renderNumber(xMean, yMean)} 57 | 58 | ) 59 | } 60 | 61 | private renderNumberCircle(x: number, y: number) { 62 | if (this.props.number === undefined) { 63 | return null 64 | } 65 | 66 | return ( 67 | 75 | ) 76 | } 77 | 78 | private renderNumber(x: number, y: number) { 79 | if (this.props.number === undefined) { 80 | return null 81 | } 82 | 83 | // Hacky offset for the 1 digit 84 | const xOffset = this.props.number == 1 ? -1 : 0 85 | 86 | return numberGlyph( 87 | this.props.number, 88 | Palette.white, 89 | { x: x + xOffset, y: y }, 90 | 7 91 | ) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/gui/components/control-points-panel/glyph-paths.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import Point2D from '../../solver/point-2d' 21 | import { Axis } from '../../types/calibration-settings' 22 | import { Group, Line } from 'react-konva' 23 | import { Palette } from '../../style/palette' 24 | 25 | const glyphPaths = { 26 | figure1: [ 27 | [ 28 | [ 29 | -0.1709821374999999, 30 | -0.17589274999999988 31 | ], 32 | [ 33 | 0.17098213750000008, 34 | -0.5 35 | ], 36 | [ 37 | 0.17098213750000008, 38 | 0.5 39 | ] 40 | ] 41 | ], 42 | figure2: [ 43 | [ 44 | [ 45 | -0.3263473330761437, 46 | -0.3682634531760995 47 | ], 48 | [ 49 | -0.18263476887466107, 50 | -0.4730539312612278 51 | ], 52 | [ 53 | -0.0029940339836018125, 54 | -0.5 55 | ], 56 | [ 57 | 0.1743789460156247, 58 | -0.42194266260127244 59 | ], 60 | [ 61 | 0.25449087314016877, 62 | -0.25149707627021006 63 | ], 64 | [ 65 | 0.19758905217851647, 66 | -0.05237164623497866 67 | ], 68 | [ 69 | -0.005988162812661348, 70 | 0.19760470167902497 71 | ], 72 | [ 73 | -0.34131738443733, 74 | 0.5 75 | ], 76 | [ 77 | 0.3413173844373297, 78 | 0.49700596601639885 79 | ] 80 | ] 81 | ], 82 | figure3: [ 83 | [ 84 | [ 85 | -0.2996154278146203, 86 | -0.5 87 | ], 88 | [ 89 | 0.29947643427758486, 90 | -0.5 91 | ], 92 | [ 93 | -0.0047942240463503425, 94 | -0.1229716069431239 95 | ], 96 | [ 97 | 0.23889560976979463, 98 | -0.0032272708127613968 99 | ], 100 | [ 101 | 0.3039342892843939, 102 | 0.23476335999326287 103 | ], 104 | [ 105 | 0.19365929735725046, 106 | 0.4326920286972525 107 | ], 108 | [ 109 | -0.010173320728757339, 110 | 0.5 111 | ], 112 | [ 113 | -0.19275115944945523, 114 | 0.423699334047801 115 | ], 116 | [ 117 | -0.3039342892843946, 118 | 0.2220440473794813 119 | ] 120 | ] 121 | ], 122 | x: [ 123 | [ 124 | [ 125 | -0.4, 126 | -0.5 127 | ], 128 | [ 129 | 0.4, 130 | 0.5 131 | ] 132 | ], 133 | [ 134 | [ 135 | -0.4, 136 | 0.5 137 | ], 138 | [ 139 | 0.4, 140 | -0.5 141 | ] 142 | ] 143 | ], 144 | y: [ 145 | [ 146 | [ 147 | -0.4, 148 | -0.5 149 | ], 150 | [ 151 | 0.0, 152 | -0.0071427499999998645 153 | ], 154 | [ 155 | 0.4, 156 | -0.5 157 | ] 158 | ], 159 | [ 160 | [ 161 | 0.0, 162 | -0.01607150000000104 163 | ], 164 | [ 165 | 0.0, 166 | 0.5 167 | ] 168 | ] 169 | ], 170 | z: [ 171 | [ 172 | [ 173 | -0.4, 174 | -0.5 175 | ], 176 | [ 177 | 0.4, 178 | -0.5 179 | ], 180 | [ 181 | -0.4, 182 | 0.5 183 | ], 184 | [ 185 | 0.4, 186 | 0.5 187 | ] 188 | ] 189 | ] 190 | } 191 | 192 | function scaledGlyphPath(position: Point2D, height: number, path: number[][][]): number[][] { 193 | let result: number[][] = [] 194 | for (let subpath of path) { 195 | let scaledSubpath: number[] = [] 196 | for (let coord of subpath) { 197 | scaledSubpath.push(position.x + height * coord[0]) 198 | scaledSubpath.push(position.y + height * coord[1]) 199 | } 200 | result.push(scaledSubpath) 201 | } 202 | 203 | return result 204 | } 205 | 206 | function glyph(glyphPath: number[][][], position: Point2D, height: number, color: string) { 207 | let scaled = scaledGlyphPath(position, height, glyphPath) 208 | 209 | return ( 210 | 211 | {scaled.map((path, i) => { 212 | return () 213 | })} 214 | 215 | ) 216 | } 217 | 218 | export function axisGlyph(axis: Axis, position: Point2D, height: number) { 219 | let glyphPath: number[][][] = [] 220 | switch (axis) { 221 | case Axis.PositiveX: 222 | glyphPath = glyphPaths.x 223 | break 224 | case Axis.PositiveY: 225 | glyphPath = glyphPaths.y 226 | break 227 | case Axis.PositiveZ: 228 | glyphPath = glyphPaths.z 229 | break 230 | default: 231 | break 232 | } 233 | 234 | return glyph(glyphPath, position, height, Palette.colorForAxis(axis)) 235 | } 236 | 237 | export function numberGlyph(num: number, color: string, position: Point2D, height: number) { 238 | let glyphPath: number[][][] = [] 239 | switch (num) { 240 | case 1: 241 | glyphPath = glyphPaths.figure1 242 | break 243 | case 2: 244 | glyphPath = glyphPaths.figure2 245 | break 246 | case 3: 247 | glyphPath = glyphPaths.figure3 248 | break 249 | default: 250 | break 251 | } 252 | return glyph(glyphPath, position, height, color) 253 | } 254 | -------------------------------------------------------------------------------- /src/gui/components/control-points-panel/horizon-control.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import ControlPolyline from './control-polyline' 21 | import ControlPoint from './control-point' 22 | import { Group } from 'react-konva' 23 | import { ControlPointPairState, ControlPointPairIndex } from '../../types/control-points-state' 24 | import Point2D from '../../solver/point-2d' 25 | import MathUtil from '../../solver/math-util' 26 | 27 | interface HorizonControlProps { 28 | vanishingPointIndex: number 29 | color: string 30 | pointPair: ControlPointPairState 31 | vanishingPoint: Point2D | null 32 | dragCallback(controlPointIndex: ControlPointPairIndex, position: Point2D): void 33 | } 34 | 35 | export default class HorizonControl extends React.PureComponent { 36 | render() { 37 | return ( 38 | 39 | {this.renderVanishingPoint()} 40 | 45 | { 48 | this.props.dragCallback(ControlPointPairIndex.First, position) 49 | }} 50 | fill={this.props.color} 51 | /> 52 | { 55 | this.props.dragCallback(ControlPointPairIndex.Second, position) 56 | }} 57 | fill={this.props.color} 58 | /> 59 | 60 | ) 61 | } 62 | 63 | private renderVanishingPoint() { 64 | if (!this.props.vanishingPoint) { 65 | return null 66 | } 67 | 68 | return ( 69 | 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/gui/components/control-points-panel/magnifying-glass.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import Point2D from '../../solver/point-2d' 21 | 22 | interface MagnifyingGlassProps { 23 | position: Point2D 24 | relativeImagePosition: Point2D 25 | imageWith: number 26 | imageHeight: number 27 | imageSrc: string | null 28 | } 29 | 30 | export default class MagnifyingGlass extends React.PureComponent { 31 | render() { 32 | const diameter = 180 33 | const zoom = 20 * diameter / this.props.imageWith 34 | if (!this.props.imageSrc) { 35 | return null 36 | } 37 | const xGlass = (this.props.position.x - diameter / 2) 38 | const yGlass = (this.props.position.y - diameter / 2) 39 | const xBg = -zoom * this.props.imageWith * this.props.relativeImagePosition.x + 0.5 * diameter 40 | const yBg = -zoom * this.props.imageHeight * this.props.relativeImagePosition.y + 0.5 * diameter 41 | const crossSize = 24 42 | return ( 43 |
54 |
64 |
65 |
73 |
81 |
82 | ) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/gui/components/control-points-panel/origin-control.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import ControlPoint from './control-point' 21 | import Point2D from '../../solver/point-2d' 22 | 23 | interface OriginControlProps { 24 | absolutePosition: Point2D 25 | dragCallback(position: Point2D): void 26 | } 27 | 28 | export default function OriginControl(props: OriginControlProps) { 29 | return ( 30 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /src/gui/components/control-points-panel/principal-point-control.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import ControlPoint from './control-point' 21 | import Point2D from '../../solver/point-2d' 22 | import { Palette } from '../../style/palette' 23 | 24 | interface PrincipalPointControlProps { 25 | absolutePosition: Point2D 26 | enabled: boolean 27 | visible: boolean 28 | dragCallback(absolutePosition: Point2D): void 29 | } 30 | 31 | export default function PrincipalPointControl(props: PrincipalPointControlProps) { 32 | if (!props.visible) { 33 | return null 34 | } 35 | 36 | return ( 37 | 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /src/gui/components/control-points-panel/reference-distance-anchor-control.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import React from 'react' 20 | import ControlPoint from './control-point' 21 | import Point2D from '../../solver/point-2d' 22 | import { Group, RegularPolygon } from 'react-konva' 23 | import { Palette } from '../../style/palette' 24 | import ControlPolyline from './control-polyline' 25 | 26 | interface ReferenceDistanceAnchorControlProps { 27 | absolutePosition: Point2D 28 | origin: Point2D 29 | uIntersection: Point2D 30 | vIntersection: Point2D 31 | anchorPositionIsValid: boolean 32 | dragCallback(position: Point2D): void 33 | } 34 | 35 | export default class ReferenceDistanceAnchorControl extends React.PureComponent { 36 | 37 | render() { 38 | return ( 39 | 40 | {this.renderLines()} 41 | {this.renderPositionWarning()} 42 | 47 | 48 | ) 49 | } 50 | 51 | private renderPositionWarning() { 52 | if (this.props.anchorPositionIsValid) { 53 | return null 54 | } 55 | 56 | return ( 57 | 65 | ) 66 | } 67 | 68 | private renderLines() { 69 | if (!this.props.anchorPositionIsValid) { 70 | return null 71 | } 72 | 73 | return ( 74 | 75 | 81 | 87 | 93 | 99 | 100 | ) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/gui/components/control-points-panel/reference-distance-control.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import ControlPoint from './control-point' 21 | import ReferenceDistanceAnchorControl from './reference-distance-anchor-control' 22 | import Point2D from '../../solver/point-2d' 23 | import { Palette } from '../../style/palette' 24 | import { Group } from 'react-konva' 25 | import ControlPolyline from './control-polyline' 26 | import MathUtil from '../../solver/math-util' 27 | import { Axis } from '../../types/calibration-settings' 28 | import Constants from '../../constants' 29 | 30 | export const dashedRulerStyle = { stroke: Palette.gray, opacity: 0.5, strokeDasharray: '2,6' } 31 | 32 | interface ReferenceDistanceControlProps { 33 | referenceAxis: Axis 34 | anchorPosition: Point2D 35 | handlePositions: [Point2D, Point2D] 36 | horizonVanishingPoints: [Point2D, Point2D] 37 | origin: Point2D 38 | uIntersection: Point2D 39 | vIntersection: Point2D 40 | anchorPositionIsValid: boolean 41 | anchorDragCallback(position: Point2D): void 42 | handleDragCallback(handleIndex: number, offset: Point2D): void 43 | } 44 | 45 | export default class ReferenceDistanceControl extends React.PureComponent { 46 | 47 | render() { 48 | if (Constants.referenceDistanceAnchorEnabled) { 49 | let horizonDir = MathUtil.normalized( 50 | MathUtil.difference( 51 | this.props.horizonVanishingPoints[0], 52 | this.props.horizonVanishingPoints[1] 53 | ) 54 | ) 55 | 56 | let l = 10000 57 | let horizonLineStart = { 58 | x: this.props.horizonVanishingPoints[0].x - l * horizonDir.x, 59 | y: this.props.horizonVanishingPoints[0].y - l * horizonDir.y 60 | } 61 | let horizonLineEnd = { 62 | x: this.props.horizonVanishingPoints[0].x + l * horizonDir.x, 63 | y: this.props.horizonVanishingPoints[0].y + l * horizonDir.y 64 | } 65 | 66 | return ( 67 | 68 | 76 | 84 | {this.renderDistanceHandles()} 85 | 86 | 87 | ) 88 | } else { 89 | return ( 90 | 91 | {this.renderDistanceHandles()} 92 | 93 | ) 94 | } 95 | } 96 | 97 | private renderDistanceHandles() { 98 | if (!this.props.anchorPositionIsValid) { 99 | return null 100 | } 101 | 102 | let normal = { 103 | x: this.props.anchorPosition.y - this.props.handlePositions[0].y, 104 | y: -this.props.anchorPosition.x + this.props.handlePositions[0].x 105 | } 106 | 107 | let axisColor = Palette.colorForAxis(this.props.referenceAxis) 108 | 109 | return ( 110 | 111 | 117 | 121 | { 125 | this.props.handleDragCallback(0, position) 126 | }} 127 | stroke={axisColor} 128 | /> 129 | { 133 | this.props.handleDragCallback(1, position) 134 | }} 135 | stroke={axisColor} 136 | /> 137 | 138 | ) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/gui/components/control-points-panel/vanishing-point-control.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import ControlPolyline from './control-polyline' 21 | import ControlPoint from './control-point' 22 | import { Group } from 'react-konva' 23 | import { VanishingPointControlState } from '../../types/control-points-state' 24 | import Point2D from '../../solver/point-2d' 25 | import MathUtil from '../../solver/math-util' 26 | 27 | interface VanishingPointControlProps { 28 | hideControlPoints?: boolean 29 | color: string 30 | controlState: VanishingPointControlState 31 | vanishingPoint: Point2D | null 32 | vanishingPointIndex: number 33 | 34 | onControlPointDrag( 35 | lineSegmentIndex: number, 36 | pointPairIndex: number, 37 | position: Point2D 38 | ): void 39 | } 40 | 41 | export default class VanishingPointControl extends React.PureComponent { 42 | render() { 43 | return ( 44 | 45 | {this.renderVanishingPoint()} 46 | {this.renderLineSegment(0)} 47 | {this.renderLineSegment(1)} 48 | 49 | ) 50 | } 51 | 52 | private renderLineSegment(index: number) { 53 | return ( 54 | 55 | 60 | 77 | ) 78 | } 79 | 80 | private renderVanishingPoint() { 81 | if (!this.props.vanishingPoint) { 82 | return null 83 | } 84 | 85 | let p1 = MathUtil.lineSegmentMidpoint( 86 | this.props.controlState.lineSegments[0] 87 | ) 88 | let p2 = this.props.vanishingPoint 89 | let p3 = MathUtil.lineSegmentMidpoint( 90 | this.props.controlState.lineSegments[1] 91 | ) 92 | return ( 93 | 98 | ) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/gui/components/result-panel/bullet-list.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import React from 'react' 20 | import { Palette } from '../../style/palette' 21 | 22 | export enum BulletListType { 23 | Warnings, 24 | Errors 25 | } 26 | 27 | interface BulletListProps { 28 | messages: string[] 29 | type: BulletListType 30 | } 31 | 32 | export default class BulletList extends React.PureComponent { 33 | render() { 34 | return ( 35 |
    36 | {this.props.messages.map((message: string, i: number) => this.renderBullet(i, message))} 37 |
38 | ) 39 | } 40 | 41 | private renderBullet(index: number, message: string) { 42 | return ( 43 |
  • 44 | {this.renderIcon()} {message} 45 |
  • 46 | ) 47 | } 48 | 49 | private renderIcon() { 50 | let isError = this.props.type == BulletListType.Errors 51 | let points: number[] = [] 52 | if (isError) { 53 | let r = 0.5 54 | for (let i = 0; i < 6; i++) { 55 | let angle = 2 * Math.PI * i / 6 56 | let x = 0.5 + r * Math.cos(angle) 57 | let y = 0.5 + r * Math.sin(angle) 58 | points.push(x) 59 | points.push(y) 60 | } 61 | } else { 62 | points = [ 63 | 0.0, 0.9, 64 | 0.5, -0.1, 65 | 1.0, 0.9 66 | ] 67 | } 68 | let color = isError ? Palette.red : Palette.orange 69 | let size = 12 70 | return ( 71 |
    72 | 73 | size * value).join(', ')} fill={color} /> 74 | 75 | 76 | 77 |
    78 | ) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/gui/components/result-panel/table-row.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import Button from '../common/button' 21 | import { clipboard } from 'electron' 22 | 23 | interface TableRowProps { 24 | title: string 25 | value: number | null 26 | isFirstRow?: boolean 27 | isLastRow?: boolean 28 | } 29 | 30 | export default class TableRow extends React.PureComponent { 31 | render() { 32 | 33 | const style: any = { display: 'flex', lineHeight: '24px' } 34 | if (this.props.isFirstRow == true) { 35 | style.marginTop = '5px' 36 | } else if (this.props.isLastRow == true) { 37 | style.marginBottom = '-5px' 38 | } 39 | 40 | return ( 41 |
    42 | {this.props.title} 43 | {this.valueDisplayString} 44 | 45 |
    50 | ) 51 | } 52 | 53 | private get valueClipboardString(): string { 54 | if (this.props.value === null) { 55 | return 'null' 56 | } 57 | return this.props.value.toString() 58 | } 59 | 60 | private get valueDisplayString(): string { 61 | if (this.props.value === null) { 62 | return 'n/a' 63 | } 64 | const value = this.props.value 65 | let result = '' 66 | if (typeof value === 'number') { 67 | if (value == Math.floor(value)) { 68 | result += this.props.value 69 | } else { 70 | result += this.props.value.toPrecision(7) 71 | } 72 | } else { 73 | result += this.props.value 74 | } 75 | 76 | return result 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/gui/components/settings-panel/axis-dropdown.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import React from 'react' 20 | import { Axis } from '../../types/calibration-settings' 21 | import Dropdown from '../common/dropdown' 22 | import { Palette } from '../../style/palette' 23 | 24 | interface AxisDropdownProps { 25 | selectedAxis: Axis 26 | onChange(axis: Axis): void 27 | } 28 | 29 | const options = [ 30 | { 31 | value: Axis.NegativeX, 32 | id: Axis.NegativeX, 33 | title: '-x', 34 | circleColor: Palette.red 35 | }, 36 | { 37 | value: Axis.PositiveX, 38 | id: Axis.PositiveX, 39 | title: 'x', 40 | circleColor: Palette.red 41 | }, 42 | { 43 | value: Axis.NegativeY, 44 | id: Axis.NegativeY, 45 | title: '-y', 46 | circleColor: Palette.green 47 | }, 48 | { 49 | value: Axis.PositiveY, 50 | id: Axis.PositiveY, 51 | title: 'y', 52 | circleColor: Palette.green 53 | }, 54 | { 55 | value: Axis.NegativeZ, 56 | id: Axis.NegativeZ, 57 | title: '-z', 58 | circleColor: Palette.blue 59 | }, 60 | { 61 | value: Axis.PositiveZ, 62 | id: Axis.PositiveZ, 63 | title: 'z', 64 | circleColor: Palette.blue 65 | } 66 | ] 67 | 68 | export default function AxisDropdown(props: AxisDropdownProps) { 69 | return ( 70 | { 76 | props.onChange(selectedValue) 77 | }} 78 | /> 79 | ) 80 | } 81 | -------------------------------------------------------------------------------- /src/gui/components/settings-panel/checkbox.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import React from 'react' 20 | 21 | interface CheckboxProps { 22 | title: string 23 | isSelected: boolean 24 | onChange(isSelected: boolean): void 25 | } 26 | 27 | export default function Checkbox(props: CheckboxProps) { 28 | return ( 29 |
    30 |
    {props.title}
    31 |
    32 | { 38 | props.onChange(event.target.checked) 39 | }} 40 | /> 41 |
    42 |
    43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/gui/components/settings-panel/overlay-3d-guide-dropdown.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import { Overlay3DGuide } from '../../types/global-settings' 21 | import Dropdown from '../common/dropdown' 22 | 23 | interface Overlay3DGuideDropdownProps { 24 | overlay3DGuide: Overlay3DGuide 25 | onChange(overlay3DGuide: Overlay3DGuide): void 26 | } 27 | 28 | const options = [ 29 | { 30 | value: Overlay3DGuide.None, 31 | id: Overlay3DGuide.None, 32 | title: 'Off' 33 | }, 34 | { 35 | value: Overlay3DGuide.Box, 36 | id: Overlay3DGuide.Box, 37 | title: 'Box' 38 | }, 39 | { 40 | value: Overlay3DGuide.YZGridFloor, 41 | id: Overlay3DGuide.YZGridFloor, 42 | title: 'yz grid floor' 43 | }, 44 | { 45 | value: Overlay3DGuide.ZXGridFloor, 46 | id: Overlay3DGuide.ZXGridFloor, 47 | title: 'xz grid floor' 48 | }, 49 | { 50 | value: Overlay3DGuide.XYGridFloor, 51 | id: Overlay3DGuide.XYGridFloor, 52 | title: 'xy grid floor' 53 | } 54 | ] 55 | 56 | export default function Overlay3DGuideDropdown(props: Overlay3DGuideDropdownProps) { 57 | return ( 58 | 63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /src/gui/components/settings-panel/reference-distance-axis-dropdown.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import React from 'react' 20 | import { Axis } from '../../types/calibration-settings' 21 | import Dropdown from '../common/dropdown' 22 | import { Palette } from '../../style/palette' 23 | import Constants from '../../constants' 24 | 25 | interface ReferenceDistanceAxisDropdownProps { 26 | selectedAxis: Axis | null 27 | onChange(axis: Axis | null): void 28 | } 29 | 30 | const options = [ 31 | { 32 | value: null, 33 | id: 'null', 34 | title: 'Default' 35 | }, 36 | { 37 | value: Axis.PositiveX, 38 | id: Axis.PositiveX, 39 | title: Constants.referenceDistanceAnchorEnabled ? 'In the x direction' : 'Along the x axis', 40 | circleColor: Palette.red 41 | }, 42 | { 43 | value: Axis.PositiveY, 44 | id: Axis.PositiveY, 45 | title: Constants.referenceDistanceAnchorEnabled ? 'In the y direction' : 'Along the y axis', 46 | circleColor: Palette.green 47 | }, 48 | { 49 | value: Axis.PositiveZ, 50 | id: Axis.PositiveZ, 51 | title: Constants.referenceDistanceAnchorEnabled ? 'In the z direction' : 'Along the z axis', 52 | circleColor: Palette.blue 53 | } 54 | ] 55 | 56 | export default function ReferenceDistanceAxisDropdown(props: ReferenceDistanceAxisDropdownProps) { 57 | return ( 58 | { 66 | props.onChange(selectedValue) 67 | }} 68 | /> 69 | ) 70 | } 71 | -------------------------------------------------------------------------------- /src/gui/components/settings-panel/reference-distance-form.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import ReferenceDistanceAxisDropdown from './reference-distance-axis-dropdown' 21 | import ReferenceDistanceUnitDropdown from './reference-distance-unit-dropdown' 22 | import NumericInputField from './../common/numeric-input-field' 23 | import PanelSpacer from './../common/panel-spacer' 24 | import { Axis, ReferenceDistanceUnit } from '../../types/calibration-settings' 25 | 26 | interface ReferenceDistanceFormProps { 27 | referenceAxis: Axis | null 28 | referenceDistance: number 29 | referenceDistanceUnit: ReferenceDistanceUnit 30 | onReferenceAxisChange(axis: Axis | null): void 31 | onReferenceDistanceChange(distance: number): void 32 | onReferenceDistanceUnitChange(unit: ReferenceDistanceUnit): void 33 | } 34 | 35 | export default class ReferenceDistanceForm extends React.PureComponent { 36 | 37 | render() { 38 | return ( 39 |
    40 | { 43 | this.props.onReferenceAxisChange(axis) 44 | }} 45 | /> 46 | { this.renderDistanceInputField() } 47 |
    48 | ) 49 | } 50 | 51 | private renderDistanceInputField() { 52 | if (this.props.referenceAxis == null) { 53 | return null 54 | } 55 | 56 | return ( 57 |
    58 | 59 |
    60 | 66 | { 70 | this.props.onReferenceDistanceUnitChange(unit) 71 | }} 72 | /> 73 |
    74 |
    75 | ) 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/gui/components/settings-panel/reference-distance-unit-dropdown.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import { ReferenceDistanceUnit } from '../../types/calibration-settings' 21 | import Dropdown from '../common/dropdown' 22 | 23 | interface ReferenceDistanceUnitDropdownProps { 24 | disabled?: boolean 25 | selectedUnit: ReferenceDistanceUnit 26 | onChange(unit: ReferenceDistanceUnit): void 27 | } 28 | 29 | const options = [ 30 | { 31 | value: ReferenceDistanceUnit.None, 32 | id: ReferenceDistanceUnit.None, 33 | title: ReferenceDistanceUnit.None 34 | }, 35 | { 36 | value: ReferenceDistanceUnit.Millimeters, 37 | id: ReferenceDistanceUnit.Millimeters, 38 | title: ReferenceDistanceUnit.Millimeters 39 | }, 40 | { 41 | value: ReferenceDistanceUnit.Centimeters, 42 | id: ReferenceDistanceUnit.Centimeters, 43 | title: ReferenceDistanceUnit.Centimeters 44 | }, 45 | { 46 | value: ReferenceDistanceUnit.Meters, 47 | id: ReferenceDistanceUnit.Meters, 48 | title: ReferenceDistanceUnit.Meters 49 | }, 50 | { 51 | value: ReferenceDistanceUnit.Kilometers, 52 | id: ReferenceDistanceUnit.Kilometers, 53 | title: ReferenceDistanceUnit.Kilometers 54 | }, 55 | { 56 | value: ReferenceDistanceUnit.Inches, 57 | id: ReferenceDistanceUnit.Inches, 58 | title: ReferenceDistanceUnit.Inches 59 | }, 60 | { 61 | value: ReferenceDistanceUnit.Feet, 62 | id: ReferenceDistanceUnit.Feet, 63 | title: ReferenceDistanceUnit.Feet 64 | }, 65 | { 66 | value: ReferenceDistanceUnit.Miles, 67 | id: ReferenceDistanceUnit.Miles, 68 | title: ReferenceDistanceUnit.Miles 69 | } 70 | ] 71 | 72 | export default function ReferenceDistanceUnitDropdown(props: ReferenceDistanceUnitDropdownProps) { 73 | return ( 74 | { 83 | props.onChange(unit) 84 | }} 85 | /> 86 | ) 87 | } 88 | -------------------------------------------------------------------------------- /src/gui/components/splash-screen.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import React from 'react' 20 | import { resourceURL } from '../io/util' 21 | import { Palette } from '../style/palette' 22 | import { remote } from 'electron' 23 | 24 | interface SplashScreenProps { 25 | onClickedLoadExampleProject(): void 26 | } 27 | 28 | export default function SplashScreen(props: SplashScreenProps) { 29 | return ( 30 |
    31 |
    { remote.app.getVersion() }
    32 |
    33 |
    34 | 38 |
    Drop an image or project here
    39 |
    40 |
    41 | 55 |
    56 | 57 |
    58 |
    59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /src/gui/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export default class Constants { 20 | // If false, the reference distance anchor is hidden and placed 21 | // at the origin control point 22 | static readonly referenceDistanceAnchorEnabled = false 23 | } 24 | -------------------------------------------------------------------------------- /src/gui/containers/result-container.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import { connect } from 'react-redux' 21 | 22 | import { AppAction, setCameraPreset, setCameraSensorSize, setFieldOfViewDisplayFormat, setOrientationDisplayFormat, setPrincipalPointDisplayFormat, SetDisplayAbsoluteFocalLength } from '../actions' 23 | 24 | import { ImageState } from '../types/image-state' 25 | import { StoreState } from '../types/store-state' 26 | import ResultPanel from '../components/result-panel/result-panel' 27 | import { SolverResult } from '../solver/solver-result' 28 | import { CalibrationSettingsBase } from '../types/calibration-settings' 29 | import { GlobalSettings } from '../types/global-settings' 30 | import { FieldOfViewFormat, OrientationFormat, PrincipalPointFormat, ResultDisplaySettings } from '../types/result-display-settings' 31 | import { Dispatch } from 'redux' 32 | 33 | interface ResultContainerProps { 34 | isVisible: boolean 35 | globalSettings: GlobalSettings 36 | calibrationSettings: CalibrationSettingsBase 37 | solverResult: SolverResult 38 | resultDisplaySettings: ResultDisplaySettings 39 | image: ImageState 40 | 41 | onCameraPresetChange(cameraPreset: string | null): void 42 | onSensorSizeChange(width: number | undefined, height: number | undefined): void 43 | onFieldOfViewDisplayFormatChanged(displayFormat: FieldOfViewFormat): void 44 | onOrientationDisplayFormatChanged(displayFormat: OrientationFormat): void 45 | onPrincipalPointDisplayFormatChanged(displayFormat: PrincipalPointFormat): void 46 | onDisplayAbsoluteFocalLengthChanged(enabled: boolean): void 47 | } 48 | 49 | class ResultContainer extends React.PureComponent { 50 | render() { 51 | if (!this.props.isVisible) { 52 | return null 53 | } 54 | 55 | return ( 56 | 69 | ) 70 | } 71 | } 72 | 73 | export function mapStateToProps(state: StoreState) { 74 | return { 75 | globalSettings: state.globalSettings, 76 | calibrationSettings: state.calibrationSettingsBase, 77 | calibrationMode: state.globalSettings.calibrationMode, 78 | solverResult: state.solverResult, 79 | resultDisplaySettings: state.resultDisplaySettings, 80 | image: state.image 81 | } 82 | } 83 | 84 | export function mapDispatchToProps(dispatch: Dispatch) { 85 | return { 86 | onCameraPresetChange: (cameraPreset: string | null) => { 87 | dispatch(setCameraPreset(cameraPreset)) 88 | }, 89 | onSensorSizeChange: (width: number | undefined, height: number | undefined) => { 90 | dispatch(setCameraSensorSize(width, height)) 91 | }, 92 | onFieldOfViewDisplayFormatChanged: (displayFormat: FieldOfViewFormat) => { 93 | dispatch(setFieldOfViewDisplayFormat(displayFormat)) 94 | }, 95 | onOrientationDisplayFormatChanged: (displayFormat: OrientationFormat) => { 96 | dispatch(setOrientationDisplayFormat(displayFormat)) 97 | }, 98 | onPrincipalPointDisplayFormatChanged: (displayFormat: PrincipalPointFormat) => { 99 | dispatch(setPrincipalPointDisplayFormat(displayFormat)) 100 | }, 101 | onDisplayAbsoluteFocalLengthChanged: (enabled: boolean) => { 102 | dispatch(SetDisplayAbsoluteFocalLength(enabled)) 103 | } 104 | } 105 | } 106 | 107 | export default connect(mapStateToProps, mapDispatchToProps)(ResultContainer) 108 | -------------------------------------------------------------------------------- /src/gui/containers/settings-container.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import { connect } from 'react-redux' 21 | import { Dispatch } from 'redux' 22 | import { AppAction, setCalibrationMode, setImageOpacity, setPrincipalPointMode1VP, setPrincipalPointMode2VP, setQuadModeEnabled, setReferenceDistanceUnit, setReferenceDistance, setReferenceDistanceAxis, setCameraPreset, setCameraSensorSize, setFirstVanishingPointAxis, setSecondVanishingPointAxis, setAbsoluteFocalLength1VP, setOverlay3DGuide } from '../actions' 23 | import SettingsPanel from '../components/settings-panel/settings-panel' 24 | import { CalibrationMode, GlobalSettings, Overlay3DGuide } from '../types/global-settings' 25 | import { StoreState } from '../types/store-state' 26 | import { CalibrationSettings1VP, CalibrationSettings2VP, PrincipalPointMode1VP, PrincipalPointMode2VP, Axis, ReferenceDistanceUnit, CalibrationSettingsBase } from '../types/calibration-settings' 27 | 28 | export interface SettingsContainerProps { 29 | isVisible: boolean 30 | globalSettings: GlobalSettings 31 | calibrationSettingsBase: CalibrationSettingsBase 32 | calibrationSettings1VP: CalibrationSettings1VP 33 | calibrationSettings2VP: CalibrationSettings2VP 34 | onCalibrationModeChange(calibrationMode: CalibrationMode): void 35 | onImageOpacityChange(opacity: number): void 36 | onOverlay3DGuideChange(overlay3DGuide: Overlay3DGuide): void 37 | onPrincipalPointModeChange1VP(principalPointMode: PrincipalPointMode1VP): void 38 | onPrincipalPointModeChange2VP(principalPointMode: PrincipalPointMode2VP): void 39 | onQuadModeEnabledChange(quadModeEnabled: boolean): void 40 | onFirstVanishingPointAxisChange(axis: Axis): void 41 | onSecondVanishingPointAxisChange(axis: Axis): void 42 | onAbsoluteFocalLengthChange1VP(absoluteFocalLength: number): void 43 | onReferenceDistanceAxisChange(axis: Axis | null): void 44 | onReferenceDistanceUnitChange(unit: ReferenceDistanceUnit): void 45 | onReferenceDistanceChange(distance: number): void 46 | onCameraPresetChange(cameraPreset: string | null): void 47 | onSensorSizeChange(width: number | undefined, height: number | undefined): void 48 | } 49 | 50 | class SettingsContainer extends React.PureComponent { 51 | render() { 52 | if (!this.props.isVisible) { 53 | return null 54 | } 55 | return ( 56 | 57 | ) 58 | } 59 | } 60 | 61 | export function mapStateToProps(state: StoreState) { 62 | return { 63 | globalSettings: state.globalSettings, 64 | calibrationSettingsBase: state.calibrationSettingsBase, 65 | calibrationSettings1VP: state.calibrationSettings1VP, 66 | calibrationSettings2VP: state.calibrationSettings2VP 67 | } 68 | } 69 | 70 | export function mapDispatchToProps(dispatch: Dispatch) { 71 | return { 72 | onCalibrationModeChange: (calibrationMode: CalibrationMode) => { 73 | dispatch(setCalibrationMode(calibrationMode)) 74 | }, 75 | onImageOpacityChange: (opacity: number) => { 76 | dispatch(setImageOpacity(opacity)) 77 | }, 78 | onOverlay3DGuideChange: (overlay3DGuide: Overlay3DGuide) => { 79 | dispatch(setOverlay3DGuide(overlay3DGuide)) 80 | }, 81 | onPrincipalPointModeChange1VP: (principalPointMode: PrincipalPointMode1VP) => { 82 | dispatch(setPrincipalPointMode1VP(principalPointMode)) 83 | }, 84 | onPrincipalPointModeChange2VP: (principalPointMode: PrincipalPointMode2VP) => { 85 | dispatch(setPrincipalPointMode2VP(principalPointMode)) 86 | }, 87 | onQuadModeEnabledChange: (quadModeEnabled: boolean) => { 88 | dispatch(setQuadModeEnabled(quadModeEnabled)) 89 | }, 90 | onFirstVanishingPointAxisChange: (axis: Axis) => { 91 | dispatch(setFirstVanishingPointAxis(axis)) 92 | }, 93 | onSecondVanishingPointAxisChange: (axis: Axis) => { 94 | dispatch(setSecondVanishingPointAxis(axis)) 95 | }, 96 | onAbsoluteFocalLengthChange1VP: (absoluteFocalLength: number) => { 97 | dispatch(setAbsoluteFocalLength1VP(absoluteFocalLength)) 98 | }, 99 | onReferenceDistanceAxisChange: (axis: Axis | null) => { 100 | dispatch(setReferenceDistanceAxis(axis)) 101 | }, 102 | onReferenceDistanceUnitChange: (unit: ReferenceDistanceUnit) => { 103 | dispatch(setReferenceDistanceUnit(unit)) 104 | }, 105 | onReferenceDistanceChange: (distance: number) => { 106 | dispatch(setReferenceDistance(distance)) 107 | }, 108 | onCameraPresetChange: (cameraPreset: string | null) => { 109 | dispatch(setCameraPreset(cameraPreset)) 110 | }, 111 | onSensorSizeChange: (width: number | undefined, height: number | undefined) => { 112 | dispatch(setCameraSensorSize(width, height)) 113 | } 114 | } 115 | } 116 | 117 | export default connect(mapStateToProps, mapDispatchToProps)(SettingsContainer) 118 | -------------------------------------------------------------------------------- /src/gui/defaults/calibration-settings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { CalibrationSettingsBase, CalibrationSettings1VP, CalibrationSettings2VP, ReferenceDistanceUnit, PrincipalPointMode1VP, Axis, PrincipalPointMode2VP } from '../types/calibration-settings' 20 | 21 | export const defaultCalibrationSettingsBase: CalibrationSettingsBase = { 22 | referenceDistanceAxis: null, 23 | referenceDistance: 4, 24 | referenceDistanceUnit: ReferenceDistanceUnit.Meters, 25 | cameraData: { 26 | presetId: null, 27 | presetData: null, 28 | customSensorWidth: 36, 29 | customSensorHeight: 24 30 | }, 31 | firstVanishingPointAxis: Axis.PositiveX, 32 | secondVanishingPointAxis: Axis.PositiveY 33 | } 34 | 35 | export const defaultCalibrationSettings1VP: CalibrationSettings1VP = { 36 | principalPointMode: PrincipalPointMode1VP.Default, 37 | absoluteFocalLength: 24 38 | } 39 | 40 | export const defaultCalibrationSettings2VP: CalibrationSettings2VP = { 41 | principalPointMode: PrincipalPointMode2VP.Default, 42 | quadModeEnabled: false 43 | } 44 | -------------------------------------------------------------------------------- /src/gui/defaults/control-points-state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ControlPointsStateBase, ControlPointsState1VP, ControlPointsState2VP } from '../types/control-points-state' 20 | import Constants from '../constants' 21 | 22 | const defaultOrigin = { 23 | x: 0.602536594761171, 24 | y: 0.36081762673040585 25 | } 26 | 27 | const defaultReferenceDistanceAnchor = { 28 | x: 0.5080116846430406, 29 | y: 0.6779647921622778 30 | } 31 | 32 | export const defaultControlPointsStateBase: ControlPointsStateBase = { 33 | principalPoint: { 34 | x: 0.5, 35 | y: 0.5 36 | }, 37 | origin: defaultOrigin, 38 | referenceDistanceAnchor: Constants.referenceDistanceAnchorEnabled ? defaultReferenceDistanceAnchor : defaultOrigin, 39 | referenceDistanceHandleOffsets: [ 40 | 0.13108430697783005, 41 | 0.065990108556575 42 | ], 43 | firstVanishingPoint: { 44 | lineSegments: [ 45 | [ 46 | { 47 | x: 0.4818953851492018, 48 | y: 0.18529755347139623 49 | }, 50 | { 51 | x: 0.3167539902845246, 52 | y: 0.2606999044047578 53 | } 54 | ], 55 | [ 56 | { 57 | x: 0.5280383992597733, 58 | y: 0.8574200419664915 59 | }, 60 | { 61 | x: 0.6498458824890123, 62 | y: 0.6917293352712968 63 | } 64 | ] 65 | ] 66 | } 67 | } 68 | 69 | export const defaultControlPointsState1VP: ControlPointsState1VP = { 70 | horizon: [ 71 | { 72 | x: 0.2, 73 | y: 0.5 74 | }, 75 | { 76 | x: 0.8, 77 | y: 0.5 78 | } 79 | ] 80 | } 81 | 82 | export const defaultControlPointsState2VP: ControlPointsState2VP = { 83 | secondVanishingPoint: { 84 | lineSegments: [ 85 | [ 86 | { 87 | x: 0.34623259623259617, 88 | y: 0.659551470318368 89 | }, 90 | { 91 | x: 0.49578722002635034, 92 | y: 0.8792706756358705 93 | } 94 | ], 95 | [ 96 | { 97 | x: 0.5056920556920557, 98 | y: 0.17069050371693362 99 | }, 100 | { 101 | x: 0.7026699426699425, 102 | y: 0.2689684285221547 103 | } 104 | ] 105 | ] 106 | }, 107 | thirdVanishingPoint: 108 | { 109 | lineSegments: [ 110 | [ 111 | { 112 | x: 0.33624078624078624, 113 | y: 0.6067665545026896 114 | }, 115 | { 116 | x: 0.30893837018837017, 117 | y: 0.3142575174095972 118 | } 119 | ], 120 | [ 121 | { 122 | x: 0.6774662162162162, 123 | y: 0.6094667009961638 124 | }, 125 | { 126 | x: 0.7070341932841933, 127 | y: 0.35057030079777063 128 | } 129 | ] 130 | ] 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/gui/defaults/global-settings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { CalibrationMode, GlobalSettings, Overlay3DGuide } from '../types/global-settings' 20 | 21 | export const defaultGlobalSettings: GlobalSettings = { 22 | calibrationMode: CalibrationMode.TwoVanishingPoints, 23 | imageOpacity: 0.2, 24 | overlay3DGuide: Overlay3DGuide.None 25 | } 26 | -------------------------------------------------------------------------------- /src/gui/defaults/image-state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ImageState } from '../types/image-state' 20 | 21 | export const defaultImageState: ImageState = { 22 | width: null, 23 | height: null, 24 | url: null, 25 | data: null 26 | } 27 | -------------------------------------------------------------------------------- /src/gui/defaults/result-display-settings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ResultDisplaySettings, OrientationFormat, PrincipalPointFormat, FieldOfViewFormat } from '../types/result-display-settings' 20 | 21 | export const defaultResultDisplaySettings: ResultDisplaySettings = { 22 | orientationFormat: OrientationFormat.AxisAngleDegrees, 23 | principalPointFormat: PrincipalPointFormat.Absolute, 24 | fieldOfViewFormat: FieldOfViewFormat.Degrees, 25 | displayAbsoluteFocalLength: false 26 | } 27 | -------------------------------------------------------------------------------- /src/gui/defaults/solver-result.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { SolverResult } from '../solver/solver-result' 20 | 21 | export const defaultSolverResult: SolverResult = { 22 | errors: [], 23 | warnings: [], 24 | cameraParameters: null 25 | } 26 | -------------------------------------------------------------------------------- /src/gui/defaults/ui-state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { UIState } from '../types/ui-state' 20 | 21 | export const defaultUIState: UIState = { 22 | sidePanelsAreVisible: true, 23 | projectFilePath: null, 24 | projectHasUnsavedChanges: false 25 | } 26 | -------------------------------------------------------------------------------- /src/gui/index.css: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | padding: 0; 22 | user-select: none; 23 | } 24 | 25 | textarea, body, button, input { 26 | font: caption; 27 | font-size: 12px; 28 | color: WindowText; 29 | } 30 | 31 | textarea { 32 | 33 | width: 100%; 34 | height: 80px; 35 | resize: none; 36 | border: none; 37 | overflow: auto; 38 | outline: none; 39 | 40 | -webkit-box-shadow: none; 41 | -moz-box-shadow: none; 42 | box-shadow: none; 43 | 44 | -webkit-box-sizing: border-box; 45 | -moz-box-sizing: border-box; 46 | box-sizing: border-box; 47 | } 48 | 49 | #app-container { 50 | display: flex; 51 | height: 100vh; 52 | overflow: hidden; 53 | } 54 | 55 | #left-panel { 56 | flex: 0 0 250px; 57 | max-width: 250px; 58 | } 59 | 60 | #panel-container { 61 | display: flex; 62 | flex-direction: column; 63 | height: 100vh; 64 | } 65 | 66 | #panel-top-container { 67 | flex-grow: 1; 68 | overflow-y: auto; 69 | } 70 | 71 | #center-panel { 72 | color: #898E91; 73 | background-color: #252B2E; 74 | flex-grow: 1; 75 | position: relative; 76 | overflow: hidden; 77 | } 78 | 79 | #right-panel { 80 | flex: 0 0 250px; 81 | max-width: 250px; 82 | } 83 | 84 | #image-panel { 85 | width: 100%; 86 | height: 100vh; 87 | } 88 | 89 | .side-panel { 90 | background-color: Menu; 91 | } 92 | 93 | .panel-group-title { 94 | margin-bottom: 5px; 95 | margin-left: 1px; 96 | } 97 | 98 | .panel-section { 99 | padding: 10px 10px 10px 10px; 100 | } 101 | 102 | .bottom-border { 103 | border-bottom: 1px solid #e0e0e0; 104 | } 105 | 106 | .top-border { 107 | border-top: 1px solid #e0e0e0; 108 | } 109 | 110 | :not(input):not(textarea), 111 | :not(input):not(textarea)::after, 112 | :not(input):not(textarea)::before { 113 | -webkit-user-select: none; 114 | user-select: none; 115 | cursor: default; 116 | } 117 | 118 | .exporter-instructions-container li { 119 | padding-bottom: 10px; 120 | } 121 | 122 | .copy-button { 123 | background-color: #e8e8e8; 124 | color: #909090; 125 | outline: none; 126 | border: none; 127 | box-shadow: none; 128 | } 129 | 130 | .copy-button:active { 131 | background-color: #c0c0c0; 132 | } -------------------------------------------------------------------------------- /src/gui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
    9 | 10 | -------------------------------------------------------------------------------- /src/gui/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as React from 'react' 20 | import * as ReactDOM from 'react-dom' 21 | import App from './App' 22 | import store from './store/store' 23 | import { Provider } from 'react-redux' 24 | import './index.css' 25 | 26 | ReactDOM.render( 27 | 28 | 29 | , 30 | document.getElementById('root') as HTMLElement 31 | ) 32 | -------------------------------------------------------------------------------- /src/gui/io/saved-state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { GlobalSettings } from '../types/global-settings' 20 | import { CalibrationSettingsBase, CalibrationSettings1VP, CalibrationSettings2VP } from '../types/calibration-settings' 21 | import { ControlPointsStateBase, ControlPointsState1VP, ControlPointsState2VP } from '../types/control-points-state' 22 | import { CameraParameters } from '../solver/solver-result' 23 | import { ResultDisplaySettings } from '../types/result-display-settings' 24 | 25 | export default interface SavedState { 26 | globalSettings: GlobalSettings 27 | 28 | calibrationSettingsBase: CalibrationSettingsBase 29 | calibrationSettings1VP: CalibrationSettings1VP 30 | calibrationSettings2VP: CalibrationSettings2VP 31 | 32 | controlPointsStateBase: ControlPointsStateBase 33 | controlPointsState1VP: ControlPointsState1VP 34 | controlPointsState2VP: ControlPointsState2VP 35 | 36 | cameraParameters: CameraParameters | null 37 | resultDisplaySettings: ResultDisplaySettings 38 | } 39 | -------------------------------------------------------------------------------- /src/gui/io/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { join } from 'path' 20 | 21 | export function loadImage( 22 | imageBuffer: Buffer, 23 | onLoad: (width: number, height: number, url: string) => void, 24 | onError: () => void 25 | ) { 26 | let blob = new Blob([imageBuffer]) 27 | let url = URL.createObjectURL(blob) 28 | let image = new Image() 29 | image.src = url 30 | image.onload = (_: Event) => { 31 | onLoad(image.width, image.height, url) 32 | } 33 | image.onerror = (_) => { 34 | onError() 35 | } 36 | } 37 | 38 | export function resourceURL(fileName: string): string { 39 | if (process.resourcesPath != null) { 40 | if (process.env.DEV) { 41 | return join(`file://${process.cwd()}`, 'assets/electron', fileName) 42 | } else { 43 | return join(process.resourcesPath, fileName) 44 | } 45 | } 46 | 47 | return '' 48 | } 49 | 50 | export function resourcePath(fileName: string): string { 51 | if (process.resourcesPath != null) { 52 | if (process.env.DEV) { 53 | return join(process.cwd(), 'assets/electron', fileName) 54 | } else { 55 | return join(process.resourcesPath, fileName) 56 | } 57 | } 58 | 59 | return '' 60 | } 61 | -------------------------------------------------------------------------------- /src/gui/ipc-messages.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ExportType } from '../main/ipc-messages' 20 | 21 | // Messages sent from the renderer process to the main process 22 | 23 | export class SpecifyProjectPathMessage { 24 | static readonly type = 'SpecifyProjectPathMessage' 25 | } 26 | 27 | export class SpecifyExportPathMessage { 28 | static readonly type = 'SpecifyExportPathMessage' 29 | readonly data: any 30 | readonly exportType: ExportType 31 | constructor(exportType: ExportType, data: any) { 32 | this.exportType = exportType 33 | this.data = data 34 | } 35 | } 36 | 37 | export class OpenDroppedProjectMessage { 38 | static readonly type = 'OpenDroppedProjectMessage' 39 | readonly filePath: string 40 | constructor(filePath: string) { 41 | this.filePath = filePath 42 | } 43 | } 44 | 45 | export class SetDocumentStateMessage { 46 | static readonly type = 'SetDocumentStateMessage' 47 | readonly hasUnsavedChanges: boolean | undefined 48 | readonly filePath: string | null | undefined 49 | readonly isExampleProject: boolean | undefined 50 | constructor( 51 | hasUnsavedChanges: boolean | undefined, 52 | filePath: string | null | undefined, 53 | isExampleProject: boolean | undefined 54 | ) { 55 | this.hasUnsavedChanges = hasUnsavedChanges 56 | this.filePath = filePath 57 | this.isExampleProject = isExampleProject 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/gui/reducers/calibration-settings-1-vp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { CalibrationSettings1VP } from '../types/calibration-settings' 20 | import { ActionTypes, AppAction } from '../actions' 21 | import { defaultCalibrationSettings1VP } from '../defaults/calibration-settings' 22 | 23 | export function calibrationSettings1VP(state: CalibrationSettings1VP | undefined, action: AppAction): CalibrationSettings1VP { 24 | if (state === undefined) { 25 | return defaultCalibrationSettings1VP 26 | } 27 | 28 | switch (action.type) { 29 | case ActionTypes.SET_PRINCIPAL_POINT_MODE_1VP: 30 | return { 31 | ...state, 32 | principalPointMode: action.principalPointMode 33 | } 34 | case ActionTypes.SET_ABSOLUTE_FOCAL_LENGTH_1VP: 35 | return { 36 | ...state, 37 | absoluteFocalLength: action.absoluteFocalLength 38 | } 39 | case ActionTypes.LOAD_STATE: 40 | return action.savedState.calibrationSettings1VP 41 | case ActionTypes.LOAD_DEFAULT_STATE: 42 | return defaultCalibrationSettings1VP 43 | } 44 | 45 | return state 46 | } 47 | -------------------------------------------------------------------------------- /src/gui/reducers/calibration-settings-2-vp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { CalibrationSettings2VP } from '../types/calibration-settings' 20 | import { ActionTypes, AppAction } from '../actions' 21 | import { defaultCalibrationSettings2VP } from '../defaults/calibration-settings' 22 | 23 | export function calibrationSettings2VP(state: CalibrationSettings2VP | undefined, action: AppAction): CalibrationSettings2VP { 24 | if (state === undefined) { 25 | return defaultCalibrationSettings2VP 26 | } 27 | 28 | switch (action.type) { 29 | case ActionTypes.SET_PRINCIPAL_POINT_MODE_2VP: 30 | return { 31 | ...state, 32 | principalPointMode: action.principalPointMode 33 | } 34 | case ActionTypes.SET_QUAD_MODE_ENABLED: 35 | return { 36 | ...state, 37 | quadModeEnabled: action.quadModeEnabled 38 | } 39 | case ActionTypes.LOAD_STATE: 40 | return action.savedState.calibrationSettings2VP 41 | case ActionTypes.LOAD_DEFAULT_STATE: 42 | return defaultCalibrationSettings2VP 43 | } 44 | 45 | return state 46 | } 47 | -------------------------------------------------------------------------------- /src/gui/reducers/calibration-settings-base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { CalibrationSettingsBase, CameraData } from '../types/calibration-settings' 20 | import { defaultCalibrationSettingsBase } from '../defaults/calibration-settings' 21 | import { ActionTypes, AppAction } from '../actions' 22 | import { cameraPresets } from '../solver/camera-presets' 23 | 24 | export function calibrationSettingsBase(state: CalibrationSettingsBase | undefined, action: AppAction): CalibrationSettingsBase { 25 | if (state === undefined) { 26 | return defaultCalibrationSettingsBase 27 | } 28 | 29 | switch (action.type) { 30 | case ActionTypes.SET_REFERENCE_DISTANCE_AXIS: 31 | return { 32 | ...state, 33 | referenceDistanceAxis: action.axis 34 | } 35 | case ActionTypes.SET_REFERENCE_DISTANCE: 36 | return { 37 | ...state, 38 | referenceDistance: action.distance 39 | } 40 | case ActionTypes.SET_REFERENCE_DISTANCE_UNIT: 41 | return { 42 | ...state, 43 | referenceDistanceUnit: action.unit 44 | } 45 | case ActionTypes.SET_CAMERA_PRESET: 46 | // Update the preset id 47 | let newCameraData: CameraData = { 48 | ...state.cameraData, 49 | presetId: action.cameraPresetId, 50 | presetData: null 51 | } 52 | // Also store the preset data. Not accessed by fSpy internally 53 | // but may come in handy for importers etc 54 | if (action.cameraPresetId) { 55 | const cameraPreset = cameraPresets[action.cameraPresetId] 56 | if (cameraPreset) { 57 | newCameraData = { 58 | ...newCameraData, 59 | presetData: cameraPreset 60 | } 61 | } 62 | } 63 | return { 64 | ...state, 65 | cameraData: newCameraData 66 | } 67 | case ActionTypes.SET_CAMERA_SENSOR_SIZE: 68 | let oldCameraData = state.cameraData 69 | return { 70 | ...state, 71 | cameraData: { 72 | ...state.cameraData, 73 | customSensorWidth: action.width != undefined ? action.width : oldCameraData.customSensorWidth, 74 | customSensorHeight: action.height != undefined ? action.height : oldCameraData.customSensorHeight 75 | } 76 | } 77 | case ActionTypes.SET_FIRST_VANISHING_POINT_AXIS: 78 | return { 79 | ...state, 80 | firstVanishingPointAxis: action.axis 81 | } 82 | case ActionTypes.SET_SECOND_VANISHING_POINT_AXIS: 83 | return { 84 | ...state, 85 | secondVanishingPointAxis: action.axis 86 | } 87 | case ActionTypes.LOAD_STATE: 88 | return action.savedState.calibrationSettingsBase 89 | case ActionTypes.LOAD_DEFAULT_STATE: 90 | return defaultCalibrationSettingsBase 91 | } 92 | 93 | return state 94 | } 95 | -------------------------------------------------------------------------------- /src/gui/reducers/control-points-1-vp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ActionTypes, AppAction } from '../actions' 20 | import { ControlPointsState1VP } from '../types/control-points-state' 21 | import { defaultControlPointsState1VP } from '../defaults/control-points-state' 22 | 23 | export function controlPointsState1VP(state: ControlPointsState1VP | undefined, action: AppAction): ControlPointsState1VP { 24 | if (state === undefined) { 25 | return { 26 | ...defaultControlPointsState1VP 27 | } 28 | } 29 | 30 | switch (action.type) { 31 | case ActionTypes.ADJUST_HORIZON: 32 | let updatedHorizon = { ...state.horizon } 33 | updatedHorizon[action.controlPointIndex] = action.position 34 | return { 35 | ...state, 36 | horizon: updatedHorizon 37 | } 38 | case ActionTypes.LOAD_STATE: 39 | return action.savedState.controlPointsState1VP 40 | case ActionTypes.LOAD_DEFAULT_STATE: 41 | return defaultControlPointsState1VP 42 | } 43 | 44 | return state 45 | } 46 | -------------------------------------------------------------------------------- /src/gui/reducers/control-points-2-vp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ActionTypes, AppAction } from '../actions' 20 | import { ControlPointsState2VP } from '../types/control-points-state' 21 | import { defaultControlPointsState2VP } from '../defaults/control-points-state' 22 | 23 | export function controlPointsState2VP(state: ControlPointsState2VP | undefined, action: AppAction): ControlPointsState2VP { 24 | if (state === undefined) { 25 | return { 26 | ...defaultControlPointsState2VP 27 | } 28 | } 29 | 30 | switch (action.type) { 31 | case ActionTypes.ADJUST_SECOND_VANISHING_POINT: { 32 | // TODO: proper deep copy 33 | let secondVanishingPoint = { ...state.secondVanishingPoint } 34 | let adjustedLineSegment = secondVanishingPoint.lineSegments[action.lineSegmentIndex] 35 | adjustedLineSegment[action.controlPointIndex] = action.position 36 | 37 | return { 38 | ...state, 39 | secondVanishingPoint: secondVanishingPoint 40 | } 41 | } 42 | case ActionTypes.ADJUST_THIRD_VANISHING_POINT: { 43 | // TODO: proper deep copy 44 | let thirdVanishingPoint = { ...state.thirdVanishingPoint } 45 | let adjustedLineSegment = thirdVanishingPoint.lineSegments[action.lineSegmentIndex] 46 | adjustedLineSegment[action.controlPointIndex] = action.position 47 | 48 | return { 49 | ...state, 50 | thirdVanishingPoint: thirdVanishingPoint 51 | } 52 | } 53 | case ActionTypes.LOAD_STATE: 54 | return action.savedState.controlPointsState2VP 55 | case ActionTypes.LOAD_DEFAULT_STATE: 56 | return defaultControlPointsState2VP 57 | } 58 | 59 | return state 60 | } 61 | -------------------------------------------------------------------------------- /src/gui/reducers/control-points-base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ControlPointsStateBase } from '../types/control-points-state' 20 | import { defaultControlPointsStateBase } from '../defaults/control-points-state' 21 | import { ActionTypes, AppAction } from '../actions' 22 | import Constants from '../constants' 23 | 24 | export function controlPointsStateBase(state: ControlPointsStateBase | undefined, action: AppAction): ControlPointsStateBase { 25 | if (state === undefined) { 26 | return { 27 | ...defaultControlPointsStateBase 28 | } 29 | } 30 | 31 | switch (action.type) { 32 | case ActionTypes.SET_ORIGIN: 33 | if (Constants.referenceDistanceAnchorEnabled) { 34 | return { 35 | ...state, 36 | origin: action.position 37 | } 38 | } else { 39 | return { 40 | ...state, 41 | origin: action.position, 42 | referenceDistanceAnchor: action.position 43 | } 44 | } 45 | 46 | case ActionTypes.SET_PRINCIPAL_POINT: 47 | return { 48 | ...state, 49 | principalPoint: action.position 50 | } 51 | case ActionTypes.SET_REFERENCE_DISTANCE_ANCHOR: 52 | return { 53 | ...state, 54 | referenceDistanceAnchor: action.position 55 | } 56 | case ActionTypes.ADJUST_REFERENCE_DISTANCE_HANDLE: 57 | let adjustedOffsets = [...state.referenceDistanceHandleOffsets] 58 | adjustedOffsets[action.handleIndex] = action.position 59 | return { 60 | ...state, 61 | referenceDistanceHandleOffsets: [adjustedOffsets[0], adjustedOffsets[1]] 62 | } 63 | case ActionTypes.ADJUST_FIRST_VANISHING_POINT: 64 | let adjustedVanishingPoint = { ...state.firstVanishingPoint } 65 | let adjustedLineSegment = adjustedVanishingPoint.lineSegments[action.lineSegmentIndex] 66 | adjustedLineSegment[action.controlPointIndex] = action.position 67 | 68 | return { 69 | ...state, 70 | firstVanishingPoint: adjustedVanishingPoint 71 | } 72 | case ActionTypes.LOAD_STATE: 73 | return action.savedState.controlPointsStateBase 74 | case ActionTypes.LOAD_DEFAULT_STATE: 75 | return defaultControlPointsStateBase 76 | } 77 | 78 | return state 79 | } 80 | -------------------------------------------------------------------------------- /src/gui/reducers/global-settings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ActionTypes, AppAction } from '../actions' 20 | import { GlobalSettings } from './../types/global-settings' 21 | import { defaultGlobalSettings } from './../defaults/global-settings' 22 | 23 | export function globalSettings(state: GlobalSettings | undefined, action: AppAction): GlobalSettings { 24 | if (state === undefined) { 25 | return defaultGlobalSettings 26 | } 27 | 28 | switch (action.type) { 29 | case ActionTypes.SET_CALIBRATION_MODE: 30 | return { 31 | ...state, 32 | calibrationMode: action.calibrationMode 33 | } 34 | case ActionTypes.SET_IMAGE_OPACITY: 35 | return { 36 | ...state, 37 | imageOpacity: action.opacity 38 | } 39 | case ActionTypes.SET_OVERLAY_3D_GUIDE: 40 | return { 41 | ...state, 42 | overlay3DGuide: action.overlay3DGuide 43 | } 44 | case ActionTypes.LOAD_STATE: 45 | return action.savedState.globalSettings 46 | case ActionTypes.LOAD_DEFAULT_STATE: 47 | return defaultGlobalSettings 48 | } 49 | 50 | return state 51 | } 52 | -------------------------------------------------------------------------------- /src/gui/reducers/image-state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ActionTypes, AppAction } from '../actions' 20 | import { ImageState } from '../types/image-state' 21 | import { defaultImageState } from '../defaults/image-state' 22 | 23 | export function imageState(state: ImageState | undefined, action: AppAction): ImageState { 24 | if (state === undefined) { 25 | return defaultImageState 26 | } 27 | 28 | switch (action.type) { 29 | case ActionTypes.SET_IMAGE: 30 | return { 31 | ...state, 32 | data: action.data, 33 | url: action.url, 34 | width: action.width, 35 | height: action.height 36 | } 37 | case ActionTypes.LOAD_STATE: 38 | return action.imageState 39 | case ActionTypes.LOAD_DEFAULT_STATE: 40 | return { 41 | ...state, 42 | data: null, 43 | url: null, 44 | width: null, 45 | height: null 46 | } 47 | } 48 | 49 | return state 50 | } 51 | -------------------------------------------------------------------------------- /src/gui/reducers/result-display-settings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ActionTypes, AppAction } from '../actions' 20 | import { ResultDisplaySettings } from '../types/result-display-settings' 21 | import { defaultResultDisplaySettings } from '../defaults/result-display-settings' 22 | 23 | export function resultDisplaySettings(state: ResultDisplaySettings | undefined, action: AppAction): ResultDisplaySettings { 24 | if (state === undefined) { 25 | return defaultResultDisplaySettings 26 | } 27 | 28 | switch (action.type) { 29 | case ActionTypes.SET_FOV_DISPLAY_FORMAT: 30 | return { 31 | ...state, 32 | fieldOfViewFormat: action.displayFormat 33 | } 34 | case ActionTypes.SET_ORIENTATION_DISPLAY_FORMAT: 35 | return { 36 | ...state, 37 | orientationFormat: action.displayFormat 38 | } 39 | case ActionTypes.SET_PRINCIPAL_POINT_DISPLAY_FORMAT: 40 | return { 41 | ...state, 42 | principalPointFormat: action.displayFormat 43 | } 44 | case ActionTypes.SET_DISPLAY_ABSOLUTE_FOCAL_LENGTH: 45 | return { 46 | ...state, 47 | displayAbsoluteFocalLength: action.displayAbsoluteFocalLength 48 | } 49 | case ActionTypes.LOAD_STATE: 50 | return { 51 | ...state, 52 | ...action.savedState.resultDisplaySettings 53 | } 54 | case ActionTypes.LOAD_DEFAULT_STATE: 55 | return { 56 | ...defaultResultDisplaySettings 57 | } 58 | } 59 | 60 | return state 61 | } 62 | -------------------------------------------------------------------------------- /src/gui/reducers/root.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { combineReducers } from 'redux' 20 | import { StoreState } from '../types/store-state' 21 | import { calibrationSettingsBase } from './calibration-settings-base' 22 | import { calibrationSettings1VP } from './calibration-settings-1-vp' 23 | import { calibrationSettings2VP } from './calibration-settings-2-vp' 24 | import { controlPointsStateBase } from './control-points-base' 25 | import { controlPointsState1VP } from './control-points-1-vp' 26 | import { controlPointsState2VP } from './control-points-2-vp' 27 | import { resultDisplaySettings } from './result-display-settings' 28 | import { solverResult } from './solver-result' 29 | import { globalSettings } from './global-settings' 30 | import { imageState } from './image-state' 31 | import { uiState } from './ui-state' 32 | 33 | const rootReducer = combineReducers({ 34 | globalSettings, 35 | calibrationSettingsBase, 36 | calibrationSettings1VP, 37 | calibrationSettings2VP, 38 | controlPointsStateBase, 39 | controlPointsState1VP, 40 | controlPointsState2VP, 41 | solverResult, 42 | image: imageState, 43 | resultDisplaySettings, 44 | uiState 45 | }) 46 | 47 | export default rootReducer 48 | -------------------------------------------------------------------------------- /src/gui/reducers/solver-result.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ActionTypes } from './../actions' 20 | import { AnyAction } from 'redux' 21 | import { SolverResult } from '../solver/solver-result' 22 | import { defaultSolverResult } from '../defaults/solver-result' 23 | 24 | export function solverResult(state: SolverResult | undefined, action: AnyAction): SolverResult { 25 | if (state === undefined) { 26 | return defaultSolverResult 27 | } 28 | 29 | switch (action.type) { 30 | case ActionTypes.SET_SOLVER_RESULT: 31 | return action.result 32 | } 33 | 34 | return state 35 | } 36 | -------------------------------------------------------------------------------- /src/gui/reducers/ui-state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ActionTypes, AppAction } from '../actions' 20 | import { UIState } from '../types/ui-state' 21 | import { defaultUIState } from '../defaults/ui-state' 22 | import { ipcRenderer } from 'electron' 23 | import { SetDocumentStateMessage } from '../ipc-messages' 24 | 25 | export function uiState(state: UIState | undefined, action: AppAction): UIState { 26 | if (state === undefined) { 27 | return defaultUIState 28 | } 29 | 30 | // TODO: move these ipc calls somewhere else? 31 | 32 | switch (action.type) { 33 | case ActionTypes.SET_PROJECT_HAS_UNSAVED_CHANGES: 34 | ipcRenderer.send( 35 | SetDocumentStateMessage.type, 36 | new SetDocumentStateMessage(true, undefined, undefined) 37 | ) 38 | return { 39 | ...state, 40 | projectHasUnsavedChanges: true 41 | } 42 | case ActionTypes.SET_PROJECT_FILE_PATH: 43 | ipcRenderer.send( 44 | SetDocumentStateMessage.type, 45 | new SetDocumentStateMessage(false, action.projectFilePath, false) 46 | ) 47 | return { 48 | ...state, 49 | projectFilePath: action.projectFilePath 50 | } 51 | case ActionTypes.LOAD_DEFAULT_STATE: 52 | ipcRenderer.send( 53 | SetDocumentStateMessage.type, 54 | new SetDocumentStateMessage(false, null, false) 55 | ) 56 | return { 57 | ...state, 58 | projectHasUnsavedChanges: false, 59 | projectFilePath: null 60 | } 61 | case ActionTypes.SET_SIDE_PANEL_VISIBILITY: 62 | return { 63 | ...state, 64 | sidePanelsAreVisible: action.panelsAreVisible 65 | } 66 | case ActionTypes.LOAD_STATE: 67 | ipcRenderer.send( 68 | SetDocumentStateMessage.type, 69 | new SetDocumentStateMessage( 70 | false, 71 | action.projectFilePath, 72 | action.isExampleProject 73 | ) 74 | ) 75 | return { 76 | ...state, 77 | projectHasUnsavedChanges: false, 78 | projectFilePath: action.isExampleProject ? null : action.projectFilePath 79 | } 80 | } 81 | 82 | return state 83 | } 84 | -------------------------------------------------------------------------------- /src/gui/solver/aabb-ops.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import AABB from './aabb' 20 | import Point2D from './point-2d' 21 | 22 | export default class AABBOps { 23 | 24 | static width(aabb: AABB): number { 25 | return Math.abs(aabb.xMax - aabb.xMin) 26 | } 27 | 28 | static height(aabb: AABB): number { 29 | return Math.abs(aabb.yMax - aabb.yMin) 30 | } 31 | 32 | static maxDimension(aabb: AABB): number { 33 | return Math.max(this.width(aabb), this.height(aabb)) 34 | } 35 | 36 | static minDimension(aabb: AABB): number { 37 | return Math.min(this.width(aabb), this.height(aabb)) 38 | } 39 | 40 | static aspectRatio(aabb: AABB): number { 41 | let height = this.height(aabb) 42 | if (height == 0) { 43 | return 0 44 | } 45 | return this.width(aabb) / height 46 | } 47 | 48 | static relativePosition(aabb: AABB, xRelative: number, yRelative: number): Point2D { 49 | return { 50 | x: aabb.xMin + xRelative * this.width(aabb), 51 | y: aabb.yMin + yRelative * this.height(aabb) 52 | } 53 | } 54 | 55 | static midPoint(aabb: AABB): Point2D { 56 | return this.relativePosition(aabb, 0.5, 0.5) 57 | } 58 | 59 | static boundingBox(points: Point2D[], padding: number = 0): AABB { 60 | if (points.length == 0) { 61 | return { xMin: 0, yMin: 0, xMax: 0, yMax: 0 } 62 | } 63 | 64 | let xMin = points[0].x 65 | let xMax = xMin 66 | let yMin = points[0].y 67 | let yMax = yMin 68 | 69 | for (let point of points) { 70 | if (point.x > xMax) { 71 | xMax = point.x 72 | } else if (point.x < xMin) { 73 | xMin = point.x 74 | } 75 | 76 | if (point.y > yMax) { 77 | yMax = point.y 78 | } else if (point.y < yMin) { 79 | yMin = point.y 80 | } 81 | } 82 | 83 | return { 84 | xMin: xMin - padding, 85 | yMin: yMin - padding, 86 | xMax: xMax + padding, 87 | yMax: yMax + padding 88 | } 89 | } 90 | 91 | /** 92 | * Returns the largest rect with a given aspect ratio 93 | * that fits inside this rect. 94 | * @param aspectRatio 95 | * @param relativeOffset 96 | */ 97 | static maximumInteriorAABB(aabb: AABB, aspectRatio: number, relativeOffset: number = 0.5): AABB { 98 | 99 | let width = this.width(aabb) 100 | let height = this.height(aabb) 101 | 102 | if (this.aspectRatio(aabb) < aspectRatio) { 103 | // The rect to fit is wider than the containing rect. Cap width. 104 | height = this.width(aabb) / aspectRatio 105 | } else { 106 | // The rect to fit is taller than the containing rect. Cap height. 107 | width = this.height(aabb) * aspectRatio 108 | } 109 | 110 | let xMin = aabb.xMin + relativeOffset * (this.width(aabb) - width) 111 | let yMin = aabb.yMin + relativeOffset * (this.height(aabb) - height) 112 | 113 | return { 114 | xMin: xMin, 115 | yMin: yMin, 116 | xMax: xMin + width, 117 | yMax: yMin + height 118 | } 119 | } 120 | 121 | static containsPoint(aabb: AABB, point: Point2D): boolean { 122 | let xInside = point.x >= aabb.xMin && point.x <= aabb.xMax 123 | let yInside = point.y >= aabb.yMin && point.y <= aabb.yMax 124 | 125 | return xInside && yInside 126 | } 127 | 128 | static overlaps(aabb1: AABB, aabb2: AABB, padding: number = 0): boolean { 129 | let xSeparation = aabb1.xMax + padding < aabb2.xMin || aabb1.xMin - padding > aabb2.xMax 130 | let ySeparation = aabb1.yMax + padding < aabb2.yMin || aabb1.yMin - padding > aabb2.yMax 131 | 132 | return !xSeparation && !ySeparation 133 | } 134 | 135 | /** 136 | * Returns true if aabb1 contains aabb2, false otherwise 137 | * @param aabb1 138 | * @param aabb2 139 | * @param padding 140 | */ 141 | static contains(aabb1: AABB, aabb2: AABB): boolean { 142 | let containsX = aabb1.xMin <= aabb2.xMin && aabb1.xMax >= aabb2.xMax 143 | let containsY = aabb1.yMin <= aabb2.yMin && aabb1.yMax >= aabb2.yMax 144 | return containsX && containsY 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/gui/solver/aabb.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | /** 20 | * An axis aligned bounding box 21 | */ 22 | export default interface AABB { 23 | xMin: number 24 | yMin: number 25 | xMax: number 26 | yMax: number 27 | } 28 | -------------------------------------------------------------------------------- /src/gui/solver/camera-presets.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export interface CameraPreset { 20 | displayName: string 21 | sensorWidth: number 22 | sensorHeight: number 23 | focalLength?: number 24 | } 25 | 26 | export const cameraPresets: { [id: string]: CameraPreset } = { 27 | 'aps-c': { 28 | displayName: 'APS-C DSLR', 29 | sensorWidth: 22.3, 30 | sensorHeight: 14.9 31 | }, 32 | 'canon_1d': { 33 | displayName: 'Canon 1D', 34 | sensorWidth: 27.9, 35 | sensorHeight: 18.6 36 | }, 37 | 'canon_1ds': { 38 | displayName: 'Canon 1DS', 39 | sensorWidth: 36, 40 | sensorHeight: 24 41 | }, 42 | 'canon_5d': { 43 | displayName: 'Canon 5D', 44 | sensorWidth: 36, 45 | sensorHeight: 24 46 | }, 47 | 'canon_7d': { 48 | displayName: 'Canon 7D', 49 | sensorWidth: 22.3, 50 | sensorHeight: 14.9 51 | }, 52 | 'canon_60d': { 53 | displayName: 'Canon 60D', 54 | sensorWidth: 22.3, 55 | sensorHeight: 14.9 56 | }, 57 | 'canon_500d': { 58 | displayName: 'Canon 500D', 59 | sensorWidth: 22.3, 60 | sensorHeight: 14.9 61 | }, 62 | 'canon_550d': { 63 | displayName: 'Canon 550D', 64 | sensorWidth: 22.3, 65 | sensorHeight: 14.9 66 | }, 67 | 'canon_600d': { 68 | displayName: 'Canon 600D', 69 | sensorWidth: 22.3, 70 | sensorHeight: 14.9 71 | }, 72 | 'canon_1100d': { 73 | displayName: 'Canon 1100D', 74 | sensorWidth: 22.2, 75 | sensorHeight: 14.7 76 | }, 77 | 'canon_g7x_mk2': { 78 | displayName: 'Canon G7X Mark II', 79 | sensorWidth: 13.2, 80 | sensorHeight: 8.8 81 | }, 82 | '35_mm_film': { 83 | displayName: '35 mm film', 84 | sensorWidth: 36, 85 | sensorHeight: 24 86 | }, 87 | 'micro_4_3rds': { 88 | displayName: 'Micro four thirds', 89 | sensorWidth: 17.3, 90 | sensorHeight: 14 91 | }, 92 | 'nikon_d3s': { 93 | displayName: 'Nikon D3S', 94 | sensorWidth: 36, 95 | sensorHeight: 23.9 96 | }, 97 | 'nikon_d90': { 98 | displayName: 'Nikon D90', 99 | sensorWidth: 23.6, 100 | sensorHeight: 15.8 101 | }, 102 | 'nikon_d300s': { 103 | displayName: 'Nikon D300S', 104 | sensorWidth: 23.6, 105 | sensorHeight: 15.8 106 | }, 107 | 'nikon_d3100': { 108 | displayName: 'Nikon D3100', 109 | sensorWidth: 23.1, 110 | sensorHeight: 15.4 111 | }, 112 | 'nikon_d5000': { 113 | displayName: 'Nikon D5000', 114 | sensorWidth: 23.6, 115 | sensorHeight: 15.8 116 | }, 117 | 'nikon_d5100': { 118 | displayName: 'Nikon D5100', 119 | sensorWidth: 23.6, 120 | sensorHeight: 15.6 121 | }, 122 | 'nikon_d7000': { 123 | displayName: 'Nikon D7000', 124 | sensorWidth: 23.6, 125 | sensorHeight: 15.6 126 | }, 127 | 'red_epic': { 128 | displayName: 'Red Epic', 129 | sensorWidth: 30, 130 | sensorHeight: 15 131 | }, 132 | 'red_epic_2k': { 133 | displayName: 'Red Epic 2k', 134 | sensorWidth: 11.1, 135 | sensorHeight: 6.24 136 | }, 137 | 'red_epic_3k': { 138 | displayName: 'Red Epic 3k', 139 | sensorWidth: 16.65, 140 | sensorHeight: 9.36 141 | }, 142 | 'red_epic_4k': { 143 | displayName: 'Red Epic 4k', 144 | sensorWidth: 22.2, 145 | sensorHeight: 12.6 146 | }, 147 | 'sony_a55': { 148 | displayName: 'Sony A55', 149 | sensorWidth: 23.4, 150 | sensorHeight: 15.6 151 | }, 152 | 'iphone_6s': { 153 | displayName: 'iPhone 6S', 154 | sensorWidth: 4.8, 155 | sensorHeight: 3.6, 156 | focalLength: 4.15 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/gui/solver/coordinates-util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import Point2D from './point-2d' 20 | 21 | /** 22 | * An enumeration of coordinate frames used to specify 23 | * points in an image 24 | */ 25 | export enum ImageCoordinateFrame { 26 | /** 27 | * x = 0 and x = 1 correspond to the left and right edges respectively. 28 | * y = 0 and y = 1 correspond to the top and bottom edges respectively, 29 | * The positive y direction is down. 30 | */ 31 | Relative, 32 | /** 33 | * x = 0 and x = w correspond to the left and right edges respectively, 34 | * where w is the image width in pixels. 35 | * y = 0 and y = h correspond to the top and bottom edges respectively, 36 | * where h is the image height in pixels. 37 | * The positive y direction is down. 38 | */ 39 | Absolute, 40 | /** 41 | * A normalized coordinate frame taking into account the aspect ratio r of 42 | * the image. 0,0 corresponds to the image midoint. 43 | * For a tall image, x min = -r, x max = r, y min = -1 and y max = 1 44 | * For a wide image, x min = -1, x max = 1, y min = -1/aspect and y max = 1/aspect 45 | * The positive y direction is up 46 | */ 47 | ImagePlane 48 | } 49 | 50 | export default class CoordinatesUtil { 51 | /** 52 | * Convert a point from one image coordinate frame to another 53 | * @param point The point to convert 54 | * @param sourceFrame The coordinate frame of the input point 55 | * @param targetFrame The frame to convert to 56 | * @param imageWidth The image width in pixels 57 | * @param imageHeight The image height in pixels 58 | */ 59 | static convert( 60 | point: Point2D, 61 | sourceFrame: ImageCoordinateFrame, 62 | targetFrame: ImageCoordinateFrame, 63 | imageWidth: number, 64 | imageHeight: number): Point2D { 65 | 66 | switch (sourceFrame) { 67 | case ImageCoordinateFrame.Absolute: { 68 | switch (targetFrame) { 69 | case ImageCoordinateFrame.Absolute: 70 | return point 71 | case ImageCoordinateFrame.ImagePlane: 72 | let relativePoint = this.absoluteToRelative(point, imageWidth, imageHeight) 73 | return this.relativeToImagePlane(relativePoint, imageWidth, imageHeight) 74 | case ImageCoordinateFrame.Relative: 75 | return this.absoluteToRelative(point, imageWidth, imageHeight) 76 | } 77 | break 78 | } 79 | case ImageCoordinateFrame.ImagePlane: { 80 | switch (targetFrame) { 81 | case ImageCoordinateFrame.Absolute: 82 | let relativePoint = this.imagePlaneToRelative(point, imageWidth, imageHeight) 83 | return this.relativeToAbsolute(relativePoint, imageWidth, imageHeight) 84 | case ImageCoordinateFrame.ImagePlane: 85 | return point 86 | case ImageCoordinateFrame.Relative: 87 | return this.imagePlaneToRelative(point, imageWidth, imageHeight) 88 | } 89 | break 90 | } 91 | case ImageCoordinateFrame.Relative: { 92 | switch (targetFrame) { 93 | case ImageCoordinateFrame.Absolute: 94 | return this.relativeToAbsolute(point, imageWidth, imageHeight) 95 | case ImageCoordinateFrame.ImagePlane: 96 | return this.relativeToImagePlane(point, imageWidth, imageHeight) 97 | case ImageCoordinateFrame.Relative: 98 | return point 99 | } 100 | break 101 | } 102 | } 103 | 104 | throw new Error('Should not end up here') 105 | } 106 | 107 | private static absoluteToRelative(point: Point2D, imageWidth: number, imageHeight: number): Point2D { 108 | return { 109 | x: point.x / imageWidth, 110 | y: point.y / imageHeight 111 | } 112 | } 113 | 114 | private static relativeToAbsolute(point: Point2D, imageWidth: number, imageHeight: number): Point2D { 115 | return { 116 | x: point.x * imageWidth, 117 | y: point.y * imageHeight 118 | } 119 | } 120 | 121 | private static relativeToImagePlane(point: Point2D, imageWidth: number, imageHeight: number): Point2D { 122 | let aspectRatio = imageWidth / imageHeight 123 | if (aspectRatio <= 1) { 124 | // tall image. [0, 1] x [0, 1] => [-aspect, aspect] x [-1, 1] 125 | return { 126 | x: (-1 + 2 * point.x) * aspectRatio, 127 | y: 1 - 2 * point.y 128 | } 129 | } else { 130 | // wide image. [0, 1] x [0, 1] => [-1, 1] x [-1 / aspect, 1 / aspect] 131 | return { 132 | x: -1 + 2 * point.x, 133 | y: (1 - 2 * point.y) / aspectRatio 134 | } 135 | } 136 | } 137 | 138 | private static imagePlaneToRelative(point: Point2D, imageWidth: number, imageHeight: number): Point2D { 139 | let imageAspect = imageWidth / imageHeight 140 | if (imageAspect <= 1) { 141 | // tall image. [-aspect, aspect] x [-1, 1] => [0, 1] x [0, 1] 142 | return { 143 | x: 0.5 * (point.x / imageAspect + 1), 144 | y: 0.5 * (-point.y + 1) 145 | } 146 | } else { 147 | // wide image. [-1, 1] x [-1 / aspect, 1 / aspect] => [0, 1] x [0, 1] 148 | return { 149 | x: 0.5 * (point.x + 1), 150 | y: 0.5 * (-point.y * imageAspect + 1) 151 | } 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/gui/solver/point-2d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export default interface Point2D { 20 | x: number 21 | y: number 22 | } 23 | -------------------------------------------------------------------------------- /src/gui/solver/solver-result.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import Transform from './transform' 20 | import Point2D from './point-2d' 21 | import { Axis } from '../types/calibration-settings' 22 | 23 | export interface CameraParameters { 24 | principalPoint: Point2D 25 | viewTransform: Transform 26 | cameraTransform: Transform // the inverse of the view transform 27 | horizontalFieldOfView: number 28 | verticalFieldOfView: number 29 | vanishingPoints: [Point2D, Point2D, Point2D] 30 | vanishingPointAxes: [Axis, Axis, Axis] 31 | relativeFocalLength: number, 32 | imageWidth: number, 33 | imageHeight: number 34 | } 35 | 36 | export interface SolverResult { 37 | errors: string[] 38 | warnings: string[] 39 | cameraParameters: CameraParameters | null 40 | } 41 | -------------------------------------------------------------------------------- /src/gui/solver/vector-3d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export default class Vector3D { 20 | x: number 21 | y: number 22 | z: number 23 | 24 | constructor(x: number = 0, y: number = 0, z: number = 0) { 25 | this.x = x 26 | this.y = y 27 | this.z = z 28 | } 29 | 30 | copy(): Vector3D { 31 | return new Vector3D(this.x, this.y, this.z) 32 | } 33 | 34 | get minCoordinate(): number { 35 | return Math.min(this.x, this.y, this.z) 36 | } 37 | 38 | get maxCoordinate(): number { 39 | return Math.max(this.x, this.y, this.z) 40 | } 41 | 42 | get minAbsCoordinate(): number { 43 | return Math.min( 44 | Math.abs(this.x), 45 | Math.abs(this.y), 46 | Math.abs(this.z) 47 | ) 48 | } 49 | 50 | get maxAbsCoordinate(): number { 51 | return Math.max( 52 | Math.abs(this.x), 53 | Math.abs(this.y), 54 | Math.abs(this.z) 55 | ) 56 | } 57 | 58 | add(other: Vector3D) { 59 | this.x += other.x 60 | this.y += other.y 61 | this.z += other.z 62 | } 63 | 64 | added(other: Vector3D): Vector3D { 65 | let result = this.copy() 66 | result.add(other) 67 | return result 68 | } 69 | 70 | subtract(other: Vector3D) { 71 | this.x -= other.x 72 | this.y -= other.y 73 | this.z -= other.z 74 | } 75 | 76 | subtracted(other: Vector3D): Vector3D { 77 | let result = this.copy() 78 | result.subtract(other) 79 | return result 80 | } 81 | 82 | get length(): number { 83 | return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z) 84 | } 85 | 86 | /** 87 | * Normalizes the vector and returns the length prior to normalization. 88 | */ 89 | normalize(): number { 90 | let length = this.length 91 | if (length > 0) { 92 | this.x /= length 93 | this.y /= length 94 | this.z /= length 95 | } 96 | 97 | return length 98 | } 99 | 100 | normalized(): Vector3D { 101 | let result = this.copy() 102 | result.normalize() 103 | return result 104 | } 105 | 106 | negate() { 107 | this.x = -this.x 108 | this.y = -this.y 109 | this.z = -this.z 110 | } 111 | 112 | negated(): Vector3D { 113 | let result = this.copy() 114 | result.negate() 115 | return result 116 | } 117 | 118 | multiplyByScalar(scalar: number) { 119 | this.x *= scalar 120 | this.y *= scalar 121 | this.z *= scalar 122 | } 123 | 124 | multipliedByScalar(scalar: number): Vector3D { 125 | let result = this.copy() 126 | result.multiplyByScalar(scalar) 127 | return result 128 | } 129 | 130 | /** 131 | * Dot product this * other 132 | * @param other 133 | */ 134 | dot(other: Vector3D): number { 135 | return this.x * other.x + this.y * other.y + this.z * other.z 136 | } 137 | 138 | /** 139 | * Cross product this x other 140 | * @param other 141 | */ 142 | cross(other: Vector3D): Vector3D { 143 | return new Vector3D( 144 | this.y * other.z - this.z * other.y, 145 | this.z * other.x - this.x * other.z, 146 | this.x * other.y - this.y * other.x 147 | ) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/gui/store/app-middleware.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { Middleware } from 'redux' 20 | import { StoreState } from '../types/store-state' 21 | import { actionTypesTriggeringRecalculation, recalculateCalibrationResult, actionTypesSettingNeedsSaveFlag, setProjectHasUnsavedChanges } from '../actions' 22 | import store from './store' 23 | 24 | // Middleware for requesting calibration result recalculation for 25 | // action types matching a set of given types 26 | export const appMiddleware: Middleware<{}, StoreState> = _ => next => action => { 27 | 28 | if (actionTypesTriggeringRecalculation.indexOf(action.type) >= 0) { 29 | store.dispatch(recalculateCalibrationResult()) 30 | } 31 | 32 | if (actionTypesSettingNeedsSaveFlag.indexOf(action.type) >= 0) { 33 | setTimeout(() => { 34 | store.dispatch(setProjectHasUnsavedChanges()) 35 | }) 36 | } 37 | 38 | return next(action) 39 | } 40 | -------------------------------------------------------------------------------- /src/gui/store/store.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { createStore, applyMiddleware, AnyAction, Store } from 'redux' 20 | import rootReducer from '../reducers/root' 21 | import { StoreState } from '../types/store-state' 22 | import thunk from 'redux-thunk' 23 | import { appMiddleware } from './app-middleware' 24 | 25 | const store: Store = createStore( 26 | rootReducer, 27 | applyMiddleware(appMiddleware, thunk) 28 | ) 29 | 30 | export default store 31 | -------------------------------------------------------------------------------- /src/gui/strings/strings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | const strings = { 20 | customCameraPresetName: 'Custom camera', 21 | unitMm: 'mm', 22 | imageSensorProportionsMismatch: 'Image/sensor proportions mismatch' 23 | } 24 | 25 | export default strings 26 | -------------------------------------------------------------------------------- /src/gui/style/palette.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { Axis } from '../types/calibration-settings' 20 | 21 | export class Palette { 22 | // Color constants 23 | static readonly red = '#EC4E37' 24 | static readonly green = '#70BF41' 25 | static readonly blue = '#359DF9' 26 | static readonly orange = '#F39019' 27 | static readonly yellow = '#F3CA05' 28 | static readonly white = '#FFFFFF' 29 | static readonly lightGray = 'Menu' 30 | static readonly gray = '#e1e1e1' 31 | static readonly black = 'WindowText' 32 | static readonly imagePanelBackgroundColor = '#252B2E' 33 | static readonly disabledTextColor = '#909090' 34 | 35 | // Color aliases 36 | static readonly xAxisColor = Palette.red 37 | static readonly yAxisColor = Palette.green 38 | static readonly zAxisColor = Palette.blue 39 | static readonly originColor = Palette.white 40 | static readonly referenceDistanceControlColor = Palette.yellow 41 | static readonly principalPointColor = Palette.orange 42 | 43 | static colorForAxis(axis: Axis): string { 44 | switch (axis) { 45 | case Axis.NegativeX: 46 | return this.red 47 | case Axis.PositiveX: 48 | return this.red 49 | case Axis.NegativeY: 50 | return this.green 51 | case Axis.PositiveY: 52 | return this.green 53 | case Axis.NegativeZ: 54 | return this.blue 55 | case Axis.PositiveZ: 56 | return this.blue 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/gui/types/calibration-settings.ts: -------------------------------------------------------------------------------- 1 | import { CameraPreset } from '../solver/camera-presets' 2 | 3 | /** 4 | * fSpy 5 | * Copyright (c) 2020 - Per Gantelius 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | 21 | export enum PrincipalPointMode1VP { 22 | Default = 'Default', 23 | Manual = 'Manual' 24 | } 25 | 26 | export enum PrincipalPointMode2VP { 27 | Default = 'Default', 28 | Manual = 'Manual', 29 | FromThirdVanishingPoint = 'FromThirdVanishingPoint' 30 | } 31 | 32 | export enum Axis { 33 | PositiveX = 'xPositive', 34 | NegativeX = 'xNegative', 35 | PositiveY = 'yPositive', 36 | NegativeY = 'yNegative', 37 | PositiveZ = 'zPositive', 38 | NegativeZ = 'zNegative' 39 | } 40 | 41 | export enum ReferenceDistanceUnit { 42 | None = 'No unit', 43 | Millimeters = 'Millimeters', 44 | Centimeters = 'Centimeters', 45 | Meters = 'Meters', 46 | Kilometers = 'Kilometers', 47 | Inches = 'Inches', 48 | Feet = 'Feet', 49 | Miles = 'Miles' 50 | } 51 | 52 | export interface CameraData { 53 | presetId: string | null 54 | presetData: CameraPreset | null 55 | customSensorWidth: number 56 | customSensorHeight: number 57 | } 58 | 59 | export interface CalibrationSettingsBase { 60 | referenceDistanceUnit: ReferenceDistanceUnit 61 | referenceDistance: number 62 | referenceDistanceAxis: Axis | null 63 | cameraData: CameraData 64 | firstVanishingPointAxis: Axis 65 | secondVanishingPointAxis: Axis 66 | } 67 | 68 | export interface CalibrationSettings1VP { 69 | principalPointMode: PrincipalPointMode1VP 70 | absoluteFocalLength: number 71 | } 72 | 73 | export interface CalibrationSettings2VP { 74 | principalPointMode: PrincipalPointMode2VP 75 | quadModeEnabled: boolean 76 | } 77 | -------------------------------------------------------------------------------- /src/gui/types/control-points-state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | /** 20 | * The state of a single control point 21 | */ 22 | export interface ControlPointState { 23 | /** Relative image coordinates [0, 1] */ 24 | x: number 25 | /** Relative image coordinates [0, 1] */ 26 | y: number 27 | } 28 | 29 | export type ControlPointPairState = [ControlPointState, ControlPointState] 30 | 31 | // TODO: Rename to BinaryIndex or something? Or remove if it doesnt provide type info 32 | export enum ControlPointPairIndex { 33 | First = 0, 34 | Second = 1 35 | } 36 | 37 | export interface VanishingPointControlState { 38 | lineSegments: [ControlPointPairState, ControlPointPairState] 39 | } 40 | 41 | export interface ControlPointsStateBase { 42 | principalPoint: ControlPointState 43 | origin: ControlPointState 44 | referenceDistanceAnchor: ControlPointState 45 | firstVanishingPoint: VanishingPointControlState 46 | // The offsets are the distances in relative image coordinates 47 | // along the axis from the anchor to the vanishing point corresponding 48 | // to the selected reference axis 49 | referenceDistanceHandleOffsets: [number, number] 50 | } 51 | 52 | export interface ControlPointsState1VP { 53 | horizon: ControlPointPairState 54 | } 55 | 56 | export interface ControlPointsState2VP { 57 | secondVanishingPoint: VanishingPointControlState 58 | thirdVanishingPoint: VanishingPointControlState 59 | } 60 | -------------------------------------------------------------------------------- /src/gui/types/global-settings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export enum CalibrationMode { 20 | OneVanishingPoint = 'OneVanishingPoint', 21 | TwoVanishingPoints = 'TwoVanishingPoints' 22 | } 23 | 24 | export enum Overlay3DGuide { 25 | None = 'None', 26 | Box = 'Box', 27 | XYGridFloor = 'XYGridFloor', 28 | YZGridFloor = 'YZGridFloor', 29 | ZXGridFloor = 'ZXGridFloor' 30 | } 31 | 32 | export interface GlobalSettings { 33 | calibrationMode: CalibrationMode 34 | overlay3DGuide: Overlay3DGuide 35 | imageOpacity: number 36 | } 37 | -------------------------------------------------------------------------------- /src/gui/types/image-state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export interface ImageState { 20 | width: number | null, 21 | height: number | null, 22 | url: string | null, 23 | data: Buffer | null 24 | } 25 | -------------------------------------------------------------------------------- /src/gui/types/result-display-settings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export enum FieldOfViewFormat { 20 | Degrees = 'Degrees', 21 | Radians = 'Radians' 22 | } 23 | 24 | export enum OrientationFormat { 25 | AxisAngleDegrees = 'AxisAngleDegrees', 26 | AxisAngleRadians = 'AxisAngleRadians', 27 | Quaterion = 'Quaterion' 28 | } 29 | 30 | export enum PrincipalPointFormat { 31 | Relative = 'Relative', 32 | Absolute = 'Absolute' 33 | } 34 | 35 | export interface ResultDisplaySettings { 36 | fieldOfViewFormat: FieldOfViewFormat 37 | orientationFormat: OrientationFormat 38 | principalPointFormat: PrincipalPointFormat 39 | displayAbsoluteFocalLength: boolean // 2 vp only 40 | } 41 | -------------------------------------------------------------------------------- /src/gui/types/store-state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { GlobalSettings } from './global-settings' 20 | import { ControlPointsState2VP, ControlPointsState1VP, ControlPointsStateBase } from './control-points-state' 21 | import { CalibrationSettings2VP, CalibrationSettings1VP, CalibrationSettingsBase } from './calibration-settings' 22 | import { ImageState } from './image-state' 23 | import { UIState } from './ui-state' 24 | import { SolverResult } from '../solver/solver-result' 25 | import { ResultDisplaySettings } from './result-display-settings' 26 | 27 | export interface StoreState { 28 | globalSettings: GlobalSettings 29 | 30 | calibrationSettingsBase: CalibrationSettingsBase 31 | calibrationSettings1VP: CalibrationSettings1VP 32 | calibrationSettings2VP: CalibrationSettings2VP 33 | 34 | controlPointsStateBase: ControlPointsStateBase 35 | controlPointsState1VP: ControlPointsState1VP 36 | controlPointsState2VP: ControlPointsState2VP 37 | 38 | image: ImageState 39 | 40 | solverResult: SolverResult 41 | resultDisplaySettings: ResultDisplaySettings 42 | uiState: UIState 43 | } 44 | -------------------------------------------------------------------------------- /src/gui/types/ui-state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export interface UIState { 20 | sidePanelsAreVisible: boolean 21 | projectFilePath: string | null 22 | projectHasUnsavedChanges: boolean 23 | } 24 | -------------------------------------------------------------------------------- /src/main/app-menu-manager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { app, Menu } from 'electron' 20 | 21 | export interface AppMenuCallbacks { 22 | onNewProject(): void 23 | onOpenProject(): void 24 | onSaveProject(): void 25 | onSaveProjectAs(): void 26 | onOpenImage(): void 27 | onOpenExampleProject(): void 28 | onQuit(): void 29 | onExportJSON(): void 30 | onExportProjectImage(): void 31 | onEnterFullScreenMode(): void 32 | onExitFullScreenMode(): void 33 | } 34 | 35 | export default class AppMenuManager { 36 | readonly menu: Menu 37 | readonly callbacks: AppMenuCallbacks 38 | 39 | constructor(callbacks: AppMenuCallbacks) { 40 | this.callbacks = callbacks 41 | 42 | let newItem = { 43 | label: 'New', 44 | accelerator: 'CommandOrControl+N', 45 | click: () => { 46 | this.callbacks.onNewProject() 47 | } 48 | } 49 | 50 | let openItem = { 51 | label: 'Open', 52 | accelerator: 'CommandOrControl+O', 53 | click: () => { 54 | this.callbacks.onOpenProject() 55 | } 56 | } 57 | 58 | let saveItem = { 59 | label: 'Save', 60 | id: 'save', 61 | accelerator: 'CommandOrControl+S', 62 | click: () => { 63 | this.callbacks.onSaveProject() 64 | } 65 | } 66 | 67 | let saveAsItem = { 68 | label: 'Save as', 69 | id: 'save-as', 70 | accelerator: 'CommandOrControl+Shift+S', 71 | click: () => { 72 | this.callbacks.onSaveProjectAs() 73 | } 74 | } 75 | 76 | let openExampleProjectItem = { 77 | label: 'Open example project', 78 | id: 'open-example-project', 79 | click: () => { 80 | this.callbacks.onOpenExampleProject() 81 | } 82 | } 83 | 84 | let openImageItem = { 85 | label: 'Open image', 86 | id: 'open-image', 87 | accelerator: 'CommandOrControl+Shift+O', 88 | click: () => { 89 | this.callbacks.onOpenImage() 90 | } 91 | } 92 | 93 | let quitMenuItem: Electron.MenuItemConstructorOptions = { 94 | label: 'Quit', 95 | accelerator: 'Command+Q', 96 | click: () => { 97 | this.callbacks.onQuit() 98 | } 99 | } 100 | 101 | let fileMenuItems: Electron.MenuItemConstructorOptions[] = [ 102 | newItem, 103 | openItem, 104 | { type: 'separator' }, 105 | openExampleProjectItem, 106 | openImageItem, 107 | { type: 'separator' }, 108 | saveItem, 109 | saveAsItem, 110 | { type: 'separator' }, 111 | { 112 | label: 'Export', 113 | submenu: [ 114 | { 115 | label: 'Camera parameters as JSON', 116 | click: () => { 117 | this.callbacks.onExportJSON() 118 | } 119 | }, 120 | { 121 | label: 'Project image', 122 | click: () => { 123 | this.callbacks.onExportProjectImage() 124 | } 125 | } 126 | ] 127 | } 128 | ] 129 | 130 | if (process.platform !== 'darwin') { 131 | fileMenuItems.push({ type: 'separator' }) 132 | fileMenuItems.push(quitMenuItem) 133 | } else { 134 | let recentDocumentsSubmenu: Electron.MenuItemConstructorOptions = { 135 | label: 'Open Recent', 136 | role: 'recentDocuments', 137 | submenu: [ 138 | { 139 | label: 'Clear Recent', 140 | role: 'clearRecentDocuments' 141 | } 142 | ] 143 | } 144 | fileMenuItems.splice(2, 0, recentDocumentsSubmenu) 145 | } 146 | 147 | let fileMenu = { 148 | label: 'File', 149 | submenu: fileMenuItems 150 | } 151 | 152 | let menus = [fileMenu] 153 | let viewMenu = { 154 | label: 'View', 155 | submenu: [ 156 | { 157 | label: 'Enter full screen mode', 158 | id: 'enter-full-screen', 159 | accelerator: 'Command+F', 160 | click: () => { 161 | this.callbacks.onEnterFullScreenMode() 162 | } 163 | }, 164 | { 165 | label: 'Exit full screen mode', 166 | id: 'exit-full-screen', 167 | accelerator: 'Escape', 168 | click: () => { 169 | this.callbacks.onExitFullScreenMode() 170 | } 171 | } 172 | ] 173 | } 174 | menus.push(viewMenu) 175 | 176 | if (process.platform === 'darwin') { 177 | menus.unshift({ 178 | label: app.name, 179 | submenu: [ 180 | quitMenuItem 181 | ] 182 | }) 183 | } 184 | 185 | this.menu = Menu.buildFromTemplate(menus) 186 | } 187 | 188 | setOpenImageItemEnabled(enabled: boolean) { 189 | this.menu.getMenuItemById('open-image').enabled = enabled 190 | } 191 | 192 | setSaveItemEnabled(enabled: boolean) { 193 | this.menu.getMenuItemById('save').enabled = enabled 194 | } 195 | 196 | setSaveAsItemEnabled(enabled: boolean) { 197 | this.menu.getMenuItemById('save-as').enabled = enabled 198 | } 199 | 200 | setEnterFullScreenItemEnabled(enabled: boolean) { 201 | this.menu.getMenuItemById('enter-full-screen').enabled = enabled 202 | } 203 | 204 | setExitFullScreenItemEnabled(enabled: boolean) { 205 | this.menu.getMenuItemById('exit-full-screen').enabled = enabled 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/main/ipc-messages.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fSpy 3 | * Copyright (c) 2020 - Per Gantelius 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | // Messages sent from the main process to the renderer process 20 | 21 | export class NewProjectMessage { 22 | static readonly type = 'newProject' 23 | } 24 | 25 | export class OpenProjectMessage { 26 | static readonly type = 'openProject' 27 | readonly filePath: string 28 | readonly isExampleProject: boolean 29 | 30 | constructor(filePath: string, isExampleProject: boolean) { 31 | this.filePath = filePath 32 | this.isExampleProject = isExampleProject 33 | } 34 | } 35 | 36 | export class SaveProjectMessage { 37 | static readonly type = 'saveProject' 38 | } 39 | 40 | export class SaveProjectAsMessage { 41 | static readonly type = 'saveProjectAs' 42 | readonly filePath: string 43 | 44 | constructor(filePath: string) { 45 | this.filePath = filePath 46 | } 47 | } 48 | 49 | export class OpenImageMessage { 50 | static readonly type = 'openImage' 51 | readonly filePath: string 52 | 53 | constructor(filePath: string) { 54 | this.filePath = filePath 55 | } 56 | } 57 | 58 | export class SetSidePanelVisibilityMessage { 59 | static readonly type = 'setSidePanelVisibility' 60 | readonly panelsAreVisible: boolean 61 | 62 | constructor(panelsAreVisible: boolean) { 63 | this.panelsAreVisible = panelsAreVisible 64 | } 65 | } 66 | 67 | export enum ExportType { 68 | CameraParametersJSON, 69 | ProjectImage 70 | } 71 | 72 | export class ExportMessage { 73 | static readonly type = 'export' 74 | readonly exportType: ExportType 75 | 76 | constructor(exportType: ExportType) { 77 | this.exportType = exportType 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test_data/1 vp control test.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/1 vp control test.fspy -------------------------------------------------------------------------------- /test_data/box.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/box.jpg -------------------------------------------------------------------------------- /test_data/canon5d_16mm.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/canon5d_16mm.fspy -------------------------------------------------------------------------------- /test_data/canoneos60d_24mm.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/canoneos60d_24mm.fspy -------------------------------------------------------------------------------- /test_data/defaults.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/defaults.fspy -------------------------------------------------------------------------------- /test_data/iphone6plus.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/iphone6plus.fspy -------------------------------------------------------------------------------- /test_data/missing horizon.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/missing horizon.fspy -------------------------------------------------------------------------------- /test_data/pp problem landscape.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/pp problem landscape.fspy -------------------------------------------------------------------------------- /test_data/pp problem.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/pp problem.fspy -------------------------------------------------------------------------------- /test_data/quad-problem-2 copy.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/quad-problem-2 copy.fspy -------------------------------------------------------------------------------- /test_data/quad-problem-2.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/quad-problem-2.fspy -------------------------------------------------------------------------------- /test_data/quad-problem-3.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/quad-problem-3.fspy -------------------------------------------------------------------------------- /test_data/quad-problem-4.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/quad-problem-4.fspy -------------------------------------------------------------------------------- /test_data/quad-problem.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/quad-problem.fspy -------------------------------------------------------------------------------- /test_data/ref_distance_in_yards.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/ref_distance_in_yards.fspy -------------------------------------------------------------------------------- /test_data/reference distance problem.fspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffmatic/fSpy/702189ec5acbbd2c8ba492db0e52ecb5fc908f5c/test_data/reference distance problem.fspy -------------------------------------------------------------------------------- /tests/gui/gui_tests.ts: -------------------------------------------------------------------------------- 1 | /// 2 | describe('GUI', () => { 3 | test('PlaceHolderFailingTest', () => { 4 | expect(1 + 2).toBe(4) 5 | }) 6 | }) 7 | -------------------------------------------------------------------------------- /tests/main/main_tests.ts: -------------------------------------------------------------------------------- 1 | /// 2 | describe('Main', () => { 3 | test('PlaceHolderPassingTest', () => { 4 | expect(1 + 2).toBe(3) 5 | }) 6 | }) 7 | -------------------------------------------------------------------------------- /tools/letters_and_numbers_paths.py: -------------------------------------------------------------------------------- 1 | import json 2 | from xml.dom.minidom import parse, parseString 3 | 4 | dom = parse('assets/master/lettersandnumbers.svg') 5 | groups = dom.getElementsByTagName('g') 6 | group_ids = ['gx', 'gy', 'gz', 'g1', 'g2', 'g3'] 7 | coord_lists_by_id = {} 8 | for group in groups: 9 | id = group.attributes['id'].value 10 | if id in group_ids: 11 | x_min = 1000000 12 | x_max = -1000000 13 | y_min = 1000000 14 | y_max = -1000000 15 | print 'processing group', id 16 | coord_lists = [] 17 | paths = group.getElementsByTagName('path') 18 | for path in paths: 19 | coords = [] 20 | d = path.attributes['d'].value 21 | print ' processing subpath', d 22 | parts = d.split(' ') 23 | for part in parts: 24 | if len(part.split(',')) == 2: 25 | x, y = part.split(',') 26 | x = float(x) 27 | y = float(y) 28 | coords.append((x, y)) 29 | if x < x_min: 30 | x_min = x 31 | if x > x_max: 32 | x_max = x 33 | if y < y_min: 34 | y_min = y 35 | if y > y_max: 36 | y_max = y 37 | coord_lists.append(coords) 38 | 39 | scale = 1.0 / (y_max - y_min) 40 | scaled_coord_lists = [] 41 | for coord_list in coord_lists: 42 | scaled_coord_list = [] 43 | for coord in coord_list: 44 | scaled_coord_list.append( 45 | ( 46 | (coord[0] - 0.5 * (x_max + x_min)) * scale, 47 | (coord[1] - 0.5 * (y_max + y_min)) * scale 48 | ) 49 | ) 50 | scaled_coord_lists.append(scaled_coord_list) 51 | 52 | coord_lists_by_id[id[1]] = scaled_coord_lists 53 | 54 | #print coord_lists_by_id 55 | print json.dumps(coord_lists_by_id, indent=2, sort_keys=True) 56 | 57 | -------------------------------------------------------------------------------- /tools/png2icns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | # Exec Paths 5 | SIPS='/usr/bin/sips' 6 | ICONUTIL='/usr/bin/iconutil' 7 | if [ ! -x "${SIPS}" ]; then 8 | echo "Cannot find required SIPS executable at: ${SIPS}" >&2 9 | exit 1; 10 | fi 11 | if [ ! -x "${ICONUTIL}" ]; then 12 | echo "Cannot find required ICONUTIL executable at: ${ICONUTIL}" >&2 13 | exit 1; 14 | fi 15 | 16 | # Parameters 17 | SOURCE=$1 18 | 19 | # Get source image 20 | if [ -z "${SOURCE}" ]; then 21 | echo "No source image specified, searching in current directory...\c" 22 | SOURCE=$( ls *.png | head -n1 ) 23 | if [ -z "${SOURCE}" ]; then 24 | echo "No source image specified and none found." 25 | exit 1; 26 | else 27 | echo "FOUND"; 28 | fi 29 | fi 30 | 31 | 32 | # File Infrastructure 33 | NAME=$(basename "${SOURCE}") 34 | EXT="${NAME##*.}" 35 | BASE="${NAME%.*}" 36 | ICONSET="${BASE}.iconset" 37 | 38 | # Debug Info 39 | echo "SOURCE: ${SOURCE}" 40 | echo "NAME: $NAME" 41 | echo "BASE: $BASE" 42 | echo "EXT: $EXT" 43 | echo "ICONSET: $ICONSET" 44 | 45 | # Get source image info 46 | SRCWIDTH=$( $SIPS -g pixelWidth "${SOURCE}" | tail -n1 | awk '{print $2}') 47 | SRCHEIGHT=$( $SIPS -g pixelHeight "${SOURCE}" | tail -n1 | awk '{print $2}' ) 48 | SRCFORMAT=$( $SIPS -g format "${SOURCE}" | tail -n1 | awk '{print $2}' ) 49 | 50 | # Debug Info 51 | echo "SRCWIDTH: $SRCWIDTH" 52 | echo "SRCHEIGHT: $SRCHEIGHT" 53 | echo "SRCFORMAT: $SRCFORMAT" 54 | 55 | # Check The Source Image 56 | if [ "x${SRCWIDTH}" != "x1024" ] || [ "x${SRCHEIGHT}" != "x1024" ]; then 57 | echo "ERR: Source image should be 1024 x 1024 pixels." >&2 58 | exit 1; 59 | fi 60 | if [ "x${SRCFORMAT}" != "xpng" ]; then 61 | echo "ERR: Source image format should be png." >&2 62 | exit 1; 63 | fi 64 | 65 | # Resample image into iconset 66 | mkdir "${ICONSET}" 67 | $SIPS -s format png --resampleWidth 1024 "${SOURCE}" --out "${ICONSET}/icon_512x512@2x.png" > /dev/null 2>&1 68 | $SIPS -s format png --resampleWidth 512 "${SOURCE}" --out "${ICONSET}/icon_512x512.png" > /dev/null 2>&1 69 | cp "${ICONSET}/icon_512x512.png" "${ICONSET}/icon_256x256@2x.png" 70 | $SIPS -s format png --resampleWidth 256 "${SOURCE}" --out "${ICONSET}/icon_256x256.png" > /dev/null 2>&1 71 | cp "${ICONSET}/icon_256x256.png" "${ICONSET}/icon_128x128@2x.png" 72 | $SIPS -s format png --resampleWidth 128 "${SOURCE}" --out "${ICONSET}/icon_128x128.png" > /dev/null 2>&1 73 | $SIPS -s format png --resampleWidth 64 "${SOURCE}" --out "${ICONSET}/icon_32x32@2x.png" > /dev/null 2>&1 74 | $SIPS -s format png --resampleWidth 32 "${SOURCE}" --out "${ICONSET}/icon_32x32.png" > /dev/null 2>&1 75 | cp "${ICONSET}/icon_32x32.png" "${ICONSET}/icon_16x16@2x.png" 76 | $SIPS -s format png --resampleWidth 16 "${SOURCE}" --out "${ICONSET}/icon_16x16.png" > /dev/null 2>&1 77 | 78 | # Create an icns file from the iconset 79 | $ICONUTIL -c icns "${ICONSET}" 80 | 81 | # Clean up the iconset 82 | rm -rf "${ICONSET}" 83 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "target": "es2015", 5 | "moduleResolution": "node", 6 | "pretty": true, 7 | "newLine": "LF", 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "noImplicitReturns": true, 13 | "sourceMap": true, 14 | "skipLibCheck": true, 15 | "allowJs": true, 16 | "jsx": "preserve" 17 | } 18 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-config-standard", 3 | "rules": { 4 | "indent": [ 5 | true, 6 | "spaces" 7 | ], 8 | "ter-indent": [ 9 | true, 10 | 2, 11 | { 12 | "SwitchCase": 1 13 | } 14 | ], 15 | "triple-equals": [ 16 | false 17 | ], 18 | "space-before-function-paren": [ 19 | true, 20 | { 21 | "anonymous": "always", 22 | "named": "never", 23 | "asyncArrow": "ignore" 24 | } 25 | ] 26 | } 27 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const commonConfig = { 4 | output: { 5 | path: path.resolve(__dirname, 'build'), 6 | filename: '[name].js' 7 | }, 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.tsx?$/, 12 | enforce: 'pre', 13 | use: [ 14 | { 15 | loader: 'tslint-loader', 16 | options: { 17 | typeCheck: false, 18 | emitErrors: true, 19 | configFile: 'tslint.json' 20 | } 21 | } 22 | ] 23 | }, 24 | { 25 | test: /\.tsx?$/, 26 | loader: ['babel-loader', 'ts-loader'] 27 | }, 28 | { 29 | test: /\.js$/, 30 | enforce: 'pre', 31 | loader: 'standard-loader', 32 | options: { 33 | typeCheck: true, 34 | emitErrors: true 35 | } 36 | }, 37 | { 38 | test: /\.jsx?$/, 39 | loader: 'babel-loader' 40 | }, 41 | { 42 | test: /\.css$/, 43 | use: ['style-loader', 'css-loader'] 44 | } 45 | ] 46 | }, 47 | resolve: { 48 | extensions: ['.js', '.ts', '.tsx', '.jsx', '.json'] 49 | }, 50 | node: { 51 | __dirname: false 52 | } 53 | } 54 | 55 | const HtmlWebpackPlugin = require('html-webpack-plugin') 56 | module.exports = [ 57 | Object.assign( 58 | { 59 | target: 'electron-main', 60 | entry: { main: './src/main/index.ts' } 61 | }, 62 | commonConfig), 63 | Object.assign( 64 | { 65 | target: 'electron-renderer', 66 | entry: { gui: './src/gui/index.tsx' }, 67 | plugins: [new HtmlWebpackPlugin({ 68 | template: 'src/gui/index.html' 69 | })] 70 | }, 71 | commonConfig) 72 | ] -------------------------------------------------------------------------------- /webpack.tests.config.js: -------------------------------------------------------------------------------- 1 | const webPack = require('./webpack.config') 2 | const fs = require('fs') 3 | const path = require('path') 4 | 5 | const readDirRecursiveSync = (folder, filter) => { 6 | const currentPath = fs.readdirSync(folder).map(f => path.join(folder, f)) 7 | const files = currentPath.filter(filter) 8 | 9 | const directories = currentPath 10 | .filter(f => fs.statSync(f).isDirectory()) 11 | .map(f => readDirRecursiveSync(f, filter)) 12 | .reduce((cur, next) => [...cur, ...next], []) 13 | 14 | return [...files, ...directories] 15 | } 16 | 17 | const getEntries = (folder) => 18 | readDirRecursiveSync(folder, f => f.match(/.*(tests|specs)\.tsx?$/)) 19 | .map((file) => { 20 | return { 21 | name: path.basename(file, path.extname(file)), 22 | path: path.resolve(file) 23 | } 24 | }) 25 | .reduce((memo, file) => { 26 | memo[file.name] = file.path 27 | return memo 28 | }, {}) 29 | 30 | module.exports = [ 31 | Object.assign({}, webPack[0], {entry: getEntries('./tests/gui/')}), 32 | Object.assign({}, webPack[0], {entry: getEntries('./tests/main/')}) 33 | ].map(s => { 34 | s.output.path = path.resolve(__dirname, '__tests__') 35 | return s 36 | }) --------------------------------------------------------------------------------