From 40a568c06094c4b9856db2737c0afea54314771f Mon Sep 17 00:00:00 2001 From: glibesyck <71762371+glibesyck@users.noreply.github.com> Date: Sat, 14 Oct 2023 13:25:07 +0300 Subject: [PATCH 1/2] Hide Metadata in Info View (#4787) * #4665 hides value of the corresponding metadata item by click on arrow * #4787 return recall button back:) * #4787 optional hide of metadata item, truncation and scrolling * remove unused import * #4787 recall parameters as separate tab in panel * #4787 remove debug code * fix(ui): undo changes to dist/locales/en.json This file is autogenerated by our translation system and shouldn't be modified directly * feat(ui): use scrollbar-enabled component for parameter recall tab * fix(ui): revert unnecessary changes to DataViewer component --------- Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com> --- invokeai/frontend/web/public/locales/en.json | 2 ++ .../ImageMetadataViewer.tsx | 20 ++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index e0baefcf9e..dd96193d5d 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -559,8 +559,10 @@ "negativePrompt": "Negative Prompt", "noImageDetails": "No image details found", "noMetaData": "No metadata found", + "noRecallParameters": "No parameters to recall found", "perlin": "Perlin Noise", "positivePrompt": "Positive Prompt", + "recallParameters": "Recall Parameters", "scheduler": "Scheduler", "seamless": "Seamless", "seed": "Seed", diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx index 8c7e69b2b1..e9cb3ffcaf 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx @@ -18,6 +18,7 @@ import ImageMetadataActions from './ImageMetadataActions'; import { useAppSelector } from '../../../../app/store/storeHooks'; import { configSelector } from '../../../system/store/configSelectors'; import { useTranslation } from 'react-i18next'; +import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent'; type ImageMetadataViewerProps = { image: ImageDTO; @@ -65,19 +66,32 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => { - - + {t('metadata.recallParameters')} {t('metadata.metadata')} {t('metadata.imageDetails')} {t('metadata.workflow')} + + {metadata ? ( + + + + ) : ( + + )} + {metadata ? ( From 75663ec81e9ca0f6d11619027e12188f5e62ce63 Mon Sep 17 00:00:00 2001 From: Paul Curry Date: Sat, 14 Oct 2023 03:34:41 -0700 Subject: [PATCH 2/2] feat (ui, generation): High Resolution Fix MVP in Text2Image Linear Flow (#4819) * added HrfScale type with initial value * working * working * working * working * working * added addHrfToGraph * continueing to implement this * working on this * comments * working * made hrf into its own collapse * working on adding strength slider * working * working * refactoring * working * change of this working: 0 * removed onnx support since apparently its not used * working * made scale integer * trying out psycicpebbles idea * working * working on this * working * added toggle * comments * self review * fixing things * remove 'any' type * fixing typing * changed initial strength value to 3 (large values cause issues) * set denoising start to be 1 - strength to resemble image to image * set initial value * added image to image * pr1 * pr2 * updating to resolution finding * working * working * working * working * working * working * working * working * working * use memo * connect rescale hw to noise * working * fixed min bug * nit * hides elements conditionally * style * feat(ui): add config for HRF, disable if feature disabled or ONNX model in use * fix(ui): use `useCallback` for HRF toggle --------- Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com> --- invokeai/app/invocations/metadata.py | 12 + .../frontend/web/src/app/types/invokeai.ts | 11 +- .../IAIInformationalPopover/constants.ts | 1 + .../nodes/util/graphBuilders/addHrfToGraph.ts | 247 ++++++++++++++++++ .../util/graphBuilders/addSaveImageNode.ts | 9 + .../buildLinearTextToImageGraph.ts | 14 + .../nodes/util/graphBuilders/constants.ts | 4 + .../HighResFix/ParamHrfCollapse.tsx | 65 +++++ .../Parameters/HighResFix/ParamHrfHeight.tsx | 87 ++++++ .../HighResFix/ParamHrfStrength.tsx | 64 +++++ .../Parameters/HighResFix/ParamHrfToggle.tsx | 27 ++ .../Parameters/HighResFix/ParamHrfWidth.tsx | 84 ++++++ .../parameters/store/generationSlice.ts | 24 ++ .../parameters/types/parameterSchemas.ts | 9 + .../src/features/system/store/configSlice.ts | 8 + .../TextToImage/TextToImageTabParameters.tsx | 2 + .../frontend/web/src/services/api/schema.d.ts | 15 ++ .../frontend/web/src/services/api/types.ts | 1 + 18 files changed, 683 insertions(+), 1 deletion(-) create mode 100644 invokeai/frontend/web/src/features/nodes/util/graphBuilders/addHrfToGraph.ts create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfCollapse.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfHeight.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfStrength.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfToggle.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfWidth.tsx diff --git a/invokeai/app/invocations/metadata.py b/invokeai/app/invocations/metadata.py index 54e385feb3..cecc01096a 100644 --- a/invokeai/app/invocations/metadata.py +++ b/invokeai/app/invocations/metadata.py @@ -156,6 +156,18 @@ class MetadataAccumulatorInvocation(BaseInvocation): description="The VAE used for decoding, if the main model's default was not used", ) + # High resolution fix metadata. + hrf_width: Optional[int] = InputField( + description="The high resolution fix height and width multipler.", + ) + hrf_height: Optional[int] = InputField( + description="The high resolution fix height and width multipler.", + ) + hrf_strength: Optional[float] = InputField( + default=None, + description="The high resolution fix img2img strength used in the upscale pass.", + ) + # SDXL positive_style_prompt: Optional[str] = InputField( default=None, diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 8331e9c979..39e4ffd27a 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -39,7 +39,8 @@ export type SDFeature = | 'hires' | 'lora' | 'embedding' - | 'vae'; + | 'vae' + | 'hrf'; /** * Configuration options for the InvokeAI UI. @@ -110,6 +111,14 @@ export type AppConfig = { fineStep: number; coarseStep: number; }; + hrfStrength: { + initial: number; + min: number; + sliderMax: number; + inputMax: number; + fineStep: number; + coarseStep: number; + }; dynamicPrompts: { maxPrompts: { initial: number; diff --git a/invokeai/frontend/web/src/common/components/IAIInformationalPopover/constants.ts b/invokeai/frontend/web/src/common/components/IAIInformationalPopover/constants.ts index f2398483bf..197f5f4068 100644 --- a/invokeai/frontend/web/src/common/components/IAIInformationalPopover/constants.ts +++ b/invokeai/frontend/web/src/common/components/IAIInformationalPopover/constants.ts @@ -2,6 +2,7 @@ import { PopoverProps } from '@chakra-ui/react'; export type Feature = | 'clipSkip' + | 'hrf' | 'paramNegativeConditioning' | 'paramPositiveConditioning' | 'paramScheduler' diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addHrfToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addHrfToGraph.ts new file mode 100644 index 0000000000..4b4a8a8a03 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addHrfToGraph.ts @@ -0,0 +1,247 @@ +import { RootState } from 'app/store/store'; +import { NonNullableGraph } from 'features/nodes/types/types'; +import { + DenoiseLatentsInvocation, + ResizeLatentsInvocation, + NoiseInvocation, + LatentsToImageInvocation, + Edge, +} from 'services/api/types'; +import { + LATENTS_TO_IMAGE, + DENOISE_LATENTS, + NOISE, + MAIN_MODEL_LOADER, + METADATA_ACCUMULATOR, + LATENTS_TO_IMAGE_HRF, + DENOISE_LATENTS_HRF, + RESCALE_LATENTS, + NOISE_HRF, + VAE_LOADER, +} from './constants'; +import { logger } from 'app/logging/logger'; + +// Copy certain connections from previous DENOISE_LATENTS to new DENOISE_LATENTS_HRF. +function copyConnectionsToDenoiseLatentsHrf(graph: NonNullableGraph): void { + const destinationFields = [ + 'vae', + 'control', + 'ip_adapter', + 'metadata', + 'unet', + 'positive_conditioning', + 'negative_conditioning', + ]; + const newEdges: Edge[] = []; + + // Loop through the existing edges connected to DENOISE_LATENTS + graph.edges.forEach((edge: Edge) => { + if ( + edge.destination.node_id === DENOISE_LATENTS && + destinationFields.includes(edge.destination.field) + ) { + // Add a similar connection to DENOISE_LATENTS_HRF + newEdges.push({ + source: { + node_id: edge.source.node_id, + field: edge.source.field, + }, + destination: { + node_id: DENOISE_LATENTS_HRF, + field: edge.destination.field, + }, + }); + } + }); + graph.edges = graph.edges.concat(newEdges); +} + +// Adds the high-res fix feature to the given graph. +export const addHrfToGraph = ( + state: RootState, + graph: NonNullableGraph +): void => { + // Double check hrf is enabled. + if ( + !state.generation.hrfEnabled || + state.config.disabledSDFeatures.includes('hrf') || + state.generation.model?.model_type === 'onnx' // TODO: ONNX support + ) { + return; + } + const log = logger('txt2img'); + + const { vae } = state.generation; + const isAutoVae = !vae; + const hrfWidth = state.generation.hrfWidth; + const hrfHeight = state.generation.hrfHeight; + + // Pre-existing (original) graph nodes. + const originalDenoiseLatentsNode = graph.nodes[DENOISE_LATENTS] as + | DenoiseLatentsInvocation + | undefined; + const originalNoiseNode = graph.nodes[NOISE] as NoiseInvocation | undefined; + // Original latents to image should pick this up. + const originalLatentsToImageNode = graph.nodes[LATENTS_TO_IMAGE] as + | LatentsToImageInvocation + | undefined; + // Check if originalDenoiseLatentsNode is undefined and log an error + if (!originalDenoiseLatentsNode) { + log.error('originalDenoiseLatentsNode is undefined'); + return; + } + // Check if originalNoiseNode is undefined and log an error + if (!originalNoiseNode) { + log.error('originalNoiseNode is undefined'); + return; + } + + // Check if originalLatentsToImageNode is undefined and log an error + if (!originalLatentsToImageNode) { + log.error('originalLatentsToImageNode is undefined'); + return; + } + // Change height and width of original noise node to initial resolution. + if (originalNoiseNode) { + originalNoiseNode.width = hrfWidth; + originalNoiseNode.height = hrfHeight; + } + + // Define new nodes. + // Denoise latents node to be run on upscaled latents. + const denoiseLatentsHrfNode: DenoiseLatentsInvocation = { + type: 'denoise_latents', + id: DENOISE_LATENTS_HRF, + is_intermediate: originalDenoiseLatentsNode?.is_intermediate, + cfg_scale: originalDenoiseLatentsNode?.cfg_scale, + scheduler: originalDenoiseLatentsNode?.scheduler, + steps: originalDenoiseLatentsNode?.steps, + denoising_start: 1 - state.generation.hrfStrength, + denoising_end: 1, + }; + + // New base resolution noise node. + const hrfNoiseNode: NoiseInvocation = { + type: 'noise', + id: NOISE_HRF, + seed: originalNoiseNode?.seed, + use_cpu: originalNoiseNode?.use_cpu, + is_intermediate: originalNoiseNode?.is_intermediate, + }; + + const rescaleLatentsNode: ResizeLatentsInvocation = { + id: RESCALE_LATENTS, + type: 'lresize', + width: state.generation.width, + height: state.generation.height, + }; + + // New node to convert latents to image. + const latentsToImageHrfNode: LatentsToImageInvocation | undefined = + originalLatentsToImageNode + ? { + type: 'l2i', + id: LATENTS_TO_IMAGE_HRF, + fp32: originalLatentsToImageNode?.fp32, + is_intermediate: originalLatentsToImageNode?.is_intermediate, + } + : undefined; + + // Add new nodes to graph. + graph.nodes[LATENTS_TO_IMAGE_HRF] = + latentsToImageHrfNode as LatentsToImageInvocation; + graph.nodes[DENOISE_LATENTS_HRF] = + denoiseLatentsHrfNode as DenoiseLatentsInvocation; + graph.nodes[NOISE_HRF] = hrfNoiseNode as NoiseInvocation; + graph.nodes[RESCALE_LATENTS] = rescaleLatentsNode as ResizeLatentsInvocation; + + // Connect nodes. + graph.edges.push( + { + // Set up rescale latents. + source: { + node_id: DENOISE_LATENTS, + field: 'latents', + }, + destination: { + node_id: RESCALE_LATENTS, + field: 'latents', + }, + }, + // Set up new noise node + { + source: { + node_id: RESCALE_LATENTS, + field: 'height', + }, + destination: { + node_id: NOISE_HRF, + field: 'height', + }, + }, + { + source: { + node_id: RESCALE_LATENTS, + field: 'width', + }, + destination: { + node_id: NOISE_HRF, + field: 'width', + }, + }, + // Set up new denoise node. + { + source: { + node_id: RESCALE_LATENTS, + field: 'latents', + }, + destination: { + node_id: DENOISE_LATENTS_HRF, + field: 'latents', + }, + }, + { + source: { + node_id: NOISE_HRF, + field: 'noise', + }, + destination: { + node_id: DENOISE_LATENTS_HRF, + field: 'noise', + }, + }, + // Set up new latents to image node. + { + source: { + node_id: DENOISE_LATENTS_HRF, + field: 'latents', + }, + destination: { + node_id: LATENTS_TO_IMAGE_HRF, + field: 'latents', + }, + }, + { + source: { + node_id: METADATA_ACCUMULATOR, + field: 'metadata', + }, + destination: { + node_id: LATENTS_TO_IMAGE_HRF, + field: 'metadata', + }, + }, + { + source: { + node_id: isAutoVae ? MAIN_MODEL_LOADER : VAE_LOADER, + field: 'vae', + }, + destination: { + node_id: LATENTS_TO_IMAGE_HRF, + field: 'vae', + }, + } + ); + + copyConnectionsToDenoiseLatentsHrf(graph); +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addSaveImageNode.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addSaveImageNode.ts index 738c69faff..d5a6addf8a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addSaveImageNode.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addSaveImageNode.ts @@ -2,6 +2,7 @@ import { NonNullableGraph } from 'features/nodes/types/types'; import { CANVAS_OUTPUT, LATENTS_TO_IMAGE, + LATENTS_TO_IMAGE_HRF, METADATA_ACCUMULATOR, NSFW_CHECKER, SAVE_IMAGE, @@ -82,6 +83,14 @@ export const addSaveImageNode = ( }, destination, }); + } else if (LATENTS_TO_IMAGE_HRF in graph.nodes) { + graph.edges.push({ + source: { + node_id: LATENTS_TO_IMAGE_HRF, + field: 'image', + }, + destination, + }); } else if (LATENTS_TO_IMAGE in graph.nodes) { graph.edges.push({ source: { diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts index c3c7fcdf1b..e692e12fa4 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts @@ -6,6 +6,7 @@ import { ONNXTextToLatentsInvocation, } from 'services/api/types'; import { addControlNetToLinearGraph } from './addControlNetToLinearGraph'; +import { addHrfToGraph } from './addHrfToGraph'; import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph'; import { addLoRAsToGraph } from './addLoRAsToGraph'; import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph'; @@ -47,6 +48,10 @@ export const buildLinearTextToImageGraph = ( seamlessXAxis, seamlessYAxis, seed, + hrfWidth, + hrfHeight, + hrfStrength, + hrfEnabled: hrfEnabled, } = state.generation; const use_cpu = shouldUseCpuNoise; @@ -254,6 +259,9 @@ export const buildLinearTextToImageGraph = ( ipAdapters: [], // populated in addIPAdapterToLinearGraph t2iAdapters: [], // populated in addT2IAdapterToLinearGraph clip_skip: clipSkip, + hrf_width: hrfEnabled ? hrfWidth : undefined, + hrf_height: hrfEnabled ? hrfHeight : undefined, + hrf_strength: hrfEnabled ? hrfStrength : undefined, }; graph.edges.push({ @@ -287,6 +295,12 @@ export const buildLinearTextToImageGraph = ( addT2IAdaptersToLinearGraph(state, graph, DENOISE_LATENTS); + // High resolution fix. + // NOTE: Not supported for onnx models. + if (state.generation.hrfEnabled && !isUsingOnnxModel) { + addHrfToGraph(state, graph); + } + // NSFW & watermark - must be last thing added to graph if (state.system.shouldUseNSFWChecker) { // must add before watermarker! diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts index e538094731..7d547d09e6 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/constants.ts @@ -2,11 +2,14 @@ export const POSITIVE_CONDITIONING = 'positive_conditioning'; export const NEGATIVE_CONDITIONING = 'negative_conditioning'; export const DENOISE_LATENTS = 'denoise_latents'; +export const DENOISE_LATENTS_HRF = 'denoise_latents_hrf'; export const LATENTS_TO_IMAGE = 'latents_to_image'; +export const LATENTS_TO_IMAGE_HRF = 'latents_to_image_hrf'; export const SAVE_IMAGE = 'save_image'; export const NSFW_CHECKER = 'nsfw_checker'; export const WATERMARKER = 'invisible_watermark'; export const NOISE = 'noise'; +export const NOISE_HRF = 'noise_hrf'; export const RANDOM_INT = 'rand_int'; export const RANGE_OF_SIZE = 'range_of_size'; export const ITERATE = 'iterate'; @@ -18,6 +21,7 @@ export const CLIP_SKIP = 'clip_skip'; export const IMAGE_TO_LATENTS = 'image_to_latents'; export const LATENTS_TO_LATENTS = 'latents_to_latents'; export const RESIZE = 'resize_image'; +export const RESCALE_LATENTS = 'rescale_latents'; export const IMG2IMG_RESIZE = 'img2img_resize'; export const CANVAS_OUTPUT = 'canvas_output'; export const INPAINT_IMAGE = 'inpaint_image'; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfCollapse.tsx new file mode 100644 index 0000000000..ef0db1af6d --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfCollapse.tsx @@ -0,0 +1,65 @@ +import { Flex } from '@chakra-ui/react'; +import { createSelector } from '@reduxjs/toolkit'; +import { RootState, stateSelector } from 'app/store/store'; +import { useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAICollapse from 'common/components/IAICollapse'; +import { useMemo } from 'react'; +import ParamHrfStrength from './ParamHrfStrength'; +import ParamHrfToggle from './ParamHrfToggle'; +import ParamHrfWidth from './ParamHrfWidth'; +import ParamHrfHeight from './ParamHrfHeight'; +import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; + +const selector = createSelector( + stateSelector, + (state: RootState) => { + const { hrfEnabled } = state.generation; + + return { hrfEnabled }; + }, + defaultSelectorOptions +); + +export default function ParamHrfCollapse() { + const isHRFFeatureEnabled = useFeatureStatus('hrf').isFeatureEnabled; + const { hrfEnabled } = useAppSelector(selector); + const activeLabel = useMemo(() => { + if (hrfEnabled) { + return 'On'; + } else { + return 'Off'; + } + }, [hrfEnabled]); + + if (!isHRFFeatureEnabled) { + return null; + } + + return ( + + + + {hrfEnabled && ( + + + + + )} + {hrfEnabled && } + + + ); +} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfHeight.tsx new file mode 100644 index 0000000000..053da11a5a --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfHeight.tsx @@ -0,0 +1,87 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAISlider, { IAIFullSliderProps } from 'common/components/IAISlider'; +import { roundToMultiple } from 'common/util/roundDownToMultiple'; +import { + setHrfHeight, + setHrfWidth, +} from 'features/parameters/store/generationSlice'; +import { memo, useCallback } from 'react'; + +function findPrevMultipleOfEight(n: number): number { + return Math.floor((n - 1) / 8) * 8; +} + +const selector = createSelector( + [stateSelector], + ({ generation, hotkeys, config }) => { + const { min, fineStep, coarseStep } = config.sd.height; + const { model, height, hrfHeight, aspectRatio, hrfEnabled } = generation; + + const step = hotkeys.shift ? fineStep : coarseStep; + + return { + model, + height, + hrfHeight, + min, + step, + aspectRatio, + hrfEnabled, + }; + }, + defaultSelectorOptions +); + +type ParamHeightProps = Omit< + IAIFullSliderProps, + 'label' | 'value' | 'onChange' +>; + +const ParamHrfHeight = (props: ParamHeightProps) => { + const { height, hrfHeight, min, step, aspectRatio, hrfEnabled } = + useAppSelector(selector); + const dispatch = useAppDispatch(); + const maxHrfHeight = Math.max(findPrevMultipleOfEight(height), min); + + const handleChange = useCallback( + (v: number) => { + dispatch(setHrfHeight(v)); + if (aspectRatio) { + const newWidth = roundToMultiple(v * aspectRatio, 8); + dispatch(setHrfWidth(newWidth)); + } + }, + [dispatch, aspectRatio] + ); + + const handleReset = useCallback(() => { + dispatch(setHrfHeight(maxHrfHeight)); + if (aspectRatio) { + const newWidth = roundToMultiple(maxHrfHeight * aspectRatio, 8); + dispatch(setHrfWidth(newWidth)); + } + }, [dispatch, maxHrfHeight, aspectRatio]); + + return ( + + ); +}; + +export default memo(ParamHrfHeight); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfStrength.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfStrength.tsx new file mode 100644 index 0000000000..ba17bc2f36 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfStrength.tsx @@ -0,0 +1,64 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { useAppSelector, useAppDispatch } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { memo, useCallback } from 'react'; +import { stateSelector } from 'app/store/store'; +import { setHrfStrength } from 'features/parameters/store/generationSlice'; +import IAISlider from 'common/components/IAISlider'; + +const selector = createSelector( + [stateSelector], + ({ generation, hotkeys, config }) => { + const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = + config.sd.hrfStrength; + const { hrfStrength, hrfEnabled } = generation; + const step = hotkeys.shift ? fineStep : coarseStep; + + return { + hrfStrength, + initial, + min, + sliderMax, + inputMax, + step, + hrfEnabled, + }; + }, + defaultSelectorOptions +); + +const ParamHrfStrength = () => { + const { hrfStrength, initial, min, sliderMax, step, hrfEnabled } = + useAppSelector(selector); + const dispatch = useAppDispatch(); + + const handleHrfStrengthReset = useCallback(() => { + dispatch(setHrfStrength(initial)); + }, [dispatch, initial]); + + const handleHrfStrengthChange = useCallback( + (v: number) => { + dispatch(setHrfStrength(v)); + }, + [dispatch] + ); + + return ( + + ); +}; + +export default memo(ParamHrfStrength); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfToggle.tsx new file mode 100644 index 0000000000..8a68ead652 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfToggle.tsx @@ -0,0 +1,27 @@ +import { RootState } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAISwitch from 'common/components/IAISwitch'; +import { setHrfEnabled } from 'features/parameters/store/generationSlice'; +import { ChangeEvent, useCallback } from 'react'; + +export default function ParamHrfToggle() { + const dispatch = useAppDispatch(); + + const hrfEnabled = useAppSelector( + (state: RootState) => state.generation.hrfEnabled + ); + + const handleHrfEnabled = useCallback( + (e: ChangeEvent) => + dispatch(setHrfEnabled(e.target.checked)), + [dispatch] + ); + + return ( + + ); +} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfWidth.tsx new file mode 100644 index 0000000000..09615d5154 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/HighResFix/ParamHrfWidth.tsx @@ -0,0 +1,84 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAISlider, { IAIFullSliderProps } from 'common/components/IAISlider'; +import { roundToMultiple } from 'common/util/roundDownToMultiple'; +import { + setHrfHeight, + setHrfWidth, +} from 'features/parameters/store/generationSlice'; +import { memo, useCallback } from 'react'; + +function findPrevMultipleOfEight(n: number): number { + return Math.floor((n - 1) / 8) * 8; +} + +const selector = createSelector( + [stateSelector], + ({ generation, hotkeys, config }) => { + const { min, fineStep, coarseStep } = config.sd.width; + const { model, width, hrfWidth, aspectRatio, hrfEnabled } = generation; + + const step = hotkeys.shift ? fineStep : coarseStep; + + return { + model, + width, + hrfWidth, + min, + step, + aspectRatio, + hrfEnabled, + }; + }, + defaultSelectorOptions +); + +type ParamWidthProps = Omit; + +const ParamHrfWidth = (props: ParamWidthProps) => { + const { width, hrfWidth, min, step, aspectRatio, hrfEnabled } = + useAppSelector(selector); + const dispatch = useAppDispatch(); + const maxHrfWidth = Math.max(findPrevMultipleOfEight(width), min); + + const handleChange = useCallback( + (v: number) => { + dispatch(setHrfWidth(v)); + if (aspectRatio) { + const newHeight = roundToMultiple(v / aspectRatio, 8); + dispatch(setHrfHeight(newHeight)); + } + }, + [dispatch, aspectRatio] + ); + + const handleReset = useCallback(() => { + dispatch(setHrfWidth(maxHrfWidth)); + if (aspectRatio) { + const newHeight = roundToMultiple(maxHrfWidth / aspectRatio, 8); + dispatch(setHrfHeight(newHeight)); + } + }, [dispatch, maxHrfWidth, aspectRatio]); + + return ( + + ); +}; + +export default memo(ParamHrfWidth); diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 84d26b6476..63d545662a 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -27,6 +27,10 @@ import { } from '../types/parameterSchemas'; export interface GenerationState { + hrfHeight: HeightParam; + hrfWidth: WidthParam; + hrfEnabled: boolean; + hrfStrength: StrengthParam; cfgScale: CfgScaleParam; height: HeightParam; img2imgStrength: StrengthParam; @@ -69,6 +73,10 @@ export interface GenerationState { } export const initialGenerationState: GenerationState = { + hrfHeight: 64, + hrfWidth: 64, + hrfStrength: 0.75, + hrfEnabled: false, cfgScale: 7.5, height: 512, img2imgStrength: 0.75, @@ -271,6 +279,18 @@ export const generationSlice = createSlice({ setClipSkip: (state, action: PayloadAction) => { state.clipSkip = action.payload; }, + setHrfHeight: (state, action: PayloadAction) => { + state.hrfHeight = action.payload; + }, + setHrfWidth: (state, action: PayloadAction) => { + state.hrfWidth = action.payload; + }, + setHrfStrength: (state, action: PayloadAction) => { + state.hrfStrength = action.payload; + }, + setHrfEnabled: (state, action: PayloadAction) => { + state.hrfEnabled = action.payload; + }, shouldUseCpuNoiseChanged: (state, action: PayloadAction) => { state.shouldUseCpuNoise = action.payload; }, @@ -355,6 +375,10 @@ export const { setSeamlessXAxis, setSeamlessYAxis, setClipSkip, + setHrfHeight, + setHrfWidth, + setHrfStrength, + setHrfEnabled, shouldUseCpuNoiseChanged, setAspectRatio, setShouldLockAspectRatio, diff --git a/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts b/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts index e548b9aeaf..7a6b6b8a5c 100644 --- a/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts +++ b/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts @@ -210,6 +210,15 @@ export type HeightParam = z.infer; export const isValidHeight = (val: unknown): val is HeightParam => zHeight.safeParse(val).success; +/** + * Zod schema for resolution parameter + */ +export const zResolution = z.tuple([zWidth, zHeight]); +/** + * Type alias for resolution parameter, inferred from its zod schema + */ +export type ResolutionParam = z.infer; + export const zBaseModel = z.enum([ 'any', 'sd-1', diff --git a/invokeai/frontend/web/src/features/system/store/configSlice.ts b/invokeai/frontend/web/src/features/system/store/configSlice.ts index 30eebab5d5..78d540203b 100644 --- a/invokeai/frontend/web/src/features/system/store/configSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/configSlice.ts @@ -69,6 +69,14 @@ export const initialConfigState: AppConfig = { fineStep: 0.01, coarseStep: 0.05, }, + hrfStrength: { + initial: 0.7, + min: 0, + sliderMax: 1, + inputMax: 1, + fineStep: 0.01, + coarseStep: 0.05, + }, dynamicPrompts: { maxPrompts: { initial: 100, diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabParameters.tsx index 0c153fd7eb..8ec8c9caed 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabParameters.tsx @@ -3,6 +3,7 @@ import ParamLoraCollapse from 'features/lora/components/ParamLoraCollapse'; import ParamAdvancedCollapse from 'features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse'; import ControlAdaptersCollapse from 'features/controlAdapters/components/ControlAdaptersCollapse'; import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse'; +import ParamHrfCollapse from 'features/parameters/components/Parameters/HighResFix/ParamHrfCollapse'; import { memo } from 'react'; import ParamPromptArea from '../../../../parameters/components/Parameters/Prompt/ParamPromptArea'; import TextToImageTabCoreParameters from './TextToImageTabCoreParameters'; @@ -16,6 +17,7 @@ const TextToImageTabParameters = () => { + ); diff --git a/invokeai/frontend/web/src/services/api/schema.d.ts b/invokeai/frontend/web/src/services/api/schema.d.ts index 6da1fa5154..d71d01e6af 100644 --- a/invokeai/frontend/web/src/services/api/schema.d.ts +++ b/invokeai/frontend/web/src/services/api/schema.d.ts @@ -6190,6 +6190,21 @@ export type components = { * @description The VAE used for decoding, if the main model's default was not used */ vae?: components["schemas"]["VAEModelField"]; + /** + * High Resolution Fix Width + * @description The high resolution fix height and width multiplier. + */ + hrf_width?: number; + /** + * High Resolution Fix Height + * @description The high resolution fix height and width multiplier. + */ + hrf_height?: number; + /** + * High Resolution Strength + * @description The high resolution fix img2img strength used in the upscale pass. + */ + hrf_strength?: number; /** * Positive Style Prompt * @description The positive style prompt parameter diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index da56d2782f..6fda849b89 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -137,6 +137,7 @@ export type CompelInvocation = s['CompelInvocation']; export type DynamicPromptInvocation = s['DynamicPromptInvocation']; export type NoiseInvocation = s['NoiseInvocation']; export type DenoiseLatentsInvocation = s['DenoiseLatentsInvocation']; +export type ResizeLatentsInvocation = s['ResizeLatentsInvocation']; export type ONNXTextToLatentsInvocation = s['ONNXTextToLatentsInvocation']; export type SDXLLoraLoaderInvocation = s['SDXLLoraLoaderInvocation']; export type ImageToLatentsInvocation = s['ImageToLatentsInvocation'];