2023-06-05 07:19:06 +00:00
onUiLoaded ( async ( ) => {
2023-06-05 07:26:08 +00:00
const elementIDs = {
img2imgTabs : "#mode_img2img .tab-nav" ,
inpaint : "#img2maskimg" ,
inpaintSketch : "#inpaint_sketch" ,
rangeGroup : "#img2img_column_size" ,
2023-06-12 19:19:22 +00:00
sketch : "#img2img_sketch"
2023-06-05 07:26:08 +00:00
} ;
const tabNameToElementId = {
"Inpaint sketch" : elementIDs . inpaintSketch ,
"Inpaint" : elementIDs . inpaint ,
2023-06-12 19:19:22 +00:00
"Sketch" : elementIDs . sketch
2023-06-05 07:26:08 +00:00
} ;
2023-06-05 07:19:06 +00:00
// Helper functions
// Get active tab
function getActiveTab ( elements , all = false ) {
const tabs = elements . img2imgTabs . querySelectorAll ( "button" ) ;
2023-05-27 22:56:48 +00:00
2023-06-05 07:19:06 +00:00
if ( all ) return tabs ;
2023-05-27 22:56:48 +00:00
2023-06-05 07:19:06 +00:00
for ( let tab of tabs ) {
if ( tab . classList . contains ( "selected" ) ) {
return tab ;
}
2023-05-28 17:22:35 +00:00
}
2023-05-27 22:56:48 +00:00
}
2023-06-05 07:19:06 +00:00
// Get tab ID
2023-06-05 07:26:08 +00:00
function getTabId ( elements ) {
2023-06-05 07:19:06 +00:00
const activeTab = getActiveTab ( elements ) ;
2023-06-05 07:26:08 +00:00
return tabNameToElementId [ activeTab . innerText ] ;
2023-06-05 07:19:06 +00:00
}
// Wait until opts loaded
async function waitForOpts ( ) {
2023-06-05 07:39:57 +00:00
for ( ; ; ) {
if ( window . opts && Object . keys ( window . opts ) . length ) {
return window . opts ;
}
await new Promise ( resolve => setTimeout ( resolve , 100 ) ) ;
}
2023-06-05 07:19:06 +00:00
}
2023-06-12 19:19:22 +00:00
// Function for defining the "Ctrl", "Shift" and "Alt" keys
function isModifierKey ( event , key ) {
switch ( key ) {
case "Ctrl" :
return event . ctrlKey ;
case "Shift" :
return event . shiftKey ;
case "Alt" :
return event . altKey ;
default :
return false ;
}
}
// Check if hotkey is valid
function isValidHotkey ( value ) {
const specialKeys = [ "Ctrl" , "Alt" , "Shift" , "Disable" ] ;
2023-06-05 07:19:06 +00:00
return (
2023-06-13 21:31:36 +00:00
( typeof value === "string" &&
value . length === 1 &&
/[a-z]/i . test ( value ) ) ||
specialKeys . includes ( value )
2023-06-05 07:19:06 +00:00
) ;
}
2023-06-13 21:31:36 +00:00
2023-06-13 21:24:25 +00:00
// Normalize hotkey
function normalizeHotkey ( hotkey ) {
return hotkey . length === 1 ? "Key" + hotkey . toUpperCase ( ) : hotkey ;
}
2023-06-13 21:31:36 +00:00
2023-06-13 21:24:25 +00:00
// Format hotkey for display
function formatHotkeyForDisplay ( hotkey ) {
return hotkey . startsWith ( "Key" ) ? hotkey . slice ( 3 ) : hotkey ;
}
2023-06-13 21:31:36 +00:00
2023-06-13 21:24:25 +00:00
// Create hotkey configuration with the provided options
2023-06-05 07:19:06 +00:00
function createHotkeyConfig ( defaultHotkeysConfig , hotkeysConfigOpts ) {
2023-06-13 21:24:25 +00:00
const result = { } ; // Resulting hotkey configuration
const usedKeys = new Set ( ) ; // Set of used hotkeys
2023-06-13 21:31:36 +00:00
2023-06-13 21:24:25 +00:00
// Iterate through defaultHotkeysConfig keys
for ( const key in defaultHotkeysConfig ) {
2023-06-13 21:31:36 +00:00
const userValue = hotkeysConfigOpts [ key ] ; // User-provided hotkey value
const defaultValue = defaultHotkeysConfig [ key ] ; // Default hotkey value
// Apply appropriate value for undefined, boolean, or object userValue
if (
userValue === undefined ||
typeof userValue === "boolean" ||
typeof userValue === "object" ||
userValue === "disable"
) {
result [ key ] =
userValue === undefined ? defaultValue : userValue ;
} else if ( isValidHotkey ( userValue ) ) {
const normalizedUserValue = normalizeHotkey ( userValue ) ;
// Check for conflicting hotkeys
if ( ! usedKeys . has ( normalizedUserValue ) ) {
usedKeys . add ( normalizedUserValue ) ;
result [ key ] = normalizedUserValue ;
} else {
console . error (
` Hotkey: ${ formatHotkeyForDisplay (
userValue
) } for $ { key } is repeated and conflicts with another hotkey . The default hotkey is used : $ { formatHotkeyForDisplay (
defaultValue
) } `
) ;
result [ key ] = defaultValue ;
}
2023-06-13 21:24:25 +00:00
} else {
2023-06-13 21:31:36 +00:00
console . error (
` Hotkey: ${ formatHotkeyForDisplay (
userValue
) } for $ { key } is not valid . The default hotkey is used : $ { formatHotkeyForDisplay (
defaultValue
) } `
) ;
result [ key ] = defaultValue ;
2023-06-01 22:04:17 +00:00
}
}
2023-06-13 21:31:36 +00:00
2023-06-05 07:19:06 +00:00
return result ;
}
2023-06-01 22:04:17 +00:00
2023-06-13 21:31:36 +00:00
// Disables functions in the config object based on the provided list of function names
2023-06-13 21:24:25 +00:00
function disableFunctions ( config , disabledFunctions ) {
2023-06-13 21:31:36 +00:00
// Bind the hasOwnProperty method to the functionMap object to avoid errors
const hasOwnProperty =
Object . prototype . hasOwnProperty . bind ( functionMap ) ;
// Loop through the disabledFunctions array and disable the corresponding functions in the config object
disabledFunctions . forEach ( funcName => {
if ( hasOwnProperty ( funcName ) ) {
const key = functionMap [ funcName ] ;
config [ key ] = "disable" ;
}
2023-06-13 21:24:25 +00:00
} ) ;
2023-06-13 21:31:36 +00:00
// Return the updated config object
2023-06-13 21:24:25 +00:00
return config ;
2023-06-13 21:31:36 +00:00
}
2023-06-13 21:24:25 +00:00
2023-06-05 07:19:06 +00:00
/ * *
* The restoreImgRedMask function displays a red mask around an image to indicate the aspect ratio .
* If the image display property is set to 'none' , the mask breaks . To fix this , the function
* temporarily sets the display property to 'block' and then hides the mask again after 300 milliseconds
* to avoid breaking the canvas . Additionally , the function adjusts the mask to work correctly on
* very long images .
* /
2023-06-05 07:26:08 +00:00
function restoreImgRedMask ( elements ) {
const mainTabId = getTabId ( elements ) ;
2023-06-04 00:04:46 +00:00
2023-06-05 07:19:06 +00:00
if ( ! mainTabId ) return ;
2023-06-04 00:04:46 +00:00
2023-06-05 07:19:06 +00:00
const mainTab = gradioApp ( ) . querySelector ( mainTabId ) ;
const img = mainTab . querySelector ( "img" ) ;
const imageARPreview = gradioApp ( ) . querySelector ( "#imageARPreview" ) ;
2023-06-04 00:04:46 +00:00
2023-06-05 07:19:06 +00:00
if ( ! img || ! imageARPreview ) return ;
2023-06-04 00:04:46 +00:00
2023-06-05 07:19:06 +00:00
imageARPreview . style . transform = "" ;
if ( parseFloat ( mainTab . style . width ) > 865 ) {
const transformString = mainTab . style . transform ;
2023-06-12 19:19:22 +00:00
const scaleMatch = transformString . match (
/scale\(([-+]?[0-9]*\.?[0-9]+)\)/
) ;
2023-06-05 07:19:06 +00:00
let zoom = 1 ; // default zoom
2023-06-04 00:04:46 +00:00
2023-06-05 07:19:06 +00:00
if ( scaleMatch && scaleMatch [ 1 ] ) {
zoom = Number ( scaleMatch [ 1 ] ) ;
}
2023-06-04 01:17:55 +00:00
2023-06-05 07:19:06 +00:00
imageARPreview . style . transformOrigin = "0 0" ;
imageARPreview . style . transform = ` scale( ${ zoom } ) ` ;
2023-06-04 01:17:55 +00:00
}
2023-06-04 00:04:46 +00:00
2023-06-05 07:19:06 +00:00
if ( img . style . display !== "none" ) return ;
2023-06-04 00:04:46 +00:00
2023-06-05 07:19:06 +00:00
img . style . display = "block" ;
2023-06-04 00:04:46 +00:00
2023-06-05 07:19:06 +00:00
setTimeout ( ( ) => {
img . style . display = "none" ;
} , 400 ) ;
}
2023-06-04 00:04:46 +00:00
2023-06-01 22:04:17 +00:00
const hotkeysConfigOpts = await waitForOpts ( ) ;
// Default config
const defaultHotkeysConfig = {
2023-06-12 19:19:22 +00:00
canvas _hotkey _zoom : "Alt" ,
canvas _hotkey _adjust : "Ctrl" ,
2023-06-01 22:04:17 +00:00
canvas _hotkey _reset : "KeyR" ,
canvas _hotkey _fullscreen : "KeyS" ,
canvas _hotkey _move : "KeyF" ,
2023-06-02 16:10:28 +00:00
canvas _hotkey _overlap : "KeyO" ,
2023-06-13 21:31:36 +00:00
canvas _disabled _functions : [ ] ,
2023-07-04 19:26:43 +00:00
canvas _show _tooltip : true ,
2023-08-08 18:28:16 +00:00
canvas _blur _prompt : false ,
canvas _auto _expand : false
2023-05-28 17:22:35 +00:00
} ;
2023-06-13 21:24:25 +00:00
const functionMap = {
"Zoom" : "canvas_hotkey_zoom" ,
"Adjust brush size" : "canvas_hotkey_adjust" ,
"Moving canvas" : "canvas_hotkey_move" ,
"Fullscreen" : "canvas_hotkey_fullscreen" ,
"Reset Zoom" : "canvas_hotkey_reset" ,
2023-06-13 21:31:36 +00:00
"Overlap" : "canvas_hotkey_overlap"
} ;
2023-06-13 21:24:25 +00:00
// Loading the configuration from opts
const preHotkeysConfig = createHotkeyConfig (
2023-06-01 22:04:17 +00:00
defaultHotkeysConfig ,
hotkeysConfigOpts
) ;
2023-06-13 21:24:25 +00:00
// Disable functions that are not needed by the user
const hotkeysConfig = disableFunctions (
preHotkeysConfig ,
preHotkeysConfig . canvas _disabled _functions
2023-06-13 21:31:36 +00:00
) ;
2023-06-13 21:24:25 +00:00
2023-05-28 17:22:35 +00:00
let isMoving = false ;
let mouseX , mouseY ;
2023-06-04 00:04:46 +00:00
let activeElement ;
2023-05-28 17:22:35 +00:00
2023-06-12 19:19:22 +00:00
const elements = Object . fromEntries (
Object . keys ( elementIDs ) . map ( id => [
id ,
gradioApp ( ) . querySelector ( elementIDs [ id ] )
] )
) ;
2023-06-04 00:04:46 +00:00
const elemData = { } ;
// Apply functionality to the range inputs. Restore redmask and correct for long images.
2023-06-12 19:19:22 +00:00
const rangeInputs = elements . rangeGroup ?
Array . from ( elements . rangeGroup . querySelectorAll ( "input" ) ) :
2023-06-04 00:04:46 +00:00
[
gradioApp ( ) . querySelector ( "#img2img_width input[type='range']" ) ,
gradioApp ( ) . querySelector ( "#img2img_height input[type='range']" )
] ;
2023-06-05 07:36:45 +00:00
for ( const input of rangeInputs ) {
input ? . addEventListener ( "input" , ( ) => restoreImgRedMask ( elements ) ) ;
}
2023-06-03 22:18:27 +00:00
2023-06-03 16:24:05 +00:00
function applyZoomAndPan ( elemId ) {
const targetElement = gradioApp ( ) . querySelector ( elemId ) ;
if ( ! targetElement ) {
console . log ( "Element not found" ) ;
return ;
}
2023-05-28 17:22:35 +00:00
targetElement . style . transformOrigin = "0 0" ;
2023-06-04 00:04:46 +00:00
elemData [ elemId ] = {
zoom : 1 ,
panX : 0 ,
panY : 0
} ;
2023-05-28 17:22:35 +00:00
let fullScreenMode = false ;
2023-05-27 19:54:45 +00:00
2023-05-31 20:02:49 +00:00
// Create tooltip
2023-06-02 16:10:28 +00:00
function createTooltip ( ) {
const toolTipElemnt =
targetElement . querySelector ( ".image-container" ) ;
const tooltip = document . createElement ( "div" ) ;
2023-06-13 21:24:25 +00:00
tooltip . className = "canvas-tooltip" ;
2023-06-02 16:10:28 +00:00
// Creating an item of information
const info = document . createElement ( "i" ) ;
2023-06-13 21:24:25 +00:00
info . className = "canvas-tooltip-info" ;
2023-06-02 16:10:28 +00:00
info . textContent = "" ;
// Create a container for the contents of the tooltip
const tooltipContent = document . createElement ( "div" ) ;
2023-06-13 21:24:25 +00:00
tooltipContent . className = "canvas-tooltip-content" ;
// Define an array with hotkey information and their actions
const hotkeysInfo = [
2023-06-13 21:31:36 +00:00
{
configKey : "canvas_hotkey_zoom" ,
action : "Zoom canvas" ,
keySuffix : " + wheel"
} ,
{
configKey : "canvas_hotkey_adjust" ,
action : "Adjust brush size" ,
keySuffix : " + wheel"
} ,
{ configKey : "canvas_hotkey_reset" , action : "Reset zoom" } ,
{
configKey : "canvas_hotkey_fullscreen" ,
action : "Fullscreen mode"
} ,
{ configKey : "canvas_hotkey_move" , action : "Move canvas" } ,
{ configKey : "canvas_hotkey_overlap" , action : "Overlap" }
2023-06-02 16:10:28 +00:00
] ;
2023-06-13 21:31:36 +00:00
2023-06-13 21:24:25 +00:00
// Create hotkeys array with disabled property based on the config values
2023-06-13 21:31:36 +00:00
const hotkeys = hotkeysInfo . map ( info => {
2023-06-13 21:24:25 +00:00
const configValue = hotkeysConfig [ info . configKey ] ;
2023-06-13 21:31:36 +00:00
const key = info . keySuffix ?
` ${ configValue } ${ info . keySuffix } ` :
configValue . charAt ( configValue . length - 1 ) ;
2023-06-13 21:24:25 +00:00
return {
2023-06-13 21:31:36 +00:00
key ,
action : info . action ,
disabled : configValue === "disable"
2023-06-13 21:24:25 +00:00
} ;
} ) ;
2023-06-13 21:31:36 +00:00
for ( const hotkey of hotkeys ) {
2023-06-13 21:24:25 +00:00
if ( hotkey . disabled ) {
2023-06-13 21:31:36 +00:00
continue ;
2023-06-12 19:19:22 +00:00
}
2023-06-13 21:31:36 +00:00
2023-06-02 16:10:28 +00:00
const p = document . createElement ( "p" ) ;
2023-06-05 07:36:45 +00:00
p . innerHTML = ` <b> ${ hotkey . key } </b> - ${ hotkey . action } ` ;
2023-06-02 16:10:28 +00:00
tooltipContent . appendChild ( p ) ;
2023-06-13 21:31:36 +00:00
}
2023-06-02 16:10:28 +00:00
// Add information and content elements to the tooltip element
tooltip . appendChild ( info ) ;
tooltip . appendChild ( tooltipContent ) ;
// Add a hint element to the target element
toolTipElemnt . appendChild ( tooltip ) ;
}
//Show tool tip if setting enable
if ( hotkeysConfig . canvas _show _tooltip ) {
createTooltip ( ) ;
}
2023-05-31 20:02:49 +00:00
2023-05-28 17:22:35 +00:00
// In the course of research, it was found that the tag img is very harmful when zooming and creates white canvases. This hack allows you to almost never think about this problem, it has no effect on webui.
function fixCanvas ( ) {
const activeTab = getActiveTab ( elements ) . textContent . trim ( ) ;
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
if ( activeTab !== "img2img" ) {
const img = targetElement . querySelector ( ` ${ elemId } img ` ) ;
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
if ( img && img . style . display !== "none" ) {
img . style . display = "none" ;
img . style . visibility = "hidden" ;
}
}
2023-05-27 19:54:45 +00:00
}
2023-05-28 17:22:35 +00:00
// Reset the zoom level and pan position of the target element to their initial values
function resetZoom ( ) {
2023-06-04 00:04:46 +00:00
elemData [ elemId ] = {
zoomLevel : 1 ,
panX : 0 ,
panY : 0
} ;
2023-05-28 17:22:35 +00:00
fixCanvas ( ) ;
2023-06-04 00:04:46 +00:00
targetElement . style . transform = ` scale( ${ elemData [ elemId ] . zoomLevel } ) translate( ${ elemData [ elemId ] . panX } px, ${ elemData [ elemId ] . panY } px) ` ;
2023-05-28 17:22:35 +00:00
2023-05-28 17:32:21 +00:00
const canvas = gradioApp ( ) . querySelector (
2023-05-28 17:22:35 +00:00
` ${ elemId } canvas[key="interface"] `
) ;
toggleOverlap ( "off" ) ;
fullScreenMode = false ;
if (
canvas &&
parseFloat ( canvas . style . width ) > 865 &&
parseFloat ( targetElement . style . width ) > 865
) {
fitToElement ( ) ;
return ;
}
targetElement . style . width = "" ;
if ( canvas ) {
targetElement . style . height = canvas . style . height ;
}
}
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
// Toggle the zIndex of the target element between two values, allowing it to overlap or be overlapped by other elements
function toggleOverlap ( forced = "" ) {
const zIndex1 = "0" ;
const zIndex2 = "998" ;
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
targetElement . style . zIndex =
targetElement . style . zIndex !== zIndex2 ? zIndex2 : zIndex1 ;
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
if ( forced === "off" ) {
targetElement . style . zIndex = zIndex1 ;
} else if ( forced === "on" ) {
targetElement . style . zIndex = zIndex2 ;
}
2023-05-27 19:54:45 +00:00
}
2023-05-28 17:22:35 +00:00
// Adjust the brush size based on the deltaY value from a mouse wheel event
function adjustBrushSize (
elemId ,
deltaY ,
withoutValue = false ,
percentage = 5
) {
const input =
2023-05-28 17:32:21 +00:00
gradioApp ( ) . querySelector (
2023-05-28 17:22:35 +00:00
` ${ elemId } input[aria-label='Brush radius'] `
) ||
2023-05-28 17:32:21 +00:00
gradioApp ( ) . querySelector (
2023-05-28 17:22:35 +00:00
` ${ elemId } button[aria-label="Use brush"] `
) ;
if ( input ) {
input . click ( ) ;
if ( ! withoutValue ) {
const maxValue =
parseFloat ( input . getAttribute ( "max" ) ) || 100 ;
const changeAmount = maxValue * ( percentage / 100 ) ;
const newValue =
parseFloat ( input . value ) +
( deltaY > 0 ? - changeAmount : changeAmount ) ;
input . value = Math . min ( Math . max ( newValue , 0 ) , maxValue ) ;
input . dispatchEvent ( new Event ( "change" ) ) ;
}
}
}
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
// Reset zoom when uploading a new image
2023-05-28 17:32:21 +00:00
const fileInput = gradioApp ( ) . querySelector (
2023-05-28 17:22:35 +00:00
` ${ elemId } input[type="file"][accept="image/*"].svelte-116rqfv `
2023-05-27 19:54:45 +00:00
) ;
2023-05-28 17:22:35 +00:00
fileInput . addEventListener ( "click" , resetZoom ) ;
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
// Update the zoom level and pan position of the target element based on the values of the zoomLevel, panX and panY variables
function updateZoom ( newZoomLevel , mouseX , mouseY ) {
newZoomLevel = Math . max ( 0.5 , Math . min ( newZoomLevel , 15 ) ) ;
2023-06-04 00:04:46 +00:00
elemData [ elemId ] . panX +=
mouseX - ( mouseX * newZoomLevel ) / elemData [ elemId ] . zoomLevel ;
elemData [ elemId ] . panY +=
mouseY - ( mouseY * newZoomLevel ) / elemData [ elemId ] . zoomLevel ;
2023-05-27 22:31:23 +00:00
2023-05-28 17:22:35 +00:00
targetElement . style . transformOrigin = "0 0" ;
2023-06-04 00:04:46 +00:00
targetElement . style . transform = ` translate( ${ elemData [ elemId ] . panX } px, ${ elemData [ elemId ] . panY } px) scale( ${ newZoomLevel } ) ` ;
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
toggleOverlap ( "on" ) ;
return newZoomLevel ;
}
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
// Change the zoom level based on user interaction
function changeZoomLevel ( operation , e ) {
2023-06-12 19:19:22 +00:00
if ( isModifierKey ( e , hotkeysConfig . canvas _hotkey _zoom ) ) {
2023-05-28 17:22:35 +00:00
e . preventDefault ( ) ;
let zoomPosX , zoomPosY ;
let delta = 0.2 ;
2023-06-04 00:04:46 +00:00
if ( elemData [ elemId ] . zoomLevel > 7 ) {
2023-05-28 17:22:35 +00:00
delta = 0.9 ;
2023-06-04 00:04:46 +00:00
} else if ( elemData [ elemId ] . zoomLevel > 2 ) {
2023-05-28 17:22:35 +00:00
delta = 0.6 ;
}
zoomPosX = e . clientX ;
zoomPosY = e . clientY ;
fullScreenMode = false ;
2023-06-04 00:04:46 +00:00
elemData [ elemId ] . zoomLevel = updateZoom (
elemData [ elemId ] . zoomLevel +
( operation === "+" ? delta : - delta ) ,
2023-05-28 17:22:35 +00:00
zoomPosX - targetElement . getBoundingClientRect ( ) . left ,
zoomPosY - targetElement . getBoundingClientRect ( ) . top
) ;
}
}
/ * *
* This function fits the target element to the screen by calculating
* the required scale and offsets . It also updates the global variables
* zoomLevel , panX , and panY to reflect the new state .
* /
function fitToElement ( ) {
//Reset Zoom
targetElement . style . transform = ` translate( ${ 0 } px, ${ 0 } px) scale( ${ 1 } ) ` ;
// Get element and screen dimensions
const elementWidth = targetElement . offsetWidth ;
const elementHeight = targetElement . offsetHeight ;
const parentElement = targetElement . parentElement ;
const screenWidth = parentElement . clientWidth ;
const screenHeight = parentElement . clientHeight ;
// Get element's coordinates relative to the parent element
const elementRect = targetElement . getBoundingClientRect ( ) ;
const parentRect = parentElement . getBoundingClientRect ( ) ;
const elementX = elementRect . x - parentRect . x ;
// Calculate scale and offsets
const scaleX = screenWidth / elementWidth ;
const scaleY = screenHeight / elementHeight ;
const scale = Math . min ( scaleX , scaleY ) ;
const transformOrigin =
window . getComputedStyle ( targetElement ) . transformOrigin ;
const [ originX , originY ] = transformOrigin . split ( " " ) ;
const originXValue = parseFloat ( originX ) ;
const originYValue = parseFloat ( originY ) ;
const offsetX =
( screenWidth - elementWidth * scale ) / 2 -
originXValue * ( 1 - scale ) ;
const offsetY =
( screenHeight - elementHeight * scale ) / 2.5 -
originYValue * ( 1 - scale ) ;
// Apply scale and offsets to the element
targetElement . style . transform = ` translate( ${ offsetX } px, ${ offsetY } px) scale( ${ scale } ) ` ;
// Update global variables
2023-06-04 00:04:46 +00:00
elemData [ elemId ] . zoomLevel = scale ;
elemData [ elemId ] . panX = offsetX ;
elemData [ elemId ] . panY = offsetY ;
2023-05-28 17:22:35 +00:00
fullScreenMode = false ;
toggleOverlap ( "off" ) ;
}
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
/ * *
* This function fits the target element to the screen by calculating
* the required scale and offsets . It also updates the global variables
* zoomLevel , panX , and panY to reflect the new state .
* /
// Fullscreen mode
function fitToScreen ( ) {
2023-05-28 17:32:21 +00:00
const canvas = gradioApp ( ) . querySelector (
2023-05-28 17:22:35 +00:00
` ${ elemId } canvas[key="interface"] `
) ;
if ( ! canvas ) return ;
if ( canvas . offsetWidth > 862 ) {
targetElement . style . width = canvas . offsetWidth + "px" ;
}
if ( fullScreenMode ) {
resetZoom ( ) ;
fullScreenMode = false ;
return ;
}
//Reset Zoom
targetElement . style . transform = ` translate( ${ 0 } px, ${ 0 } px) scale( ${ 1 } ) ` ;
2023-05-30 13:35:52 +00:00
// Get scrollbar width to right-align the image
2023-05-31 20:02:49 +00:00
const scrollbarWidth =
window . innerWidth - document . documentElement . clientWidth ;
2023-05-30 13:35:52 +00:00
2023-05-28 17:22:35 +00:00
// Get element and screen dimensions
const elementWidth = targetElement . offsetWidth ;
const elementHeight = targetElement . offsetHeight ;
2023-05-30 13:35:52 +00:00
const screenWidth = window . innerWidth - scrollbarWidth ;
2023-05-28 17:22:35 +00:00
const screenHeight = window . innerHeight ;
// Get element's coordinates relative to the page
const elementRect = targetElement . getBoundingClientRect ( ) ;
const elementY = elementRect . y ;
const elementX = elementRect . x ;
// Calculate scale and offsets
const scaleX = screenWidth / elementWidth ;
const scaleY = screenHeight / elementHeight ;
const scale = Math . min ( scaleX , scaleY ) ;
// Get the current transformOrigin
const computedStyle = window . getComputedStyle ( targetElement ) ;
const transformOrigin = computedStyle . transformOrigin ;
const [ originX , originY ] = transformOrigin . split ( " " ) ;
const originXValue = parseFloat ( originX ) ;
const originYValue = parseFloat ( originY ) ;
// Calculate offsets with respect to the transformOrigin
const offsetX =
( screenWidth - elementWidth * scale ) / 2 -
elementX -
originXValue * ( 1 - scale ) ;
const offsetY =
( screenHeight - elementHeight * scale ) / 2 -
elementY -
originYValue * ( 1 - scale ) ;
// Apply scale and offsets to the element
targetElement . style . transform = ` translate( ${ offsetX } px, ${ offsetY } px) scale( ${ scale } ) ` ;
// Update global variables
2023-06-04 00:04:46 +00:00
elemData [ elemId ] . zoomLevel = scale ;
elemData [ elemId ] . panX = offsetX ;
elemData [ elemId ] . panY = offsetY ;
2023-05-28 17:22:35 +00:00
fullScreenMode = true ;
toggleOverlap ( "on" ) ;
}
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
// Handle keydown events
function handleKeyDown ( event ) {
2023-07-02 16:20:49 +00:00
// Disable key locks to make pasting from the buffer work correctly
2023-07-04 19:26:43 +00:00
if ( ( event . ctrlKey && event . code === 'KeyV' ) || ( event . ctrlKey && event . code === 'KeyC' ) || event . code === "F5" ) {
2023-07-02 16:20:49 +00:00
return ;
}
2023-06-30 10:49:26 +00:00
// before activating shortcut, ensure user is not actively typing in an input field
2023-07-04 19:26:43 +00:00
if ( ! hotkeysConfig . canvas _blur _prompt ) {
if ( event . target . nodeName === 'TEXTAREA' || event . target . nodeName === 'INPUT' ) {
return ;
}
2023-07-02 16:20:49 +00:00
}
2023-05-28 17:22:35 +00:00
2023-06-12 19:19:22 +00:00
2023-07-02 16:20:49 +00:00
const hotkeyActions = {
[ hotkeysConfig . canvas _hotkey _reset ] : resetZoom ,
[ hotkeysConfig . canvas _hotkey _overlap ] : toggleOverlap ,
[ hotkeysConfig . canvas _hotkey _fullscreen ] : fitToScreen
} ;
2023-06-30 10:49:26 +00:00
2023-07-02 16:20:49 +00:00
const action = hotkeyActions [ event . code ] ;
if ( action ) {
event . preventDefault ( ) ;
action ( event ) ;
}
if (
isModifierKey ( event , hotkeysConfig . canvas _hotkey _zoom ) ||
isModifierKey ( event , hotkeysConfig . canvas _hotkey _adjust )
) {
event . preventDefault ( ) ;
2023-06-12 19:19:22 +00:00
}
2023-05-28 17:22:35 +00:00
}
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
// Get Mouse position
function getMousePosition ( e ) {
mouseX = e . offsetX ;
mouseY = e . offsetY ;
}
2023-05-27 19:54:45 +00:00
2023-08-08 18:28:16 +00:00
// Simulation of the function to put a long image into the screen.
// We define the size of the canvas, make a fullscreen to reveal the image, then reduce it to fit into the element.
// We hide the image and show it to the user when it is ready.
function autoExpand ( e ) {
const canvas = document . querySelector ( ` ${ elemId } canvas[key="interface"] ` ) ;
const isMainTab = activeElement === elementIDs . inpaint || activeElement === elementIDs . inpaintSketch || activeElement === elementIDs . sketch ;
if ( canvas && isMainTab ) {
if ( canvas && parseInt ( targetElement . style . width ) > 862 || parseInt ( canvas . width ) < 862 ) {
return ;
}
if ( canvas ) {
targetElement . style . visibility = "hidden" ;
setTimeout ( ( ) => {
fitToScreen ( ) ;
resetZoom ( ) ;
targetElement . style . visibility = "visible" ;
} , 10 ) ;
}
}
}
2023-05-28 17:22:35 +00:00
targetElement . addEventListener ( "mousemove" , getMousePosition ) ;
2023-08-08 18:28:16 +00:00
// Apply auto expand if enabled
if ( hotkeysConfig . canvas _auto _expand ) {
targetElement . addEventListener ( "mousemove" , autoExpand ) ;
}
2023-05-28 17:22:35 +00:00
// Handle events only inside the targetElement
let isKeyDownHandlerAttached = false ;
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
function handleMouseMove ( ) {
if ( ! isKeyDownHandlerAttached ) {
document . addEventListener ( "keydown" , handleKeyDown ) ;
isKeyDownHandlerAttached = true ;
2023-06-04 00:04:46 +00:00
activeElement = elemId ;
2023-05-28 17:22:35 +00:00
}
2023-05-27 19:54:45 +00:00
}
2023-05-28 17:22:35 +00:00
function handleMouseLeave ( ) {
if ( isKeyDownHandlerAttached ) {
document . removeEventListener ( "keydown" , handleKeyDown ) ;
isKeyDownHandlerAttached = false ;
2023-06-04 00:04:46 +00:00
activeElement = null ;
2023-05-28 17:22:35 +00:00
}
}
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
// Add mouse event handlers
targetElement . addEventListener ( "mousemove" , handleMouseMove ) ;
targetElement . addEventListener ( "mouseleave" , handleMouseLeave ) ;
// Reset zoom when click on another tab
elements . img2imgTabs . addEventListener ( "click" , resetZoom ) ;
elements . img2imgTabs . addEventListener ( "click" , ( ) => {
// targetElement.style.width = "";
if ( parseInt ( targetElement . style . width ) > 865 ) {
setTimeout ( fitToElement , 0 ) ;
}
} ) ;
targetElement . addEventListener ( "wheel" , e => {
// change zoom level
const operation = e . deltaY > 0 ? "-" : "+" ;
changeZoomLevel ( operation , e ) ;
// Handle brush size adjustment with ctrl key pressed
2023-06-12 19:19:22 +00:00
if ( isModifierKey ( e , hotkeysConfig . canvas _hotkey _adjust ) ) {
2023-05-28 17:22:35 +00:00
e . preventDefault ( ) ;
// Increase or decrease brush size based on scroll direction
adjustBrushSize ( elemId , e . deltaY ) ;
}
} ) ;
2023-05-31 20:02:49 +00:00
// Handle the move event for pan functionality. Updates the panX and panY variables and applies the new transform to the target element.
2023-05-28 17:22:35 +00:00
function handleMoveKeyDown ( e ) {
2023-07-02 16:20:49 +00:00
// Disable key locks to make pasting from the buffer work correctly
2023-07-04 19:26:43 +00:00
if ( ( e . ctrlKey && e . code === 'KeyV' ) || ( e . ctrlKey && event . code === 'KeyC' ) || e . code === "F5" ) {
2023-07-02 16:20:49 +00:00
return ;
}
// before activating shortcut, ensure user is not actively typing in an input field
2023-07-04 19:26:43 +00:00
if ( ! hotkeysConfig . canvas _blur _prompt ) {
if ( e . target . nodeName === 'TEXTAREA' || e . target . nodeName === 'INPUT' ) {
return ;
}
2023-07-02 16:20:49 +00:00
}
2023-07-04 19:26:43 +00:00
2023-06-01 22:04:17 +00:00
if ( e . code === hotkeysConfig . canvas _hotkey _move ) {
2023-07-02 16:20:49 +00:00
if ( ! e . ctrlKey && ! e . metaKey && isKeyDownHandlerAttached ) {
e . preventDefault ( ) ;
document . activeElement . blur ( ) ;
isMoving = true ;
2023-05-28 17:22:35 +00:00
}
}
}
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
function handleMoveKeyUp ( e ) {
2023-06-01 22:04:17 +00:00
if ( e . code === hotkeysConfig . canvas _hotkey _move ) {
2023-05-28 17:22:35 +00:00
isMoving = false ;
}
}
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
document . addEventListener ( "keydown" , handleMoveKeyDown ) ;
document . addEventListener ( "keyup" , handleMoveKeyUp ) ;
2023-05-27 19:54:45 +00:00
2023-05-28 17:22:35 +00:00
// Detect zoom level and update the pan speed.
function updatePanPosition ( movementX , movementY ) {
2023-06-04 00:04:46 +00:00
let panSpeed = 2 ;
2023-05-27 19:54:45 +00:00
2023-06-04 00:04:46 +00:00
if ( elemData [ elemId ] . zoomLevel > 8 ) {
panSpeed = 3.5 ;
2023-05-28 17:22:35 +00:00
}
2023-05-27 19:54:45 +00:00
2023-06-04 00:38:21 +00:00
elemData [ elemId ] . panX += movementX * panSpeed ;
elemData [ elemId ] . panY += movementY * panSpeed ;
2023-05-27 19:54:45 +00:00
2023-06-04 00:38:21 +00:00
// Delayed redraw of an element
requestAnimationFrame ( ( ) => {
targetElement . style . transform = ` translate( ${ elemData [ elemId ] . panX } px, ${ elemData [ elemId ] . panY } px) scale( ${ elemData [ elemId ] . zoomLevel } ) ` ;
toggleOverlap ( "on" ) ;
} ) ;
2023-05-28 17:22:35 +00:00
}
function handleMoveByKey ( e ) {
2023-06-04 00:04:46 +00:00
if ( isMoving && elemId === activeElement ) {
2023-05-28 17:22:35 +00:00
updatePanPosition ( e . movementX , e . movementY ) ;
targetElement . style . pointerEvents = "none" ;
} else {
targetElement . style . pointerEvents = "auto" ;
}
}
2023-05-31 20:02:49 +00:00
// Prevents sticking to the mouse
window . onblur = function ( ) {
isMoving = false ;
} ;
2023-05-28 17:32:21 +00:00
gradioApp ( ) . addEventListener ( "mousemove" , handleMoveByKey ) ;
2023-05-28 17:22:35 +00:00
}
2023-05-27 19:54:45 +00:00
2023-06-03 16:24:05 +00:00
applyZoomAndPan ( elementIDs . sketch ) ;
applyZoomAndPan ( elementIDs . inpaint ) ;
applyZoomAndPan ( elementIDs . inpaintSketch ) ;
// Make the function global so that other extensions can take advantage of this solution
window . applyZoomAndPan = applyZoomAndPan ;
2023-05-27 19:54:45 +00:00
} ) ;