Merge branch 'main' into workspaces
Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
commit
ff687c5f64
23 changed files with 1408 additions and 637 deletions
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,37 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ""
|
||||
labels: bug
|
||||
assignees: zero01101, seijihariki
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
|
||||
- OS and Version: [e.g. iOS 15, win10x64 v22h2, palmOS 3]
|
||||
- GPU: [e.g. GTX1660, RTX3090, RX750, Voodoo2]
|
||||
- Browser and Version: [e.g. chrome 108, safari 16.1]
|
||||
- Any browser extensions: [rationale: [microsoft editor chrome addon hates overmask](https://github.com/zero01101/openOutpaint/discussions/88#discussioncomment-4498341)]
|
||||
- Commit of openOutpaint: [e.g. 3d29847, https://github.com/zero01101/openOutpaint/commit/b9fcc7ad00be46f05dd6b54cfac7c05904c84f87]
|
||||
- Commit of A1111 webUI: [e.g. c5bdba2, https://github.com/AUTOMATIC1111/stable-diffusion-webui/commit/5f1dfbbc959855fd90ba80c0c76301d2063772fa]
|
||||
- How you interact with A1111 webUI: [e.g. local hardware, colab, cloud host like linode]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
91
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
91
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
name: Bug Report
|
||||
description: You think somethings is broken in the UI
|
||||
title: "[Bug]: "
|
||||
labels: ["bug"]
|
||||
assignees: zero01101, seijihariki
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
*Please complete this form with as much detailed information as possible.*
|
||||
- type: textarea
|
||||
id: what-did
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: What happened that you weren't expecting, or what happened incorrectly?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to reproduce the problem
|
||||
description: Please provide us with precise step-by-step information on how to reproduce the issue
|
||||
value: |
|
||||
1. Go to ....
|
||||
2. Press ....
|
||||
3. ... [etc]
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: what-should
|
||||
attributes:
|
||||
label: What should have happened?
|
||||
description: Describe what you believe should have ocurred instead of what actually happened.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: commit
|
||||
attributes:
|
||||
label: Commit where the problem happens
|
||||
description: Which commit are you running? (i.e. https://github.com/zero01101/openOutpaint/commit/bf21c19ae352800d9e1b37bb490e817b6848e533, bf21c19)
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: platforms
|
||||
attributes:
|
||||
label: What platforms do you use to access openOutpaint?
|
||||
multiple: true
|
||||
options:
|
||||
- Windows
|
||||
- Linux
|
||||
- MacOS
|
||||
- iOS
|
||||
- Android
|
||||
- Other/Cloud
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: What browsers do you use to access the UI ?
|
||||
multiple: true
|
||||
options:
|
||||
- Mozilla Firefox
|
||||
- Google Chrome
|
||||
- Brave
|
||||
- Apple Safari
|
||||
- Microsoft Edge
|
||||
- Opera
|
||||
- Other (please list in additional information)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: browser-extensions
|
||||
attributes:
|
||||
label: Browser Extensions/Addons
|
||||
description: Please list all browser extensions/addons you have running. Some have been known to cause issues with openOutpaint.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: webui-commandline
|
||||
attributes:
|
||||
label: AUTOMATIC1111 webUI Commandline Arguments
|
||||
description: Please list all used commandline arguments passed to A1111 webUI (i.e. `--api`).
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: misc
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Please provide us with any relevant additional information, context, screenshots, etc.
|
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,19 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ""
|
||||
labels: enhancement
|
||||
assignees: zero01101, seijihariki
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
48
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
48
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature Request]: "
|
||||
labels: ["enhancement"]
|
||||
assignees: zero01101, seijihariki
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
*Please complete this form with as much detailed information as possible.*
|
||||
- type: textarea
|
||||
id: related
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: feature
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen, preferably with example use-case scenario.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: workflow
|
||||
attributes:
|
||||
label: Proposed workflow
|
||||
description: Please provide us with step by step information on how you'd like the feature to be accessed and used
|
||||
value: |
|
||||
1. Go to ....
|
||||
2. Press ....
|
||||
3. ...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: misc
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the feature request here.
|
|
@ -29,16 +29,16 @@ this is a completely vanilla javascript and html canvas outpainting convenience
|
|||
- optional (visibly) inverted mask mode - red masks get mutated, blue masks stay the same, but you can't take both pills at once
|
||||
- inpainting color brush to bring out your inner vincent van bob ross
|
||||
- dedicated img2img tool with optional border masking for enhanced output coherence with existing subject matter
|
||||
- marquee select tool to select regions and arbitrarily scale, create stamps, move chunks, peek at lower layers, do all sorts of damage
|
||||
- marquee select tool to select regions and arbitrarily scale, rotate, create stamps, move chunks, peek at lower layers, do all sorts of damage
|
||||
- optionally decoupled cursor size and output resolution
|
||||
- interrogate tool
|
||||
- floating control panel to easily change models/samplers/steps/prompts/CFG/etc options for each dream summoned from the latent void _(NOTE: model switching requires A1111 webUI to be on commit [5a6387e](https://github.com/AUTOMATIC1111/stable-diffusion-webui/commit/5a6387e189dc365c47a7979b9040d5b6fdd7ba43) or more recent)_
|
||||
- floating toolbox with handy keyboard shortcuts
|
||||
- optional grid snapping for precision
|
||||
- optional hi-res fix for blank/txt2img dreams
|
||||
- **_NOTE: as of v0.0.12.5/webUI commit [ef27a18](https://github.com/AUTOMATIC1111/stable-diffusion-webui/commit/ef27a18b6b7cb1a8eebdc9b2e88d25baf2c2414d), HRfix has been COMPLETELY reworked and no longer works even remotely the same and webUI would actively use the newly "invalid" firstpass sizes as the_ desired output resolution _after yelling about them being invalid, thus openOutpaint's implementation is no longer compatible with versions of A1111 predating that. You will be alerted to the outdated webUI and the HRfix option will become disabled in this event._**
|
||||
- **_NOTE: as of v0.0.12.5/webUI commit [ef27a18](https://github.com/AUTOMATIC1111/stable-diffusion-webui/commit/ef27a18b6b7cb1a8eebdc9b2e88d25baf2c2414d), HRfix has been COMPLETELY reworked and no longer works remotely the same, thus openOutpaint's implementation is no longer compatible with versions of A1111 predating that. You will be alerted to the outdated webUI and the HRfix option will become limited to simply using [reticle dimensions / 2] in this event. Please see the [manual entry](https://github.com/zero01101/openOutpaint/wiki/Manual#hrfix) regarding HRfix and its available options._**
|
||||
- optional overmasking for potentially better seams between outpaints - set overmask px value to 0 to disable the feature
|
||||
- import arbitrary images and scale/stamp on the canvas whenever, wherever you'd like
|
||||
- import arbitrary images and rotate/scale/stamp on the canvas whenever, wherever you'd like
|
||||
- upscaler support for final output images
|
||||
- saves your preferences/imported images to browser localstorage for maximum convenience
|
||||
- reset to defaults button to unsave your preferences if things go squirrely
|
||||
|
@ -48,6 +48,8 @@ this is a completely vanilla javascript and html canvas outpainting convenience
|
|||
|
||||
## operation
|
||||
|
||||
**_NOTE: [PLEASE SEE DOCUMENTATION REGARDING NEW HRfix FEATURES](https://github.com/zero01101/openOutpaint/wiki/Manual#hrfix) IMPLEMENTED AS OF webUI COMMIT [ef27a18](https://github.com/AUTOMATIC1111/stable-diffusion-webui/commit/ef27a18b6b7cb1a8eebdc9b2e88d25baf2c2414d)_**
|
||||
|
||||
### prerequisities
|
||||
|
||||
you'll obviously need A1111's webUI installed before you can use this, thus you're presumed to have an operational python install up and running to boot.
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
bottom: 15%;
|
||||
|
||||
mask-size: contain;
|
||||
-webkit-mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
|
||||
max-height: 70%;
|
||||
aspect-ratio: 1;
|
||||
|
|
|
@ -644,3 +644,9 @@ select > .style-select-option:active {
|
|||
.button.tool.active {
|
||||
background-color: rgb(60, 60, 130);
|
||||
}
|
||||
|
||||
/* Miscellaneous garbage */
|
||||
|
||||
.thirdwidth {
|
||||
max-width: 33%;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
.layer-render-target .collection {
|
||||
position: absolute;
|
||||
transform-origin: 0px 0px;
|
||||
}
|
||||
|
||||
.layer-render-target .collection > .collection-input-overlay {
|
||||
|
@ -45,7 +46,7 @@
|
|||
right: 0;
|
||||
}
|
||||
|
||||
#layer-overlay {
|
||||
.overlay-canvas {
|
||||
position: fixed;
|
||||
|
||||
top: 0;
|
||||
|
|
|
@ -231,6 +231,7 @@ div.autocomplete > .autocomplete-list {
|
|||
}
|
||||
|
||||
div.autocomplete > .autocomplete-list > .autocomplete-option {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
overflow: hidden;
|
||||
|
|
|
@ -158,7 +158,9 @@
|
|||
|
||||
background-color: #293d3d77;
|
||||
|
||||
-webkit-mask-image: url("../../res/icons/chevron-up.svg");
|
||||
mask-image: url("../../res/icons/chevron-up.svg");
|
||||
-webkit-mask-size: contain;
|
||||
mask-size: contain;
|
||||
|
||||
width: 60px;
|
||||
|
|
60
index.html
60
index.html
|
@ -5,15 +5,15 @@
|
|||
<title>openOutpaint 🐠</title>
|
||||
<!-- CSS Variables -->
|
||||
<link href="css/colors.css?v=3f81e80" rel="stylesheet" />
|
||||
<link href="css/icons.css?v=caa702e" rel="stylesheet" />
|
||||
<link href="css/icons.css?v=9ae0466" rel="stylesheet" />
|
||||
|
||||
<link href="css/index.css?v=69d3b9e" rel="stylesheet" />
|
||||
<link href="css/layers.css?v=b4fbf61" rel="stylesheet" />
|
||||
<link href="css/index.css?v=5b8d4d6" rel="stylesheet" />
|
||||
<link href="css/layers.css?v=92c0352" rel="stylesheet" />
|
||||
|
||||
<link href="css/ui/generic.css?v=4b9afe2" rel="stylesheet" />
|
||||
<link href="css/ui/generic.css?v=802bd41" rel="stylesheet" />
|
||||
|
||||
<link href="css/ui/history.css?v=0b03861" rel="stylesheet" />
|
||||
<link href="css/ui/layers.css?v=4fd95fe" rel="stylesheet" />
|
||||
<link href="css/ui/layers.css?v=ae472cd" rel="stylesheet" />
|
||||
<link href="css/ui/toolbar.css?v=109c78f" rel="stylesheet" />
|
||||
|
||||
<!-- Tool Specific CSS -->
|
||||
|
@ -49,6 +49,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- Prompts section -->
|
||||
<button type="button" class="collapsible">Prompts</button>
|
||||
<div class="content prompt">
|
||||
|
@ -98,10 +99,20 @@
|
|||
step="1" />
|
||||
<br />
|
||||
<input type="checkbox" id="cbxHRFix" onchange="changeHiResFix()" />
|
||||
<label for="cbxHRFix">Apply txt2img HRfix</label>
|
||||
<label for="cbxHRFix">Apply Txt2Img HRfix</label>
|
||||
<br />
|
||||
<input
|
||||
type="checkbox"
|
||||
id="cbxHRFSquare"
|
||||
onchange="changeHiResSquare()"
|
||||
class="hrfix" />
|
||||
<label for="cbxHRFSquare" class="hrfix">
|
||||
Square Firstpass Aspect
|
||||
</label>
|
||||
<br class="hrfix" />
|
||||
<div id="hrFixScale" class="hrfix"></div>
|
||||
<div id="hrFixLockPx" class="hrfix"></div>
|
||||
<div id="hrFixSteps" class="hrfix"></div>
|
||||
<label id="hrFixLabel" class="hrfix">Choose HRfix upscaler</label>
|
||||
<div id="hrFixUpscaler" class="hrfix"></div>
|
||||
<div id="hrDenoising" class="hrfix"></div>
|
||||
|
@ -191,7 +202,13 @@
|
|||
<br />
|
||||
<span id="version">
|
||||
<a href="https://github.com/zero01101/openOutpaint" target="_blank">
|
||||
Alpha release v0.0.12.5
|
||||
Alpha release v0.0.13
|
||||
</a>
|
||||
<br />
|
||||
<a
|
||||
href="https://github.com/zero01101/openOutpaint/wiki/Manual"
|
||||
target="_blank">
|
||||
User Manual
|
||||
</a>
|
||||
</span>
|
||||
<br />
|
||||
|
@ -303,7 +320,8 @@
|
|||
<div id="layer-render" class="layer-render-target"></div>
|
||||
|
||||
<!-- Overlay -->
|
||||
<canvas id="layer-overlay" class="layer-overlay"></canvas>
|
||||
<canvas id="layer-overlay" class="overlay-canvas"></canvas>
|
||||
<canvas id="layer-debug-overlay" class="overlay-canvas"></canvas>
|
||||
|
||||
<!-- Page Overlay -->
|
||||
<div id="page-overlay-wrapper" class="page-overlay invisible">
|
||||
|
@ -315,7 +333,7 @@
|
|||
<div class="ui separator"></div>
|
||||
<iframe
|
||||
id="page-overlay"
|
||||
src="pages/configuration.html?v=3d710ce"></iframe>
|
||||
src="pages/configuration.html?v=973baf2"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -324,26 +342,28 @@
|
|||
<script src="js/defaults.js?v=5b06818" type="text/javascript"></script>
|
||||
|
||||
<!-- Base Libs -->
|
||||
<script src="js/lib/util.js?v=7f6847c" type="text/javascript"></script>
|
||||
<script src="js/lib/util.js?v=49a78a6" type="text/javascript"></script>
|
||||
<script src="js/lib/events.js?v=2ab7933" type="text/javascript"></script>
|
||||
<script src="js/lib/workspaces.js?v=4fbd55b" type="text/javascript"></script>
|
||||
<script
|
||||
src="js/lib/workspaces.js?v=4fbd55b"
|
||||
type="text/javascript"></script>
|
||||
<script src="js/lib/input.js?v=09298ac" type="text/javascript"></script>
|
||||
<script src="js/lib/layers.js?v=a1f8aea" type="text/javascript"></script>
|
||||
<script src="js/lib/commands.js?v=bf23c83" type="text/javascript"></script>
|
||||
|
||||
<script src="js/lib/toolbar.js?v=ca3fccf" type="text/javascript"></script>
|
||||
<script src="js/lib/toolbar.js?v=306d637" type="text/javascript"></script>
|
||||
<script src="js/lib/ui.js?v=76ede2b" type="text/javascript"></script>
|
||||
|
||||
<script
|
||||
src="js/initalize/layers.populate.js?v=c81f0a5"
|
||||
src="js/initalize/layers.populate.js?v=39785ac"
|
||||
type="text/javascript"></script>
|
||||
|
||||
<!-- Configuration -->
|
||||
<script src="js/config.js?v=8da6a43" type="text/javascript"></script>
|
||||
<script src="js/config.js?v=e0345e0" type="text/javascript"></script>
|
||||
|
||||
<!-- Content -->
|
||||
<script src="js/prompt.js?v=7a1c68c" type="text/javascript"></script>
|
||||
<script src="js/index.js?v=a0ae6b5" type="text/javascript"></script>
|
||||
<script src="js/index.js?v=9d20cb0" type="text/javascript"></script>
|
||||
|
||||
<script
|
||||
src="js/ui/floating/history.js?v=fc92d14"
|
||||
|
@ -354,20 +374,20 @@
|
|||
|
||||
<!-- Load Tools -->
|
||||
<script
|
||||
src="js/ui/tool/generic.js?v=2bcd36d"
|
||||
src="js/ui/tool/generic.js?v=3e678e0"
|
||||
type="text/javascript"></script>
|
||||
|
||||
<script src="js/ui/tool/dream.js?v=1f10ae6" type="text/javascript"></script>
|
||||
<script src="js/ui/tool/dream.js?v=eb98dc9" type="text/javascript"></script>
|
||||
<script
|
||||
src="js/ui/tool/maskbrush.js?v=1e8a893"
|
||||
type="text/javascript"></script>
|
||||
<script
|
||||
src="js/ui/tool/colorbrush.js?v=8acb4f6"
|
||||
src="js/ui/tool/colorbrush.js?v=3f8c01a"
|
||||
type="text/javascript"></script>
|
||||
<script
|
||||
src="js/ui/tool/select.js?v=ade791e"
|
||||
src="js/ui/tool/select.js?v=e27bbdf"
|
||||
type="text/javascript"></script>
|
||||
<script src="js/ui/tool/stamp.js?v=3c07ac8" type="text/javascript"></script>
|
||||
<script src="js/ui/tool/stamp.js?v=4a86ff8" type="text/javascript"></script>
|
||||
<script
|
||||
src="js/ui/tool/interrogate.js?v=e579ff1"
|
||||
type="text/javascript"></script>
|
||||
|
|
28
js/config.js
28
js/config.js
|
@ -5,9 +5,37 @@
|
|||
*/
|
||||
const config = makeReadOnly(
|
||||
{
|
||||
// Grid Size
|
||||
gridSize: 64,
|
||||
|
||||
// Scroll Tick Limit (How much must scroll to reach next tick)
|
||||
wheelTickSize: 50,
|
||||
|
||||
/** Select Tool */
|
||||
// Handle Draw Size
|
||||
handleDrawSize: 12,
|
||||
// Handle Draw Hover Scale
|
||||
handleDrawHoverScale: 1.3,
|
||||
// Handle Detect Size
|
||||
handleDetectSize: 20,
|
||||
// Rotate Handle Distance (from selection)
|
||||
rotateHandleDistance: 32,
|
||||
|
||||
// Rotation Snapping Distance
|
||||
rotationSnappingDistance: (10 * Math.PI) / 180,
|
||||
// Rotation Snapping Angles
|
||||
rotationSnappingAngles: [
|
||||
(-Math.PI * 4) / 4,
|
||||
(-Math.PI * 3) / 4,
|
||||
(-Math.PI * 2) / 4,
|
||||
(-Math.PI * 1) / 4,
|
||||
0,
|
||||
(Math.PI * 1) / 4,
|
||||
(Math.PI * 2) / 4,
|
||||
(Math.PI * 3) / 4,
|
||||
(Math.PI * 4) / 4,
|
||||
],
|
||||
|
||||
// Endpoint
|
||||
api: makeReadOnly({path: "/sdapi/v1/"}),
|
||||
},
|
||||
|
|
230
js/index.js
230
js/index.js
|
@ -111,6 +111,10 @@ var stableDiffusionData = {
|
|||
//firstphase_height: 0, //20230102 welp looks like the entire way HRfix is implemented has become bonkersly different
|
||||
hr_scale: 2.0,
|
||||
hr_upscaler: "None",
|
||||
hr_second_pass_steps: 0,
|
||||
hr_resize_x: 0,
|
||||
hr_resize_y: 0,
|
||||
hr_square_aspect: false,
|
||||
styles: [],
|
||||
// here's some more fields that might be useful
|
||||
|
||||
|
@ -139,6 +143,7 @@ var stableDiffusionData = {
|
|||
var host = "";
|
||||
var url = "/sdapi/v1/";
|
||||
const basePixelCount = 64; //64 px - ALWAYS 64 PX
|
||||
var focused = true;
|
||||
|
||||
function getSDData() {
|
||||
const w = workspaces.current.settings;
|
||||
|
@ -174,8 +179,10 @@ function startup() {
|
|||
changeSmoothRendering();
|
||||
changeSeed();
|
||||
changeHiResFix();
|
||||
changeHiResSquare();
|
||||
changeRestoreFaces();
|
||||
changeSyncCursorSize();
|
||||
checkFocus();
|
||||
}
|
||||
|
||||
function setFixedHost(h, changePromptMessage) {
|
||||
|
@ -343,7 +350,23 @@ async function testHostConnection() {
|
|||
|
||||
let checkInProgress = false;
|
||||
|
||||
const checkConnection = async (notify = false) => {
|
||||
const checkConnection = async (
|
||||
notify = false,
|
||||
simpleProgressStatus = false
|
||||
) => {
|
||||
const apiIssueResult = () => {
|
||||
setConnectionStatus("apiissue");
|
||||
const message = `The host is online, but the API seems to be disabled.\nHave you run the webui with the flag '--api', or is the flag '--gradio-debug' currently active?`;
|
||||
console.error(message);
|
||||
if (notify) alert(message);
|
||||
};
|
||||
|
||||
const offlineResult = () => {
|
||||
setConnectionStatus("offline");
|
||||
const message = `The connection with the host returned an error: ${response.status} - ${response.statusText}`;
|
||||
console.error(message);
|
||||
if (notify) alert(message);
|
||||
};
|
||||
if (checkInProgress)
|
||||
throw new CheckInProgressError(
|
||||
"Check is currently in progress, please try again"
|
||||
|
@ -352,50 +375,63 @@ async function testHostConnection() {
|
|||
var url = document.getElementById("host").value + "/startup-events";
|
||||
// Attempt normal request
|
||||
try {
|
||||
// Check if API is available
|
||||
const response = await fetch(
|
||||
document.getElementById("host").value + "/sdapi/v1/options"
|
||||
);
|
||||
const optionsdata = await response.json();
|
||||
if (optionsdata["use_scale_latent_for_hires_fix"]) {
|
||||
const message = `You are using an outdated version of A1111 webUI.\nThe HRfix options will not work until you update to at least commit ef27a18 or newer.\n(https://github.com/AUTOMATIC1111/stable-diffusion-webui/commit/ef27a18b6b7cb1a8eebdc9b2e88d25baf2c2414d)\nHRfix will fallback to half-resolution only.`;
|
||||
console.warn(message);
|
||||
if (notify) alert(message);
|
||||
// Hide all new hrfix options
|
||||
document
|
||||
.querySelectorAll(".hrfix")
|
||||
.forEach((el) => (el.style.display = "none"));
|
||||
|
||||
// We are using old HRFix
|
||||
global.isOldHRFix = true;
|
||||
stableDiffusionData.enable_hr = false;
|
||||
}
|
||||
switch (response.status) {
|
||||
case 200: {
|
||||
setConnectionStatus("online");
|
||||
// Load data as soon as connection is first stablished
|
||||
if (firstTimeOnline) {
|
||||
getConfig();
|
||||
getStyles();
|
||||
getSamplers();
|
||||
getUpscalers();
|
||||
getModels();
|
||||
firstTimeOnline = false;
|
||||
if (simpleProgressStatus) {
|
||||
const response = await fetch(
|
||||
document.getElementById("host").value + "/sdapi/v1/progress" // seems to be the "lightest" endpoint?
|
||||
);
|
||||
switch (response.status) {
|
||||
case 200: {
|
||||
setConnectionStatus("online");
|
||||
break;
|
||||
}
|
||||
case 404: {
|
||||
apiIssueResult();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
offlineResult();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 404: {
|
||||
setConnectionStatus("apiissue");
|
||||
const message = `The host is online, but the API seems to be disabled.\nHave you run the webui with the flag '--api', or is the flag '--gradio-debug' currently active?`;
|
||||
console.error(message);
|
||||
} else {
|
||||
// Check if API is available
|
||||
const response = await fetch(
|
||||
document.getElementById("host").value + "/sdapi/v1/options"
|
||||
);
|
||||
const optionsdata = await response.json();
|
||||
if (optionsdata["use_scale_latent_for_hires_fix"]) {
|
||||
const message = `You are using an outdated version of A1111 webUI.\nThe HRfix options will not work until you update to at least commit ef27a18 or newer.\n(https://github.com/AUTOMATIC1111/stable-diffusion-webui/commit/ef27a18b6b7cb1a8eebdc9b2e88d25baf2c2414d)\nHRfix will fallback to half-resolution only.`;
|
||||
console.warn(message);
|
||||
if (notify) alert(message);
|
||||
break;
|
||||
// Hide all new hrfix options
|
||||
document
|
||||
.querySelectorAll(".hrfix")
|
||||
.forEach((el) => (el.style.display = "none"));
|
||||
|
||||
// We are using old HRFix
|
||||
global.isOldHRFix = true;
|
||||
stableDiffusionData.enable_hr = false;
|
||||
}
|
||||
default: {
|
||||
setConnectionStatus("offline");
|
||||
const message = `The connection with the host returned an error: ${response.status} - ${response.statusText}`;
|
||||
console.error(message);
|
||||
if (notify) alert(message);
|
||||
switch (response.status) {
|
||||
case 200: {
|
||||
setConnectionStatus("online");
|
||||
// Load data as soon as connection is first stablished
|
||||
if (firstTimeOnline) {
|
||||
getConfig();
|
||||
getStyles();
|
||||
getSamplers();
|
||||
getUpscalers();
|
||||
getModels();
|
||||
firstTimeOnline = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 404: {
|
||||
apiIssueResult();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
offlineResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -421,7 +457,9 @@ async function testHostConnection() {
|
|||
return status;
|
||||
};
|
||||
|
||||
await checkConnection(!urlParams.has("noprompt"));
|
||||
if (focused || firstTimeOnline) {
|
||||
await checkConnection(!urlParams.has("noprompt"));
|
||||
}
|
||||
|
||||
// On click, attempt to refresh
|
||||
connectionIndicator.onclick = async () => {
|
||||
|
@ -433,15 +471,23 @@ async function testHostConnection() {
|
|||
}
|
||||
};
|
||||
|
||||
// Checks every 5 seconds if offline, 30 seconds if online
|
||||
// Checks every 5 seconds if offline, 60 seconds if online
|
||||
const checkAgain = () => {
|
||||
setTimeout(
|
||||
async () => {
|
||||
await checkConnection();
|
||||
checkFocus();
|
||||
if (focused || firstTimeOnline) {
|
||||
setTimeout(
|
||||
async () => {
|
||||
let simple = !firstTimeOnline;
|
||||
await checkConnection(false, simple);
|
||||
checkAgain();
|
||||
},
|
||||
connectionStatus ? 60000 : 5000
|
||||
);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
checkAgain();
|
||||
},
|
||||
connectionStatus ? 30000 : 5000
|
||||
);
|
||||
}, 60000);
|
||||
}
|
||||
};
|
||||
|
||||
checkAgain();
|
||||
|
@ -692,6 +738,17 @@ const lockPxSlider = makeSlider(
|
|||
1
|
||||
);
|
||||
|
||||
const hrStepsSlider = makeSlider(
|
||||
"HRfix Steps",
|
||||
document.getElementById("hrFixSteps"),
|
||||
"hr_second_pass_steps",
|
||||
0,
|
||||
localStorage.getItem("openoutpaint/settings.max-steps") || 70,
|
||||
5,
|
||||
0,
|
||||
1
|
||||
);
|
||||
|
||||
function changeMaskBlur() {
|
||||
stableDiffusionData.mask_blur = parseInt(
|
||||
document.getElementById("maskBlur").value
|
||||
|
@ -704,33 +761,43 @@ function changeSeed() {
|
|||
localStorage.setItem("openoutpaint/seed", stableDiffusionData.seed);
|
||||
}
|
||||
|
||||
function changeHRFX() {
|
||||
stableDiffusionData.hr_resize_x =
|
||||
document.getElementById("hr_resize_x").value;
|
||||
}
|
||||
|
||||
function changeHRFY() {
|
||||
stableDiffusionData.hr_resize_y =
|
||||
document.getElementById("hr_resize_y").value;
|
||||
}
|
||||
|
||||
function changeHiResFix() {
|
||||
stableDiffusionData.enable_hr = Boolean(
|
||||
document.getElementById("cbxHRFix").checked
|
||||
);
|
||||
localStorage.setItem("openoutpaint/enable_hr", stableDiffusionData.enable_hr);
|
||||
var hrfSlider = document.getElementById("hrFixScale");
|
||||
var hrfOpotions = document.getElementById("hrFixUpscaler");
|
||||
var hrfLabel = document.getElementById("hrFixLabel");
|
||||
var hrfDenoiseSlider = document.getElementById("hrDenoising");
|
||||
var hrfLockPxSlider = document.getElementById("hrFixLockPx");
|
||||
// var hrfSlider = document.getElementById("hrFixScale");
|
||||
// var hrfOpotions = document.getElementById("hrFixUpscaler");
|
||||
// var hrfLabel = document.getElementById("hrFixLabel");
|
||||
// var hrfDenoiseSlider = document.getElementById("hrDenoising");
|
||||
// var hrfLockPxSlider = document.getElementById("hrFixLockPx");
|
||||
if (stableDiffusionData.enable_hr) {
|
||||
hrfSlider.classList.remove("invisible");
|
||||
hrfOpotions.classList.remove("invisible");
|
||||
hrfLabel.classList.remove("invisible");
|
||||
hrfDenoiseSlider.classList.remove("invisible");
|
||||
hrfLockPxSlider.classList.remove("invisible");
|
||||
//state.ctxmenu.keepUnmaskedBlurSliderLinebreak.classList.add("invisible");
|
||||
document
|
||||
.querySelectorAll(".hrfix")
|
||||
.forEach((el) => el.classList.remove("invisible"));
|
||||
} else {
|
||||
hrfSlider.classList.add("invisible");
|
||||
hrfOpotions.classList.add("invisible");
|
||||
hrfLabel.classList.add("invisible");
|
||||
hrfDenoiseSlider.classList.add("invisible");
|
||||
hrfLockPxSlider.classList.add("invisible");
|
||||
//state.ctxmenu.keepUnmaskedBlurSliderLinebreak.classList.remove("invisible");
|
||||
document
|
||||
.querySelectorAll(".hrfix")
|
||||
.forEach((el) => el.classList.add("invisible"));
|
||||
}
|
||||
}
|
||||
|
||||
function changeHiResSquare() {
|
||||
stableDiffusionData.hr_square_aspect = Boolean(
|
||||
document.getElementById("cbxHRFSquare").checked
|
||||
);
|
||||
}
|
||||
|
||||
function changeRestoreFaces() {
|
||||
stableDiffusionData.restore_faces = Boolean(
|
||||
document.getElementById("cbxRestoreFaces").checked
|
||||
|
@ -828,7 +895,17 @@ async function getUpscalers() {
|
|||
.split(",")
|
||||
.map((v) => v.trim()); // need "None" for stupid hrfix changes razza frazza
|
||||
const upscalers = upscalersPlusNone.filter((v) => v !== "None"); // converting the result to a list of upscalers
|
||||
// upscalersPlusNone.push([
|
||||
// "Latent",
|
||||
// "Latent (antialiased)",
|
||||
// "Latent (bicubic)",
|
||||
// "Latent (bicubic, antialiased)",
|
||||
// "Latent (nearest)",
|
||||
// ]);
|
||||
upscalersPlusNone.push("Latent");
|
||||
upscalersPlusNone.push("Latent (antialiased)");
|
||||
upscalersPlusNone.push("Latent (bicubic)");
|
||||
upscalersPlusNone.push("Latent (bicubic, antialiased)");
|
||||
upscalersPlusNone.push("Latent (nearest)"); // GRUMBLE GRUMBLE
|
||||
|
||||
upscalerAutoComplete.options = upscalers.map((u) => {
|
||||
|
@ -1214,3 +1291,24 @@ function resetToDefaults() {
|
|||
localStorage.clear();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
checkFocus();
|
||||
});
|
||||
|
||||
window.addEventListener("blur", () => {
|
||||
checkFocus();
|
||||
});
|
||||
|
||||
window.addEventListener("focus", () => {
|
||||
checkFocus();
|
||||
});
|
||||
|
||||
function checkFocus() {
|
||||
let hasFocus = document.hasFocus();
|
||||
if (document.hidden || !hasFocus) {
|
||||
focused = false;
|
||||
} else {
|
||||
focused = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,61 +160,72 @@ debugLayer.hide(); // Hidden by default
|
|||
* The global viewport object (may be modularized in the future). All
|
||||
* coordinates given are of the center of the viewport
|
||||
*
|
||||
* cx and cy are the viewport's world coordinates, scaled to zoom level.
|
||||
* _x and _y are actual coordinates in the DOM space
|
||||
* cx and cy are the viewport's world coordinates.
|
||||
*
|
||||
* The transform() function does some transforms and writes them to the
|
||||
* provided element.
|
||||
*/
|
||||
const viewport = {
|
||||
get cx() {
|
||||
return this._x * this.zoom;
|
||||
},
|
||||
class Viewport {
|
||||
cx = 0;
|
||||
cy = 0;
|
||||
|
||||
set cx(v) {
|
||||
return (this._x = v / this.zoom);
|
||||
},
|
||||
_x: 0,
|
||||
get cy() {
|
||||
return this._y * this.zoom;
|
||||
},
|
||||
set cy(v) {
|
||||
return (this._y = v / this.zoom);
|
||||
},
|
||||
_y: 0,
|
||||
zoom: 1,
|
||||
rotation: 0,
|
||||
zoom = 1;
|
||||
|
||||
/**
|
||||
* Gets viewport width in canvas coordinates
|
||||
*/
|
||||
get w() {
|
||||
return (window.innerWidth * 1) / this.zoom;
|
||||
},
|
||||
return window.innerWidth * this.zoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets viewport height in canvas coordinates
|
||||
*/
|
||||
get h() {
|
||||
return (window.innerHeight * 1) / this.zoom;
|
||||
},
|
||||
return window.innerHeight * this.zoom;
|
||||
}
|
||||
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
get v2c() {
|
||||
const m = new DOMMatrix();
|
||||
|
||||
m.translateSelf(-this.w / 2, -this.h / 2);
|
||||
m.translateSelf(this.cx, this.cy);
|
||||
m.scaleSelf(this.zoom);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
get c2v() {
|
||||
return this.v2c.invertSelf();
|
||||
}
|
||||
|
||||
viewToCanvas(x, y) {
|
||||
return {
|
||||
x: this.cx + this.w * (x / window.innerWidth - 0.5),
|
||||
y: this.cy + this.h * (y / window.innerHeight - 0.5),
|
||||
};
|
||||
},
|
||||
if (x.x !== undefined) return this.v2c.transformPoint(x);
|
||||
return this.v2c.transformPoint({x, y});
|
||||
}
|
||||
|
||||
canvasToView(x, y) {
|
||||
return {
|
||||
x: window.innerWidth * ((x - this.cx) / this.w) + window.innerWidth / 2,
|
||||
y: window.innerHeight * ((y - this.cy) / this.h) + window.innerHeight / 2,
|
||||
};
|
||||
},
|
||||
if (x.x !== undefined) return this.c2v.transformPoint(x);
|
||||
return this.c2v.transformPoint({x, y});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply transformation
|
||||
*
|
||||
* @param {HTMLElement} el Element to apply CSS transform to
|
||||
*/
|
||||
transform(el) {
|
||||
el.style.transformOrigin = `${this.cx}px ${this.cy}px`;
|
||||
el.style.transform = `scale(${this.zoom}) translate(${-(
|
||||
this._x -
|
||||
this.w / 2
|
||||
)}px, ${-(this._y - this.h / 2)}px)`;
|
||||
},
|
||||
};
|
||||
el.style.transformOrigin = "0px 0px";
|
||||
el.style.transform = this.c2v;
|
||||
}
|
||||
}
|
||||
|
||||
const viewport = new Viewport(0, 0);
|
||||
|
||||
viewport.cx = imageCollection.size.w / 2;
|
||||
viewport.cy = imageCollection.size.h / 2;
|
||||
|
@ -296,7 +307,7 @@ mouse.listen.camera.onwheel.on((evn) => {
|
|||
const pcy = viewport.cy;
|
||||
|
||||
// Apply zoom
|
||||
viewport.zoom *= 1 - evn.delta * 0.0002;
|
||||
viewport.zoom *= 1 + evn.delta * 0.0002;
|
||||
|
||||
// Apply normal zoom (center of viewport)
|
||||
viewport.cx = pcx;
|
||||
|
@ -305,13 +316,13 @@ mouse.listen.camera.onwheel.on((evn) => {
|
|||
viewport.transform(imageCollection.element);
|
||||
|
||||
// Calculate new viewport center and move
|
||||
const newCursorPosition = viewport.viewToCanvas(evn.x, evn.y);
|
||||
viewport.cx = pcx - (newCursorPosition.x - cursorPosition.x);
|
||||
viewport.cy = pcy - (newCursorPosition.y - cursorPosition.y);
|
||||
//const newCursorPosition = viewport.viewToCanvas(evn.x, evn.y);
|
||||
//viewport.cx = pcx - (newCursorPosition.x - cursorPosition.x);
|
||||
//viewport.cy = pcy - (newCursorPosition.y - cursorPosition.y);
|
||||
|
||||
viewport.transform(imageCollection.element);
|
||||
//viewport.transform(imageCollection.element);
|
||||
|
||||
toolbar.currentTool.redraw();
|
||||
toolbar._current_tool.redrawui && toolbar._current_tool.redrawui();
|
||||
});
|
||||
|
||||
const cameraPaintStart = (evn) => {
|
||||
|
@ -320,8 +331,8 @@ const cameraPaintStart = (evn) => {
|
|||
|
||||
const cameraPaint = (evn) => {
|
||||
if (worldInit) {
|
||||
viewport.cx = worldInit.x + (evn.ix - evn.x) / viewport.zoom;
|
||||
viewport.cy = worldInit.y + (evn.iy - evn.y) / viewport.zoom;
|
||||
viewport.cx = worldInit.x + (evn.ix - evn.x) * viewport.zoom;
|
||||
viewport.cy = worldInit.y + (evn.iy - evn.y) * viewport.zoom;
|
||||
|
||||
// Limits
|
||||
viewport.cx = Math.max(
|
||||
|
@ -337,6 +348,9 @@ const cameraPaint = (evn) => {
|
|||
}
|
||||
|
||||
viewport.transform(imageCollection.element);
|
||||
toolbar._current_tool.state.redrawui &&
|
||||
toolbar._current_tool.state.redrawui();
|
||||
|
||||
if (global.debug) {
|
||||
debugCtx.clearRect(0, 0, debugCanvas.width, debugCanvas.height);
|
||||
debugCtx.fillStyle = "#F0F";
|
||||
|
|
|
@ -90,14 +90,20 @@ const toolbar = {
|
|||
name: toolname,
|
||||
enabled: false,
|
||||
_element: null,
|
||||
state: {},
|
||||
state: {
|
||||
redrawui: () => tool.state.redraw && tool.state.redraw(),
|
||||
},
|
||||
options,
|
||||
/**
|
||||
* If the tool has a redraw() function in its state, then run it
|
||||
*/
|
||||
redraw: () => {
|
||||
tool.state.redrawui && tool.state.redrawui();
|
||||
tool.state.redraw && tool.state.redraw();
|
||||
},
|
||||
redrawui: () => {
|
||||
tool.state.redrawui && tool.state.redrawui();
|
||||
},
|
||||
enable: (opt = null) => {
|
||||
if (toolbar._locked) return;
|
||||
|
||||
|
|
|
@ -32,6 +32,31 @@ class BoundingBox {
|
|||
w = 0;
|
||||
h = 0;
|
||||
|
||||
/** @type {Point} */
|
||||
get tl() {
|
||||
return {x: this.x, y: this.y};
|
||||
}
|
||||
|
||||
/** @type {Point} */
|
||||
get tr() {
|
||||
return {x: this.x + this.w, y: this.y};
|
||||
}
|
||||
|
||||
/** @type {Point} */
|
||||
get bl() {
|
||||
return {x: this.x, y: this.y + this.h};
|
||||
}
|
||||
|
||||
/** @type {Point} */
|
||||
get br() {
|
||||
return {x: this.x + this.w, y: this.y + this.h};
|
||||
}
|
||||
|
||||
/** @type {Point} */
|
||||
get center() {
|
||||
return {x: this.x + this.w / 2, y: this.y + this.h / 2};
|
||||
}
|
||||
|
||||
constructor({x, y, w, h} = {x: 0, y: 0, w: 0, h: 0}) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
@ -64,6 +89,18 @@ class BoundingBox {
|
|||
h: maxy - miny,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a transformed bounding box (using top-left, bottom-right points)
|
||||
*
|
||||
* @param {DOMMatrix} transform Transformation matrix to transform points
|
||||
*/
|
||||
transform(transform) {
|
||||
return BoundingBox.fromStartEnd(
|
||||
transform.transformPoint({x: this.x, y: this.y}),
|
||||
transform.transformPoint({x: this.x + this.w, y: this.y + this.h})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -176,7 +176,7 @@ const colorBrushTool = () =>
|
|||
uiCtx.arc(
|
||||
vcp.x,
|
||||
vcp.y,
|
||||
(state.eyedropper ? 50 : state.brushSize / 2) * viewport.zoom,
|
||||
(state.eyedropper ? 50 : state.brushSize / 2) / viewport.zoom,
|
||||
0,
|
||||
2 * Math.PI,
|
||||
true
|
||||
|
@ -197,7 +197,7 @@ const colorBrushTool = () =>
|
|||
uiCtx.arc(
|
||||
vcp.x,
|
||||
vcp.y,
|
||||
(state.brushSize / 2) * viewport.zoom,
|
||||
state.brushSize / (2 * viewport.zoom),
|
||||
0,
|
||||
2 * Math.PI,
|
||||
true
|
||||
|
|
|
@ -156,6 +156,7 @@ const _dream = async (endpoint, request) => {
|
|||
generating(false);
|
||||
}
|
||||
var responseSubdata = JSON.parse(data.info);
|
||||
console.debug(responseSubdata);
|
||||
var returnData = {
|
||||
images: data.images,
|
||||
seeds: responseSubdata.all_seeds,
|
||||
|
@ -176,6 +177,7 @@ const _dream = async (endpoint, request) => {
|
|||
* @returns {Promise<HTMLImageElement | null>}
|
||||
*/
|
||||
const _generate = async (endpoint, request, bb, options = {}) => {
|
||||
var alertCount = 0;
|
||||
defaultOpt(options, {
|
||||
drawEvery: 0.2 / request.n_iter,
|
||||
keepUnmask: null,
|
||||
|
@ -515,6 +517,17 @@ const _generate = async (endpoint, request, bb, options = {}) => {
|
|||
});
|
||||
};
|
||||
|
||||
const removeImg = async () => {
|
||||
if (!images[at]) return;
|
||||
images.splice(at, 1);
|
||||
seeds.splice(at, 1);
|
||||
if (at >= images.length) at = 0;
|
||||
imageindextxt.textContent = `${at}/${images.length - 1}`;
|
||||
var seed = seeds[at];
|
||||
seedbtn.title = "Use seed " + seed;
|
||||
redraw();
|
||||
};
|
||||
|
||||
const makeMore = async () => {
|
||||
const moreQ = await waitQueue();
|
||||
try {
|
||||
|
@ -531,9 +544,14 @@ const _generate = async (endpoint, request, bb, options = {}) => {
|
|||
seeds.push(...dreamData.seeds);
|
||||
imageindextxt.textContent = `${at}/${images.length - 1}`;
|
||||
} catch (e) {
|
||||
alert(
|
||||
`Error generating images. Please try again or see console for more details`
|
||||
);
|
||||
if (alertCount < 2) {
|
||||
alert(
|
||||
`Error generating images. Please try again or see console for more details`
|
||||
);
|
||||
} else {
|
||||
eagerGenerateCount = 0;
|
||||
}
|
||||
alertCount++;
|
||||
console.warn(`[dream] Error generating images:`);
|
||||
console.warn(e);
|
||||
} finally {
|
||||
|
@ -589,6 +607,9 @@ const _generate = async (endpoint, request, bb, options = {}) => {
|
|||
case "+":
|
||||
makeMore();
|
||||
break;
|
||||
case "-":
|
||||
removeImg();
|
||||
break;
|
||||
default:
|
||||
switch (evn.code) {
|
||||
case "ArrowRight":
|
||||
|
@ -652,7 +673,11 @@ const _generate = async (endpoint, request, bb, options = {}) => {
|
|||
const oncancelhandler = mouse.listen.world.btn.right.onclick.on(
|
||||
(evn, state) => {
|
||||
if (!state.dream_processed && bb.contains(evn.x, evn.y)) {
|
||||
discardImg();
|
||||
if (images.length > 1) {
|
||||
removeImg();
|
||||
} else {
|
||||
discardImg();
|
||||
}
|
||||
imageCollection.inputElement.style.cursor = "auto";
|
||||
state.dream_processed = true;
|
||||
}
|
||||
|
@ -736,6 +761,12 @@ const _generate = async (endpoint, request, bb, options = {}) => {
|
|||
morebtn.addEventListener("click", makeMore);
|
||||
imageSelectMenu.appendChild(morebtn);
|
||||
|
||||
const removebtn = document.createElement("button");
|
||||
removebtn.textContent = "-";
|
||||
removebtn.title = "Remove From Batch";
|
||||
removebtn.addEventListener("click", removeImg);
|
||||
imageSelectMenu.appendChild(removebtn);
|
||||
|
||||
const acceptbtn = document.createElement("button");
|
||||
acceptbtn.textContent = "Y";
|
||||
acceptbtn.title = "Apply Current";
|
||||
|
@ -815,41 +846,64 @@ const dream_generate_callback = async (bb, resolution, state) => {
|
|||
|
||||
// Use txt2img if canvas is blank
|
||||
if (isCanvasBlank(0, 0, bb.w, bb.h, visibleCanvas)) {
|
||||
if (
|
||||
!global.isOldHRFix &&
|
||||
request.enable_hr &&
|
||||
localStorage.getItem("openoutpaint/settings.hrfix-liar") == "true"
|
||||
) {
|
||||
if (!global.isOldHRFix && request.enable_hr) {
|
||||
/**
|
||||
* try and make the new HRfix method useful for our purposes
|
||||
* since it now returns an image that's been upscaled x the hr_scale parameter,
|
||||
* we cheekily lie to SD and tell it that the original dimensions are _divided_
|
||||
* by the scale factor so it returns something about the same size as we wanted initially
|
||||
*/
|
||||
|
||||
// ok so instead, only do that if stableDiffusionData.hr_fix_lock_px > 0
|
||||
if (stableDiffusionData.hr_fix_lock_px > 0) {
|
||||
// find the appropriate scale factor for hrfix
|
||||
// laziness convenience
|
||||
let lockpx = stableDiffusionData.hr_fix_lock_px;
|
||||
if (lockpx > 0) {
|
||||
// find the most appropriate scale factor for hrfix
|
||||
var widthFactor =
|
||||
request.width / stableDiffusionData.hr_fix_lock_px <= 4
|
||||
? request.width / stableDiffusionData.hr_fix_lock_px
|
||||
: 4;
|
||||
request.width / lockpx <= 4 ? request.width / lockpx : 4;
|
||||
var heightFactor =
|
||||
request.height / stableDiffusionData.hr_fix_lock_px <= 4
|
||||
? request.height / stableDiffusionData.hr_fix_lock_px
|
||||
: 4;
|
||||
request.height / lockpx <= 4 ? request.height / lockpx : 4;
|
||||
var factor = heightFactor > widthFactor ? heightFactor : widthFactor;
|
||||
request.hr_scale = hrFixScaleSlider.value = factor < 1 ? 1 : factor;
|
||||
}
|
||||
// moar laziness convenience
|
||||
var divW = Math.floor(request.width / request.hr_scale);
|
||||
var divH = Math.floor(request.height / request.hr_scale);
|
||||
|
||||
var newWidth = Math.floor(request.width / request.hr_scale);
|
||||
var newHeight = Math.floor(request.height / request.hr_scale);
|
||||
request.width = newWidth;
|
||||
request.height = newHeight;
|
||||
if (localStorage.getItem("openoutpaint/settings.hrfix-liar") == "true") {
|
||||
/**
|
||||
* since it now returns an image that's been upscaled x the hr_scale parameter,
|
||||
* we cheekily lie to SD and tell it that the original dimensions are _divided_
|
||||
* by the scale factor so it returns something about the same size as we wanted initially
|
||||
*/
|
||||
var firstpassWidth = divW;
|
||||
var firstpassHeight = divH; // liar's firstpass output resolution
|
||||
var desiredWidth = request.width;
|
||||
var desiredHeight = request.height; // truthful desired output resolution
|
||||
} else {
|
||||
// use scale normally, dump supersampled image into undersized reticle
|
||||
var desiredWidth = request.width * request.hr_scale;
|
||||
var desiredHeight = request.height * request.hr_scale; //desired 2nd-pass output resolution
|
||||
var firstpassWidth = request.width;
|
||||
var firstpassHeight = request.height;
|
||||
}
|
||||
|
||||
// ensure firstpass "resolution" complies with lockpx
|
||||
if (lockpx > 0) {
|
||||
//sigh repeated loop
|
||||
firstpassWidth = divW < lockpx ? divW : lockpx;
|
||||
firstpassHeight = divH < lockpx ? divH : lockpx;
|
||||
}
|
||||
|
||||
if (stableDiffusionData.hr_square_aspect) {
|
||||
larger =
|
||||
firstpassWidth > firstpassHeight ? firstpassWidth : firstpassHeight;
|
||||
firstpassWidth = firstpassHeight = larger;
|
||||
}
|
||||
request.width = firstpassWidth;
|
||||
request.height = firstpassHeight;
|
||||
request.hr_resize_x = desiredWidth;
|
||||
request.hr_resize_y = desiredHeight;
|
||||
}
|
||||
|
||||
// For compatibility with the old HRFix API
|
||||
if (global.isOldHRFix && request.enable_hr) {
|
||||
// For compatibility with the old HRFix API
|
||||
request.firstphase_width = request.width / 2;
|
||||
request.firstphase_height = request.height / 2;
|
||||
}
|
||||
|
@ -1187,10 +1241,16 @@ const _dream_onwheel = (evn, state) => {
|
|||
return;
|
||||
}
|
||||
|
||||
// A simple but (I hope) effective fix for mouse wheel behavior
|
||||
_dream_wheel_accum += evn.delta;
|
||||
let delta = evn.delta;
|
||||
if (evn.evn.shiftKey) delta *= 0.01;
|
||||
|
||||
if (Math.abs(_dream_wheel_accum) > config.wheelTickSize) {
|
||||
// A simple but (I hope) effective fix for mouse wheel behavior
|
||||
_dream_wheel_accum += delta;
|
||||
|
||||
if (
|
||||
!evn.evn.shiftKey &&
|
||||
Math.abs(_dream_wheel_accum) > config.wheelTickSize
|
||||
) {
|
||||
// Snap to next or previous position
|
||||
const v =
|
||||
state.cursorSize -
|
||||
|
@ -1199,6 +1259,12 @@ const _dream_onwheel = (evn, state) => {
|
|||
state.cursorSize = state.setCursorSize(v + snap(v, 0, 128));
|
||||
state.mousemovecb(evn);
|
||||
|
||||
_dream_wheel_accum = 0; // Zero accumulation
|
||||
} else if (evn.evn.shiftKey && Math.abs(_dream_wheel_accum) >= 1) {
|
||||
const v = state.cursorSize - _dream_wheel_accum;
|
||||
state.cursorSize = state.setCursorSize(v);
|
||||
state.mousemovecb(evn);
|
||||
|
||||
_dream_wheel_accum = 0; // Zero accumulation
|
||||
}
|
||||
};
|
||||
|
@ -1888,11 +1954,10 @@ const img2imgTool = () =>
|
|||
return;
|
||||
}
|
||||
|
||||
const bbvp = {
|
||||
...viewport.canvasToView(bb.x, bb.y),
|
||||
w: viewport.zoom * bb.w,
|
||||
h: viewport.zoom * bb.h,
|
||||
};
|
||||
const bbvp = BoundingBox.fromStartEnd(
|
||||
viewport.canvasToView(bb.tl),
|
||||
viewport.canvasToView(bb.br)
|
||||
);
|
||||
|
||||
// For displaying border mask
|
||||
const bbCanvas = document.createElement("canvas");
|
||||
|
|
|
@ -27,11 +27,7 @@ const _tool = {
|
|||
reticleStyle: global.hasActiveInput ? "#BBF" : "#FFF",
|
||||
});
|
||||
|
||||
const bbvp = {
|
||||
...viewport.canvasToView(bb.x, bb.y),
|
||||
w: viewport.zoom * bb.w,
|
||||
h: viewport.zoom * bb.h,
|
||||
};
|
||||
const bbvp = bb.transform(viewport.c2v);
|
||||
|
||||
uiCtx.save();
|
||||
|
||||
|
@ -174,6 +170,8 @@ const _tool = {
|
|||
|
||||
/**
|
||||
* Gets the selection bounding box
|
||||
*
|
||||
* @returns {BoundingBox}
|
||||
*/
|
||||
get bb() {
|
||||
if (this._dirty_bb && this._selected) {
|
||||
|
@ -273,4 +271,389 @@ const _tool = {
|
|||
|
||||
return selection;
|
||||
},
|
||||
|
||||
/**
|
||||
* Processes cursor position
|
||||
*
|
||||
* @param {Point} wpoint World coordinate of the cursor
|
||||
* @param {boolean} snapToGrid Snap to grid
|
||||
*/
|
||||
_process_cursor(wpoint, snapToGrid) {
|
||||
// Get cursor positions
|
||||
let x = wpoint.x;
|
||||
let y = wpoint.y;
|
||||
let sx = x;
|
||||
let sy = y;
|
||||
|
||||
if (snapToGrid) {
|
||||
sx += snap(x, 0, config.gridSize);
|
||||
sy += snap(y, 0, config.gridSize);
|
||||
}
|
||||
|
||||
const vpc = viewport.canvasToView(x, y);
|
||||
const vpsc = viewport.canvasToView(sx, sy);
|
||||
|
||||
return {
|
||||
// World Coordinates
|
||||
x,
|
||||
y,
|
||||
sx,
|
||||
sy,
|
||||
|
||||
// Viewport Coordinates
|
||||
vpx: vpc.x,
|
||||
vpy: vpc.y,
|
||||
vpsx: vpsc.x,
|
||||
vpsy: vpsc.y,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Represents a marquee selection with an image
|
||||
*/
|
||||
MarqueeSelection: class {
|
||||
/** @type {HTMLCanvasElement} */
|
||||
canvas;
|
||||
|
||||
_dirty = false;
|
||||
_position = {x: 0, y: 0};
|
||||
/**
|
||||
* @type {Point}
|
||||
*/
|
||||
get position() {
|
||||
return this._position;
|
||||
}
|
||||
set position(v) {
|
||||
this._dirty = true;
|
||||
this._position = v;
|
||||
}
|
||||
|
||||
_scale = {x: 1, y: 1};
|
||||
/**
|
||||
* @type {Point}
|
||||
*/
|
||||
get scale() {
|
||||
return this._scale;
|
||||
}
|
||||
set scale(v) {
|
||||
if (v.x === 0 || v.y === 0) return;
|
||||
this._dirty = true;
|
||||
this._scale = v;
|
||||
}
|
||||
|
||||
_rotation = 0;
|
||||
get rotation() {
|
||||
return this._rotation;
|
||||
}
|
||||
set rotation(v) {
|
||||
this._dirty = true;
|
||||
this._rotation = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLCanvasElement} canvas Selected image canvas
|
||||
* @param {Point} position Initial position of the selection
|
||||
*/
|
||||
constructor(canvas, position = {x: 0, y: 0}) {
|
||||
this.canvas = canvas;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
/** @type {DOMMatrix} */
|
||||
_rtmatrix = null;
|
||||
get rtmatrix() {
|
||||
if (!this._rtmatrix || this._dirty) {
|
||||
const m = new DOMMatrix();
|
||||
|
||||
m.translateSelf(this.position.x, this.position.y);
|
||||
m.rotateSelf((this.rotation * 180) / Math.PI);
|
||||
|
||||
this._rtmatrix = m;
|
||||
}
|
||||
|
||||
return this._rtmatrix;
|
||||
}
|
||||
|
||||
/** @type {DOMMatrix} */
|
||||
_matrix = null;
|
||||
get matrix() {
|
||||
if (!this._matrix || this._dirty) {
|
||||
this._matrix = this.rtmatrix.scaleSelf(this.scale.x, this.scale.y);
|
||||
}
|
||||
return this._matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the main marquee box contains a given point
|
||||
*
|
||||
* @param {number} x X coordinate of the point
|
||||
* @param {number} y Y coordinate of the point
|
||||
*/
|
||||
contains(x, y) {
|
||||
const p = this.matrix.invertSelf().transformPoint({x, y});
|
||||
|
||||
return (
|
||||
Math.abs(p.x) < this.canvas.width / 2 &&
|
||||
Math.abs(p.y) < this.canvas.height / 2
|
||||
);
|
||||
}
|
||||
|
||||
hoveringRotateHandle(x, y, scale = 1) {
|
||||
const localc = this.rtmatrix.inverse().transformPoint({x, y});
|
||||
const localrh = {
|
||||
x: 0,
|
||||
y:
|
||||
(-this.scale.y * this.canvas.height) / 2 -
|
||||
config.rotateHandleDistance * scale,
|
||||
};
|
||||
|
||||
const dx = Math.abs(localc.x - localrh.x);
|
||||
const dy = Math.abs(localc.y - localrh.y);
|
||||
|
||||
return (
|
||||
dx * dx + dy * dy <
|
||||
(scale * scale * config.handleDetectSize * config.handleDetectSize) / 4
|
||||
);
|
||||
}
|
||||
|
||||
hoveringHandle(x, y, scale = 1) {
|
||||
const localbb = new BoundingBox({
|
||||
x: (this.scale.x * -this.canvas.width) / 2,
|
||||
y: (this.scale.y * -this.canvas.height) / 2,
|
||||
w: this.canvas.width * this.scale.x,
|
||||
h: this.canvas.height * this.scale.y,
|
||||
});
|
||||
|
||||
const localc = this.rtmatrix.inverse().transformPoint({x, y});
|
||||
const ontl =
|
||||
Math.max(
|
||||
Math.abs(localc.x - localbb.tl.x),
|
||||
Math.abs(localc.y - localbb.tl.y)
|
||||
) <
|
||||
(config.handleDetectSize / 2) * scale;
|
||||
const ontr =
|
||||
Math.max(
|
||||
Math.abs(localc.x - localbb.tr.x),
|
||||
Math.abs(localc.y - localbb.tr.y)
|
||||
) <
|
||||
(config.handleDetectSize / 2) * scale;
|
||||
const onbl =
|
||||
Math.max(
|
||||
Math.abs(localc.x - localbb.bl.x),
|
||||
Math.abs(localc.y - localbb.bl.y)
|
||||
) <
|
||||
(config.handleDetectSize / 2) * scale;
|
||||
const onbr =
|
||||
Math.max(
|
||||
Math.abs(localc.x - localbb.br.x),
|
||||
Math.abs(localc.y - localbb.br.y)
|
||||
) <
|
||||
(config.handleDetectSize / 2) * scale;
|
||||
|
||||
return {onHandle: ontl || ontr || onbl || onbr, ontl, ontr, onbl, onbr};
|
||||
}
|
||||
|
||||
hoveringBox(x, y) {
|
||||
const localbb = new BoundingBox({
|
||||
x: -this.canvas.width / 2,
|
||||
y: -this.canvas.height / 2,
|
||||
w: this.canvas.width,
|
||||
h: this.canvas.height,
|
||||
});
|
||||
|
||||
const localc = this.matrix.inverse().transformPoint({x, y});
|
||||
|
||||
return (
|
||||
!this.hoveringHandle(x, y).onHandle &&
|
||||
localbb.contains(localc.x, localc.y)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the marquee selector box
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} context A context for rendering the box to
|
||||
* @param {Point} cursor Cursor position
|
||||
* @param {DOMMatrix} transform A transformation matrix to transform the position by
|
||||
*/
|
||||
drawBox(context, cursor, transform = new DOMMatrix()) {
|
||||
const drawscale =
|
||||
1 / Math.sqrt(transform.a * transform.a + transform.b * transform.b);
|
||||
const m = transform.multiply(this.matrix);
|
||||
|
||||
context.save();
|
||||
|
||||
const localbb = new BoundingBox({
|
||||
x: -this.canvas.width / 2,
|
||||
y: -this.canvas.height / 2,
|
||||
w: this.canvas.width,
|
||||
h: this.canvas.height,
|
||||
});
|
||||
|
||||
// Line Style
|
||||
context.strokeStyle = "#FFF";
|
||||
context.lineWidth = 2;
|
||||
|
||||
const tl = m.transformPoint(localbb.tl);
|
||||
const tr = m.transformPoint(localbb.tr);
|
||||
const bl = m.transformPoint(localbb.bl);
|
||||
const br = m.transformPoint(localbb.br);
|
||||
|
||||
const bbc = m.transformPoint({x: 0, y: 0});
|
||||
|
||||
context.beginPath();
|
||||
context.arc(bbc.x, bbc.y, 5, 0, Math.PI * 2);
|
||||
context.stroke();
|
||||
|
||||
context.setLineDash([4, 2]);
|
||||
|
||||
// Draw main rectangle
|
||||
context.beginPath();
|
||||
context.moveTo(tl.x, tl.y);
|
||||
context.lineTo(tr.x, tr.y);
|
||||
context.lineTo(br.x, br.y);
|
||||
context.lineTo(bl.x, bl.y);
|
||||
context.lineTo(tl.x, tl.y);
|
||||
context.stroke();
|
||||
|
||||
// Draw rotation handle
|
||||
context.setLineDash([]);
|
||||
|
||||
const hm = new DOMMatrix().rotateSelf((this.rotation * 180) / Math.PI);
|
||||
const tm = m.transformPoint({x: 0, y: -this.canvas.height / 2});
|
||||
const rho = hm.transformPoint({x: 0, y: -config.rotateHandleDistance});
|
||||
const rh = {x: tm.x + rho.x, y: tm.y + rho.y};
|
||||
|
||||
let handleRadius = config.handleDrawSize / 2;
|
||||
if (this.hoveringRotateHandle(cursor.x, cursor.y, drawscale))
|
||||
handleRadius *= config.handleDrawHoverScale;
|
||||
|
||||
context.beginPath();
|
||||
context.moveTo(tm.x, tm.y);
|
||||
context.lineTo(rh.x, rh.y);
|
||||
context.stroke();
|
||||
|
||||
context.beginPath();
|
||||
context.arc(rh.x, rh.y, handleRadius, 0, 2 * Math.PI);
|
||||
context.stroke();
|
||||
|
||||
// Draw handles
|
||||
const drawHandle = (pt, hover) => {
|
||||
let hsz = config.handleDrawSize / 2;
|
||||
if (hover) hsz *= config.handleDrawHoverScale;
|
||||
|
||||
const htl = hm.transformPoint({x: -hsz, y: -hsz});
|
||||
const htr = hm.transformPoint({x: hsz, y: -hsz});
|
||||
const hbr = hm.transformPoint({x: hsz, y: hsz});
|
||||
const hbl = hm.transformPoint({x: -hsz, y: hsz});
|
||||
|
||||
context.beginPath();
|
||||
context.moveTo(htl.x + pt.x, htl.y + pt.y);
|
||||
context.lineTo(htr.x + pt.x, htr.y + pt.y);
|
||||
context.lineTo(hbr.x + pt.x, hbr.y + pt.y);
|
||||
context.lineTo(hbl.x + pt.x, hbl.y + pt.y);
|
||||
context.lineTo(htl.x + pt.x, htl.y + pt.y);
|
||||
context.stroke();
|
||||
};
|
||||
|
||||
context.strokeStyle = "#FFF";
|
||||
context.lineWidth = 2;
|
||||
context.setLineDash([]);
|
||||
|
||||
const {ontl, ontr, onbl, onbr} = this.hoveringHandle(
|
||||
cursor.x,
|
||||
cursor.y,
|
||||
drawscale
|
||||
);
|
||||
|
||||
drawHandle(tl, ontl);
|
||||
drawHandle(tr, ontr);
|
||||
drawHandle(bl, onbl);
|
||||
drawHandle(br, onbr);
|
||||
|
||||
context.restore();
|
||||
|
||||
return () => {
|
||||
const border = config.handleDrawSize * config.handleDrawHoverScale;
|
||||
|
||||
const minx = Math.min(tl.x, tr.x, bl.x, br.x, rh.x) - border;
|
||||
const maxx = Math.max(tl.x, tr.x, bl.x, br.x, rh.x) + border;
|
||||
const miny = Math.min(tl.y, tr.y, bl.y, br.y, rh.y) - border;
|
||||
const maxy = Math.max(tl.y, tr.y, bl.y, br.y, rh.y) + border;
|
||||
|
||||
context.clearRect(minx, miny, maxx - minx, maxy - miny);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the selected image
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} context A context for rendering the image to
|
||||
* @param {CanvasRenderingContext2D} peekctx A context for rendering the layer peeking to
|
||||
* @param {object} options
|
||||
* @param {DOMMatrix} options.transform A transformation matrix to transform the position by
|
||||
* @param {number} options.opacity Opacity of the peek display
|
||||
*/
|
||||
drawImage(context, peekctx, options = {}) {
|
||||
defaultOpt(options, {
|
||||
transform: new DOMMatrix(),
|
||||
opacity: 0.4,
|
||||
});
|
||||
|
||||
context.save();
|
||||
peekctx.save();
|
||||
|
||||
const m = options.transform.multiply(this.matrix);
|
||||
|
||||
// Draw image
|
||||
context.setTransform(m);
|
||||
context.drawImage(
|
||||
this.canvas,
|
||||
-this.canvas.width / 2,
|
||||
-this.canvas.height / 2,
|
||||
this.canvas.width,
|
||||
this.canvas.height
|
||||
);
|
||||
|
||||
// Draw peek
|
||||
peekctx.filter = `opacity(${options.opacity * 100}%)`;
|
||||
peekctx.setTransform(m);
|
||||
peekctx.drawImage(
|
||||
this.canvas,
|
||||
-this.canvas.width / 2,
|
||||
-this.canvas.height / 2,
|
||||
this.canvas.width,
|
||||
this.canvas.height
|
||||
);
|
||||
|
||||
peekctx.restore();
|
||||
context.restore();
|
||||
|
||||
return () => {
|
||||
// Here we only save transform for performance
|
||||
const pt = context.getTransform();
|
||||
const ppt = context.getTransform();
|
||||
|
||||
context.setTransform(m);
|
||||
peekctx.setTransform(m);
|
||||
|
||||
context.clearRect(
|
||||
-this.canvas.width / 2 - 10,
|
||||
-this.canvas.height / 2 - 10,
|
||||
this.canvas.width + 20,
|
||||
this.canvas.height + 20
|
||||
);
|
||||
|
||||
peekctx.clearRect(
|
||||
-this.canvas.width / 2 - 10,
|
||||
-this.canvas.height / 2 - 10,
|
||||
this.canvas.width + 20,
|
||||
this.canvas.height + 20
|
||||
);
|
||||
|
||||
context.setTransform(pt);
|
||||
peekctx.setTransform(ppt);
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ const selectTransformTool = () =>
|
|||
mouse.listen.world.onmousemove.on(state.movecb);
|
||||
mouse.listen.world.btn.left.onclick.on(state.clickcb);
|
||||
mouse.listen.world.btn.left.ondragstart.on(state.dragstartcb);
|
||||
mouse.listen.world.btn.left.ondrag.on(state.dragcb);
|
||||
mouse.listen.world.btn.left.ondragend.on(state.dragendcb);
|
||||
|
||||
// Canvas right mouse handler
|
||||
|
@ -29,12 +30,19 @@ const selectTransformTool = () =>
|
|||
keyboard.onShortcut({ctrl: true, key: "KeyX"}, state.ctrlxcb);
|
||||
|
||||
state.selected = null;
|
||||
|
||||
// Register Layer
|
||||
state.originalDisplayLayer = imageCollection.registerLayer(null, {
|
||||
after: uil.layer,
|
||||
category: "select-display",
|
||||
});
|
||||
},
|
||||
(state, opt) => {
|
||||
// Clear all those listeners and shortcuts we set up
|
||||
mouse.listen.world.onmousemove.clear(state.movecb);
|
||||
mouse.listen.world.btn.left.onclick.clear(state.clickcb);
|
||||
mouse.listen.world.btn.left.ondragstart.clear(state.dragstartcb);
|
||||
mouse.listen.world.btn.left.ondrag.clear(state.dragcb);
|
||||
mouse.listen.world.btn.left.ondragend.clear(state.dragendcb);
|
||||
|
||||
mouse.listen.world.btn.right.onclick.clear(state.cancelcb);
|
||||
|
@ -55,6 +63,10 @@ const selectTransformTool = () =>
|
|||
|
||||
// Clears overlay
|
||||
imageCollection.inputElement.style.cursor = "auto";
|
||||
|
||||
// Delete Layer
|
||||
imageCollection.deleteLayer(state.originalDisplayLayer);
|
||||
state.originalDisplayLayer = null;
|
||||
},
|
||||
{
|
||||
init: (state) => {
|
||||
|
@ -69,7 +81,6 @@ const selectTransformTool = () =>
|
|||
state.selectionPeekOpacity = 40;
|
||||
|
||||
state.original = null;
|
||||
state.dragging = null;
|
||||
state._selected = null;
|
||||
Object.defineProperty(state, "selected", {
|
||||
get: () => state._selected,
|
||||
|
@ -80,7 +91,6 @@ const selectTransformTool = () =>
|
|||
return (state._selected = v);
|
||||
},
|
||||
});
|
||||
state.moving = null;
|
||||
|
||||
// Some things to easy request for a redraw
|
||||
state.lastMouseTarget = null;
|
||||
|
@ -97,423 +107,334 @@ const selectTransformTool = () =>
|
|||
}
|
||||
};
|
||||
|
||||
/** @type {{selected: Point, offset: Point} | null} */
|
||||
let moving = null;
|
||||
/** @type {{handle: Point} | null} */
|
||||
let scaling = null;
|
||||
let rotating = false;
|
||||
|
||||
// Clears selection and make things right
|
||||
state.reset = (erase = false) => {
|
||||
if (state.selected && !erase)
|
||||
state.originalLayer.ctx.drawImage(
|
||||
state.original.image,
|
||||
state.original.layer.ctx.drawImage(
|
||||
state.selected.canvas,
|
||||
state.original.x,
|
||||
state.original.y
|
||||
);
|
||||
|
||||
if (state.originalDisplayLayer) {
|
||||
imageCollection.deleteLayer(state.originalDisplayLayer);
|
||||
state.originalDisplayLayer = null;
|
||||
state.originalDisplayLayer.clear();
|
||||
}
|
||||
|
||||
if (state.dragging) state.dragging = null;
|
||||
else state.selected = null;
|
||||
|
||||
state.rotation = 0;
|
||||
state.original = null;
|
||||
moving = null;
|
||||
scaling = null;
|
||||
rotating = null;
|
||||
|
||||
state.redraw();
|
||||
};
|
||||
|
||||
// Selection bounding box object. Has some witchery to deal with handles.
|
||||
const selectionBB = (x1, y1, x2, y2) => {
|
||||
x1 = Math.round(x1);
|
||||
y1 = Math.round(y1);
|
||||
x2 = Math.round(x2);
|
||||
y2 = Math.round(y2);
|
||||
return {
|
||||
original: {
|
||||
x: Math.min(x1, x2),
|
||||
y: Math.min(y1, y2),
|
||||
w: Math.abs(x1 - x2),
|
||||
h: Math.abs(y1 - y2),
|
||||
},
|
||||
x: Math.min(x1, x2),
|
||||
y: Math.min(y1, y2),
|
||||
w: Math.abs(x1 - x2),
|
||||
h: Math.abs(y1 - y2),
|
||||
updateOriginal() {
|
||||
this.original.x = this.x;
|
||||
this.original.y = this.y;
|
||||
this.original.w = this.w;
|
||||
this.original.h = this.h;
|
||||
},
|
||||
contains(x, y) {
|
||||
return (
|
||||
this.x <= x &&
|
||||
x <= this.x + this.w &&
|
||||
this.y <= y &&
|
||||
y <= this.y + this.h
|
||||
);
|
||||
},
|
||||
handles() {
|
||||
const _createHandle = (x, y, originOffset = null) => {
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
scaleTo: (tx, ty, keepAspectRatio = true) => {
|
||||
const origin = {
|
||||
x: this.original.x + this.original.w / 2,
|
||||
y: this.original.y + this.original.h / 2,
|
||||
};
|
||||
let nx = tx;
|
||||
let ny = ty;
|
||||
// Selection Handlers
|
||||
const selection = _tool._draggable_selection(state);
|
||||
|
||||
let xRatio = (nx - origin.x) / (x - origin.x);
|
||||
let yRatio = (ny - origin.y) / (y - origin.y);
|
||||
if (keepAspectRatio)
|
||||
xRatio = yRatio = Math.min(xRatio, yRatio);
|
||||
// UI Erasers
|
||||
let eraseSelectedBox = () => null;
|
||||
let eraseSelectedImage = () => null;
|
||||
let eraseCursor = () => null;
|
||||
let eraseSelection = () => null;
|
||||
|
||||
if (Number.isFinite(xRatio)) {
|
||||
let left = this.original.x;
|
||||
let right = this.original.x + this.original.w;
|
||||
// Redraw UI
|
||||
state.redrawui = () => {
|
||||
// Get cursor positions
|
||||
const {x, y, sx, sy} = _tool._process_cursor(
|
||||
state.lastMouseMove,
|
||||
state.snapToGrid
|
||||
);
|
||||
|
||||
left = (left - origin.x) * xRatio + origin.x;
|
||||
right = (right - origin.x) * xRatio + origin.x;
|
||||
|
||||
this.x = left;
|
||||
this.w = right - left;
|
||||
}
|
||||
|
||||
if (Number.isFinite(yRatio)) {
|
||||
let top = this.original.y;
|
||||
let bottom = this.original.y + this.original.h;
|
||||
|
||||
top = (top - origin.y) * yRatio + origin.y;
|
||||
bottom = (bottom - origin.y) * yRatio + origin.y;
|
||||
|
||||
this.y = top;
|
||||
this.h = bottom - top;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const size = viewport.zoom * 10;
|
||||
return [
|
||||
_createHandle(this.x, this.y, size),
|
||||
_createHandle(this.x + this.w, this.y, size),
|
||||
_createHandle(this.x, this.y + this.h, size),
|
||||
_createHandle(this.x + this.w, this.y + this.h, size),
|
||||
];
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// Mouse move handler. As always, also renders cursor
|
||||
state.movecb = (evn) => {
|
||||
ovLayer.clear();
|
||||
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||
state.erasePrevCursor && state.erasePrevCursor();
|
||||
imageCollection.inputElement.style.cursor = "auto";
|
||||
state.lastMouseTarget = evn.target;
|
||||
state.lastMouseMove = evn;
|
||||
let x = evn.x;
|
||||
let y = evn.y;
|
||||
if (state.snapToGrid) {
|
||||
x += snap(evn.x, 0, 64);
|
||||
y += snap(evn.y, 0, 64);
|
||||
}
|
||||
|
||||
const vpc = viewport.canvasToView(x, y);
|
||||
|
||||
uiCtx.save();
|
||||
|
||||
// Update scale
|
||||
if (state.scaling) {
|
||||
state.scaling.scaleTo(x, y, state.keepAspectRatio);
|
||||
}
|
||||
|
||||
// Update position
|
||||
if (state.moving) {
|
||||
state.selected.x = Math.round(x - state.moving.offset.x);
|
||||
state.selected.y = Math.round(y - state.moving.offset.y);
|
||||
state.selected.updateOriginal();
|
||||
}
|
||||
|
||||
// Draw dragging box
|
||||
if (state.dragging) {
|
||||
uiCtx.setLineDash([2, 2]);
|
||||
uiCtx.lineWidth = 1;
|
||||
uiCtx.strokeStyle = "#FFF";
|
||||
|
||||
const ix = state.dragging.ix;
|
||||
const iy = state.dragging.iy;
|
||||
|
||||
const bb = selectionBB(ix, iy, x, y);
|
||||
|
||||
const bbvp = {
|
||||
...viewport.canvasToView(bb.x, bb.y),
|
||||
w: viewport.zoom * bb.w,
|
||||
h: viewport.zoom * bb.h,
|
||||
};
|
||||
|
||||
uiCtx.strokeRect(bbvp.x, bbvp.y, bbvp.w, bbvp.h);
|
||||
uiCtx.setLineDash([]);
|
||||
}
|
||||
eraseSelectedBox();
|
||||
|
||||
if (state.selected) {
|
||||
ovCtx.lineWidth = 1;
|
||||
ovCtx.strokeStyle = "#FFF";
|
||||
|
||||
const bb = {
|
||||
x: state.selected.x,
|
||||
y: state.selected.y,
|
||||
w: state.selected.w,
|
||||
h: state.selected.h,
|
||||
};
|
||||
|
||||
const bbvp = {
|
||||
...viewport.canvasToView(bb.x, bb.y),
|
||||
w: viewport.zoom * bb.w,
|
||||
h: viewport.zoom * bb.h,
|
||||
};
|
||||
|
||||
// Draw Image
|
||||
ovCtx.save();
|
||||
ovCtx.filter = `opacity(${state.selectionPeekOpacity}%)`;
|
||||
ovCtx.drawImage(
|
||||
state.selected.image,
|
||||
0,
|
||||
0,
|
||||
state.selected.image.width,
|
||||
state.selected.image.height,
|
||||
state.selected.x,
|
||||
state.selected.y,
|
||||
state.selected.w,
|
||||
state.selected.h
|
||||
eraseSelectedBox = state.selected.drawBox(
|
||||
uiCtx,
|
||||
{x, y},
|
||||
viewport.c2v
|
||||
);
|
||||
ovCtx.restore();
|
||||
}
|
||||
};
|
||||
|
||||
state.originalDisplayLayer.clear();
|
||||
state.originalDisplayLayer.ctx.save();
|
||||
state.originalDisplayLayer.ctx.drawImage(
|
||||
state.selected.image,
|
||||
0,
|
||||
0,
|
||||
state.selected.image.width,
|
||||
state.selected.image.height,
|
||||
state.selected.x,
|
||||
state.selected.y,
|
||||
state.selected.w,
|
||||
state.selected.h
|
||||
// Mouse Move Handler
|
||||
state.movecb = (evn) => {
|
||||
state.lastMouseMove = evn;
|
||||
|
||||
// Get cursor positions
|
||||
const {x, y, sx, sy} = _tool._process_cursor(evn, state.snapToGrid);
|
||||
|
||||
// Erase Cursor
|
||||
eraseSelectedBox();
|
||||
eraseSelectedImage();
|
||||
eraseSelection();
|
||||
eraseCursor();
|
||||
imageCollection.inputElement.style.cursor = "default";
|
||||
|
||||
// Draw Box and Selected Image
|
||||
if (state.selected) {
|
||||
eraseSelectedBox = state.selected.drawBox(
|
||||
uiCtx,
|
||||
{x, y},
|
||||
viewport.c2v
|
||||
);
|
||||
state.originalDisplayLayer.ctx.restore();
|
||||
|
||||
// Draw selection box
|
||||
uiCtx.strokeStyle = "#FFF";
|
||||
uiCtx.setLineDash([4, 2]);
|
||||
uiCtx.strokeRect(bbvp.x, bbvp.y, bbvp.w, bbvp.h);
|
||||
uiCtx.setLineDash([]);
|
||||
|
||||
// Draw Scaling/Rotation Origin
|
||||
uiCtx.beginPath();
|
||||
uiCtx.arc(
|
||||
bbvp.x + bbvp.w / 2,
|
||||
bbvp.y + bbvp.h / 2,
|
||||
5,
|
||||
0,
|
||||
2 * Math.PI
|
||||
);
|
||||
uiCtx.stroke();
|
||||
|
||||
// Draw Scaling Handles
|
||||
let cursorInHandle = false;
|
||||
state.selected.handles().forEach((handle) => {
|
||||
const bbvph = {
|
||||
...viewport.canvasToView(handle.x, handle.y),
|
||||
w: 10,
|
||||
h: 10,
|
||||
};
|
||||
|
||||
bbvph.x -= 5;
|
||||
bbvph.y -= 5;
|
||||
|
||||
const inhandle =
|
||||
evn.evn.clientX > bbvph.x &&
|
||||
evn.evn.clientX < bbvph.x + bbvph.w &&
|
||||
evn.evn.clientY > bbvph.y &&
|
||||
evn.evn.clientY < bbvph.y + bbvph.h;
|
||||
|
||||
if (inhandle) {
|
||||
cursorInHandle = true;
|
||||
uiCtx.strokeRect(
|
||||
bbvph.x - 1,
|
||||
bbvph.y - 1,
|
||||
bbvph.w + 2,
|
||||
bbvph.h + 2
|
||||
);
|
||||
} else {
|
||||
uiCtx.strokeRect(bbvph.x, bbvph.y, bbvph.w, bbvph.h);
|
||||
}
|
||||
});
|
||||
|
||||
// Change cursor
|
||||
if (cursorInHandle || state.selected.contains(evn.x, evn.y))
|
||||
if (
|
||||
state.selected.hoveringBox(x, y) ||
|
||||
state.selected.hoveringHandle(x, y, viewport.zoom).onHandle ||
|
||||
state.selected.hoveringRotateHandle(x, y, viewport.zoom)
|
||||
) {
|
||||
imageCollection.inputElement.style.cursor = "pointer";
|
||||
}
|
||||
|
||||
eraseSelectedImage = state.selected.drawImage(
|
||||
state.originalDisplayLayer.ctx,
|
||||
ovCtx,
|
||||
{opacity: state.selectionPeekOpacity / 100}
|
||||
);
|
||||
}
|
||||
|
||||
// Draw current cursor location
|
||||
state.erasePrevCursor = _tool._cursor_draw(x, y);
|
||||
// Draw Selection
|
||||
if (selection.exists) {
|
||||
uiCtx.save();
|
||||
uiCtx.setLineDash([2, 2]);
|
||||
uiCtx.lineWidth = 2;
|
||||
uiCtx.strokeStyle = "#FFF";
|
||||
|
||||
uiCtx.restore();
|
||||
const bbvp = selection.bb.transform(viewport.c2v);
|
||||
uiCtx.beginPath();
|
||||
uiCtx.strokeRect(bbvp.x, bbvp.y, bbvp.w, bbvp.h);
|
||||
uiCtx.stroke();
|
||||
|
||||
eraseSelection = () =>
|
||||
uiCtx.clearRect(
|
||||
bbvp.x - 10,
|
||||
bbvp.y - 10,
|
||||
bbvp.w + 20,
|
||||
bbvp.h + 20
|
||||
);
|
||||
|
||||
uiCtx.restore();
|
||||
}
|
||||
|
||||
// Draw cursor
|
||||
eraseCursor = _tool._cursor_draw(sx, sy);
|
||||
};
|
||||
|
||||
// Handles left mouse clicks
|
||||
state.clickcb = (evn) => {
|
||||
if (
|
||||
!state.original ||
|
||||
(state.originalLayer === uil.layer &&
|
||||
state.original.x === state.selected.x &&
|
||||
state.original.y === state.selected.y &&
|
||||
state.original.w === state.selected.w &&
|
||||
state.original.h === state.selected.h)
|
||||
state.selected &&
|
||||
!(
|
||||
state.selected.rotation === 0 &&
|
||||
state.selected.scale.x === 1 &&
|
||||
state.selected.scale.y === 1 &&
|
||||
state.selected.position.x === state.original.sx &&
|
||||
state.selected.position.y === state.original.sy &&
|
||||
state.original.layer === uil.layer
|
||||
)
|
||||
) {
|
||||
state.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// If something is selected, commit changes to the canvas
|
||||
if (state.selected) {
|
||||
state.originalLayer.ctx.drawImage(
|
||||
state.selected.image,
|
||||
// Put original image back
|
||||
state.original.layer.ctx.drawImage(
|
||||
state.selected.canvas,
|
||||
state.original.x,
|
||||
state.original.y
|
||||
);
|
||||
commands.runCommand("eraseImage", "Image Transform Erase", {
|
||||
...state.original,
|
||||
ctx: state.originalLayer.ctx,
|
||||
|
||||
// Erase Original Selection Area
|
||||
commands.runCommand("eraseImage", "Transform Tool Erase", {
|
||||
ctx: state.original.layer.ctx,
|
||||
x: state.original.x,
|
||||
y: state.original.y,
|
||||
w: state.selected.canvas.width,
|
||||
h: state.selected.canvas.height,
|
||||
});
|
||||
commands.runCommand("drawImage", "Image Transform Draw", {
|
||||
image: state.selected.image,
|
||||
x: Math.round(state.selected.x),
|
||||
y: Math.round(state.selected.y),
|
||||
w: Math.round(state.selected.w),
|
||||
h: Math.round(state.selected.h),
|
||||
|
||||
// Draw Image
|
||||
const {canvas, bb} = cropCanvas(state.originalDisplayLayer.canvas, {
|
||||
border: 10,
|
||||
});
|
||||
commands.runCommand("drawImage", "Transform Tool Apply", {
|
||||
image: canvas,
|
||||
...bb,
|
||||
});
|
||||
|
||||
state.reset(true);
|
||||
} else {
|
||||
state.reset();
|
||||
}
|
||||
};
|
||||
|
||||
// Handles left mouse drag start events
|
||||
state.dragstartcb = (evn) => {
|
||||
const {
|
||||
x: ix,
|
||||
y: iy,
|
||||
sx: six,
|
||||
sy: siy,
|
||||
} = _tool._process_cursor({x: evn.ix, y: evn.iy}, state.snapToGrid);
|
||||
const {x, y, sx, sy} = _tool._process_cursor(evn, state.snapToGrid);
|
||||
|
||||
if (state.selected) {
|
||||
const hoveringBox = state.selected.hoveringBox(ix, iy);
|
||||
const hoveringHandle = state.selected.hoveringHandle(
|
||||
ix,
|
||||
iy,
|
||||
viewport.zoom
|
||||
);
|
||||
const hoveringRotateHandle = state.selected.hoveringRotateHandle(
|
||||
ix,
|
||||
iy,
|
||||
viewport.zoom
|
||||
);
|
||||
|
||||
if (hoveringBox) {
|
||||
// Start dragging
|
||||
moving = {
|
||||
selected: state.selected.position,
|
||||
offset: {
|
||||
x: six - state.selected.position.x,
|
||||
y: siy - state.selected.position.y,
|
||||
},
|
||||
};
|
||||
return;
|
||||
} else if (hoveringHandle.onHandle) {
|
||||
// Start scaling
|
||||
let handle = {x: 0, y: 0};
|
||||
|
||||
const lbb = new BoundingBox({
|
||||
x: -state.selected.canvas.width / 2,
|
||||
y: -state.selected.canvas.height / 2,
|
||||
w: state.selected.canvas.width,
|
||||
h: state.selected.canvas.height,
|
||||
});
|
||||
|
||||
if (hoveringHandle.ontl) {
|
||||
handle = lbb.tl;
|
||||
} else if (hoveringHandle.ontr) {
|
||||
handle = lbb.tr;
|
||||
} else if (hoveringHandle.onbl) {
|
||||
handle = lbb.bl;
|
||||
} else {
|
||||
handle = lbb.br;
|
||||
}
|
||||
|
||||
scaling = {
|
||||
handle,
|
||||
};
|
||||
return;
|
||||
} else if (hoveringRotateHandle) {
|
||||
rotating = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
selection.dragstartcb(evn);
|
||||
};
|
||||
|
||||
const transform = (evn, x, y, sx, sy) => {
|
||||
if (moving) {
|
||||
state.selected.position = {
|
||||
x: sx - moving.offset.x,
|
||||
y: sy - moving.offset.y,
|
||||
};
|
||||
}
|
||||
|
||||
if (scaling) {
|
||||
/** @type {DOMMatrix} */
|
||||
const m = state.selected.rtmatrix.invertSelf();
|
||||
const lscursor = m.transformPoint({x: sx, y: sy});
|
||||
|
||||
const xs = lscursor.x / scaling.handle.x;
|
||||
const xy = lscursor.y / scaling.handle.y;
|
||||
|
||||
if (!state.keepAspectRatio) state.selected.scale = {x: xs, y: xy};
|
||||
else {
|
||||
const scale = Math.max(xs, xy);
|
||||
state.selected.scale = {x: scale, y: scale};
|
||||
}
|
||||
}
|
||||
|
||||
if (rotating) {
|
||||
const center = state.selected.matrix.transformPoint({x: 0, y: 0});
|
||||
let angle = Math.atan2(x - center.x, center.y - y);
|
||||
|
||||
if (evn.evn.shiftKey)
|
||||
angle =
|
||||
config.rotationSnappingAngles.find(
|
||||
(v) => Math.abs(v - angle) < config.rotationSnappingDistance
|
||||
) ?? angle;
|
||||
|
||||
state.selected.rotation = angle;
|
||||
}
|
||||
};
|
||||
|
||||
// Handles left mouse drag events
|
||||
state.dragstartcb = (evn) => {
|
||||
let ix = evn.ix;
|
||||
let iy = evn.iy;
|
||||
if (state.snapToGrid) {
|
||||
ix += snap(evn.ix, 0, 64);
|
||||
iy += snap(evn.iy, 0, 64);
|
||||
}
|
||||
state.dragcb = (evn) => {
|
||||
const {x, y, sx, sy} = _tool._process_cursor(evn, state.snapToGrid);
|
||||
|
||||
// If is selected, check if drag is in handles/body and act accordingly
|
||||
if (state.selected) {
|
||||
const handles = state.selected.handles();
|
||||
if (state.selected) transform(evn, x, y, sx, sy);
|
||||
|
||||
const activeHandle = handles.find((v) => {
|
||||
const vpc = viewport.canvasToView(v.x, v.y);
|
||||
const tlc = viewport.viewToCanvas(vpc.x - 5, vpc.y - 5);
|
||||
const brc = viewport.viewToCanvas(vpc.x + 5, vpc.y + 5);
|
||||
const bb = {
|
||||
x: tlc.x,
|
||||
y: tlc.y,
|
||||
w: brc.x - tlc.x,
|
||||
h: brc.y - tlc.y,
|
||||
};
|
||||
|
||||
return (
|
||||
evn.ix > bb.x &&
|
||||
evn.ix < bb.x + bb.w &&
|
||||
evn.iy > bb.y &&
|
||||
evn.iy < bb.y + bb.h
|
||||
);
|
||||
});
|
||||
if (activeHandle) {
|
||||
state.scaling = activeHandle;
|
||||
return;
|
||||
} else if (state.selected.contains(ix, iy)) {
|
||||
state.moving = {
|
||||
offset: {x: ix - state.selected.x, y: iy - state.selected.y},
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If it is not, just create new selection
|
||||
state.reset();
|
||||
state.dragging = {ix, iy};
|
||||
if (selection.exists) selection.dragcb(evn);
|
||||
};
|
||||
|
||||
// Handles left mouse drag end events
|
||||
state.dragendcb = (evn) => {
|
||||
let x = evn.x;
|
||||
let y = evn.y;
|
||||
if (state.snapToGrid) {
|
||||
x += snap(evn.x, 0, 64);
|
||||
y += snap(evn.y, 0, 64);
|
||||
const {x, y, sx, sy} = _tool._process_cursor(evn, state.snapToGrid);
|
||||
|
||||
if (selection.exists) {
|
||||
selection.dragendcb(evn);
|
||||
|
||||
const bb = selection.bb;
|
||||
|
||||
state.reset();
|
||||
|
||||
if (selection.exists && bb.w !== 0 && bb.h !== 0) {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = bb.w;
|
||||
canvas.height = bb.h;
|
||||
canvas
|
||||
.getContext("2d")
|
||||
.drawImage(
|
||||
uil.canvas,
|
||||
bb.x,
|
||||
bb.y,
|
||||
bb.w,
|
||||
bb.h,
|
||||
0,
|
||||
0,
|
||||
bb.w,
|
||||
bb.h
|
||||
);
|
||||
|
||||
uil.ctx.clearRect(bb.x, bb.y, bb.w, bb.h);
|
||||
|
||||
state.original = {
|
||||
...bb,
|
||||
sx: selection.bb.center.x,
|
||||
sy: selection.bb.center.y,
|
||||
layer: uil.layer,
|
||||
};
|
||||
state.selected = new _tool.MarqueeSelection(canvas, bb.center);
|
||||
}
|
||||
|
||||
selection.deselect();
|
||||
}
|
||||
|
||||
// If we are scaling, stop scaling and do some handler magic
|
||||
if (state.scaling) {
|
||||
state.selected.updateOriginal();
|
||||
state.scaling = null;
|
||||
// If we are moving the selection, just... stop
|
||||
} else if (state.moving) {
|
||||
state.moving = null;
|
||||
/**
|
||||
* If we are dragging, create a cutout selection area and save to an auxiliar image
|
||||
* We will be rendering the image to the overlay, so it will not be noticeable
|
||||
*/
|
||||
} else if (state.dragging) {
|
||||
state.original = selectionBB(
|
||||
state.dragging.ix,
|
||||
state.dragging.iy,
|
||||
x,
|
||||
y
|
||||
);
|
||||
state.selected = selectionBB(
|
||||
state.dragging.ix,
|
||||
state.dragging.iy,
|
||||
x,
|
||||
y
|
||||
);
|
||||
state.originalLayer = uil.layer;
|
||||
state.originalDisplayLayer = imageCollection.registerLayer(null, {
|
||||
after: uil.layer,
|
||||
category: "select-display",
|
||||
});
|
||||
if (state.selected) transform(evn, x, y, sx, sy);
|
||||
|
||||
// Cut out selected portion of the image for manipulation
|
||||
const cvs = document.createElement("canvas");
|
||||
cvs.width = state.selected.w;
|
||||
cvs.height = state.selected.h;
|
||||
const ctx = cvs.getContext("2d");
|
||||
moving = null;
|
||||
scaling = null;
|
||||
rotating = false;
|
||||
|
||||
ctx.drawImage(
|
||||
uil.canvas,
|
||||
state.selected.x,
|
||||
state.selected.y,
|
||||
state.selected.w,
|
||||
state.selected.h,
|
||||
0,
|
||||
0,
|
||||
state.selected.w,
|
||||
state.selected.h
|
||||
);
|
||||
|
||||
uil.ctx.clearRect(
|
||||
state.selected.x,
|
||||
state.selected.y,
|
||||
state.selected.w,
|
||||
state.selected.h
|
||||
);
|
||||
state.selected.image = cvs;
|
||||
state.original.image = cvs;
|
||||
|
||||
if (state.selected.w === 0 || state.selected.h === 0)
|
||||
state.selected = null;
|
||||
|
||||
state.dragging = null;
|
||||
}
|
||||
state.redraw();
|
||||
};
|
||||
|
||||
|
@ -550,11 +471,11 @@ const selectTransformTool = () =>
|
|||
|
||||
ctx.clearRect(0, 0, state.selected.w, state.selected.h);
|
||||
ctx.drawImage(
|
||||
state.selected.image,
|
||||
state.selected.canvas,
|
||||
0,
|
||||
0,
|
||||
state.selected.image.width,
|
||||
state.selected.image.height,
|
||||
state.selected.canvas.width,
|
||||
state.selected.canvas.height,
|
||||
0,
|
||||
0,
|
||||
state.selected.w,
|
||||
|
@ -684,7 +605,7 @@ const selectTransformTool = () =>
|
|||
saveSelectionButton.onclick = () => {
|
||||
downloadCanvas({
|
||||
cropToContent: false,
|
||||
canvas: state.selected.image,
|
||||
canvas: state.selected.canvas,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -695,7 +616,7 @@ const selectTransformTool = () =>
|
|||
createResourceButton.title = "Saves Selection as a Resource";
|
||||
createResourceButton.onclick = () => {
|
||||
const image = document.createElement("img");
|
||||
image.src = state.selected.image.toDataURL();
|
||||
image.src = state.selected.canvas.toDataURL();
|
||||
image.onload = () => {
|
||||
tools.stamp.state.addResource("Selection Resource", image);
|
||||
tools.stamp.enable();
|
||||
|
|
|
@ -1,3 +1,41 @@
|
|||
/**
|
||||
* Generic wheel handler
|
||||
*/
|
||||
let _stamp_wheel_accum = 0;
|
||||
|
||||
const _stamp_onwheel = (evn, state) => {
|
||||
if (evn.mode !== WheelEvent.DOM_DELTA_PIXEL) {
|
||||
// We don't really handle non-pixel scrolling
|
||||
return;
|
||||
}
|
||||
|
||||
let delta = evn.delta;
|
||||
if (evn.evn.shiftKey) delta *= 0.01;
|
||||
|
||||
// A simple but (I hope) effective fix for mouse wheel behavior
|
||||
_stamp_wheel_accum += delta;
|
||||
|
||||
if (
|
||||
!evn.evn.shiftKey &&
|
||||
Math.abs(_stamp_wheel_accum) > config.wheelTickSize
|
||||
) {
|
||||
// Snap to next or previous position
|
||||
const v =
|
||||
state.scale - 0.1 * (_stamp_wheel_accum / Math.abs(_stamp_wheel_accum));
|
||||
|
||||
state.setScale(v + snap(v, 0, 0.1));
|
||||
state.redraw(evn);
|
||||
|
||||
_stamp_wheel_accum = 0; // Zero accumulation
|
||||
} else if (evn.evn.shiftKey && Math.abs(_stamp_wheel_accum) >= 1) {
|
||||
const v = state.scale - _stamp_wheel_accum * 0.01;
|
||||
state.setScale(v);
|
||||
state.redraw(evn);
|
||||
|
||||
_stamp_wheel_accum = 0; // Zero accumulation
|
||||
}
|
||||
};
|
||||
|
||||
const stampTool = () =>
|
||||
toolbar.registerTool(
|
||||
"./res/icons/file-up.svg",
|
||||
|
@ -14,6 +52,12 @@ const stampTool = () =>
|
|||
mouse.listen.world.btn.left.onclick.on(state.drawcb);
|
||||
mouse.listen.world.btn.right.onclick.on(state.cancelcb);
|
||||
|
||||
mouse.listen.world.btn.left.ondragstart.on(state.dragstartcb);
|
||||
mouse.listen.world.btn.left.ondrag.on(state.dragcb);
|
||||
mouse.listen.world.btn.left.ondragend.on(state.dragendcb);
|
||||
|
||||
mouse.listen.world.onwheel.on(state.onwheelcb);
|
||||
|
||||
// For calls from other tools to paste image
|
||||
if (opt && opt.image) {
|
||||
state.addResource(
|
||||
|
@ -41,6 +85,12 @@ const stampTool = () =>
|
|||
mouse.listen.world.btn.left.onclick.clear(state.drawcb);
|
||||
mouse.listen.world.btn.right.onclick.clear(state.cancelcb);
|
||||
|
||||
mouse.listen.world.btn.left.ondragstart.clear(state.dragstartcb);
|
||||
mouse.listen.world.btn.left.ondrag.clear(state.dragcb);
|
||||
mouse.listen.world.btn.left.ondragend.clear(state.dragendcb);
|
||||
|
||||
mouse.listen.world.onwheel.clear(state.onwheelcb);
|
||||
|
||||
ovLayer.clear();
|
||||
},
|
||||
{
|
||||
|
@ -54,7 +104,15 @@ const stampTool = () =>
|
|||
state.lastMouseMove = {x: 0, y: 0};
|
||||
state.block_res_change = true;
|
||||
|
||||
// Current Rotation
|
||||
let rotation = 0;
|
||||
let rotating = null;
|
||||
// Current Scale
|
||||
state.scale = 1;
|
||||
|
||||
state.selectResource = (resource, nolock = true, deselect = true) => {
|
||||
rotation = 0;
|
||||
state.setScale(1);
|
||||
if (nolock && state.ctxmenu.uploadButton.disabled) return;
|
||||
|
||||
console.debug(
|
||||
|
@ -290,32 +348,65 @@ const stampTool = () =>
|
|||
syncResources();
|
||||
};
|
||||
|
||||
state.movecb = (evn) => {
|
||||
let x = evn.x;
|
||||
let y = evn.y;
|
||||
if (state.snapToGrid) {
|
||||
x += snap(evn.x, 0, 64);
|
||||
y += snap(evn.y, 0, 64);
|
||||
state.onwheelcb = (evn) => {
|
||||
_stamp_onwheel(evn, state);
|
||||
};
|
||||
|
||||
state.dragstartcb = (evn) => {
|
||||
const {x, y, sx, sy} = _tool._process_cursor(evn, state.snapToGrid);
|
||||
rotating = {x: sx, y: sy};
|
||||
};
|
||||
|
||||
state.dragcb = (evn) => {
|
||||
if (rotating) {
|
||||
rotation = Math.atan2(rotating.x - evn.x, evn.y - rotating.y);
|
||||
|
||||
if (evn.evn.shiftKey)
|
||||
rotation =
|
||||
config.rotationSnappingAngles.find(
|
||||
(v) =>
|
||||
Math.abs(v - rotation) < config.rotationSnappingDistance
|
||||
) ?? rotation;
|
||||
}
|
||||
};
|
||||
|
||||
const vpc = viewport.canvasToView(x, y);
|
||||
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||
state.erasePrevCursor && state.erasePrevCursor();
|
||||
state.dragendcb = (evn) => {
|
||||
rotating = null;
|
||||
};
|
||||
|
||||
uiCtx.save();
|
||||
let erasePrevCursor = () => null;
|
||||
|
||||
state.movecb = (evn) => {
|
||||
const {x, y, sx, sy} = _tool._process_cursor(evn, state.snapToGrid);
|
||||
|
||||
// Erase Previous Cursors
|
||||
erasePrevCursor();
|
||||
|
||||
state.lastMouseMove = evn;
|
||||
|
||||
ovLayer.clear();
|
||||
|
||||
let px = sx;
|
||||
let py = sy;
|
||||
|
||||
if (rotating) {
|
||||
px = rotating.x;
|
||||
py = rotating.y;
|
||||
}
|
||||
|
||||
// Draw selected image
|
||||
if (state.selected) {
|
||||
ovCtx.drawImage(state.selected.image, x, y);
|
||||
ovCtx.save();
|
||||
ovCtx.translate(px, py);
|
||||
ovCtx.scale(state.scale, state.scale);
|
||||
ovCtx.rotate(rotation);
|
||||
|
||||
ovCtx.drawImage(state.selected.image, 0, 0);
|
||||
ovCtx.restore();
|
||||
}
|
||||
|
||||
// Draw current cursor location
|
||||
state.erasePrevCursor = _tool._cursor_draw(x, y);
|
||||
uiCtx.restore();
|
||||
erasePrevCursor = _tool._cursor_draw(px, py);
|
||||
};
|
||||
|
||||
state.redraw = () => {
|
||||
|
@ -323,20 +414,16 @@ const stampTool = () =>
|
|||
};
|
||||
|
||||
state.drawcb = (evn) => {
|
||||
let x = evn.x;
|
||||
let y = evn.y;
|
||||
if (state.snapToGrid) {
|
||||
x += snap(evn.x, 0, 64);
|
||||
y += snap(evn.y, 0, 64);
|
||||
}
|
||||
const {x, y, sx, sy} = _tool._process_cursor(evn, state.snapToGrid);
|
||||
|
||||
const resource = state.selected;
|
||||
|
||||
if (resource) {
|
||||
const {canvas, bb} = cropCanvas(ovCanvas, {border: 10});
|
||||
commands.runCommand("drawImage", "Image Stamp", {
|
||||
image: resource.image,
|
||||
x,
|
||||
y,
|
||||
image: canvas,
|
||||
x: bb.x,
|
||||
y: bb.y,
|
||||
});
|
||||
|
||||
if (resource.temporary) {
|
||||
|
@ -380,6 +467,16 @@ const stampTool = () =>
|
|||
);
|
||||
state.ctxmenu.snapToGridLabel = array;
|
||||
|
||||
// Scale Slider
|
||||
const scaleSlider = _toolbar_input.slider(state, "scale", "Scale", {
|
||||
min: 0.01,
|
||||
max: 10,
|
||||
step: 0.1,
|
||||
textStep: 0.001,
|
||||
});
|
||||
state.ctxmenu.scaleSlider = scaleSlider.slider;
|
||||
state.setScale = scaleSlider.setValue;
|
||||
|
||||
// Create resource list
|
||||
const uploadButtonId = `upload-btn-${guid()}`;
|
||||
|
||||
|
@ -528,6 +625,7 @@ const stampTool = () =>
|
|||
},
|
||||
populateContextMenu: (menu, state) => {
|
||||
menu.appendChild(state.ctxmenu.snapToGridLabel);
|
||||
menu.appendChild(state.ctxmenu.scaleSlider);
|
||||
menu.appendChild(state.ctxmenu.resourceManager);
|
||||
},
|
||||
shortcut: "U",
|
||||
|
|
|
@ -5,15 +5,15 @@
|
|||
<title>openOutpaint 🐠</title>
|
||||
<!-- CSS Variables -->
|
||||
<link href="../css/colors.css?v=3f81e80" rel="stylesheet" />
|
||||
<link href="../css/icons.css?v=caa702e" rel="stylesheet" />
|
||||
<link href="../css/icons.css?v=9ae0466" rel="stylesheet" />
|
||||
|
||||
<link href="../css/index.css?v=69d3b9e" rel="stylesheet" />
|
||||
<link href="../css/layers.css?v=b4fbf61" rel="stylesheet" />
|
||||
<link href="../css/index.css?v=5b8d4d6" rel="stylesheet" />
|
||||
<link href="../css/layers.css?v=92c0352" rel="stylesheet" />
|
||||
|
||||
<link href="../css/ui/generic.css?v=4b9afe2" rel="stylesheet" />
|
||||
<link href="../css/ui/generic.css?v=802bd41" rel="stylesheet" />
|
||||
|
||||
<link href="../css/ui/history.css?v=0b03861" rel="stylesheet" />
|
||||
<link href="../css/ui/layers.css?v=4fd95fe" rel="stylesheet" />
|
||||
<link href="../css/ui/layers.css?v=ae472cd" rel="stylesheet" />
|
||||
<link href="../css/ui/toolbar.css?v=109c78f" rel="stylesheet" />
|
||||
|
||||
<!-- Tool Specific CSS -->
|
||||
|
@ -84,11 +84,12 @@
|
|||
step="0.1"
|
||||
value="30.0" />
|
||||
</label>
|
||||
<!-- <p>Refresh the page to apply aabove.</p> -->
|
||||
<hr />
|
||||
<label style="display: flex">
|
||||
Lie to HRfix:
|
||||
<input id="hrfix-liar" class="canvas-size-input" type="checkbox" />
|
||||
</label>
|
||||
<p>Refresh the page to apply settings.</p>
|
||||
|
||||
<script>
|
||||
const canvasWidth = document.getElementById("canvas-width");
|
||||
|
@ -127,8 +128,11 @@
|
|||
localStorage.getItem("openoutpaint/settings.min-cfg") || -1;
|
||||
maxCfg.value =
|
||||
localStorage.getItem("openoutpaint/settings.max-cfg") || 30;
|
||||
hrfixLiar.checked =
|
||||
localStorage.getItem("openoutpaint/settings.hrfix-liar") || true;
|
||||
let _enable_dishonesty =
|
||||
localStorage.getItem("openoutpaint/settings.hrfix-liar") === null
|
||||
? true
|
||||
: localStorage.getItem("openoutpaint/settings.hrfix-liar") === "true";
|
||||
hrfixLiar.checked = _enable_dishonesty;
|
||||
|
||||
writeToLocalStorage();
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
<iframe
|
||||
id="openoutpaint"
|
||||
style="width: 100%; height: 800px"
|
||||
src="../index.html?v=95a96ad"
|
||||
src="../index.html?v=95a96ad"
|
||||
src="../index.html?v=daf18de"
|
||||
src="../index.html?v=daf18de"
|
||||
frameborder="0"></iframe>
|
||||
<button id="add-res">Add Resource</button>
|
||||
<script>
|
||||
|
|
Loading…
Reference in a new issue