diff options
-rw-r--r-- | src/client/views/nodes/ImageBox.scss | 7 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 156 | ||||
-rw-r--r-- | src/server/ApiManagers/FireflyManager.ts | 12 |
3 files changed, 135 insertions, 40 deletions
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 3adc16879..9f7a5d03f 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -242,12 +242,9 @@ } } .imageBox-regenerate-dialog { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + position: absolute; background: white; - padding: 20px; + padding: 10px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); z-index: 10000; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 31a135fa7..d16baada6 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,4 +1,4 @@ -import { Button, Colors, EditableText, Size, Type } from '@dash/components'; +import { Button, Colors, EditableText, IconButton, Size, Toggle, ToggleType, Type } from '@dash/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Slider, Tooltip } from '@mui/material'; import axios from 'axios'; @@ -353,6 +353,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @action openOutpaintPrompt = () => { + this._outpaintVAlign = ''; + this._outpaintAlign = ''; this._showOutpaintPrompt = true; }; @@ -362,6 +364,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }; @action + cancelOutpaintPrompt = () => { + const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']); + const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']); + this.Document._width = origWidth; + this.Document._height = origHeight; + this._outpaintingInProgress = false; + this.closeOutpaintPrompt(); + }; + + @action handlePromptChange = (val: string | number) => { this._outpaintPromptInput = '' + val; }; @@ -377,17 +389,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (!field) return; - const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']); - const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']); - // Set flag that outpainting is in progress this._outpaintingInProgress = true; // Revert dimensions if prompt is blank (acts like Cancel) if (!customPrompt) { - this.Document._width = origWidth; - this.Document._height = origHeight; - this._outpaintingInProgress = false; + this.cancelOutpaintPrompt(); return; } @@ -410,11 +417,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>'; this._mainCont?.appendChild(loadingOverlay); + const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']); + const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']); const response = await Networking.PostToServer('/outpaintImage', { imageUrl: currentPath, prompt: customPrompt, originalDimensions: { width: Math.min(newWidth, origWidth), height: Math.min(newHeight, origHeight) }, newDimensions: { width: newWidth, height: newHeight }, + halignment: this._outpaintAlign, + valignment: this._outpaintVAlign, }); const error = ('error' in response && (response.error as string)) || ''; @@ -447,8 +458,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this.Document[this.fieldKey + '_outpaintOriginalWidth'] = undefined; this.Document[this.fieldKey + '_outpaintOriginalHeight'] = undefined; } else { - this.Document._width = origWidth; - this.Document._height = origHeight; + this.cancelOutpaintPrompt(); alert('Failed to receive a valid image URL from server.'); } batch.end(); @@ -456,30 +466,106 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this._mainCont?.removeChild(loadingOverlay); } catch (error) { - console.error('Error during outpainting:', error); - this.Document._width = origWidth; - this.Document._height = origHeight; - alert('An error occurred while outpainting. Please try again.'); + this.cancelOutpaintPrompt(); + alert('An error occurred while outpainting.' + error); } finally { runInAction(() => (this._outpaintingInProgress = false)); } }; - componentUI = () => + @observable _outpaintAlign = ''; + @observable _outpaintVAlign = ''; + @computed get outpaintVertical() { + return this._props.PanelWidth() / this._props.PanelHeight() < this.nativeSize.nativeWidth / this.nativeSize.nativeHeight; + } + + componentUI = (/* boundsLeft: number, boundsTop: number*/) => !this._showOutpaintPrompt ? null : ( - <div key="imageBox-componentui" className="imageBox-regenerate-dialog" style={{ backgroundColor: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}> - <h3>Outpaint Image</h3> - <EditableText - placeholder="Enter a prompt for extending the image:" - setVal={val => this.handlePromptChange(val)} - val={this._outpaintPromptInput} - type={Type.TERT} - color={SettingsManager.userColor} - background={SettingsManager.userBackgroundColor} - /> - <div className="buttons"> - <Button text="Cancel" type={Type.TERT} onClick={this.closeOutpaintPrompt} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} /> - <Button text="Generate" type={Type.TERT} onClick={this.submitOutpaintPrompt} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} /> + <div + key="imageBox-componentui" + className="imageBox-regenerate-dialog" + style={{ + top: -70 + (this._props.DocumentView?.().getBounds?.top ?? 0), + left: this._props.DocumentView?.().getBounds?.left ?? 0, + backgroundColor: SettingsManager.userBackgroundColor, + color: SettingsManager.userColor, + }}> + <div style={{ position: 'absolute', top: 5, right: 5 }}> + <IconButton type={Type.TERT} onClick={this.cancelOutpaintPrompt} icon={<FontAwesomeIcon icon="times" color={'red'} />} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} /> + </div> + <div>Outpaint Image</div> + <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}> + <EditableText + placeholder="Enter a prompt for extending the image:" + setVal={val => this.handlePromptChange(val)} + val={this._outpaintPromptInput} + type={Type.TERT} + color={SettingsManager.userColor} + background={SettingsManager.userBackgroundColor} + /> + <div className="buttons" style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}> + <IconButton type={Type.TERT} onClick={this.submitOutpaintPrompt} icon={<AiOutlineSend />} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} /> + <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}> + {this.outpaintVertical ? null : ( + <Toggle + type={Type.TERT} + toggleType={ToggleType.BUTTON} + toggleStatus={this._outpaintAlign === 'left' && this._outpaintVAlign === ''} + onClick={action(() => (this._outpaintAlign = 'left'))} + icon={<FontAwesomeIcon icon="chevron-left" color={SnappingManager.userColor} />} + color={SnappingManager.userColor} + background={SnappingManager.userVariantColor} + /> + )} + <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}> + {!this.outpaintVertical ? null : ( + <Toggle + type={Type.TERT} + toggleType={ToggleType.BUTTON} + toggleStatus={this._outpaintAlign === '' && this._outpaintVAlign === 'top'} + onClick={action(() => (this._outpaintVAlign = 'top'))} + icon={<FontAwesomeIcon icon="chevron-up" color={SnappingManager.userColor} />} + color={SnappingManager.userColor} + background={SnappingManager.userColor} + /> + )} + <Toggle + type={Type.TERT} + toggleType={ToggleType.BUTTON} + toggleStatus={this._outpaintAlign === '' && this._outpaintVAlign === ''} + onClick={action(() => { + this._outpaintAlign = ''; + this._outpaintVAlign = ''; + })} + icon={<FontAwesomeIcon icon="bullseye" color={SnappingManager.userColor} />} + color={SnappingManager.userColor} + background={SnappingManager.userColor} + /> + {!this.outpaintVertical ? null : ( + <Toggle + type={Type.TERT} + toggleType={ToggleType.BUTTON} + toggleStatus={this._outpaintAlign === '' && this._outpaintVAlign === 'bottom'} + onClick={action(() => (this._outpaintVAlign = 'bottom'))} + icon={<FontAwesomeIcon icon="chevron-down" color={SnappingManager.userColor} />} + color={SnappingManager.userColor} + background={SnappingManager.userColor} + /> + )} + </div> + {this.outpaintVertical ? null : ( + <Toggle + type={Type.TERT} + toggleType={ToggleType.BUTTON} + toggleStatus={this._outpaintAlign === 'right' && this._outpaintVAlign === ''} + onClick={action(() => (this._outpaintAlign = 'right'))} + icon={<FontAwesomeIcon icon="chevron-right" color={SnappingManager.userColor} />} + color={SnappingManager.userColor} + background={SnappingManager.userColor} + /> + )} + </div> + </div> </div> </div> ); @@ -718,13 +804,25 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { })} key={this.layoutDoc[Id]} onPointerDown={this.marqueeDown}> - <div className="imageBox-fader" style={{ opacity: backAlpha }}> + <div + className="imageBox-fader" + style={{ + opacity: backAlpha, + flexDirection: this._outpaintVAlign ? 'row' : 'column', + alignItems: this._outpaintAlign === 'left' || this._outpaintVAlign === 'top' ? 'flex-start' : this._outpaintAlign === 'right' || this._outpaintVAlign === 'bottom' ? 'flex-end' : undefined, + }}> <img alt="" ref={action((r: HTMLImageElement | null) => (this.imageRef = r))} key="paths" src={srcpath} - style={{ transform, transformOrigin, height: this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined ? '100%' : undefined }} + style={{ + position: 'relative', + transform, + transformOrigin, + width: this._outpaintAlign ? 'max-content' : this._outpaintAlign ? '100%' : undefined, + height: this._outpaintVAlign ? 'max-content' : this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined ? '100%' : undefined, + }} onError={action(e => (this._error = e.toString()))} draggable={false} width={nativeWidth} diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index 1b8a85a5c..e934e635b 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -343,14 +343,14 @@ export default class FireflyManager extends ApiManager { numVariations: 1, placement: { inset: { - left: Math.round((req.body.newDimensions.width - req.body.originalDimensions.width) / 2), - top: Math.round((req.body.newDimensions.height - req.body.originalDimensions.height) / 2), - right: Math.round((req.body.newDimensions.width - req.body.originalDimensions.width) / 2), - bottom: Math.round((req.body.newDimensions.height - req.body.originalDimensions.height) / 2), + left: 0, // Math.round((req.body.newDimensions.width - req.body.originalDimensions.width) / 2), + top: 0, // Math.round((req.body.newDimensions.height - req.body.originalDimensions.height) / 2), + right: 0, // Math.round((req.body.newDimensions.width - req.body.originalDimensions.width) / 2), + bottom: 0, // Math.round((req.body.newDimensions.height - req.body.originalDimensions.height) / 2), }, alignment: { - horizontal: 'center', - vertical: 'center', + horizontal: req.body.halignment || 'center', + vertical: req.body.valignment || 'center', }, }, }), |