From fc966ceeef931579a0cb6ec68fdae69161d15dee Mon Sep 17 00:00:00 2001 From: Jorge Segnini Cubero <47682209+Jorge0998@users.noreply.github.com> Date: Thu, 30 Mar 2023 07:03:01 -0600 Subject: [PATCH 01/55] UI work in progress --- frontends/krita/krita_diff/__init__.py | 9 ++ frontends/krita/krita_diff/defaults.py | 15 ++ frontends/krita/krita_diff/pages/__init__.py | 1 + .../krita/krita_diff/pages/controlnet_base.py | 129 ++++++++++++++++++ .../krita/krita_diff/widgets/__init__.py | 1 + .../krita/krita_diff/widgets/image_loader.py | 39 ++++++ 6 files changed, 194 insertions(+) create mode 100644 frontends/krita/krita_diff/pages/controlnet_base.py create mode 100644 frontends/krita/krita_diff/widgets/image_loader.py diff --git a/frontends/krita/krita_diff/__init__.py b/frontends/krita/krita_diff/__init__.py index 42af00ed..e47e70cf 100644 --- a/frontends/krita/krita_diff/__init__.py +++ b/frontends/krita/krita_diff/__init__.py @@ -8,6 +8,7 @@ TAB_SDCOMMON, TAB_TXT2IMG, TAB_UPSCALE, + TAB_CONTROLNET ) from .docker import create_docker from .extension import SDPluginExtension @@ -18,6 +19,7 @@ SDCommonPage, Txt2ImgPage, UpscalePage, + ControlNetPageBase ) from .pages.preview import PreviewPage from .script import script @@ -60,6 +62,13 @@ create_docker(UpscalePage), ) ) +instance.addDockWidgetFactory( + DockWidgetFactory( + TAB_CONTROLNET, + DockWidgetFactoryBase.DockLeft, + create_docker(ControlNetPageBase), + ) +) instance.addDockWidgetFactory( DockWidgetFactory( TAB_CONFIG, diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index 02508df2..6b8b0dbf 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -39,6 +39,7 @@ TAB_IMG2IMG = "krita_diff_img2img" TAB_INPAINT = "krita_diff_inpaint" TAB_UPSCALE = "krita_diff_upscale" +TAB_CONTROLNET = "krita_diff_controlnet" TAB_PREVIEW = "krita_diff_preview" @@ -128,5 +129,19 @@ class Defaults: upscale_upscaler_name: str = "None" upscale_downscale_first: bool = False + controlnet_units: int = 1 + controlnet0_enable: bool = False + controlnet0_use_selection_as_input: bool = True + controlnet0_invert_input_color: bool = False + controlnet0_RGB_to_BGR: bool = False + controlnet0_low_vram: bool = False + controlnet0_guess_mode: bool = False + controlnet0_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet0_preprocessor: str = "None" + controlnet0_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet0_model: str = "None" + controlnet0_weight: float = 1.0 + controlnet0_guidance_start: float = 0 + controlnet0_guidance_end: float = 1 DEFAULTS = Defaults() diff --git a/frontends/krita/krita_diff/pages/__init__.py b/frontends/krita/krita_diff/pages/__init__.py index aba4901a..bc26fa1c 100644 --- a/frontends/krita/krita_diff/pages/__init__.py +++ b/frontends/krita/krita_diff/pages/__init__.py @@ -5,3 +5,4 @@ from .preview import PreviewPage from .txt2img import Txt2ImgPage from .upscale import UpscalePage +from .controlnet_base import ControlNetPageBase diff --git a/frontends/krita/krita_diff/pages/controlnet_base.py b/frontends/krita/krita_diff/pages/controlnet_base.py new file mode 100644 index 00000000..9a88b644 --- /dev/null +++ b/frontends/krita/krita_diff/pages/controlnet_base.py @@ -0,0 +1,129 @@ +from krita import QWidget, QVBoxLayout, QHBoxLayout + +from ..script import script +from ..widgets import StatusBar, ImageLoader, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout + +class ControlNetPageBase(QWidget): + name = "ControlNet" + + def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): + super(ControlNetPageBase, self).__init__(*args, **kwargs) + self.status_bar = StatusBar() + + #Top checkbox + self.enable = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_enable", "Enable ControlNet" + ) + self.use_selection_as_input = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_use_selection_as_input", "Use selection as input" + ) + + self.image_loader = ImageLoader() + + #Main settings + self.invert_input_color = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_invert_input_color", "Invert input color" + ) + self.RGB_to_BGR = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_RGB_to_BGR", "RGB to BGR" + ) + self.low_vram = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_low_vram", "Low VRAM" + ) + self.guess_mode = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_guess_mode", "Guess mode" + ) + + #Tips + self.tips = TipsLayout( + ["Invert colors if your image has white background."] + ) + + #Preprocessor list + self.preprocessor_layout = QComboBoxLayout( + script.cfg, + f"controlnet{cfg_unit_number}_preprocessor_list", + f"controlnet{cfg_unit_number}_preprocessor", + label="Preprocessor:" + ) + + #Model list + self.model_layout = QComboBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" + ) + + self.weight_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_weight", label="Weight:", min=0, max=2, step=0.05 + ) + + self.guidance_start_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_guidance_start", label="Guidance start:", min=0, max=1, step=0.01 + ) + + self.guidance_end_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_guidance_end", label="Guidance end:", min=0, max=1, step=0.01 + ) + + top_checkbox_layout = QHBoxLayout() + top_checkbox_layout.addWidget(self.enable) + top_checkbox_layout.addWidget(self.use_selection_as_input) + + main_settings_layout = QHBoxLayout() + main_settings_layout.addWidget(self.invert_input_color) + main_settings_layout.addWidget(self.RGB_to_BGR) + main_settings_layout.addWidget(self.low_vram) + main_settings_layout.addWidget(self.guess_mode) + + guidance_layout = QHBoxLayout() + guidance_layout.addLayout(self.weight_layout) + guidance_layout.addLayout(self.guidance_start_layout) + guidance_layout.addLayout(self.guidance_end_layout) + + layout = QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.status_bar) + layout.addLayout(top_checkbox_layout) + layout.addWidget(self.image_loader) + layout.addLayout(self.tips) + layout.addLayout(main_settings_layout) + layout.addLayout(self.preprocessor_layout) + layout.addLayout(self.model_layout) + layout.addLayout(guidance_layout) + layout.addStretch() + self.setLayout(layout) + + def change_image_loader_state(self, state): + if state == 1 or state == 2: + self.image_loader.setEnabled(False) + else: + self.image_loader.setEnabled(True) + + def cfg_init(self): + self.enable.cfg_init() + self.use_selection_as_input.cfg_init() + self.invert_input_color.cfg_init() + self.RGB_to_BGR.cfg_init() + self.low_vram.cfg_init() + self.guess_mode.cfg_init() + self.preprocessor_layout.cfg_init() + self.model_layout.cfg_init() + self.weight_layout.cfg_init() + self.guidance_start_layout.cfg_init() + self.guidance_end_layout.cfg_init() + self.tips.setVisible(not script.cfg("minimize_ui", bool)) + self.change_image_loader_state(self.use_selection_as_input.checkState()) + + def cfg_connect(self): + self.enable.cfg_connect() + self.use_selection_as_input.cfg_connect() + self.invert_input_color.cfg_connect() + self.RGB_to_BGR.cfg_connect() + self.low_vram.cfg_connect() + self.guess_mode.cfg_connect() + self.preprocessor_layout.cfg_connect() + self.model_layout.cfg_connect() + self.weight_layout.cfg_connect() + self.guidance_start_layout.cfg_connect() + self.guidance_end_layout.cfg_connect() + self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) + script.status_changed.connect(lambda s: self.status_bar.set_status(s)) \ No newline at end of file diff --git a/frontends/krita/krita_diff/widgets/__init__.py b/frontends/krita/krita_diff/widgets/__init__.py index bb46b96d..ca7138b9 100644 --- a/frontends/krita/krita_diff/widgets/__init__.py +++ b/frontends/krita/krita_diff/widgets/__init__.py @@ -6,3 +6,4 @@ from .spin_box import QSpinBoxLayout from .status_bar import StatusBar from .tips import TipsLayout +from .image_loader import ImageLoader diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py new file mode 100644 index 00000000..96ed2fb7 --- /dev/null +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -0,0 +1,39 @@ +from krita import QWidget, QFileDialog, QPixmap, QPushButton, QVBoxLayout, QHBoxLayout, Qt +from ..widgets import QLabel + +class ImageLoader(QWidget): + def __init__(self, *args, **kwargs): + super(ImageLoader, self).__init__(*args, **kwargs) + + self.preview = QLabel() + self.preview.setAlignment(Qt.AlignCenter) + self.importBtn = QPushButton('Import image') + self.clearBtn = QPushButton('Clear') + + btnLayout = QHBoxLayout() + btnLayout.addWidget(self.importBtn) + btnLayout.addWidget(self.clearBtn) + + layout = QVBoxLayout() + layout.addLayout(btnLayout) + layout.addWidget(self.preview) + self.setLayout(layout) + + self.importBtn.released.connect(self.load_image) + self.clearBtn.released.connect(self.clear_image) + + def load_image(self): + file_name, _ = QFileDialog.getOpenFileName(self, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') + if file_name: + pixmap = QPixmap(file_name) + + if pixmap.width() > self.preview.width(): + pixmap = pixmap.scaledToWidth(self.preview.width(), Qt.SmoothTransformation) + + self.preview.setPixmap(pixmap) + + def clear_image(self): + self.preview.setPixmap(QPixmap()) + + + \ No newline at end of file From 3dccbec91ef0f78377cd07283f37df5abcec3d62 Mon Sep 17 00:00:00 2001 From: Jason Segnini Date: Thu, 30 Mar 2023 07:03:01 -0600 Subject: [PATCH 02/55] UI work in progress --- frontends/krita/krita_diff/__init__.py | 9 ++ frontends/krita/krita_diff/defaults.py | 15 ++ frontends/krita/krita_diff/pages/__init__.py | 1 + .../krita/krita_diff/pages/controlnet_base.py | 129 ++++++++++++++++++ .../krita/krita_diff/widgets/__init__.py | 1 + .../krita/krita_diff/widgets/image_loader.py | 39 ++++++ 6 files changed, 194 insertions(+) create mode 100644 frontends/krita/krita_diff/pages/controlnet_base.py create mode 100644 frontends/krita/krita_diff/widgets/image_loader.py diff --git a/frontends/krita/krita_diff/__init__.py b/frontends/krita/krita_diff/__init__.py index 42af00ed..e47e70cf 100644 --- a/frontends/krita/krita_diff/__init__.py +++ b/frontends/krita/krita_diff/__init__.py @@ -8,6 +8,7 @@ TAB_SDCOMMON, TAB_TXT2IMG, TAB_UPSCALE, + TAB_CONTROLNET ) from .docker import create_docker from .extension import SDPluginExtension @@ -18,6 +19,7 @@ SDCommonPage, Txt2ImgPage, UpscalePage, + ControlNetPageBase ) from .pages.preview import PreviewPage from .script import script @@ -60,6 +62,13 @@ create_docker(UpscalePage), ) ) +instance.addDockWidgetFactory( + DockWidgetFactory( + TAB_CONTROLNET, + DockWidgetFactoryBase.DockLeft, + create_docker(ControlNetPageBase), + ) +) instance.addDockWidgetFactory( DockWidgetFactory( TAB_CONFIG, diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index 02508df2..6b8b0dbf 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -39,6 +39,7 @@ TAB_IMG2IMG = "krita_diff_img2img" TAB_INPAINT = "krita_diff_inpaint" TAB_UPSCALE = "krita_diff_upscale" +TAB_CONTROLNET = "krita_diff_controlnet" TAB_PREVIEW = "krita_diff_preview" @@ -128,5 +129,19 @@ class Defaults: upscale_upscaler_name: str = "None" upscale_downscale_first: bool = False + controlnet_units: int = 1 + controlnet0_enable: bool = False + controlnet0_use_selection_as_input: bool = True + controlnet0_invert_input_color: bool = False + controlnet0_RGB_to_BGR: bool = False + controlnet0_low_vram: bool = False + controlnet0_guess_mode: bool = False + controlnet0_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet0_preprocessor: str = "None" + controlnet0_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet0_model: str = "None" + controlnet0_weight: float = 1.0 + controlnet0_guidance_start: float = 0 + controlnet0_guidance_end: float = 1 DEFAULTS = Defaults() diff --git a/frontends/krita/krita_diff/pages/__init__.py b/frontends/krita/krita_diff/pages/__init__.py index aba4901a..bc26fa1c 100644 --- a/frontends/krita/krita_diff/pages/__init__.py +++ b/frontends/krita/krita_diff/pages/__init__.py @@ -5,3 +5,4 @@ from .preview import PreviewPage from .txt2img import Txt2ImgPage from .upscale import UpscalePage +from .controlnet_base import ControlNetPageBase diff --git a/frontends/krita/krita_diff/pages/controlnet_base.py b/frontends/krita/krita_diff/pages/controlnet_base.py new file mode 100644 index 00000000..9a88b644 --- /dev/null +++ b/frontends/krita/krita_diff/pages/controlnet_base.py @@ -0,0 +1,129 @@ +from krita import QWidget, QVBoxLayout, QHBoxLayout + +from ..script import script +from ..widgets import StatusBar, ImageLoader, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout + +class ControlNetPageBase(QWidget): + name = "ControlNet" + + def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): + super(ControlNetPageBase, self).__init__(*args, **kwargs) + self.status_bar = StatusBar() + + #Top checkbox + self.enable = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_enable", "Enable ControlNet" + ) + self.use_selection_as_input = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_use_selection_as_input", "Use selection as input" + ) + + self.image_loader = ImageLoader() + + #Main settings + self.invert_input_color = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_invert_input_color", "Invert input color" + ) + self.RGB_to_BGR = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_RGB_to_BGR", "RGB to BGR" + ) + self.low_vram = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_low_vram", "Low VRAM" + ) + self.guess_mode = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_guess_mode", "Guess mode" + ) + + #Tips + self.tips = TipsLayout( + ["Invert colors if your image has white background."] + ) + + #Preprocessor list + self.preprocessor_layout = QComboBoxLayout( + script.cfg, + f"controlnet{cfg_unit_number}_preprocessor_list", + f"controlnet{cfg_unit_number}_preprocessor", + label="Preprocessor:" + ) + + #Model list + self.model_layout = QComboBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" + ) + + self.weight_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_weight", label="Weight:", min=0, max=2, step=0.05 + ) + + self.guidance_start_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_guidance_start", label="Guidance start:", min=0, max=1, step=0.01 + ) + + self.guidance_end_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_guidance_end", label="Guidance end:", min=0, max=1, step=0.01 + ) + + top_checkbox_layout = QHBoxLayout() + top_checkbox_layout.addWidget(self.enable) + top_checkbox_layout.addWidget(self.use_selection_as_input) + + main_settings_layout = QHBoxLayout() + main_settings_layout.addWidget(self.invert_input_color) + main_settings_layout.addWidget(self.RGB_to_BGR) + main_settings_layout.addWidget(self.low_vram) + main_settings_layout.addWidget(self.guess_mode) + + guidance_layout = QHBoxLayout() + guidance_layout.addLayout(self.weight_layout) + guidance_layout.addLayout(self.guidance_start_layout) + guidance_layout.addLayout(self.guidance_end_layout) + + layout = QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.status_bar) + layout.addLayout(top_checkbox_layout) + layout.addWidget(self.image_loader) + layout.addLayout(self.tips) + layout.addLayout(main_settings_layout) + layout.addLayout(self.preprocessor_layout) + layout.addLayout(self.model_layout) + layout.addLayout(guidance_layout) + layout.addStretch() + self.setLayout(layout) + + def change_image_loader_state(self, state): + if state == 1 or state == 2: + self.image_loader.setEnabled(False) + else: + self.image_loader.setEnabled(True) + + def cfg_init(self): + self.enable.cfg_init() + self.use_selection_as_input.cfg_init() + self.invert_input_color.cfg_init() + self.RGB_to_BGR.cfg_init() + self.low_vram.cfg_init() + self.guess_mode.cfg_init() + self.preprocessor_layout.cfg_init() + self.model_layout.cfg_init() + self.weight_layout.cfg_init() + self.guidance_start_layout.cfg_init() + self.guidance_end_layout.cfg_init() + self.tips.setVisible(not script.cfg("minimize_ui", bool)) + self.change_image_loader_state(self.use_selection_as_input.checkState()) + + def cfg_connect(self): + self.enable.cfg_connect() + self.use_selection_as_input.cfg_connect() + self.invert_input_color.cfg_connect() + self.RGB_to_BGR.cfg_connect() + self.low_vram.cfg_connect() + self.guess_mode.cfg_connect() + self.preprocessor_layout.cfg_connect() + self.model_layout.cfg_connect() + self.weight_layout.cfg_connect() + self.guidance_start_layout.cfg_connect() + self.guidance_end_layout.cfg_connect() + self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) + script.status_changed.connect(lambda s: self.status_bar.set_status(s)) \ No newline at end of file diff --git a/frontends/krita/krita_diff/widgets/__init__.py b/frontends/krita/krita_diff/widgets/__init__.py index bb46b96d..ca7138b9 100644 --- a/frontends/krita/krita_diff/widgets/__init__.py +++ b/frontends/krita/krita_diff/widgets/__init__.py @@ -6,3 +6,4 @@ from .spin_box import QSpinBoxLayout from .status_bar import StatusBar from .tips import TipsLayout +from .image_loader import ImageLoader diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py new file mode 100644 index 00000000..96ed2fb7 --- /dev/null +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -0,0 +1,39 @@ +from krita import QWidget, QFileDialog, QPixmap, QPushButton, QVBoxLayout, QHBoxLayout, Qt +from ..widgets import QLabel + +class ImageLoader(QWidget): + def __init__(self, *args, **kwargs): + super(ImageLoader, self).__init__(*args, **kwargs) + + self.preview = QLabel() + self.preview.setAlignment(Qt.AlignCenter) + self.importBtn = QPushButton('Import image') + self.clearBtn = QPushButton('Clear') + + btnLayout = QHBoxLayout() + btnLayout.addWidget(self.importBtn) + btnLayout.addWidget(self.clearBtn) + + layout = QVBoxLayout() + layout.addLayout(btnLayout) + layout.addWidget(self.preview) + self.setLayout(layout) + + self.importBtn.released.connect(self.load_image) + self.clearBtn.released.connect(self.clear_image) + + def load_image(self): + file_name, _ = QFileDialog.getOpenFileName(self, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') + if file_name: + pixmap = QPixmap(file_name) + + if pixmap.width() > self.preview.width(): + pixmap = pixmap.scaledToWidth(self.preview.width(), Qt.SmoothTransformation) + + self.preview.setPixmap(pixmap) + + def clear_image(self): + self.preview.setPixmap(QPixmap()) + + + \ No newline at end of file From c1f337a1df07a233e3ee3ac6e031161c793b78e7 Mon Sep 17 00:00:00 2001 From: JasonS09 <47682209+Jorge0998@users.noreply.github.com> Date: Fri, 31 Mar 2023 05:47:24 -0600 Subject: [PATCH 03/55] Added controlnet preprocessors settings --- frontends/krita/krita_diff/defaults.py | 145 ++++++++- frontends/krita/krita_diff/pages/__init__.py | 2 +- .../krita/krita_diff/pages/controlnet.py | 280 ++++++++++++++++++ .../krita/krita_diff/pages/controlnet_base.py | 129 -------- .../krita/krita_diff/widgets/__init__.py | 2 +- .../krita/krita_diff/widgets/image_loader.py | 22 +- 6 files changed, 439 insertions(+), 141 deletions(-) create mode 100644 frontends/krita/krita_diff/pages/controlnet.py delete mode 100644 frontends/krita/krita_diff/pages/controlnet_base.py diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index 6b8b0dbf..088ad7f0 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -130,18 +130,157 @@ class Defaults: upscale_downscale_first: bool = False controlnet_units: int = 1 + controlnet_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet0_enable: bool = False controlnet0_use_selection_as_input: bool = True controlnet0_invert_input_color: bool = False controlnet0_RGB_to_BGR: bool = False controlnet0_low_vram: bool = False controlnet0_guess_mode: bool = False - controlnet0_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) - controlnet0_preprocessor: str = "None" - controlnet0_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet0_preprocessor: str = "None" controlnet0_model: str = "None" controlnet0_weight: float = 1.0 controlnet0_guidance_start: float = 0 controlnet0_guidance_end: float = 1 + controlnet0_preprocessor_resolution: int = 512 + controlnet0_treshold_a: float = 0 + controlnet0_treshold_b: float = 0 + + controlnet1_enable: bool = False + controlnet1_use_selection_as_input: bool = True + controlnet1_invert_input_color: bool = False + controlnet1_RGB_to_BGR: bool = False + controlnet1_low_vram: bool = False + controlnet1_guess_mode: bool = False + controlnet1_preprocessor: str = "None" + controlnet1_model: str = "None" + controlnet1_weight: float = 1.0 + controlnet1_guidance_start: float = 0 + controlnet1_guidance_end: float = 1 + controlnet1_preprocessor_resolution: int = 512 + controlnet1_treshold_a: float = 0 + controlnet1_treshold_b: float = 0 + + controlnet2_enable: bool = False + controlnet2_use_selection_as_input: bool = True + controlnet2_invert_input_color: bool = False + controlnet2_RGB_to_BGR: bool = False + controlnet2_low_vram: bool = False + controlnet2_guess_mode: bool = False + controlnet2_preprocessor: str = "None" + controlnet2_model: str = "None" + controlnet2_weight: float = 1.0 + controlnet2_guidance_start: float = 0 + controlnet2_guidance_end: float = 1 + controlnet2_preprocessor_resolution: int = 512 + controlnet2_treshold_a: float = 0 + controlnet2_treshold_b: float = 0 + + controlnet3_enable: bool = False + controlnet3_use_selection_as_input: bool = True + controlnet3_invert_input_color: bool = False + controlnet3_RGB_to_BGR: bool = False + controlnet3_low_vram: bool = False + controlnet3_guess_mode: bool = False + controlnet3_preprocessor: str = "None" + controlnet3_model: str = "None" + controlnet3_weight: float = 1.0 + controlnet3_guidance_start: float = 0 + controlnet3_guidance_end: float = 1 + controlnet3_preprocessor_resolution: int = 512 + controlnet3_treshold_a: float = 0 + controlnet3_treshold_b: float = 0 + + controlnet4_enable: bool = False + controlnet4_use_selection_as_input: bool = True + controlnet4_invert_input_color: bool = False + controlnet4_RGB_to_BGR: bool = False + controlnet4_low_vram: bool = False + controlnet4_guess_mode: bool = False + controlnet4_preprocessor: str = "None" + controlnet4_model: str = "None" + controlnet4_weight: float = 1.0 + controlnet4_guidance_start: float = 0 + controlnet4_guidance_end: float = 1 + controlnet4_preprocessor_resolution: int = 512 + controlnet4_treshold_a: float = 0 + controlnet4_treshold_b: float = 0 + + controlnet5_enable: bool = False + controlnet5_use_selection_as_input: bool = True + controlnet5_invert_input_color: bool = False + controlnet5_RGB_to_BGR: bool = False + controlnet5_low_vram: bool = False + controlnet5_guess_mode: bool = False + controlnet5_preprocessor: str = "None" + controlnet5_model: str = "None" + controlnet5_weight: float = 1.0 + controlnet5_guidance_start: float = 0 + controlnet5_guidance_end: float = 1 + controlnet5_preprocessor_resolution: int = 512 + controlnet5_treshold_a: float = 0 + controlnet5_treshold_b: float = 0 + + controlnet6_enable: bool = False + controlnet6_use_selection_as_input: bool = True + controlnet6_invert_input_color: bool = False + controlnet6_RGB_to_BGR: bool = False + controlnet6_low_vram: bool = False + controlnet6_guess_mode: bool = False + controlnet6_preprocessor: str = "None" + controlnet6_model: str = "None" + controlnet6_weight: float = 1.0 + controlnet6_guidance_start: float = 0 + controlnet6_guidance_end: float = 1 + controlnet6_preprocessor_resolution: int = 512 + controlnet6_treshold_a: float = 0 + controlnet6_treshold_b: float = 0 + + controlnet7_enable: bool = False + controlnet7_use_selection_as_input: bool = True + controlnet7_invert_input_color: bool = False + controlnet7_RGB_to_BGR: bool = False + controlnet7_low_vram: bool = False + controlnet7_guess_mode: bool = False + controlnet7_preprocessor: str = "None" + controlnet7_model: str = "None" + controlnet7_weight: float = 1.0 + controlnet7_guidance_start: float = 0 + controlnet7_guidance_end: float = 1 + controlnet7_preprocessor_resolution: int = 512 + controlnet7_treshold_a: float = 0 + controlnet7_treshold_b: float = 0 + + controlnet8_enable: bool = False + controlnet8_use_selection_as_input: bool = True + controlnet8_invert_input_color: bool = False + controlnet8_RGB_to_BGR: bool = False + controlnet8_low_vram: bool = False + controlnet8_guess_mode: bool = False + controlnet8_preprocessor: str = "None" + controlnet8_model: str = "None" + controlnet8_weight: float = 1.0 + controlnet8_guidance_start: float = 0 + controlnet8_guidance_end: float = 1 + controlnet8_preprocessor_resolution: int = 512 + controlnet8_treshold_a: float = 0 + controlnet8_treshold_b: float = 0 + + controlnet9_enable: bool = False + controlnet9_use_selection_as_input: bool = True + controlnet9_invert_input_color: bool = False + controlnet9_RGB_to_BGR: bool = False + controlnet9_low_vram: bool = False + controlnet9_guess_mode: bool = False + controlnet9_preprocessor: str = "None" + controlnet9_model: str = "None" + controlnet9_weight: float = 1.0 + controlnet9_guidance_start: float = 0 + controlnet9_guidance_end: float = 1 + controlnet9_preprocessor_resolution: int = 512 + controlnet9_treshold_a: float = 0 + controlnet9_treshold_b: float = 0 DEFAULTS = Defaults() diff --git a/frontends/krita/krita_diff/pages/__init__.py b/frontends/krita/krita_diff/pages/__init__.py index bc26fa1c..3c8ccfcb 100644 --- a/frontends/krita/krita_diff/pages/__init__.py +++ b/frontends/krita/krita_diff/pages/__init__.py @@ -5,4 +5,4 @@ from .preview import PreviewPage from .txt2img import Txt2ImgPage from .upscale import UpscalePage -from .controlnet_base import ControlNetPageBase +from .controlnet import ControlNetPageBase diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py new file mode 100644 index 00000000..edc9d5c4 --- /dev/null +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -0,0 +1,280 @@ +from krita import QWidget, QVBoxLayout, QHBoxLayout + +from ..script import script +from ..widgets import StatusBar, ImageLoaderLayout, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout + +class ControlNetPageBase(QWidget): + name = "ControlNet" + + def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): + super(ControlNetPageBase, self).__init__(*args, **kwargs) + self.status_bar = StatusBar() + + #Top checkbox + self.enable = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_enable", "Enable ControlNet" + ) + self.use_selection_as_input = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_use_selection_as_input", "Use selection as input" + ) + + self.image_loader = ImageLoaderLayout() + + #Main settings + self.invert_input_color = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_invert_input_color", "Invert input color" + ) + self.RGB_to_BGR = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_RGB_to_BGR", "RGB to BGR" + ) + self.low_vram = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_low_vram", "Low VRAM" + ) + self.guess_mode = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_guess_mode", "Guess mode" + ) + + #Tips + self.tips = TipsLayout( + ["Invert colors if your image has white background."] + ) + + #Preprocessor list + self.preprocessor_layout = QComboBoxLayout( + script.cfg, + f"controlnet_preprocessor_list", f"controlnet{cfg_unit_number}_preprocessor", label="Preprocessor:" + ) + + #Model list + self.model_layout = QComboBoxLayout( + script.cfg, f"controlnet_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" + ) + + self.weight_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_weight", label="Weight:", min=0, max=2, step=0.05 + ) + + self.guidance_start_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_guidance_start", label="Guidance start:", min=0, max=1, step=0.01 + ) + + self.guidance_end_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_guidance_end", label="Guidance end:", min=0, max=1, step=0.01 + ) + + #Preprocessor settings + self.annotator_resolution = QSpinBoxLayout( + script.cfg, + f"controlnet{cfg_unit_number}_preprocessor_resolution", + label="Preprocessor resolution:", + min=64, + max=2048, + step=1 + ) + self.treshold_a = QSpinBoxLayout( + script.cfg, + f"controlnet{cfg_unit_number}_treshold_a", + label="Treshold A:", + min=1, + max=255, + step=1 + ) + self.treshold_b = QSpinBoxLayout( + script.cfg, + f"controlnet{cfg_unit_number}_treshold_b", + label="Treshold B:", + min=1, + max=255, + step=1 + ) + + top_checkbox_layout = QHBoxLayout() + top_checkbox_layout.addWidget(self.enable) + top_checkbox_layout.addWidget(self.use_selection_as_input) + + main_settings_layout = QHBoxLayout() + main_settings_layout.addWidget(self.invert_input_color) + main_settings_layout.addWidget(self.RGB_to_BGR) + main_settings_layout.addWidget(self.low_vram) + main_settings_layout.addWidget(self.guess_mode) + + guidance_layout = QHBoxLayout() + guidance_layout.addLayout(self.weight_layout) + guidance_layout.addLayout(self.guidance_start_layout) + guidance_layout.addLayout(self.guidance_end_layout) + + preprocessor_settings_layout = QHBoxLayout() + preprocessor_settings_layout.addLayout(self.annotator_resolution) + preprocessor_settings_layout.addLayout(self.treshold_a) + preprocessor_settings_layout.addLayout(self.treshold_b) + + layout = QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.status_bar) + layout.addLayout(top_checkbox_layout) + layout.addLayout(self.image_loader) + layout.addLayout(self.tips) + layout.addLayout(main_settings_layout) + layout.addLayout(self.preprocessor_layout) + layout.addLayout(self.model_layout) + layout.addLayout(guidance_layout) + layout.addLayout(preprocessor_settings_layout) + layout.addStretch() + self.setLayout(layout) + + def change_image_loader_state(self, state): + if state == 1 or state == 2: + self.image_loader.disable() + else: + self.image_loader.enable() + + def preprocessor_changed(self, selected: str): + self.init_preprocessor_layouts(selected) + if selected in preprocessor_settings: + self.annotator_resolution.label = preprocessor_settings[selected]["resolution_label"] \ + if "resolution_label" in preprocessor_settings[selected] else "Preprocessor resolution:" + + if "treshold_a_label" in preprocessor_settings[selected]: + self.treshold_a.label = preprocessor_settings[selected]["treshold_a_label"] + self.treshold_a.min = preprocessor_settings[selected]["treshold_a_min_value"] \ + if "treshold_a_min_value" in preprocessor_settings[selected] else 0 + self.treshold_a.max = preprocessor_settings[selected]["treshold_a_max_value"] \ + if "treshold_a_max_value" in preprocessor_settings[selected] else 0 + self.treshold_a.value = preprocessor_settings[selected]["treshold_a_value"] \ + if "treshold_a_value" in preprocessor_settings[selected] else 0 + self.treshold_a.step = preprocessor_settings[selected]["treshold_step"] \ + if "treshold_step" in preprocessor_settings[selected] else 1 + else: + self.treshold_a.hide() + + if "treshold_b_label" in preprocessor_settings[selected]: + self.treshold_b.label = preprocessor_settings[selected]["treshold_b_label"] + self.treshold_b.min = preprocessor_settings[selected]["treshold_b_min_value"] \ + if "treshold_b_min_value" in preprocessor_settings[selected] else 0 + self.treshold_b.max = preprocessor_settings[selected]["treshold_b_max_value"] \ + if "treshold_b_max_value" in preprocessor_settings[selected] else 0 + self.treshold_b.value = preprocessor_settings[selected]["treshold_b_value"] \ + if "treshold_b_value" in preprocessor_settings[selected] else 0 + self.treshold_b.step = preprocessor_settings[selected]["treshold_step"] \ + if "treshold_step" in preprocessor_settings[selected] else 1 + + def init_preprocessor_layouts(self, selected: str): + if selected in preprocessor_settings: + self.show_preprocessor_options() + else: + self.hide_preprocessor_options() + + def hide_preprocessor_options(self): + self.annotator_resolution.qlabel.hide() + self.annotator_resolution.qspin.hide() + self.treshold_a.qlabel.hide() + self.treshold_a.qspin.hide() + self.treshold_b.qlabel.hide() + self.treshold_b.qspin.hide() + + def show_preprocessor_options(self): + self.annotator_resolution.qlabel.show() + self.annotator_resolution.qspin.show() + self.treshold_a.qlabel.show() + self.treshold_a.qspin.show() + self.treshold_b.qlabel.show() + self.treshold_b.qspin.show() + + def cfg_init(self): + self.enable.cfg_init() + self.use_selection_as_input.cfg_init() + self.invert_input_color.cfg_init() + self.RGB_to_BGR.cfg_init() + self.low_vram.cfg_init() + self.guess_mode.cfg_init() + self.preprocessor_layout.cfg_init() + self.model_layout.cfg_init() + self.weight_layout.cfg_init() + self.guidance_start_layout.cfg_init() + self.guidance_end_layout.cfg_init() + self.tips.setVisible(not script.cfg("minimize_ui", bool)) + self.change_image_loader_state(self.use_selection_as_input.checkState()) + self.init_preprocessor_layouts(self.preprocessor_layout.qcombo.currentText()) + + def cfg_connect(self): + self.enable.cfg_connect() + self.use_selection_as_input.cfg_connect() + self.invert_input_color.cfg_connect() + self.RGB_to_BGR.cfg_connect() + self.low_vram.cfg_connect() + self.guess_mode.cfg_connect() + self.preprocessor_layout.cfg_connect() + self.model_layout.cfg_connect() + self.weight_layout.cfg_connect() + self.guidance_start_layout.cfg_connect() + self.guidance_end_layout.cfg_connect() + self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) + self.preprocessor_layout.qcombo.currentTextChanged.connect(self.preprocessor_changed) + script.status_changed.connect(lambda s: self.status_bar.set_status(s)) + +preprocessor_settings = { + "canny": { + "resolution_label": "Annotator resolution", + "treshold_a_label": "Canny low treshold", + "treshold_b_label": "Canny high treshold", + "treshold_a_value": 100, + "treshold_b_value": 200, + "treshold_a_min_value": 1, + "treshold_a_max_value": 255, + "treshold_b_min_value": 1, + "treshold_b_max_value": 255 + }, + "depth": { + "resolution_label": "Midas resolution", + }, + "depth_leres": { + "resolution_label": "LeReS resolution", + "treshold_a_label": "Remove near %", + "treshold_b_label": "Remove background %", + "treshold_a_min_value": 0, + "treshold_a_max_value": 100, + "treshold_b_min_value": 0, + "treshold_b_max_value": 100 + }, + "hed": { + "resolution_label": "HED resolution", + }, + "mlsd": { + "resolution_label": "Hough resolution", + "treshold_a_label": "Hough value threshold (MLSD)", + "treshold_b_label": "Hough distance threshold (MLSD)", + "treshold_a_value": 0.1, + "treshold_b_value": 0.1, + "treshold_a_min_value": 0.01, + "treshold_b_max_value": 2, + "treshold_a_min_value": 0.01, + "treshold_b_max_value": 20, + "treshold_step": 0.01 + }, + "normal_map": { + "treshold_a_label": "Normal background threshold", + "treshold_a_value": 0.4, + "treshold_a_min_value": 0, + "treshold_a_max_value": 1, + "treshold_b_min_value": 0, + "treshold_b_max_value": 1, + "treshold_step": 0.01 + }, + "openpose": {}, + "openpose_hand": {}, + "clip_vision": {}, + "color": {}, + "pidinet": {}, + "scribble": {}, + "fake_scribble": { + "resolution_label": "HED resolution", + }, + "segmentation": {}, + "binary": { + "treshold_a_label": "Binary threshold", + "treshold_a_min_value": 0, + "treshold_a_max_value": 255, + "treshold_b_min_value": 0, + "treshold_b_max_value": 255, + } +} \ No newline at end of file diff --git a/frontends/krita/krita_diff/pages/controlnet_base.py b/frontends/krita/krita_diff/pages/controlnet_base.py deleted file mode 100644 index 9a88b644..00000000 --- a/frontends/krita/krita_diff/pages/controlnet_base.py +++ /dev/null @@ -1,129 +0,0 @@ -from krita import QWidget, QVBoxLayout, QHBoxLayout - -from ..script import script -from ..widgets import StatusBar, ImageLoader, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout - -class ControlNetPageBase(QWidget): - name = "ControlNet" - - def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): - super(ControlNetPageBase, self).__init__(*args, **kwargs) - self.status_bar = StatusBar() - - #Top checkbox - self.enable = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_enable", "Enable ControlNet" - ) - self.use_selection_as_input = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_use_selection_as_input", "Use selection as input" - ) - - self.image_loader = ImageLoader() - - #Main settings - self.invert_input_color = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_invert_input_color", "Invert input color" - ) - self.RGB_to_BGR = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_RGB_to_BGR", "RGB to BGR" - ) - self.low_vram = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_low_vram", "Low VRAM" - ) - self.guess_mode = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_guess_mode", "Guess mode" - ) - - #Tips - self.tips = TipsLayout( - ["Invert colors if your image has white background."] - ) - - #Preprocessor list - self.preprocessor_layout = QComboBoxLayout( - script.cfg, - f"controlnet{cfg_unit_number}_preprocessor_list", - f"controlnet{cfg_unit_number}_preprocessor", - label="Preprocessor:" - ) - - #Model list - self.model_layout = QComboBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" - ) - - self.weight_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_weight", label="Weight:", min=0, max=2, step=0.05 - ) - - self.guidance_start_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_guidance_start", label="Guidance start:", min=0, max=1, step=0.01 - ) - - self.guidance_end_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_guidance_end", label="Guidance end:", min=0, max=1, step=0.01 - ) - - top_checkbox_layout = QHBoxLayout() - top_checkbox_layout.addWidget(self.enable) - top_checkbox_layout.addWidget(self.use_selection_as_input) - - main_settings_layout = QHBoxLayout() - main_settings_layout.addWidget(self.invert_input_color) - main_settings_layout.addWidget(self.RGB_to_BGR) - main_settings_layout.addWidget(self.low_vram) - main_settings_layout.addWidget(self.guess_mode) - - guidance_layout = QHBoxLayout() - guidance_layout.addLayout(self.weight_layout) - guidance_layout.addLayout(self.guidance_start_layout) - guidance_layout.addLayout(self.guidance_end_layout) - - layout = QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.status_bar) - layout.addLayout(top_checkbox_layout) - layout.addWidget(self.image_loader) - layout.addLayout(self.tips) - layout.addLayout(main_settings_layout) - layout.addLayout(self.preprocessor_layout) - layout.addLayout(self.model_layout) - layout.addLayout(guidance_layout) - layout.addStretch() - self.setLayout(layout) - - def change_image_loader_state(self, state): - if state == 1 or state == 2: - self.image_loader.setEnabled(False) - else: - self.image_loader.setEnabled(True) - - def cfg_init(self): - self.enable.cfg_init() - self.use_selection_as_input.cfg_init() - self.invert_input_color.cfg_init() - self.RGB_to_BGR.cfg_init() - self.low_vram.cfg_init() - self.guess_mode.cfg_init() - self.preprocessor_layout.cfg_init() - self.model_layout.cfg_init() - self.weight_layout.cfg_init() - self.guidance_start_layout.cfg_init() - self.guidance_end_layout.cfg_init() - self.tips.setVisible(not script.cfg("minimize_ui", bool)) - self.change_image_loader_state(self.use_selection_as_input.checkState()) - - def cfg_connect(self): - self.enable.cfg_connect() - self.use_selection_as_input.cfg_connect() - self.invert_input_color.cfg_connect() - self.RGB_to_BGR.cfg_connect() - self.low_vram.cfg_connect() - self.guess_mode.cfg_connect() - self.preprocessor_layout.cfg_connect() - self.model_layout.cfg_connect() - self.weight_layout.cfg_connect() - self.guidance_start_layout.cfg_connect() - self.guidance_end_layout.cfg_connect() - self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) - script.status_changed.connect(lambda s: self.status_bar.set_status(s)) \ No newline at end of file diff --git a/frontends/krita/krita_diff/widgets/__init__.py b/frontends/krita/krita_diff/widgets/__init__.py index ca7138b9..5b950669 100644 --- a/frontends/krita/krita_diff/widgets/__init__.py +++ b/frontends/krita/krita_diff/widgets/__init__.py @@ -6,4 +6,4 @@ from .spin_box import QSpinBoxLayout from .status_bar import StatusBar from .tips import TipsLayout -from .image_loader import ImageLoader +from .image_loader import ImageLoaderLayout diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py index 96ed2fb7..c3ce8691 100644 --- a/frontends/krita/krita_diff/widgets/image_loader.py +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -1,9 +1,9 @@ from krita import QWidget, QFileDialog, QPixmap, QPushButton, QVBoxLayout, QHBoxLayout, Qt from ..widgets import QLabel -class ImageLoader(QWidget): +class ImageLoaderLayout(QVBoxLayout): def __init__(self, *args, **kwargs): - super(ImageLoader, self).__init__(*args, **kwargs) + super(ImageLoaderLayout, self).__init__(*args, **kwargs) self.preview = QLabel() self.preview.setAlignment(Qt.AlignCenter) @@ -14,16 +14,24 @@ def __init__(self, *args, **kwargs): btnLayout.addWidget(self.importBtn) btnLayout.addWidget(self.clearBtn) - layout = QVBoxLayout() - layout.addLayout(btnLayout) - layout.addWidget(self.preview) - self.setLayout(layout) + self.addLayout(btnLayout) + self.addWidget(self.preview) self.importBtn.released.connect(self.load_image) self.clearBtn.released.connect(self.clear_image) + def disable(self): + self.preview.setEnabled(False) + self.importBtn.setEnabled(False) + self.clearBtn.setEnabled(False) + + def enable(self): + self.preview.setEnabled(True) + self.importBtn.setEnabled(True) + self.clearBtn.setEnabled(True) + def load_image(self): - file_name, _ = QFileDialog.getOpenFileName(self, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') + file_name, _ = QFileDialog.getOpenFileName(self.importBtn, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') if file_name: pixmap = QPixmap(file_name) From 75ead57be5cfa62628c35d1c57b1ae0da3558f9c Mon Sep 17 00:00:00 2001 From: Jason Segnini Date: Fri, 31 Mar 2023 05:47:24 -0600 Subject: [PATCH 04/55] Added controlnet preprocessors settings --- frontends/krita/krita_diff/defaults.py | 145 ++++++++- frontends/krita/krita_diff/pages/__init__.py | 2 +- .../krita/krita_diff/pages/controlnet.py | 280 ++++++++++++++++++ .../krita/krita_diff/pages/controlnet_base.py | 129 -------- .../krita/krita_diff/widgets/__init__.py | 2 +- .../krita/krita_diff/widgets/image_loader.py | 22 +- 6 files changed, 439 insertions(+), 141 deletions(-) create mode 100644 frontends/krita/krita_diff/pages/controlnet.py delete mode 100644 frontends/krita/krita_diff/pages/controlnet_base.py diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index 6b8b0dbf..088ad7f0 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -130,18 +130,157 @@ class Defaults: upscale_downscale_first: bool = False controlnet_units: int = 1 + controlnet_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet0_enable: bool = False controlnet0_use_selection_as_input: bool = True controlnet0_invert_input_color: bool = False controlnet0_RGB_to_BGR: bool = False controlnet0_low_vram: bool = False controlnet0_guess_mode: bool = False - controlnet0_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) - controlnet0_preprocessor: str = "None" - controlnet0_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet0_preprocessor: str = "None" controlnet0_model: str = "None" controlnet0_weight: float = 1.0 controlnet0_guidance_start: float = 0 controlnet0_guidance_end: float = 1 + controlnet0_preprocessor_resolution: int = 512 + controlnet0_treshold_a: float = 0 + controlnet0_treshold_b: float = 0 + + controlnet1_enable: bool = False + controlnet1_use_selection_as_input: bool = True + controlnet1_invert_input_color: bool = False + controlnet1_RGB_to_BGR: bool = False + controlnet1_low_vram: bool = False + controlnet1_guess_mode: bool = False + controlnet1_preprocessor: str = "None" + controlnet1_model: str = "None" + controlnet1_weight: float = 1.0 + controlnet1_guidance_start: float = 0 + controlnet1_guidance_end: float = 1 + controlnet1_preprocessor_resolution: int = 512 + controlnet1_treshold_a: float = 0 + controlnet1_treshold_b: float = 0 + + controlnet2_enable: bool = False + controlnet2_use_selection_as_input: bool = True + controlnet2_invert_input_color: bool = False + controlnet2_RGB_to_BGR: bool = False + controlnet2_low_vram: bool = False + controlnet2_guess_mode: bool = False + controlnet2_preprocessor: str = "None" + controlnet2_model: str = "None" + controlnet2_weight: float = 1.0 + controlnet2_guidance_start: float = 0 + controlnet2_guidance_end: float = 1 + controlnet2_preprocessor_resolution: int = 512 + controlnet2_treshold_a: float = 0 + controlnet2_treshold_b: float = 0 + + controlnet3_enable: bool = False + controlnet3_use_selection_as_input: bool = True + controlnet3_invert_input_color: bool = False + controlnet3_RGB_to_BGR: bool = False + controlnet3_low_vram: bool = False + controlnet3_guess_mode: bool = False + controlnet3_preprocessor: str = "None" + controlnet3_model: str = "None" + controlnet3_weight: float = 1.0 + controlnet3_guidance_start: float = 0 + controlnet3_guidance_end: float = 1 + controlnet3_preprocessor_resolution: int = 512 + controlnet3_treshold_a: float = 0 + controlnet3_treshold_b: float = 0 + + controlnet4_enable: bool = False + controlnet4_use_selection_as_input: bool = True + controlnet4_invert_input_color: bool = False + controlnet4_RGB_to_BGR: bool = False + controlnet4_low_vram: bool = False + controlnet4_guess_mode: bool = False + controlnet4_preprocessor: str = "None" + controlnet4_model: str = "None" + controlnet4_weight: float = 1.0 + controlnet4_guidance_start: float = 0 + controlnet4_guidance_end: float = 1 + controlnet4_preprocessor_resolution: int = 512 + controlnet4_treshold_a: float = 0 + controlnet4_treshold_b: float = 0 + + controlnet5_enable: bool = False + controlnet5_use_selection_as_input: bool = True + controlnet5_invert_input_color: bool = False + controlnet5_RGB_to_BGR: bool = False + controlnet5_low_vram: bool = False + controlnet5_guess_mode: bool = False + controlnet5_preprocessor: str = "None" + controlnet5_model: str = "None" + controlnet5_weight: float = 1.0 + controlnet5_guidance_start: float = 0 + controlnet5_guidance_end: float = 1 + controlnet5_preprocessor_resolution: int = 512 + controlnet5_treshold_a: float = 0 + controlnet5_treshold_b: float = 0 + + controlnet6_enable: bool = False + controlnet6_use_selection_as_input: bool = True + controlnet6_invert_input_color: bool = False + controlnet6_RGB_to_BGR: bool = False + controlnet6_low_vram: bool = False + controlnet6_guess_mode: bool = False + controlnet6_preprocessor: str = "None" + controlnet6_model: str = "None" + controlnet6_weight: float = 1.0 + controlnet6_guidance_start: float = 0 + controlnet6_guidance_end: float = 1 + controlnet6_preprocessor_resolution: int = 512 + controlnet6_treshold_a: float = 0 + controlnet6_treshold_b: float = 0 + + controlnet7_enable: bool = False + controlnet7_use_selection_as_input: bool = True + controlnet7_invert_input_color: bool = False + controlnet7_RGB_to_BGR: bool = False + controlnet7_low_vram: bool = False + controlnet7_guess_mode: bool = False + controlnet7_preprocessor: str = "None" + controlnet7_model: str = "None" + controlnet7_weight: float = 1.0 + controlnet7_guidance_start: float = 0 + controlnet7_guidance_end: float = 1 + controlnet7_preprocessor_resolution: int = 512 + controlnet7_treshold_a: float = 0 + controlnet7_treshold_b: float = 0 + + controlnet8_enable: bool = False + controlnet8_use_selection_as_input: bool = True + controlnet8_invert_input_color: bool = False + controlnet8_RGB_to_BGR: bool = False + controlnet8_low_vram: bool = False + controlnet8_guess_mode: bool = False + controlnet8_preprocessor: str = "None" + controlnet8_model: str = "None" + controlnet8_weight: float = 1.0 + controlnet8_guidance_start: float = 0 + controlnet8_guidance_end: float = 1 + controlnet8_preprocessor_resolution: int = 512 + controlnet8_treshold_a: float = 0 + controlnet8_treshold_b: float = 0 + + controlnet9_enable: bool = False + controlnet9_use_selection_as_input: bool = True + controlnet9_invert_input_color: bool = False + controlnet9_RGB_to_BGR: bool = False + controlnet9_low_vram: bool = False + controlnet9_guess_mode: bool = False + controlnet9_preprocessor: str = "None" + controlnet9_model: str = "None" + controlnet9_weight: float = 1.0 + controlnet9_guidance_start: float = 0 + controlnet9_guidance_end: float = 1 + controlnet9_preprocessor_resolution: int = 512 + controlnet9_treshold_a: float = 0 + controlnet9_treshold_b: float = 0 DEFAULTS = Defaults() diff --git a/frontends/krita/krita_diff/pages/__init__.py b/frontends/krita/krita_diff/pages/__init__.py index bc26fa1c..3c8ccfcb 100644 --- a/frontends/krita/krita_diff/pages/__init__.py +++ b/frontends/krita/krita_diff/pages/__init__.py @@ -5,4 +5,4 @@ from .preview import PreviewPage from .txt2img import Txt2ImgPage from .upscale import UpscalePage -from .controlnet_base import ControlNetPageBase +from .controlnet import ControlNetPageBase diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py new file mode 100644 index 00000000..edc9d5c4 --- /dev/null +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -0,0 +1,280 @@ +from krita import QWidget, QVBoxLayout, QHBoxLayout + +from ..script import script +from ..widgets import StatusBar, ImageLoaderLayout, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout + +class ControlNetPageBase(QWidget): + name = "ControlNet" + + def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): + super(ControlNetPageBase, self).__init__(*args, **kwargs) + self.status_bar = StatusBar() + + #Top checkbox + self.enable = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_enable", "Enable ControlNet" + ) + self.use_selection_as_input = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_use_selection_as_input", "Use selection as input" + ) + + self.image_loader = ImageLoaderLayout() + + #Main settings + self.invert_input_color = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_invert_input_color", "Invert input color" + ) + self.RGB_to_BGR = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_RGB_to_BGR", "RGB to BGR" + ) + self.low_vram = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_low_vram", "Low VRAM" + ) + self.guess_mode = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_guess_mode", "Guess mode" + ) + + #Tips + self.tips = TipsLayout( + ["Invert colors if your image has white background."] + ) + + #Preprocessor list + self.preprocessor_layout = QComboBoxLayout( + script.cfg, + f"controlnet_preprocessor_list", f"controlnet{cfg_unit_number}_preprocessor", label="Preprocessor:" + ) + + #Model list + self.model_layout = QComboBoxLayout( + script.cfg, f"controlnet_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" + ) + + self.weight_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_weight", label="Weight:", min=0, max=2, step=0.05 + ) + + self.guidance_start_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_guidance_start", label="Guidance start:", min=0, max=1, step=0.01 + ) + + self.guidance_end_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_guidance_end", label="Guidance end:", min=0, max=1, step=0.01 + ) + + #Preprocessor settings + self.annotator_resolution = QSpinBoxLayout( + script.cfg, + f"controlnet{cfg_unit_number}_preprocessor_resolution", + label="Preprocessor resolution:", + min=64, + max=2048, + step=1 + ) + self.treshold_a = QSpinBoxLayout( + script.cfg, + f"controlnet{cfg_unit_number}_treshold_a", + label="Treshold A:", + min=1, + max=255, + step=1 + ) + self.treshold_b = QSpinBoxLayout( + script.cfg, + f"controlnet{cfg_unit_number}_treshold_b", + label="Treshold B:", + min=1, + max=255, + step=1 + ) + + top_checkbox_layout = QHBoxLayout() + top_checkbox_layout.addWidget(self.enable) + top_checkbox_layout.addWidget(self.use_selection_as_input) + + main_settings_layout = QHBoxLayout() + main_settings_layout.addWidget(self.invert_input_color) + main_settings_layout.addWidget(self.RGB_to_BGR) + main_settings_layout.addWidget(self.low_vram) + main_settings_layout.addWidget(self.guess_mode) + + guidance_layout = QHBoxLayout() + guidance_layout.addLayout(self.weight_layout) + guidance_layout.addLayout(self.guidance_start_layout) + guidance_layout.addLayout(self.guidance_end_layout) + + preprocessor_settings_layout = QHBoxLayout() + preprocessor_settings_layout.addLayout(self.annotator_resolution) + preprocessor_settings_layout.addLayout(self.treshold_a) + preprocessor_settings_layout.addLayout(self.treshold_b) + + layout = QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.status_bar) + layout.addLayout(top_checkbox_layout) + layout.addLayout(self.image_loader) + layout.addLayout(self.tips) + layout.addLayout(main_settings_layout) + layout.addLayout(self.preprocessor_layout) + layout.addLayout(self.model_layout) + layout.addLayout(guidance_layout) + layout.addLayout(preprocessor_settings_layout) + layout.addStretch() + self.setLayout(layout) + + def change_image_loader_state(self, state): + if state == 1 or state == 2: + self.image_loader.disable() + else: + self.image_loader.enable() + + def preprocessor_changed(self, selected: str): + self.init_preprocessor_layouts(selected) + if selected in preprocessor_settings: + self.annotator_resolution.label = preprocessor_settings[selected]["resolution_label"] \ + if "resolution_label" in preprocessor_settings[selected] else "Preprocessor resolution:" + + if "treshold_a_label" in preprocessor_settings[selected]: + self.treshold_a.label = preprocessor_settings[selected]["treshold_a_label"] + self.treshold_a.min = preprocessor_settings[selected]["treshold_a_min_value"] \ + if "treshold_a_min_value" in preprocessor_settings[selected] else 0 + self.treshold_a.max = preprocessor_settings[selected]["treshold_a_max_value"] \ + if "treshold_a_max_value" in preprocessor_settings[selected] else 0 + self.treshold_a.value = preprocessor_settings[selected]["treshold_a_value"] \ + if "treshold_a_value" in preprocessor_settings[selected] else 0 + self.treshold_a.step = preprocessor_settings[selected]["treshold_step"] \ + if "treshold_step" in preprocessor_settings[selected] else 1 + else: + self.treshold_a.hide() + + if "treshold_b_label" in preprocessor_settings[selected]: + self.treshold_b.label = preprocessor_settings[selected]["treshold_b_label"] + self.treshold_b.min = preprocessor_settings[selected]["treshold_b_min_value"] \ + if "treshold_b_min_value" in preprocessor_settings[selected] else 0 + self.treshold_b.max = preprocessor_settings[selected]["treshold_b_max_value"] \ + if "treshold_b_max_value" in preprocessor_settings[selected] else 0 + self.treshold_b.value = preprocessor_settings[selected]["treshold_b_value"] \ + if "treshold_b_value" in preprocessor_settings[selected] else 0 + self.treshold_b.step = preprocessor_settings[selected]["treshold_step"] \ + if "treshold_step" in preprocessor_settings[selected] else 1 + + def init_preprocessor_layouts(self, selected: str): + if selected in preprocessor_settings: + self.show_preprocessor_options() + else: + self.hide_preprocessor_options() + + def hide_preprocessor_options(self): + self.annotator_resolution.qlabel.hide() + self.annotator_resolution.qspin.hide() + self.treshold_a.qlabel.hide() + self.treshold_a.qspin.hide() + self.treshold_b.qlabel.hide() + self.treshold_b.qspin.hide() + + def show_preprocessor_options(self): + self.annotator_resolution.qlabel.show() + self.annotator_resolution.qspin.show() + self.treshold_a.qlabel.show() + self.treshold_a.qspin.show() + self.treshold_b.qlabel.show() + self.treshold_b.qspin.show() + + def cfg_init(self): + self.enable.cfg_init() + self.use_selection_as_input.cfg_init() + self.invert_input_color.cfg_init() + self.RGB_to_BGR.cfg_init() + self.low_vram.cfg_init() + self.guess_mode.cfg_init() + self.preprocessor_layout.cfg_init() + self.model_layout.cfg_init() + self.weight_layout.cfg_init() + self.guidance_start_layout.cfg_init() + self.guidance_end_layout.cfg_init() + self.tips.setVisible(not script.cfg("minimize_ui", bool)) + self.change_image_loader_state(self.use_selection_as_input.checkState()) + self.init_preprocessor_layouts(self.preprocessor_layout.qcombo.currentText()) + + def cfg_connect(self): + self.enable.cfg_connect() + self.use_selection_as_input.cfg_connect() + self.invert_input_color.cfg_connect() + self.RGB_to_BGR.cfg_connect() + self.low_vram.cfg_connect() + self.guess_mode.cfg_connect() + self.preprocessor_layout.cfg_connect() + self.model_layout.cfg_connect() + self.weight_layout.cfg_connect() + self.guidance_start_layout.cfg_connect() + self.guidance_end_layout.cfg_connect() + self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) + self.preprocessor_layout.qcombo.currentTextChanged.connect(self.preprocessor_changed) + script.status_changed.connect(lambda s: self.status_bar.set_status(s)) + +preprocessor_settings = { + "canny": { + "resolution_label": "Annotator resolution", + "treshold_a_label": "Canny low treshold", + "treshold_b_label": "Canny high treshold", + "treshold_a_value": 100, + "treshold_b_value": 200, + "treshold_a_min_value": 1, + "treshold_a_max_value": 255, + "treshold_b_min_value": 1, + "treshold_b_max_value": 255 + }, + "depth": { + "resolution_label": "Midas resolution", + }, + "depth_leres": { + "resolution_label": "LeReS resolution", + "treshold_a_label": "Remove near %", + "treshold_b_label": "Remove background %", + "treshold_a_min_value": 0, + "treshold_a_max_value": 100, + "treshold_b_min_value": 0, + "treshold_b_max_value": 100 + }, + "hed": { + "resolution_label": "HED resolution", + }, + "mlsd": { + "resolution_label": "Hough resolution", + "treshold_a_label": "Hough value threshold (MLSD)", + "treshold_b_label": "Hough distance threshold (MLSD)", + "treshold_a_value": 0.1, + "treshold_b_value": 0.1, + "treshold_a_min_value": 0.01, + "treshold_b_max_value": 2, + "treshold_a_min_value": 0.01, + "treshold_b_max_value": 20, + "treshold_step": 0.01 + }, + "normal_map": { + "treshold_a_label": "Normal background threshold", + "treshold_a_value": 0.4, + "treshold_a_min_value": 0, + "treshold_a_max_value": 1, + "treshold_b_min_value": 0, + "treshold_b_max_value": 1, + "treshold_step": 0.01 + }, + "openpose": {}, + "openpose_hand": {}, + "clip_vision": {}, + "color": {}, + "pidinet": {}, + "scribble": {}, + "fake_scribble": { + "resolution_label": "HED resolution", + }, + "segmentation": {}, + "binary": { + "treshold_a_label": "Binary threshold", + "treshold_a_min_value": 0, + "treshold_a_max_value": 255, + "treshold_b_min_value": 0, + "treshold_b_max_value": 255, + } +} \ No newline at end of file diff --git a/frontends/krita/krita_diff/pages/controlnet_base.py b/frontends/krita/krita_diff/pages/controlnet_base.py deleted file mode 100644 index 9a88b644..00000000 --- a/frontends/krita/krita_diff/pages/controlnet_base.py +++ /dev/null @@ -1,129 +0,0 @@ -from krita import QWidget, QVBoxLayout, QHBoxLayout - -from ..script import script -from ..widgets import StatusBar, ImageLoader, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout - -class ControlNetPageBase(QWidget): - name = "ControlNet" - - def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): - super(ControlNetPageBase, self).__init__(*args, **kwargs) - self.status_bar = StatusBar() - - #Top checkbox - self.enable = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_enable", "Enable ControlNet" - ) - self.use_selection_as_input = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_use_selection_as_input", "Use selection as input" - ) - - self.image_loader = ImageLoader() - - #Main settings - self.invert_input_color = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_invert_input_color", "Invert input color" - ) - self.RGB_to_BGR = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_RGB_to_BGR", "RGB to BGR" - ) - self.low_vram = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_low_vram", "Low VRAM" - ) - self.guess_mode = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_guess_mode", "Guess mode" - ) - - #Tips - self.tips = TipsLayout( - ["Invert colors if your image has white background."] - ) - - #Preprocessor list - self.preprocessor_layout = QComboBoxLayout( - script.cfg, - f"controlnet{cfg_unit_number}_preprocessor_list", - f"controlnet{cfg_unit_number}_preprocessor", - label="Preprocessor:" - ) - - #Model list - self.model_layout = QComboBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" - ) - - self.weight_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_weight", label="Weight:", min=0, max=2, step=0.05 - ) - - self.guidance_start_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_guidance_start", label="Guidance start:", min=0, max=1, step=0.01 - ) - - self.guidance_end_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_guidance_end", label="Guidance end:", min=0, max=1, step=0.01 - ) - - top_checkbox_layout = QHBoxLayout() - top_checkbox_layout.addWidget(self.enable) - top_checkbox_layout.addWidget(self.use_selection_as_input) - - main_settings_layout = QHBoxLayout() - main_settings_layout.addWidget(self.invert_input_color) - main_settings_layout.addWidget(self.RGB_to_BGR) - main_settings_layout.addWidget(self.low_vram) - main_settings_layout.addWidget(self.guess_mode) - - guidance_layout = QHBoxLayout() - guidance_layout.addLayout(self.weight_layout) - guidance_layout.addLayout(self.guidance_start_layout) - guidance_layout.addLayout(self.guidance_end_layout) - - layout = QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.status_bar) - layout.addLayout(top_checkbox_layout) - layout.addWidget(self.image_loader) - layout.addLayout(self.tips) - layout.addLayout(main_settings_layout) - layout.addLayout(self.preprocessor_layout) - layout.addLayout(self.model_layout) - layout.addLayout(guidance_layout) - layout.addStretch() - self.setLayout(layout) - - def change_image_loader_state(self, state): - if state == 1 or state == 2: - self.image_loader.setEnabled(False) - else: - self.image_loader.setEnabled(True) - - def cfg_init(self): - self.enable.cfg_init() - self.use_selection_as_input.cfg_init() - self.invert_input_color.cfg_init() - self.RGB_to_BGR.cfg_init() - self.low_vram.cfg_init() - self.guess_mode.cfg_init() - self.preprocessor_layout.cfg_init() - self.model_layout.cfg_init() - self.weight_layout.cfg_init() - self.guidance_start_layout.cfg_init() - self.guidance_end_layout.cfg_init() - self.tips.setVisible(not script.cfg("minimize_ui", bool)) - self.change_image_loader_state(self.use_selection_as_input.checkState()) - - def cfg_connect(self): - self.enable.cfg_connect() - self.use_selection_as_input.cfg_connect() - self.invert_input_color.cfg_connect() - self.RGB_to_BGR.cfg_connect() - self.low_vram.cfg_connect() - self.guess_mode.cfg_connect() - self.preprocessor_layout.cfg_connect() - self.model_layout.cfg_connect() - self.weight_layout.cfg_connect() - self.guidance_start_layout.cfg_connect() - self.guidance_end_layout.cfg_connect() - self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) - script.status_changed.connect(lambda s: self.status_bar.set_status(s)) \ No newline at end of file diff --git a/frontends/krita/krita_diff/widgets/__init__.py b/frontends/krita/krita_diff/widgets/__init__.py index ca7138b9..5b950669 100644 --- a/frontends/krita/krita_diff/widgets/__init__.py +++ b/frontends/krita/krita_diff/widgets/__init__.py @@ -6,4 +6,4 @@ from .spin_box import QSpinBoxLayout from .status_bar import StatusBar from .tips import TipsLayout -from .image_loader import ImageLoader +from .image_loader import ImageLoaderLayout diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py index 96ed2fb7..c3ce8691 100644 --- a/frontends/krita/krita_diff/widgets/image_loader.py +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -1,9 +1,9 @@ from krita import QWidget, QFileDialog, QPixmap, QPushButton, QVBoxLayout, QHBoxLayout, Qt from ..widgets import QLabel -class ImageLoader(QWidget): +class ImageLoaderLayout(QVBoxLayout): def __init__(self, *args, **kwargs): - super(ImageLoader, self).__init__(*args, **kwargs) + super(ImageLoaderLayout, self).__init__(*args, **kwargs) self.preview = QLabel() self.preview.setAlignment(Qt.AlignCenter) @@ -14,16 +14,24 @@ def __init__(self, *args, **kwargs): btnLayout.addWidget(self.importBtn) btnLayout.addWidget(self.clearBtn) - layout = QVBoxLayout() - layout.addLayout(btnLayout) - layout.addWidget(self.preview) - self.setLayout(layout) + self.addLayout(btnLayout) + self.addWidget(self.preview) self.importBtn.released.connect(self.load_image) self.clearBtn.released.connect(self.clear_image) + def disable(self): + self.preview.setEnabled(False) + self.importBtn.setEnabled(False) + self.clearBtn.setEnabled(False) + + def enable(self): + self.preview.setEnabled(True) + self.importBtn.setEnabled(True) + self.clearBtn.setEnabled(True) + def load_image(self): - file_name, _ = QFileDialog.getOpenFileName(self, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') + file_name, _ = QFileDialog.getOpenFileName(self.importBtn, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') if file_name: pixmap = QPixmap(file_name) From a9984e059160a91f52a41a55cd214a51647a0910 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Sat, 1 Apr 2023 04:03:34 -0600 Subject: [PATCH 05/55] Added controlnet base layout --- frontends/krita/krita_diff/__init__.py | 4 +- frontends/krita/krita_diff/defaults.py | 3 +- frontends/krita/krita_diff/pages/__init__.py | 2 +- .../krita/krita_diff/pages/controlnet.py | 58 +++++++++++++++---- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/frontends/krita/krita_diff/__init__.py b/frontends/krita/krita_diff/__init__.py index e47e70cf..a087dbf3 100644 --- a/frontends/krita/krita_diff/__init__.py +++ b/frontends/krita/krita_diff/__init__.py @@ -19,7 +19,7 @@ SDCommonPage, Txt2ImgPage, UpscalePage, - ControlNetPageBase + ControlNetPage ) from .pages.preview import PreviewPage from .script import script @@ -66,7 +66,7 @@ DockWidgetFactory( TAB_CONTROLNET, DockWidgetFactoryBase.DockLeft, - create_docker(ControlNetPageBase), + create_docker(ControlNetPage), ) ) instance.addDockWidgetFactory( diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index 088ad7f0..f731ba47 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -129,7 +129,8 @@ class Defaults: upscale_upscaler_name: str = "None" upscale_downscale_first: bool = False - controlnet_units: int = 1 + controlnet_unit: int = 0 + controlnet_unit_list: List[str] = field(default_factory=lambda: list(range(1, 11))) controlnet_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) controlnet_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) diff --git a/frontends/krita/krita_diff/pages/__init__.py b/frontends/krita/krita_diff/pages/__init__.py index 3c8ccfcb..7715bc20 100644 --- a/frontends/krita/krita_diff/pages/__init__.py +++ b/frontends/krita/krita_diff/pages/__init__.py @@ -5,4 +5,4 @@ from .preview import PreviewPage from .txt2img import Txt2ImgPage from .upscale import UpscalePage -from .controlnet import ControlNetPageBase +from .controlnet import ControlNetPage \ No newline at end of file diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index edc9d5c4..a3b8cc88 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -1,18 +1,57 @@ -from krita import QWidget, QVBoxLayout, QHBoxLayout +from krita import QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout from ..script import script from ..widgets import StatusBar, ImageLoaderLayout, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout -class ControlNetPageBase(QWidget): +class ControlNetPage(QWidget): name = "ControlNet" - def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): - super(ControlNetPageBase, self).__init__(*args, **kwargs) + def __init__(self, *args, **kwargs): + super(ControlNetPage, self).__init__(*args, **kwargs) self.status_bar = StatusBar() + self.controlnet_unit = QComboBoxLayout( + script.cfg, "controlnet_unit_list", "controlnet_unit", label="Unit:" + ) + self.controlnet_unit.qcombo.setEditable(False) + self.controlnet_unit_layout_list = list(ControlNetUnitSettings(i) + for i in range(0, len(script.cfg("controlnet_unit_list")))) + + self.units_stacked_layout = QStackedLayout() + + for unit_layout in self.controlnet_unit_layout_list: + self.units_stacked_layout.addWidget(unit_layout) + + layout = QVBoxLayout() + layout.addWidget(self.status_bar) + layout.addLayout(self.controlnet_unit) + layout.addLayout(self.units_stacked_layout) + self.setLayout(layout) + + def controlnet_unit_changed(self, selected: str): + self.units_stacked_layout.setCurrentIndex(int(selected)-1) + + def cfg_init(self): + self.controlnet_unit.cfg_init() + + for controlnet_unit_layout in self.controlnet_unit_layout_list: + controlnet_unit_layout.cfg_init() + + def cfg_connect(self): + self.controlnet_unit.cfg_connect() + + for controlnet_unit_layout in self.controlnet_unit_layout_list: + controlnet_unit_layout.cfg_connect() + + self.controlnet_unit.qcombo.currentTextChanged.connect(self.controlnet_unit_changed) + script.status_changed.connect(lambda s: self.status_bar.set_status(s)) + +class ControlNetUnitSettings(QWidget): + def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): + super(ControlNetUnitSettings, self).__init__(*args, **kwargs) #Top checkbox self.enable = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_enable", "Enable ControlNet" + script.cfg, f"controlnet{cfg_unit_number}_enable", f"Enable ControlNet {cfg_unit_number}" ) self.use_selection_as_input = QCheckBox( script.cfg, f"controlnet{cfg_unit_number}_use_selection_as_input", "Use selection as input" @@ -41,13 +80,12 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): #Preprocessor list self.preprocessor_layout = QComboBoxLayout( - script.cfg, - f"controlnet_preprocessor_list", f"controlnet{cfg_unit_number}_preprocessor", label="Preprocessor:" + script.cfg, "controlnet_preprocessor_list", f"controlnet{cfg_unit_number}_preprocessor", label="Preprocessor:" ) #Model list self.model_layout = QComboBoxLayout( - script.cfg, f"controlnet_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" + script.cfg, "controlnet_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" ) self.weight_layout = QSpinBoxLayout( @@ -110,7 +148,6 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.status_bar) layout.addLayout(top_checkbox_layout) layout.addLayout(self.image_loader) layout.addLayout(self.tips) @@ -120,6 +157,7 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): layout.addLayout(guidance_layout) layout.addLayout(preprocessor_settings_layout) layout.addStretch() + self.setLayout(layout) def change_image_loader_state(self, state): @@ -192,7 +230,6 @@ def cfg_init(self): self.weight_layout.cfg_init() self.guidance_start_layout.cfg_init() self.guidance_end_layout.cfg_init() - self.tips.setVisible(not script.cfg("minimize_ui", bool)) self.change_image_loader_state(self.use_selection_as_input.checkState()) self.init_preprocessor_layouts(self.preprocessor_layout.qcombo.currentText()) @@ -210,7 +247,6 @@ def cfg_connect(self): self.guidance_end_layout.cfg_connect() self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) self.preprocessor_layout.qcombo.currentTextChanged.connect(self.preprocessor_changed) - script.status_changed.connect(lambda s: self.status_bar.set_status(s)) preprocessor_settings = { "canny": { From 44a473ac7336d8350871f1be93d5a5a797ced26b Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Sat, 1 Apr 2023 11:00:16 -0600 Subject: [PATCH 06/55] Added the ability to paste copied image to the image loader layout. --- frontends/krita/krita_diff/widgets/image_loader.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py index c3ce8691..9eeaf87a 100644 --- a/frontends/krita/krita_diff/widgets/image_loader.py +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -1,4 +1,4 @@ -from krita import QWidget, QFileDialog, QPixmap, QPushButton, QVBoxLayout, QHBoxLayout, Qt +from krita import QApplication, QFileDialog, QPixmap, QPushButton, QVBoxLayout, QHBoxLayout, Qt from ..widgets import QLabel class ImageLoaderLayout(QVBoxLayout): @@ -8,29 +8,35 @@ def __init__(self, *args, **kwargs): self.preview = QLabel() self.preview.setAlignment(Qt.AlignCenter) self.importBtn = QPushButton('Import image') + self.pasteBtn = QPushButton('Paste image') self.clearBtn = QPushButton('Clear') btnLayout = QHBoxLayout() btnLayout.addWidget(self.importBtn) - btnLayout.addWidget(self.clearBtn) + btnLayout.addWidget(self.pasteBtn) self.addLayout(btnLayout) + self.addWidget(self.clearBtn) self.addWidget(self.preview) self.importBtn.released.connect(self.load_image) + self.pasteBtn.released.connect(self.paste_image) self.clearBtn.released.connect(self.clear_image) def disable(self): self.preview.setEnabled(False) self.importBtn.setEnabled(False) + self.pasteBtn.setEnabled(False) self.clearBtn.setEnabled(False) def enable(self): self.preview.setEnabled(True) self.importBtn.setEnabled(True) + self.pasteBtn.setEnabled(True) self.clearBtn.setEnabled(True) def load_image(self): + self.clear_image() file_name, _ = QFileDialog.getOpenFileName(self.importBtn, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') if file_name: pixmap = QPixmap(file_name) @@ -40,6 +46,10 @@ def load_image(self): self.preview.setPixmap(pixmap) + def paste_image(self): + self.clear_image() + self.preview.setPixmap(QApplication.clipboard().pixmap()) + def clear_image(self): self.preview.setPixmap(QPixmap()) From 0ab45793732d9ea1ac2c9a96b3a5bbcf75e3f8ef Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Sun, 2 Apr 2023 03:40:30 -0600 Subject: [PATCH 07/55] Get the models and modules from the backend --- frontends/krita/krita_diff/client.py | 28 +++ frontends/krita/krita_diff/defaults.py | 66 ++++++- frontends/krita/krita_diff/pages/config.py | 2 + .../krita/krita_diff/pages/controlnet.py | 180 +++++++----------- frontends/krita/krita_diff/script.py | 4 + .../krita/krita_diff/widgets/image_loader.py | 36 ++-- 6 files changed, 185 insertions(+), 131 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 2f89a393..91d29c57 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -14,6 +14,7 @@ LONG_TIMEOUT, OFFICIAL_ROUTE_PREFIX, ROUTE_PREFIX, + CONTROLNET_ROUTE_PREFIX, SHORT_TIMEOUT, STATE_DONE, STATE_READY, @@ -294,6 +295,33 @@ def cb(obj): self.get("config", cb, ignore_no_connection=True) + #Get config for controlnet + def get_controlnet_config(self): + def check_response(obj, key: str): + try: + assert key in obj + except: + self.status.emit( + f"{STATE_URLERROR}: incompatible response, are you running the right API?" + ) + print("Invalid Response:\n", obj) + return + + def set_model_list(obj): + key = "model_list" + check_response(obj, key) + self.cfg.set("controlnet_model_list", ["None"] + obj[key]) + + def set_preprocessor_list(obj): + key = "module_list" + check_response(obj, key) + self.cfg.set("controlnet_preprocessor_list", obj[key]) + + #Get controlnet API url + url = get_url(self.cfg, prefix=CONTROLNET_ROUTE_PREFIX) + self.get("model_list", set_model_list, base_url=url) + self.get("module_list", set_preprocessor_list, base_url=url) + def post_txt2img(self, cb, width, height, has_selection): params = dict(orig_width=width, orig_height=height) if not self.cfg("just_use_yaml", bool): diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index f731ba47..1405a448 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -25,6 +25,7 @@ THREADED = True ROUTE_PREFIX = "/sdapi/interpause/" OFFICIAL_ROUTE_PREFIX = "/sdapi/v1/" +CONTROLNET_ROUTE_PREFIX = "/controlnet/" # error messages ERR_MISSING_CONFIG = "Report this bug, developer missed out a config key somewhere." @@ -42,6 +43,69 @@ TAB_CONTROLNET = "krita_diff_controlnet" TAB_PREVIEW = "krita_diff_preview" +# controlnet +CONTROLNET_PREPROCESSOR_SETTINGS = { + "canny": { + "resolution_label": "Annotator resolution", + "treshold_a_label": "Canny low treshold", + "treshold_b_label": "Canny high treshold", + "treshold_a_value": 100, + "treshold_b_value": 200, + "treshold_a_min_value": 1, + "treshold_a_max_value": 255, + "treshold_b_min_value": 1, + "treshold_b_max_value": 255 + }, + "depth": { + "resolution_label": "Midas resolution", + }, + "depth_leres": { + "resolution_label": "LeReS resolution", + "treshold_a_label": "Remove near %", + "treshold_b_label": "Remove background %", + "treshold_a_min_value": 0, + "treshold_a_max_value": 100, + "treshold_b_min_value": 0, + "treshold_b_max_value": 100 + }, + "hed": { + "resolution_label": "HED resolution", + }, + "mlsd": { + "resolution_label": "Hough resolution", + "treshold_a_label": "Hough value threshold (MLSD)", + "treshold_b_label": "Hough distance threshold (MLSD)", + "treshold_a_value": 0.1, + "treshold_b_value": 0.1, + "treshold_a_min_value": 0.01, + "treshold_b_max_value": 2, + "treshold_a_min_value": 0.01, + "treshold_b_max_value": 20, + "treshold_step": 0.01 + }, + "normal_map": { + "treshold_a_label": "Normal background threshold", + "treshold_a_value": 0.4, + "treshold_a_min_value": 0, + "treshold_a_max_value": 1, + "treshold_step": 0.01 + }, + "openpose": {}, + "openpose_hand": {}, + "clip_vision": {}, + "color": {}, + "pidinet": {}, + "scribble": {}, + "fake_scribble": { + "resolution_label": "HED resolution", + }, + "segmentation": {}, + "binary": { + "treshold_a_label": "Binary threshold", + "treshold_a_min_value": 0, + "treshold_a_max_value": 255, + } +} @dataclass(frozen=True) class Defaults: @@ -130,7 +194,7 @@ class Defaults: upscale_downscale_first: bool = False controlnet_unit: int = 0 - controlnet_unit_list: List[str] = field(default_factory=lambda: list(range(1, 11))) + controlnet_unit_list: List[str] = field(default_factory=lambda: list(range(10))) controlnet_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) controlnet_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) diff --git a/frontends/krita/krita_diff/pages/config.py b/frontends/krita/krita_diff/pages/config.py index 9060c58d..a9a813bf 100644 --- a/frontends/krita/krita_diff/pages/config.py +++ b/frontends/krita/krita_diff/pages/config.py @@ -153,6 +153,7 @@ def cfg_connect(self): self.base_url.textChanged.connect(partial(script.cfg.set, "base_url")) # NOTE: this triggers on every keystroke; theres no focus lost signal... self.base_url.textChanged.connect(lambda: script.action_update_config()) + self.base_url.textChanged.connect(lambda: script.action_update_controlnet_config()) self.base_url_reset.released.connect( lambda: self.base_url.setText(DEFAULTS.base_url) ) @@ -178,6 +179,7 @@ def restore_defaults(): script.cfg.set("first_setup", False) # retrieve list of available stuff again script.action_update_config() + script.action_update_controlnet_config() self.refresh_btn.released.connect(lambda: script.action_update_config()) self.restore_defaults.released.connect(restore_defaults) diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index a3b8cc88..f81813eb 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -1,5 +1,6 @@ -from krita import QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout +from krita import QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout +from ..defaults import CONTROLNET_PREPROCESSOR_SETTINGS from ..script import script from ..widgets import StatusBar, ImageLoaderLayout, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout @@ -14,7 +15,7 @@ def __init__(self, *args, **kwargs): ) self.controlnet_unit.qcombo.setEditable(False) self.controlnet_unit_layout_list = list(ControlNetUnitSettings(i) - for i in range(0, len(script.cfg("controlnet_unit_list")))) + for i in range(len(script.cfg("controlnet_unit_list")))) self.units_stacked_layout = QStackedLayout() @@ -28,7 +29,7 @@ def __init__(self, *args, **kwargs): self.setLayout(layout) def controlnet_unit_changed(self, selected: str): - self.units_stacked_layout.setCurrentIndex(int(selected)-1) + self.units_stacked_layout.setCurrentIndex(int(selected)) def cfg_init(self): self.controlnet_unit.cfg_init() @@ -88,6 +89,9 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): script.cfg, "controlnet_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" ) + #Refresh button + self.refresh_button = QPushButton("Refresh") + self.weight_layout = QSpinBoxLayout( script.cfg, f"controlnet{cfg_unit_number}_weight", label="Weight:", min=0, max=2, step=0.05 ) @@ -137,14 +141,12 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): main_settings_layout.addWidget(self.guess_mode) guidance_layout = QHBoxLayout() - guidance_layout.addLayout(self.weight_layout) guidance_layout.addLayout(self.guidance_start_layout) guidance_layout.addLayout(self.guidance_end_layout) - preprocessor_settings_layout = QHBoxLayout() - preprocessor_settings_layout.addLayout(self.annotator_resolution) - preprocessor_settings_layout.addLayout(self.treshold_a) - preprocessor_settings_layout.addLayout(self.treshold_b) + treshold_layout = QHBoxLayout() + treshold_layout.addLayout(self.treshold_a) + treshold_layout.addLayout(self.treshold_b) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) @@ -154,8 +156,11 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): layout.addLayout(main_settings_layout) layout.addLayout(self.preprocessor_layout) layout.addLayout(self.model_layout) + layout.addWidget(self.refresh_button) + layout.addLayout(self.weight_layout) layout.addLayout(guidance_layout) - layout.addLayout(preprocessor_settings_layout) + layout.addLayout(self.annotator_resolution) + layout.addLayout(treshold_layout) layout.addStretch() self.setLayout(layout) @@ -166,45 +171,51 @@ def change_image_loader_state(self, state): else: self.image_loader.enable() - def preprocessor_changed(self, selected: str): - self.init_preprocessor_layouts(selected) - if selected in preprocessor_settings: - self.annotator_resolution.label = preprocessor_settings[selected]["resolution_label"] \ - if "resolution_label" in preprocessor_settings[selected] else "Preprocessor resolution:" + def set_preprocessor_options(self, selected: str): + if selected in CONTROLNET_PREPROCESSOR_SETTINGS: + self.show_preprocessor_options() + self.annotator_resolution.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["resolution_label"] \ + if "resolution_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else "Preprocessor resolution:") - if "treshold_a_label" in preprocessor_settings[selected]: - self.treshold_a.label = preprocessor_settings[selected]["treshold_a_label"] - self.treshold_a.min = preprocessor_settings[selected]["treshold_a_min_value"] \ - if "treshold_a_min_value" in preprocessor_settings[selected] else 0 - self.treshold_a.max = preprocessor_settings[selected]["treshold_a_max_value"] \ - if "treshold_a_max_value" in preprocessor_settings[selected] else 0 - self.treshold_a.value = preprocessor_settings[selected]["treshold_a_value"] \ - if "treshold_a_value" in preprocessor_settings[selected] else 0 - self.treshold_a.step = preprocessor_settings[selected]["treshold_step"] \ - if "treshold_step" in preprocessor_settings[selected] else 1 + if "treshold_a_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: + self.treshold_a.qlabel.show() + self.treshold_a.qspin.show() + self.treshold_a.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_label"]) + self.treshold_a.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_min_value"] \ + if "treshold_a_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.treshold_a.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_max_value"] \ + if "treshold_a_max_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.treshold_a.qspin.setValue(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_value"] \ + if "treshold_a_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else self.treshold_a.qspin.minimum()) + self.treshold_a.qspin.setSingleStep(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_step"] \ + if "treshold_step" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 1) else: - self.treshold_a.hide() + self.treshold_a.qlabel.hide() + self.treshold_a.qspin.hide() - if "treshold_b_label" in preprocessor_settings[selected]: - self.treshold_b.label = preprocessor_settings[selected]["treshold_b_label"] - self.treshold_b.min = preprocessor_settings[selected]["treshold_b_min_value"] \ - if "treshold_b_min_value" in preprocessor_settings[selected] else 0 - self.treshold_b.max = preprocessor_settings[selected]["treshold_b_max_value"] \ - if "treshold_b_max_value" in preprocessor_settings[selected] else 0 - self.treshold_b.value = preprocessor_settings[selected]["treshold_b_value"] \ - if "treshold_b_value" in preprocessor_settings[selected] else 0 - self.treshold_b.step = preprocessor_settings[selected]["treshold_step"] \ - if "treshold_step" in preprocessor_settings[selected] else 1 - - def init_preprocessor_layouts(self, selected: str): - if selected in preprocessor_settings: - self.show_preprocessor_options() + if "treshold_b_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: + self.treshold_b.qlabel.show() + self.treshold_b.qspin.show() + self.treshold_b.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_label"]) + self.treshold_b.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_min_value"] \ + if "treshold_b_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.treshold_b.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_max_value"] \ + if "treshold_b_max_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.treshold_b.qspin.setValue(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_value"] \ + if "treshold_b_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else self.treshold_b.qspin.minimum()) + self.treshold_b.qspin.setSingleStep(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_step"] \ + if "treshold_b_step" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 1) + else: + self.treshold_b.qlabel.hide() + self.treshold_b.qspin.hide() else: - self.hide_preprocessor_options() + self.hide_preprocessor_options(selected) - def hide_preprocessor_options(self): - self.annotator_resolution.qlabel.hide() - self.annotator_resolution.qspin.hide() + def hide_preprocessor_options(self, selected: str): + if selected == "none": + self.annotator_resolution.qlabel.hide() + self.annotator_resolution.qspin.hide() + self.treshold_a.qlabel.hide() self.treshold_a.qspin.hide() self.treshold_b.qlabel.hide() @@ -218,6 +229,10 @@ def show_preprocessor_options(self): self.treshold_b.qlabel.show() self.treshold_b.qspin.show() + def enabled_changed(self, state): + if state == 1 or state == 2: + script.action_update_controlnet_config() + def cfg_init(self): self.enable.cfg_init() self.use_selection_as_input.cfg_init() @@ -230,8 +245,11 @@ def cfg_init(self): self.weight_layout.cfg_init() self.guidance_start_layout.cfg_init() self.guidance_end_layout.cfg_init() + self.annotator_resolution.cfg_init() + self.treshold_a.cfg_init() + self.treshold_b.cfg_init() self.change_image_loader_state(self.use_selection_as_input.checkState()) - self.init_preprocessor_layouts(self.preprocessor_layout.qcombo.currentText()) + self.set_preprocessor_options(self.preprocessor_layout.qcombo.currentText()) def cfg_connect(self): self.enable.cfg_connect() @@ -245,72 +263,10 @@ def cfg_connect(self): self.weight_layout.cfg_connect() self.guidance_start_layout.cfg_connect() self.guidance_end_layout.cfg_connect() + self.annotator_resolution.cfg_connect() + self.treshold_a.cfg_connect() + self.treshold_b.cfg_connect() + self.enable.stateChanged.connect(self.enabled_changed) self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) - self.preprocessor_layout.qcombo.currentTextChanged.connect(self.preprocessor_changed) - -preprocessor_settings = { - "canny": { - "resolution_label": "Annotator resolution", - "treshold_a_label": "Canny low treshold", - "treshold_b_label": "Canny high treshold", - "treshold_a_value": 100, - "treshold_b_value": 200, - "treshold_a_min_value": 1, - "treshold_a_max_value": 255, - "treshold_b_min_value": 1, - "treshold_b_max_value": 255 - }, - "depth": { - "resolution_label": "Midas resolution", - }, - "depth_leres": { - "resolution_label": "LeReS resolution", - "treshold_a_label": "Remove near %", - "treshold_b_label": "Remove background %", - "treshold_a_min_value": 0, - "treshold_a_max_value": 100, - "treshold_b_min_value": 0, - "treshold_b_max_value": 100 - }, - "hed": { - "resolution_label": "HED resolution", - }, - "mlsd": { - "resolution_label": "Hough resolution", - "treshold_a_label": "Hough value threshold (MLSD)", - "treshold_b_label": "Hough distance threshold (MLSD)", - "treshold_a_value": 0.1, - "treshold_b_value": 0.1, - "treshold_a_min_value": 0.01, - "treshold_b_max_value": 2, - "treshold_a_min_value": 0.01, - "treshold_b_max_value": 20, - "treshold_step": 0.01 - }, - "normal_map": { - "treshold_a_label": "Normal background threshold", - "treshold_a_value": 0.4, - "treshold_a_min_value": 0, - "treshold_a_max_value": 1, - "treshold_b_min_value": 0, - "treshold_b_max_value": 1, - "treshold_step": 0.01 - }, - "openpose": {}, - "openpose_hand": {}, - "clip_vision": {}, - "color": {}, - "pidinet": {}, - "scribble": {}, - "fake_scribble": { - "resolution_label": "HED resolution", - }, - "segmentation": {}, - "binary": { - "treshold_a_label": "Binary threshold", - "treshold_a_min_value": 0, - "treshold_a_max_value": 255, - "treshold_b_min_value": 0, - "treshold_b_max_value": 255, - } -} \ No newline at end of file + self.preprocessor_layout.qcombo.currentTextChanged.connect(self.set_preprocessor_options) + self.refresh_button.released.connect(lambda: script.action_update_controlnet_config()) \ No newline at end of file diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 069eb27f..2ad33ed5 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -444,6 +444,10 @@ def action_simple_upscale(self): def action_update_config(self): """Update certain config/state from the backend.""" self.client.get_config() + + def action_update_controlnet_config(self): + """Update controlnet config from the backend.""" + self.client.get_controlnet_config() def action_interrupt(self): def cb(resp=None): diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py index 9eeaf87a..78ff05ac 100644 --- a/frontends/krita/krita_diff/widgets/image_loader.py +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -7,37 +7,37 @@ def __init__(self, *args, **kwargs): self.preview = QLabel() self.preview.setAlignment(Qt.AlignCenter) - self.importBtn = QPushButton('Import image') - self.pasteBtn = QPushButton('Paste image') - self.clearBtn = QPushButton('Clear') + self.import_button = QPushButton('Import image') + self.paste_button = QPushButton('Paste image') + self.clear_button = QPushButton('Clear') - btnLayout = QHBoxLayout() - btnLayout.addWidget(self.importBtn) - btnLayout.addWidget(self.pasteBtn) + button_layout = QHBoxLayout() + button_layout.addWidget(self.import_button) + button_layout.addWidget(self.paste_button) - self.addLayout(btnLayout) - self.addWidget(self.clearBtn) + self.addLayout(button_layout) + self.addWidget(self.clear_button) self.addWidget(self.preview) - self.importBtn.released.connect(self.load_image) - self.pasteBtn.released.connect(self.paste_image) - self.clearBtn.released.connect(self.clear_image) + self.import_button.released.connect(self.load_image) + self.paste_button.released.connect(self.paste_image) + self.clear_button.released.connect(self.clear_image) def disable(self): self.preview.setEnabled(False) - self.importBtn.setEnabled(False) - self.pasteBtn.setEnabled(False) - self.clearBtn.setEnabled(False) + self.import_button.setEnabled(False) + self.paste_button.setEnabled(False) + self.clear_button.setEnabled(False) def enable(self): self.preview.setEnabled(True) - self.importBtn.setEnabled(True) - self.pasteBtn.setEnabled(True) - self.clearBtn.setEnabled(True) + self.import_button.setEnabled(True) + self.paste_button.setEnabled(True) + self.clear_button.setEnabled(True) def load_image(self): self.clear_image() - file_name, _ = QFileDialog.getOpenFileName(self.importBtn, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') + file_name, _ = QFileDialog.getOpenFileName(self.import_button, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') if file_name: pixmap = QPixmap(file_name) From f87f803ae5a0dcd6fb8b80610a9bbb617ad494cd Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Sun, 2 Apr 2023 10:44:20 -0600 Subject: [PATCH 08/55] Added the ability to preview annotators --- frontends/krita/krita_diff/client.py | 13 ++ frontends/krita/krita_diff/defaults.py | 112 ++++++------ .../krita/krita_diff/pages/controlnet.py | 171 +++++++++--------- frontends/krita/krita_diff/script.py | 23 +++ .../krita/krita_diff/widgets/image_loader.py | 12 -- 5 files changed, 174 insertions(+), 157 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 91d29c57..2d2f32f9 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -425,6 +425,19 @@ def post_upscale(self, cb, src_img): ) self.post("upscale", params, cb) + def post_controlnet_preview(self, cb, src_img, unit): + params = ( + { + "controlnet_module": self.cfg(f"controlnet{unit}_preprocessor"), + "controlnet_input_images": [img_to_b64(src_img)], + "controlnet_processor_res": self.cfg(f"controlnet{unit}_preprocessor_resolution"), + "controlnet_threshold_a": self.cfg(f"controlnet{unit}_threshold_a"), + "controlnet_threshold_b": self.cfg(f"controlnet{unit}_threshold_b") + } #Not sure if it's necessary to make the just_use_yaml validation here + ) + url = get_url(self.cfg, prefix=CONTROLNET_ROUTE_PREFIX) + self.post("detect", params, cb, url) + def post_interrupt(self, cb): # get official API url url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index 1405a448..faf2fc87 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -47,48 +47,48 @@ CONTROLNET_PREPROCESSOR_SETTINGS = { "canny": { "resolution_label": "Annotator resolution", - "treshold_a_label": "Canny low treshold", - "treshold_b_label": "Canny high treshold", - "treshold_a_value": 100, - "treshold_b_value": 200, - "treshold_a_min_value": 1, - "treshold_a_max_value": 255, - "treshold_b_min_value": 1, - "treshold_b_max_value": 255 + "threshold_a_label": "Canny low threshold", + "threshold_b_label": "Canny high threshold", + "threshold_a_value": 100, + "threshold_b_value": 200, + "threshold_a_min_value": 1, + "threshold_a_max_value": 255, + "threshold_b_min_value": 1, + "threshold_b_max_value": 255 }, "depth": { "resolution_label": "Midas resolution", }, "depth_leres": { "resolution_label": "LeReS resolution", - "treshold_a_label": "Remove near %", - "treshold_b_label": "Remove background %", - "treshold_a_min_value": 0, - "treshold_a_max_value": 100, - "treshold_b_min_value": 0, - "treshold_b_max_value": 100 + "threshold_a_label": "Remove near %", + "threshold_b_label": "Remove background %", + "threshold_a_min_value": 0, + "threshold_a_max_value": 100, + "threshold_b_min_value": 0, + "threshold_b_max_value": 100 }, "hed": { "resolution_label": "HED resolution", }, "mlsd": { "resolution_label": "Hough resolution", - "treshold_a_label": "Hough value threshold (MLSD)", - "treshold_b_label": "Hough distance threshold (MLSD)", - "treshold_a_value": 0.1, - "treshold_b_value": 0.1, - "treshold_a_min_value": 0.01, - "treshold_b_max_value": 2, - "treshold_a_min_value": 0.01, - "treshold_b_max_value": 20, - "treshold_step": 0.01 + "threshold_a_label": "Hough value threshold (MLSD)", + "threshold_b_label": "Hough distance threshold (MLSD)", + "threshold_a_value": 0.1, + "threshold_b_value": 0.1, + "threshold_a_min_value": 0.01, + "threshold_b_max_value": 2, + "threshold_a_min_value": 0.01, + "threshold_b_max_value": 20, + "threshold_step": 0.01 }, "normal_map": { - "treshold_a_label": "Normal background threshold", - "treshold_a_value": 0.4, - "treshold_a_min_value": 0, - "treshold_a_max_value": 1, - "treshold_step": 0.01 + "threshold_a_label": "Normal background threshold", + "threshold_a_value": 0.4, + "threshold_a_min_value": 0, + "threshold_a_max_value": 1, + "threshold_step": 0.01 }, "openpose": {}, "openpose_hand": {}, @@ -101,9 +101,9 @@ }, "segmentation": {}, "binary": { - "treshold_a_label": "Binary threshold", - "treshold_a_min_value": 0, - "treshold_a_max_value": 255, + "threshold_a_label": "Binary threshold", + "threshold_a_min_value": 0, + "threshold_a_max_value": 255, } } @@ -199,7 +199,6 @@ class Defaults: controlnet_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) controlnet0_enable: bool = False - controlnet0_use_selection_as_input: bool = True controlnet0_invert_input_color: bool = False controlnet0_RGB_to_BGR: bool = False controlnet0_low_vram: bool = False @@ -210,11 +209,10 @@ class Defaults: controlnet0_guidance_start: float = 0 controlnet0_guidance_end: float = 1 controlnet0_preprocessor_resolution: int = 512 - controlnet0_treshold_a: float = 0 - controlnet0_treshold_b: float = 0 + controlnet0_threshold_a: float = 0 + controlnet0_threshold_b: float = 0 controlnet1_enable: bool = False - controlnet1_use_selection_as_input: bool = True controlnet1_invert_input_color: bool = False controlnet1_RGB_to_BGR: bool = False controlnet1_low_vram: bool = False @@ -225,11 +223,10 @@ class Defaults: controlnet1_guidance_start: float = 0 controlnet1_guidance_end: float = 1 controlnet1_preprocessor_resolution: int = 512 - controlnet1_treshold_a: float = 0 - controlnet1_treshold_b: float = 0 + controlnet1_threshold_a: float = 0 + controlnet1_threshold_b: float = 0 controlnet2_enable: bool = False - controlnet2_use_selection_as_input: bool = True controlnet2_invert_input_color: bool = False controlnet2_RGB_to_BGR: bool = False controlnet2_low_vram: bool = False @@ -240,11 +237,10 @@ class Defaults: controlnet2_guidance_start: float = 0 controlnet2_guidance_end: float = 1 controlnet2_preprocessor_resolution: int = 512 - controlnet2_treshold_a: float = 0 - controlnet2_treshold_b: float = 0 + controlnet2_threshold_a: float = 0 + controlnet2_threshold_b: float = 0 controlnet3_enable: bool = False - controlnet3_use_selection_as_input: bool = True controlnet3_invert_input_color: bool = False controlnet3_RGB_to_BGR: bool = False controlnet3_low_vram: bool = False @@ -255,11 +251,10 @@ class Defaults: controlnet3_guidance_start: float = 0 controlnet3_guidance_end: float = 1 controlnet3_preprocessor_resolution: int = 512 - controlnet3_treshold_a: float = 0 - controlnet3_treshold_b: float = 0 + controlnet3_threshold_a: float = 0 + controlnet3_threshold_b: float = 0 controlnet4_enable: bool = False - controlnet4_use_selection_as_input: bool = True controlnet4_invert_input_color: bool = False controlnet4_RGB_to_BGR: bool = False controlnet4_low_vram: bool = False @@ -270,11 +265,10 @@ class Defaults: controlnet4_guidance_start: float = 0 controlnet4_guidance_end: float = 1 controlnet4_preprocessor_resolution: int = 512 - controlnet4_treshold_a: float = 0 - controlnet4_treshold_b: float = 0 + controlnet4_threshold_a: float = 0 + controlnet4_threshold_b: float = 0 controlnet5_enable: bool = False - controlnet5_use_selection_as_input: bool = True controlnet5_invert_input_color: bool = False controlnet5_RGB_to_BGR: bool = False controlnet5_low_vram: bool = False @@ -285,11 +279,10 @@ class Defaults: controlnet5_guidance_start: float = 0 controlnet5_guidance_end: float = 1 controlnet5_preprocessor_resolution: int = 512 - controlnet5_treshold_a: float = 0 - controlnet5_treshold_b: float = 0 + controlnet5_threshold_a: float = 0 + controlnet5_threshold_b: float = 0 controlnet6_enable: bool = False - controlnet6_use_selection_as_input: bool = True controlnet6_invert_input_color: bool = False controlnet6_RGB_to_BGR: bool = False controlnet6_low_vram: bool = False @@ -300,11 +293,10 @@ class Defaults: controlnet6_guidance_start: float = 0 controlnet6_guidance_end: float = 1 controlnet6_preprocessor_resolution: int = 512 - controlnet6_treshold_a: float = 0 - controlnet6_treshold_b: float = 0 + controlnet6_threshold_a: float = 0 + controlnet6_threshold_b: float = 0 controlnet7_enable: bool = False - controlnet7_use_selection_as_input: bool = True controlnet7_invert_input_color: bool = False controlnet7_RGB_to_BGR: bool = False controlnet7_low_vram: bool = False @@ -315,11 +307,10 @@ class Defaults: controlnet7_guidance_start: float = 0 controlnet7_guidance_end: float = 1 controlnet7_preprocessor_resolution: int = 512 - controlnet7_treshold_a: float = 0 - controlnet7_treshold_b: float = 0 + controlnet7_threshold_a: float = 0 + controlnet7_threshold_b: float = 0 controlnet8_enable: bool = False - controlnet8_use_selection_as_input: bool = True controlnet8_invert_input_color: bool = False controlnet8_RGB_to_BGR: bool = False controlnet8_low_vram: bool = False @@ -330,11 +321,10 @@ class Defaults: controlnet8_guidance_start: float = 0 controlnet8_guidance_end: float = 1 controlnet8_preprocessor_resolution: int = 512 - controlnet8_treshold_a: float = 0 - controlnet8_treshold_b: float = 0 + controlnet8_threshold_a: float = 0 + controlnet8_threshold_b: float = 0 controlnet9_enable: bool = False - controlnet9_use_selection_as_input: bool = True controlnet9_invert_input_color: bool = False controlnet9_RGB_to_BGR: bool = False controlnet9_low_vram: bool = False @@ -345,7 +335,7 @@ class Defaults: controlnet9_guidance_start: float = 0 controlnet9_guidance_end: float = 1 controlnet9_preprocessor_resolution: int = 512 - controlnet9_treshold_a: float = 0 - controlnet9_treshold_b: float = 0 + controlnet9_threshold_a: float = 0 + controlnet9_threshold_b: float = 0 DEFAULTS = Defaults() diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index f81813eb..142cd7ed 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -1,8 +1,8 @@ -from krita import QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout +from krita import QPixmap, QImage, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout, Qt from ..defaults import CONTROLNET_PREPROCESSOR_SETTINGS from ..script import script -from ..widgets import StatusBar, ImageLoaderLayout, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout +from ..widgets import QLabel, StatusBar, ImageLoaderLayout, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout class ControlNetPage(QWidget): name = "ControlNet" @@ -49,90 +49,89 @@ def cfg_connect(self): class ControlNetUnitSettings(QWidget): def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): super(ControlNetUnitSettings, self).__init__(*args, **kwargs) + self.unit = cfg_unit_number #Top checkbox self.enable = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_enable", f"Enable ControlNet {cfg_unit_number}" - ) - self.use_selection_as_input = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_use_selection_as_input", "Use selection as input" + script.cfg, f"controlnet{self.unit}_enable", f"Enable ControlNet {self.unit}" ) self.image_loader = ImageLoaderLayout() #Main settings self.invert_input_color = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_invert_input_color", "Invert input color" + script.cfg, f"controlnet{self.unit}_invert_input_color", "Invert input color" ) self.RGB_to_BGR = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_RGB_to_BGR", "RGB to BGR" + script.cfg, f"controlnet{self.unit}_RGB_to_BGR", "RGB to BGR" ) self.low_vram = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_low_vram", "Low VRAM" + script.cfg, f"controlnet{self.unit}_low_vram", "Low VRAM" ) self.guess_mode = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_guess_mode", "Guess mode" + script.cfg, f"controlnet{self.unit}_guess_mode", "Guess mode" ) #Tips self.tips = TipsLayout( - ["Invert colors if your image has white background."] + ["Invert colors if your image has white background.", + "Selection will be used as input if no image has been uploaded or pasted."] ) #Preprocessor list self.preprocessor_layout = QComboBoxLayout( - script.cfg, "controlnet_preprocessor_list", f"controlnet{cfg_unit_number}_preprocessor", label="Preprocessor:" + script.cfg, "controlnet_preprocessor_list", f"controlnet{self.unit}_preprocessor", label="Preprocessor:" ) #Model list self.model_layout = QComboBoxLayout( - script.cfg, "controlnet_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" + script.cfg, "controlnet_model_list", f"controlnet{self.unit}_model", label="Model:" ) #Refresh button self.refresh_button = QPushButton("Refresh") self.weight_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_weight", label="Weight:", min=0, max=2, step=0.05 + script.cfg, f"controlnet{self.unit}_weight", label="Weight:", min=0, max=2, step=0.05 ) - self.guidance_start_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_guidance_start", label="Guidance start:", min=0, max=1, step=0.01 + script.cfg, f"controlnet{self.unit}_guidance_start", label="Guidance start:", min=0, max=1, step=0.01 ) - self.guidance_end_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_guidance_end", label="Guidance end:", min=0, max=1, step=0.01 + script.cfg, f"controlnet{self.unit}_guidance_end", label="Guidance end:", min=0, max=1, step=0.01 ) #Preprocessor settings self.annotator_resolution = QSpinBoxLayout( script.cfg, - f"controlnet{cfg_unit_number}_preprocessor_resolution", + f"controlnet{self.unit}_preprocessor_resolution", label="Preprocessor resolution:", min=64, max=2048, step=1 ) - self.treshold_a = QSpinBoxLayout( + self.threshold_a = QSpinBoxLayout( script.cfg, - f"controlnet{cfg_unit_number}_treshold_a", - label="Treshold A:", + f"controlnet{self.unit}_threshold_a", + label="Threshold A:", min=1, max=255, step=1 ) - self.treshold_b = QSpinBoxLayout( + self.threshold_b = QSpinBoxLayout( script.cfg, - f"controlnet{cfg_unit_number}_treshold_b", - label="Treshold B:", + f"controlnet{self.unit}_threshold_b", + label="Threshold B:", min=1, max=255, step=1 ) - top_checkbox_layout = QHBoxLayout() - top_checkbox_layout.addWidget(self.enable) - top_checkbox_layout.addWidget(self.use_selection_as_input) + #Preview annotator + self.annotator_preview = QLabel() + self.annotator_preview.setAlignment(Qt.AlignCenter) + self.annotator_preview_button = QPushButton("Preview annotator") + self.annotator_clear_button = QPushButton("Clear preview") main_settings_layout = QHBoxLayout() main_settings_layout.addWidget(self.invert_input_color) @@ -144,13 +143,13 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): guidance_layout.addLayout(self.guidance_start_layout) guidance_layout.addLayout(self.guidance_end_layout) - treshold_layout = QHBoxLayout() - treshold_layout.addLayout(self.treshold_a) - treshold_layout.addLayout(self.treshold_b) + threshold_layout = QHBoxLayout() + threshold_layout.addLayout(self.threshold_a) + threshold_layout.addLayout(self.threshold_b) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) - layout.addLayout(top_checkbox_layout) + layout.addWidget(self.enable) layout.addLayout(self.image_loader) layout.addLayout(self.tips) layout.addLayout(main_settings_layout) @@ -160,74 +159,75 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): layout.addLayout(self.weight_layout) layout.addLayout(guidance_layout) layout.addLayout(self.annotator_resolution) - layout.addLayout(treshold_layout) + layout.addLayout(threshold_layout) + layout.addWidget(self.annotator_preview) + layout.addWidget(self.annotator_preview_button) + layout.addWidget(self.annotator_clear_button) layout.addStretch() self.setLayout(layout) - def change_image_loader_state(self, state): - if state == 1 or state == 2: - self.image_loader.disable() - else: - self.image_loader.enable() - def set_preprocessor_options(self, selected: str): if selected in CONTROLNET_PREPROCESSOR_SETTINGS: self.show_preprocessor_options() self.annotator_resolution.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["resolution_label"] \ if "resolution_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else "Preprocessor resolution:") - if "treshold_a_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: - self.treshold_a.qlabel.show() - self.treshold_a.qspin.show() - self.treshold_a.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_label"]) - self.treshold_a.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_min_value"] \ - if "treshold_a_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) - self.treshold_a.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_max_value"] \ - if "treshold_a_max_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) - self.treshold_a.qspin.setValue(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_value"] \ - if "treshold_a_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else self.treshold_a.qspin.minimum()) - self.treshold_a.qspin.setSingleStep(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_step"] \ - if "treshold_step" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 1) + if "threshold_a_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: + self.threshold_a.qlabel.show() + self.threshold_a.qspin.show() + self.threshold_a.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_label"]) + self.threshold_a.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_min_value"] \ + if "threshold_a_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.threshold_a.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_max_value"] \ + if "threshold_a_max_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.threshold_a.qspin.setValue(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_value"] \ + if "threshold_a_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else self.threshold_a.qspin.minimum()) + self.threshold_a.qspin.setSingleStep(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_step"] \ + if "threshold_step" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 1) else: - self.treshold_a.qlabel.hide() - self.treshold_a.qspin.hide() + self.threshold_a.qlabel.hide() + self.threshold_a.qspin.hide() - if "treshold_b_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: - self.treshold_b.qlabel.show() - self.treshold_b.qspin.show() - self.treshold_b.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_label"]) - self.treshold_b.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_min_value"] \ - if "treshold_b_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) - self.treshold_b.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_max_value"] \ - if "treshold_b_max_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) - self.treshold_b.qspin.setValue(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_value"] \ - if "treshold_b_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else self.treshold_b.qspin.minimum()) - self.treshold_b.qspin.setSingleStep(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_step"] \ - if "treshold_b_step" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 1) + if "threshold_b_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: + self.threshold_b.qlabel.show() + self.threshold_b.qspin.show() + self.threshold_b.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_label"]) + self.threshold_b.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_min_value"] \ + if "threshold_b_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.threshold_b.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_max_value"] \ + if "threshold_b_max_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.threshold_b.qspin.setValue(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_value"] \ + if "threshold_b_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else self.threshold_b.qspin.minimum()) + self.threshold_b.qspin.setSingleStep(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_step"] \ + if "threshold_b_step" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 1) else: - self.treshold_b.qlabel.hide() - self.treshold_b.qspin.hide() + self.threshold_b.qlabel.hide() + self.threshold_b.qspin.hide() else: self.hide_preprocessor_options(selected) def hide_preprocessor_options(self, selected: str): + #Hide all annotator settings if no annotator chosen. + #if there is an annotator that hasn't been listed in defaults, + #just show resolution option. Users may be able to play + #with new unsupported annotators, but they may or not work. if selected == "none": self.annotator_resolution.qlabel.hide() self.annotator_resolution.qspin.hide() - self.treshold_a.qlabel.hide() - self.treshold_a.qspin.hide() - self.treshold_b.qlabel.hide() - self.treshold_b.qspin.hide() + self.threshold_a.qlabel.hide() + self.threshold_a.qspin.hide() + self.threshold_b.qlabel.hide() + self.threshold_b.qspin.hide() def show_preprocessor_options(self): self.annotator_resolution.qlabel.show() self.annotator_resolution.qspin.show() - self.treshold_a.qlabel.show() - self.treshold_a.qspin.show() - self.treshold_b.qlabel.show() - self.treshold_b.qspin.show() + self.threshold_a.qlabel.show() + self.threshold_a.qspin.show() + self.threshold_b.qlabel.show() + self.threshold_b.qspin.show() def enabled_changed(self, state): if state == 1 or state == 2: @@ -235,7 +235,6 @@ def enabled_changed(self, state): def cfg_init(self): self.enable.cfg_init() - self.use_selection_as_input.cfg_init() self.invert_input_color.cfg_init() self.RGB_to_BGR.cfg_init() self.low_vram.cfg_init() @@ -246,14 +245,12 @@ def cfg_init(self): self.guidance_start_layout.cfg_init() self.guidance_end_layout.cfg_init() self.annotator_resolution.cfg_init() - self.treshold_a.cfg_init() - self.treshold_b.cfg_init() - self.change_image_loader_state(self.use_selection_as_input.checkState()) + self.threshold_a.cfg_init() + self.threshold_b.cfg_init() self.set_preprocessor_options(self.preprocessor_layout.qcombo.currentText()) def cfg_connect(self): self.enable.cfg_connect() - self.use_selection_as_input.cfg_connect() self.invert_input_color.cfg_connect() self.RGB_to_BGR.cfg_connect() self.low_vram.cfg_connect() @@ -264,9 +261,15 @@ def cfg_connect(self): self.guidance_start_layout.cfg_connect() self.guidance_end_layout.cfg_connect() self.annotator_resolution.cfg_connect() - self.treshold_a.cfg_connect() - self.treshold_b.cfg_connect() + self.threshold_a.cfg_connect() + self.threshold_b.cfg_connect() self.enable.stateChanged.connect(self.enabled_changed) - self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) self.preprocessor_layout.qcombo.currentTextChanged.connect(self.set_preprocessor_options) - self.refresh_button.released.connect(lambda: script.action_update_controlnet_config()) \ No newline at end of file + self.refresh_button.released.connect(lambda: script.action_update_controlnet_config()) + self.annotator_preview_button.released.connect(lambda: script.action_preview_controlnet_annotator( + self.image_loader.preview.pixmap().toImage().convertToFormat(QImage.Format_RGBA8888).rgbSwapped() + if self.image_loader.preview.pixmap() is not None else None, + self.annotator_preview, + self.unit + )) + self.annotator_clear_button.released.connect(lambda: self.annotator_preview.setPixmap(QPixmap())) \ No newline at end of file diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 2ad33ed5..c18ea9ed 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -9,6 +9,7 @@ Node, QImage, QObject, + QPixmap, Qt, QTimer, Selection, @@ -333,6 +334,20 @@ def cb(response): self.selection is not None, ) + def apply_controlnet_preview_annotator(self, image, preview_label, unit: int): + image = self.get_selection_image() if image is None else image + + def cb(response): + assert response is not None, "Backend Error, check terminal" + output = response["images"][0] + pixmap = QPixmap.fromImage(b64_to_img(output)) + + if pixmap.width() > preview_label.width(): + pixmap = pixmap.scaledToWidth(preview_label.width(), Qt.SmoothTransformation) + preview_label.setPixmap(pixmap) + + self.client.post_controlnet_preview(cb, image, unit) + def apply_simple_upscale(self): insert, _ = self.img_inserter(self.x, self.y, self.width, self.height) sel_image = self.get_selection_image() @@ -449,6 +464,14 @@ def action_update_controlnet_config(self): """Update controlnet config from the backend.""" self.client.get_controlnet_config() + def action_preview_controlnet_annotator(self, image, label, unit: int): + self.status_changed.emit(STATE_WAIT) + self.update_selection() + if not self.doc: + return + self.adjust_selection() + self.apply_controlnet_preview_annotator(image, label, unit) + def action_interrupt(self): def cb(resp=None): self.status_changed.emit(STATE_INTERRUPT) diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py index 78ff05ac..7b6dc728 100644 --- a/frontends/krita/krita_diff/widgets/image_loader.py +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -23,18 +23,6 @@ def __init__(self, *args, **kwargs): self.paste_button.released.connect(self.paste_image) self.clear_button.released.connect(self.clear_image) - def disable(self): - self.preview.setEnabled(False) - self.import_button.setEnabled(False) - self.paste_button.setEnabled(False) - self.clear_button.setEnabled(False) - - def enable(self): - self.preview.setEnabled(True) - self.import_button.setEnabled(True) - self.paste_button.setEnabled(True) - self.clear_button.setEnabled(True) - def load_image(self): self.clear_image() file_name, _ = QFileDialog.getOpenFileName(self.import_button, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') From a0a3f55e8aaba3292a426e8eb378034dece959e4 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Mon, 3 Apr 2023 11:03:40 -0600 Subject: [PATCH 09/55] txt2img work in progress --- frontends/krita/krita_diff/client.py | 116 +++++++++++++++++- frontends/krita/krita_diff/defaults.py | 10 ++ .../krita/krita_diff/pages/controlnet.py | 30 +++-- frontends/krita/krita_diff/script.py | 58 +++++++-- frontends/krita/krita_diff/utils.py | 24 ++-- 5 files changed, 211 insertions(+), 27 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 2d2f32f9..bdb83257 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -21,7 +21,14 @@ STATE_URLERROR, THREADED, ) -from .utils import bytewise_xor, fix_prompt, get_ext_args, get_ext_key, img_to_b64 +from .utils import ( + bytewise_xor, + fix_prompt, + get_ext_args, + get_ext_key, + img_to_b64, + calculate_resized_image_dimensions +) # NOTE: backend queues up responses, so no explicit need to block multiple requests # except to prevent user from spamming themselves @@ -239,6 +246,53 @@ def common_params(self, has_selection): save_samples=self.cfg("save_temp_images", bool), ) return params + + def options_params(self): + """Parameters that are specific for the official API options endpoint.""" + params = dict( + sd_model_checkpoint=self.cfg("sd_model", str), + sd_vae=self.cfg("sd_vae", str), + CLIP_stop_at_last_layers=self.cfg("clip_skip", int), + upscaler_for_img2img=self.cfg("upscaler_name", str), + face_restoration_model=self.cfg("face_restorer_model", str), + code_former_weight=self.cfg("codeformer_weight", float), + #Couldn't find filter_nsfw option for official API. + img2img_fix_steps=self.cfg("do_exact_steps", bool), #Not sure if this is matched correctly. + return_grid=self.cfg("include_grid", bool) + ) + return params + + def official_api_common_params(self, has_selection, width, height): + """Parameters used by most official API endpoints.""" + tiling = self.cfg("sd_tiling", bool) and not ( + self.cfg("only_full_img_tiling", bool) and has_selection + ) + + params = dict( + batch_size=self.cfg("sd_batch_size", int), + width=width, + height=height, + tiling=tiling, + restore_faces=self.cfg("face_restorer_model", str) != "None", + save_images=self.cfg("save_temp_images", bool), + ) + return params + + def controlnet_unit_params(self, image: str, unit: int): + params = dict( + input_image=image, + module=self.cfg(f"controlnet{unit}_preprocessor"), + model=self.cfg(f"controlnet{unit}_model"), + weight=self.cfg(f"controlnet{unit}_weight"), + lowvram=self.cfg(f"controlnet{unit}_low_vram"), + processor_res=self.cfg(f"controlnet{unit}_preprocessor_resolution"), + threshold_a=self.cfg(f"controlnet{unit}_threshold_a"), + threshold_b=self.cfg(f"controlnet{unit}_threshold_b"), + guidance_start=self.cfg(f"controlnet{unit}_guidance_start"), + guidance_end=self.cfg(f"controlnet{unit}_guidance_end"), + guessmode=self.cfg(f"controlnet{unit}_guess_mode") + ) + return params def get_config(self): def cb(obj): @@ -295,8 +349,8 @@ def cb(obj): self.get("config", cb, ignore_no_connection=True) - #Get config for controlnet def get_controlnet_config(self): + '''Get models and modules for ControlNet''' def check_response(obj, key: str): try: assert key in obj @@ -322,6 +376,15 @@ def set_preprocessor_list(obj): self.get("model_list", set_model_list, base_url=url) self.get("module_list", set_preprocessor_list, base_url=url) + def post_options(self): + """Sets the options for the backend, using the official API""" + def cb(response): + assert response is not None, "Backend Error, check terminal" + + params = self.options_params() + url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) + self.post("options", params, cb, base_url=url) + def post_txt2img(self, cb, width, height, has_selection): params = dict(orig_width=width, orig_height=height) if not self.cfg("just_use_yaml", bool): @@ -348,6 +411,52 @@ def post_txt2img(self, cb, width, height, has_selection): self.post("txt2img", params, cb) + def post_controlnet_txt2image(self, cb, width, height, has_selection, src_imgs: dict): + """Uses official API""" + if not self.cfg("just_use_yaml", bool): + seed = ( + int(self.cfg("txt2img_seed", str)) # Qt casts int as 32-bit int + if not self.cfg("txt2img_seed", str).strip() == "" + else -1 + ) + ext_name = self.cfg("txt2img_script", str) + ext_args = get_ext_args(self.ext_cfg, "scripts_txt2img", ext_name) + resized_width, resized_height = calculate_resized_image_dimensions( + self.cfg("sd_base_size", int), self.cfg("sd_max_size", int), width, height + ) + params = self.official_api_common_params( + has_selection, resized_width, resized_height + ) + params.update( + prompt=fix_prompt(self.cfg("txt2img_prompt", str)), + negative_prompt=fix_prompt(self.cfg("txt2img_negative_prompt", str)), + sampler_name=self.cfg("txt2img_sampler", str), + steps=self.cfg("txt2img_steps", int), + cfg_scale=self.cfg("txt2img_cfg_scale", float), + seed=seed, + enable_hr=self.cfg("txt2img_highres", bool), + hr_upscaler=self.cfg("upscaler_name", str), + hr_resize_x=width, + hr_resize_y=height, + denoising_strength=self.cfg("txt2img_denoising_strength", float), + script=ext_name, + script_args=ext_args, + ) + + controlnet_units_param = list() + + for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))): + if self.cfg(f"controlnet{i}_enable", bool): + controlnet_units_param.append( + self.controlnet_unit_params(img_to_b64(src_imgs[str(i)]), i) + ) + + params.update(controlnet_units=controlnet_units_param) + + self.post_options() + url = get_url(self.cfg, prefix=CONTROLNET_ROUTE_PREFIX) + self.post("txt2img", params, cb, base_url=url) + def post_img2img(self, cb, src_img, mask_img, has_selection): params = dict(is_inpaint=False, src_img=img_to_b64(src_img)) if not self.cfg("just_use_yaml", bool): @@ -425,7 +534,8 @@ def post_upscale(self, cb, src_img): ) self.post("upscale", params, cb) - def post_controlnet_preview(self, cb, src_img, unit): + def post_controlnet_preview(self, cb, src_img): + unit = self.cfg("controlnet_unit") params = ( { "controlnet_module": self.cfg(f"controlnet{unit}_preprocessor"), diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index faf2fc87..79fb9e38 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -211,6 +211,7 @@ class Defaults: controlnet0_preprocessor_resolution: int = 512 controlnet0_threshold_a: float = 0 controlnet0_threshold_b: float = 0 + controlnet0_input_image: str = "" controlnet1_enable: bool = False controlnet1_invert_input_color: bool = False @@ -225,6 +226,7 @@ class Defaults: controlnet1_preprocessor_resolution: int = 512 controlnet1_threshold_a: float = 0 controlnet1_threshold_b: float = 0 + controlnet1_input_image: str = "" controlnet2_enable: bool = False controlnet2_invert_input_color: bool = False @@ -239,6 +241,7 @@ class Defaults: controlnet2_preprocessor_resolution: int = 512 controlnet2_threshold_a: float = 0 controlnet2_threshold_b: float = 0 + controlnet2_input_image: str = "" controlnet3_enable: bool = False controlnet3_invert_input_color: bool = False @@ -253,6 +256,7 @@ class Defaults: controlnet3_preprocessor_resolution: int = 512 controlnet3_threshold_a: float = 0 controlnet3_threshold_b: float = 0 + controlnet3_input_image: str = "" controlnet4_enable: bool = False controlnet4_invert_input_color: bool = False @@ -267,6 +271,7 @@ class Defaults: controlnet4_preprocessor_resolution: int = 512 controlnet4_threshold_a: float = 0 controlnet4_threshold_b: float = 0 + controlnet4_input_image: str = "" controlnet5_enable: bool = False controlnet5_invert_input_color: bool = False @@ -281,6 +286,7 @@ class Defaults: controlnet5_preprocessor_resolution: int = 512 controlnet5_threshold_a: float = 0 controlnet5_threshold_b: float = 0 + controlnet5_input_image: str = "" controlnet6_enable: bool = False controlnet6_invert_input_color: bool = False @@ -295,6 +301,7 @@ class Defaults: controlnet6_preprocessor_resolution: int = 512 controlnet6_threshold_a: float = 0 controlnet6_threshold_b: float = 0 + controlnet6_input_image: str = "" controlnet7_enable: bool = False controlnet7_invert_input_color: bool = False @@ -309,6 +316,7 @@ class Defaults: controlnet7_preprocessor_resolution: int = 512 controlnet7_threshold_a: float = 0 controlnet7_threshold_b: float = 0 + controlnet7_input_image: str = "" controlnet8_enable: bool = False controlnet8_invert_input_color: bool = False @@ -323,6 +331,7 @@ class Defaults: controlnet8_preprocessor_resolution: int = 512 controlnet8_threshold_a: float = 0 controlnet8_threshold_b: float = 0 + controlnet8_input_image: str = "" controlnet9_enable: bool = False controlnet9_invert_input_color: bool = False @@ -337,5 +346,6 @@ class Defaults: controlnet9_preprocessor_resolution: int = 512 controlnet9_threshold_a: float = 0 controlnet9_threshold_b: float = 0 + controlnet9_input_image: str = "" DEFAULTS = Defaults() diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index 142cd7ed..c07ae10a 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -1,8 +1,10 @@ from krita import QPixmap, QImage, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout, Qt +from functools import partial from ..defaults import CONTROLNET_PREPROCESSOR_SETTINGS from ..script import script from ..widgets import QLabel, StatusBar, ImageLoaderLayout, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout +from ..utils import img_to_b64, b64_to_img class ControlNetPage(QWidget): name = "ControlNet" @@ -48,7 +50,7 @@ def cfg_connect(self): class ControlNetUnitSettings(QWidget): def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): - super(ControlNetUnitSettings, self).__init__(*args, **kwargs) + super(ControlNetUnitSettings, self).__init__(*args, **kwargs) self.unit = cfg_unit_number #Top checkbox @@ -57,6 +59,10 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): ) self.image_loader = ImageLoaderLayout() + input_image = script.cfg(f"controlnet{self.unit}_input_image", str) + self.image_loader.preview.setPixmap( + QPixmap.fromImage(b64_to_img(input_image) if input_image else QImage()) + ) #Main settings self.invert_input_color = QCheckBox( @@ -229,10 +235,14 @@ def show_preprocessor_options(self): self.threshold_b.qlabel.show() self.threshold_b.qspin.show() - def enabled_changed(self, state): + def enable_changed(self, state): if state == 1 or state == 2: script.action_update_controlnet_config() + def image_loaded(self): + image = self.image_loader.preview.pixmap().toImage().convertToFormat(QImage.Format_RGBA8888) + script.cfg.set(f"controlnet{self.unit}_input_image", img_to_b64(image)) + def cfg_init(self): self.enable.cfg_init() self.invert_input_color.cfg_init() @@ -263,13 +273,15 @@ def cfg_connect(self): self.annotator_resolution.cfg_connect() self.threshold_a.cfg_connect() self.threshold_b.cfg_connect() - self.enable.stateChanged.connect(self.enabled_changed) + self.enable.stateChanged.connect(self.enable_changed) + self.image_loader.import_button.released.connect(self.image_loaded) + self.image_loader.paste_button.released.connect(self.image_loaded) + self.image_loader.clear_button.released.connect( + partial(script.cfg.set, f"controlnet{self.unit}_input_image", "") + ) self.preprocessor_layout.qcombo.currentTextChanged.connect(self.set_preprocessor_options) self.refresh_button.released.connect(lambda: script.action_update_controlnet_config()) - self.annotator_preview_button.released.connect(lambda: script.action_preview_controlnet_annotator( - self.image_loader.preview.pixmap().toImage().convertToFormat(QImage.Format_RGBA8888).rgbSwapped() - if self.image_loader.preview.pixmap() is not None else None, - self.annotator_preview, - self.unit - )) + self.annotator_preview_button.released.connect( + lambda: script.action_preview_controlnet_annotator(self.annotator_preview) + ) self.annotator_clear_button.released.connect(lambda: self.annotator_preview.setPixmap(QPixmap())) \ No newline at end of file diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index c18ea9ed..2a9ab7c0 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -29,6 +29,7 @@ ) from .utils import ( b64_to_img, + img_to_b64, find_optimal_selection_region, get_desc_from_resp, img_to_ba, @@ -250,6 +251,29 @@ def insert(layer_name, enc): return layer return insert, glayer + + def check_controlnet_enabled(self): + for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))): + if self.cfg(f"controlnet{i}_enable", bool): + return True + + def get_controlnet_input_images(self, selected): + input_images = dict() + + for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))): + if self.cfg(f"controlnet{i}_enable", bool): + input_image = b64_to_img(self.cfg(f"controlnet{i}_input_image", str)) + + if input_image: + if self.cfg(f"controlnet{i}_invert_input_color", bool) or \ + self.cfg(f"controlnet{i}_RGB_to_BGR", bool): + input_image.rgbSwapped() + + input_images.update({f"{i}": input_image}) + else: + input_images.update({f"{i}": selected}) + + return input_images def apply_txt2img(self): # freeze selection region @@ -277,9 +301,17 @@ def cb(response): mask_trigger(layers) self.eta_timer.start(ETA_REFRESH_INTERVAL) - self.client.post_txt2img( - cb, self.width, self.height, self.selection is not None - ) + + if (self.check_controlnet_enabled()): + sel_image = self.get_selection_image() + self.client.post_controlnet_txt2image( + cb, self.width, self.height, self.selection is not None, + self.get_controlnet_input_images(sel_image) + ) + else: + self.client.post_txt2img( + cb, self.width, self.height, self.selection is not None + ) def apply_img2img(self, is_inpaint): insert, glayer = self.img_inserter( @@ -334,8 +366,18 @@ def cb(response): self.selection is not None, ) - def apply_controlnet_preview_annotator(self, image, preview_label, unit: int): - image = self.get_selection_image() if image is None else image + def apply_controlnet_preview_annotator(self, preview_label): + unit = self.cfg("controlnet_unit") + if self.cfg(f"controlnet{unit}_input_image"): + image = b64_to_img(self.cfg(f"controlnet{unit}_input_image")) + + #self.get_selection_image() already performs a image.rgbSwapped() + #so, I have decided not to play with it. + if self.cfg(f"controlnet{unit}_invert_input_color", bool) or \ + self.cfg(f"controlnet{unit}_RGB_to_BGR", bool): + image.rgbSwapped() + else: + image = self.get_selection_image() def cb(response): assert response is not None, "Backend Error, check terminal" @@ -346,7 +388,7 @@ def cb(response): pixmap = pixmap.scaledToWidth(preview_label.width(), Qt.SmoothTransformation) preview_label.setPixmap(pixmap) - self.client.post_controlnet_preview(cb, image, unit) + self.client.post_controlnet_preview(cb, image) def apply_simple_upscale(self): insert, _ = self.img_inserter(self.x, self.y, self.width, self.height) @@ -464,13 +506,13 @@ def action_update_controlnet_config(self): """Update controlnet config from the backend.""" self.client.get_controlnet_config() - def action_preview_controlnet_annotator(self, image, label, unit: int): + def action_preview_controlnet_annotator(self, preview_label): self.status_changed.emit(STATE_WAIT) self.update_selection() if not self.doc: return self.adjust_selection() - self.apply_controlnet_preview_annotator(image, label, unit) + self.apply_controlnet_preview_annotator(preview_label) def action_interrupt(self): def cb(resp=None): diff --git a/frontends/krita/krita_diff/utils.py b/frontends/krita/krita_diff/utils.py index ff948be1..44195742 100644 --- a/frontends/krita/krita_diff/utils.py +++ b/frontends/krita/krita_diff/utils.py @@ -14,6 +14,7 @@ TAB_SDCOMMON, TAB_TXT2IMG, TAB_UPSCALE, + TAB_CONTROLNET ) @@ -50,14 +51,11 @@ def get_ext_args(ext_cfg: Config, ext_type: str, ext_name: str): args.append(val) return args - -def find_fixed_aspect_ratio( - base_size: int, max_size: int, orig_width: int, orig_height: int +def calculate_resized_image_dimensions( + base_size: int, max_size: int, orig_width: int, orig_height: int ): - """Copy of `krita_server.utils.sddebz_highres_fix()`. - - This is used by `find_optimal_selection_region()` below to adjust the selected region. - """ + """Finds the dimensions of the resized images based on base_size and max_size. + See https://github.com/Interpause/auto-sd-paint-ext#faq for more details.""" def rnd(r, x, z=64): """Scale dimension x with stride z while attempting to preserve aspect ratio r.""" @@ -75,7 +73,17 @@ def rnd(r, x, z=64): width, height = base_size, rnd(1 / ratio, base_size) if height > max_size: width, height = rnd(ratio, max_size), max_size + + return width, height + +def find_fixed_aspect_ratio( + base_size: int, max_size: int, orig_width: int, orig_height: int +): + """Copy of `krita_server.utils.sddebz_highres_fix()`. + This is used by `find_optimal_selection_region()` below to adjust the selected region. + """ + width, height = calculate_resized_image_dimensions(base_size, max_size, orig_width, orig_height) return width / height @@ -233,8 +241,10 @@ def reset_docker_layout(): qmainwindow.tabifyDockWidget(dockers[TAB_SDCOMMON], dockers[TAB_CONFIG]) qmainwindow.tabifyDockWidget(dockers[TAB_SDCOMMON], dockers[TAB_PREVIEW]) + qmainwindow.tabifyDockWidget(dockers[TAB_SDCOMMON], dockers[TAB_CONTROLNET]) qmainwindow.tabifyDockWidget(dockers[TAB_TXT2IMG], dockers[TAB_IMG2IMG]) qmainwindow.tabifyDockWidget(dockers[TAB_TXT2IMG], dockers[TAB_INPAINT]) qmainwindow.tabifyDockWidget(dockers[TAB_TXT2IMG], dockers[TAB_UPSCALE]) dockers[TAB_SDCOMMON].raise_() dockers[TAB_INPAINT].raise_() + From 8dccecdcc59d922de1a2028eeacd4b6481a38bcb Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Tue, 4 Apr 2023 00:18:26 -0600 Subject: [PATCH 10/55] Working txt2img --- frontends/krita/krita_diff/client.py | 82 +++++++++++-------- .../krita/krita_diff/pages/controlnet.py | 9 ++ frontends/krita/krita_diff/script.py | 27 +++--- .../krita/krita_diff/widgets/image_loader.py | 8 +- 4 files changed, 75 insertions(+), 51 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index bdb83257..6c1f72ab 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -248,7 +248,8 @@ def common_params(self, has_selection): return params def options_params(self): - """Parameters that are specific for the official API options endpoint.""" + """Parameters that are specific for the official API options endpoint + or overriding settings.""" params = dict( sd_model_checkpoint=self.cfg("sd_model", str), sd_vae=self.cfg("sd_vae", str), @@ -262,7 +263,8 @@ def options_params(self): ) return params - def official_api_common_params(self, has_selection, width, height): + def official_api_common_params(self, has_selection, width, height, + controlnet_src_imgs): """Parameters used by most official API endpoints.""" tiling = self.cfg("sd_tiling", bool) and not ( self.cfg("only_full_img_tiling", bool) and has_selection @@ -275,22 +277,41 @@ def official_api_common_params(self, has_selection, width, height): tiling=tiling, restore_faces=self.cfg("face_restorer_model", str) != "None", save_images=self.cfg("save_temp_images", bool), + override_settings=self.options_params(), + override_settings_restore_afterwards=False, + alwayson_scripts={} ) + + if controlnet_src_imgs: + controlnet_units_param = list() + + for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))): + if self.cfg(f"controlnet{i}_enable", bool): + controlnet_units_param.append( + self.controlnet_unit_params(img_to_b64(controlnet_src_imgs[str(i)]), i) + ) + + params["alwayson_scripts"].update({ + "controlnet": { + "args": controlnet_units_param + } + }) + return params def controlnet_unit_params(self, image: str, unit: int): params = dict( input_image=image, - module=self.cfg(f"controlnet{unit}_preprocessor"), - model=self.cfg(f"controlnet{unit}_model"), - weight=self.cfg(f"controlnet{unit}_weight"), - lowvram=self.cfg(f"controlnet{unit}_low_vram"), - processor_res=self.cfg(f"controlnet{unit}_preprocessor_resolution"), - threshold_a=self.cfg(f"controlnet{unit}_threshold_a"), - threshold_b=self.cfg(f"controlnet{unit}_threshold_b"), - guidance_start=self.cfg(f"controlnet{unit}_guidance_start"), - guidance_end=self.cfg(f"controlnet{unit}_guidance_end"), - guessmode=self.cfg(f"controlnet{unit}_guess_mode") + module=self.cfg(f"controlnet{unit}_preprocessor", str), + model=self.cfg(f"controlnet{unit}_model", str), + weight=self.cfg(f"controlnet{unit}_weight", float), + lowvram=self.cfg(f"controlnet{unit}_low_vram", bool), + processor_res=self.cfg(f"controlnet{unit}_preprocessor_resolution", int), + threshold_a=self.cfg(f"controlnet{unit}_threshold_a", float), + threshold_b=self.cfg(f"controlnet{unit}_threshold_b", float), + guidance_start=self.cfg(f"controlnet{unit}_guidance_start", float), + guidance_end=self.cfg(f"controlnet{unit}_guidance_end", float), + guessmode=self.cfg(f"controlnet{unit}_guess_mode", bool) ) return params @@ -376,14 +397,14 @@ def set_preprocessor_list(obj): self.get("model_list", set_model_list, base_url=url) self.get("module_list", set_preprocessor_list, base_url=url) - def post_options(self): - """Sets the options for the backend, using the official API""" - def cb(response): - assert response is not None, "Backend Error, check terminal" + # def post_options(self): + # """Sets the options for the backend, using the official API""" + # def cb(response): + # assert response is not None, "Backend Error, check terminal" - params = self.options_params() - url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) - self.post("options", params, cb, base_url=url) + # params = self.options_params() + # url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) + # self.post("options", params, cb, base_url=url) def post_txt2img(self, cb, width, height, has_selection): params = dict(orig_width=width, orig_height=height) @@ -411,8 +432,9 @@ def post_txt2img(self, cb, width, height, has_selection): self.post("txt2img", params, cb) - def post_controlnet_txt2image(self, cb, width, height, has_selection, src_imgs: dict): - """Uses official API""" + def post_official_api_txt2img(self, cb, width, height, has_selection, + controlnet_src_imgs: dict = {}): + """Uses official API. Leave controlnet_src_imgs empty to not use controlnet.""" if not self.cfg("just_use_yaml", bool): seed = ( int(self.cfg("txt2img_seed", str)) # Qt casts int as 32-bit int @@ -425,7 +447,7 @@ def post_controlnet_txt2image(self, cb, width, height, has_selection, src_imgs: self.cfg("sd_base_size", int), self.cfg("sd_max_size", int), width, height ) params = self.official_api_common_params( - has_selection, resized_width, resized_height + has_selection, resized_width, resized_height, controlnet_src_imgs ) params.update( prompt=fix_prompt(self.cfg("txt2img_prompt", str)), @@ -440,21 +462,11 @@ def post_controlnet_txt2image(self, cb, width, height, has_selection, src_imgs: hr_resize_y=height, denoising_strength=self.cfg("txt2img_denoising_strength", float), script=ext_name, - script_args=ext_args, + script_args=ext_args ) - controlnet_units_param = list() - - for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))): - if self.cfg(f"controlnet{i}_enable", bool): - controlnet_units_param.append( - self.controlnet_unit_params(img_to_b64(src_imgs[str(i)]), i) - ) - - params.update(controlnet_units=controlnet_units_param) - - self.post_options() - url = get_url(self.cfg, prefix=CONTROLNET_ROUTE_PREFIX) + url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) + print(params) self.post("txt2img", params, cb, base_url=url) def post_img2img(self, cb, src_img, mask_img, has_selection): diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index c07ae10a..2cc6e0e5 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -259,6 +259,11 @@ def cfg_init(self): self.threshold_b.cfg_init() self.set_preprocessor_options(self.preprocessor_layout.qcombo.currentText()) + if (self.preprocessor_layout.qcombo.currentText() == "none"): + self.annotator_preview_button.setEnabled(False) + else: + self.annotator_preview_button.setEnabled(True) + def cfg_connect(self): self.enable.cfg_connect() self.invert_input_color.cfg_connect() @@ -280,6 +285,10 @@ def cfg_connect(self): partial(script.cfg.set, f"controlnet{self.unit}_input_image", "") ) self.preprocessor_layout.qcombo.currentTextChanged.connect(self.set_preprocessor_options) + self.preprocessor_layout.qcombo.currentTextChanged.connect( + lambda: self.annotator_preview_button.setEnabled(False) if + self.preprocessor_layout.qcombo.currentText() == "none" else self.annotator_preview_button.setEnabled(True) + ) self.refresh_button.released.connect(lambda: script.action_update_controlnet_config()) self.annotator_preview_button.released.connect( lambda: script.action_preview_controlnet_annotator(self.annotator_preview) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 2a9ab7c0..c9fde08e 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -262,21 +262,21 @@ def get_controlnet_input_images(self, selected): for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))): if self.cfg(f"controlnet{i}_enable", bool): - input_image = b64_to_img(self.cfg(f"controlnet{i}_input_image", str)) - - if input_image: - if self.cfg(f"controlnet{i}_invert_input_color", bool) or \ - self.cfg(f"controlnet{i}_RGB_to_BGR", bool): - input_image.rgbSwapped() + input_image = b64_to_img(self.cfg(f"controlnet{i}_input_image", str)) if \ + self.cfg(f"controlnet{i}_input_image", str) else selected + + if self.cfg(f"controlnet{i}_invert_input_color", bool) or \ + self.cfg(f"controlnet{i}_RGB_to_BGR", bool): + input_image.rgbSwapped() - input_images.update({f"{i}": input_image}) - else: - input_images.update({f"{i}": selected}) + input_images.update({f"{i}": input_image}) return input_images def apply_txt2img(self): # freeze selection region + controlnet_enabled = self.check_controlnet_enabled() + insert, glayer = self.img_inserter( self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool) ) @@ -286,7 +286,8 @@ def cb(response): if len(self.client.long_reqs) == 1: # last request self.eta_timer.stop() assert response is not None, "Backend Error, check terminal" - outputs = response["outputs"] + #response key varies for official api used for controlnet + outputs = response["outputs"] if not controlnet_enabled else response["images"] glayer_name, layer_names = get_desc_from_resp(response, "txt2img") layers = [ insert(name if name else f"txt2img {i + 1}", output) @@ -302,9 +303,9 @@ def cb(response): self.eta_timer.start(ETA_REFRESH_INTERVAL) - if (self.check_controlnet_enabled()): + if (controlnet_enabled): sel_image = self.get_selection_image() - self.client.post_controlnet_txt2image( + self.client.post_official_api_txt2img( cb, self.width, self.height, self.selection is not None, self.get_controlnet_input_images(sel_image) ) @@ -371,8 +372,6 @@ def apply_controlnet_preview_annotator(self, preview_label): if self.cfg(f"controlnet{unit}_input_image"): image = b64_to_img(self.cfg(f"controlnet{unit}_input_image")) - #self.get_selection_image() already performs a image.rgbSwapped() - #so, I have decided not to play with it. if self.cfg(f"controlnet{unit}_invert_input_color", bool) or \ self.cfg(f"controlnet{unit}_RGB_to_BGR", bool): image.rgbSwapped() diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py index 7b6dc728..a97a45cc 100644 --- a/frontends/krita/krita_diff/widgets/image_loader.py +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -24,7 +24,6 @@ def __init__(self, *args, **kwargs): self.clear_button.released.connect(self.clear_image) def load_image(self): - self.clear_image() file_name, _ = QFileDialog.getOpenFileName(self.import_button, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') if file_name: pixmap = QPixmap(file_name) @@ -36,7 +35,12 @@ def load_image(self): def paste_image(self): self.clear_image() - self.preview.setPixmap(QApplication.clipboard().pixmap()) + pixmap = QPixmap(QApplication.clipboard().pixmap()) + + if pixmap.width() > self.preview.width(): + pixmap = pixmap.scaledToWidth(self.preview.width(), Qt.SmoothTransformation) + + self.preview.setPixmap(pixmap) def clear_image(self): self.preview.setPixmap(QPixmap()) From ac41be9cda842a312a6a929dc882edd57624773b Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Tue, 4 Apr 2023 07:49:11 -0600 Subject: [PATCH 11/55] Img2img work in progress --- frontends/krita/krita_diff/client.py | 103 +++++++++++++++++- .../krita/krita_diff/pages/controlnet.py | 3 +- frontends/krita/krita_diff/script.py | 55 +++++++--- .../krita/krita_diff/widgets/image_loader.py | 1 - 4 files changed, 141 insertions(+), 21 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 6c1f72ab..84031d87 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -5,7 +5,7 @@ from urllib.parse import urljoin, urlparse from urllib.request import Request, urlopen -from krita import QObject, QThread, pyqtSignal +from krita import QObject, QThread, pyqtSignal, QMessageBox from .config import Config from .defaults import ( @@ -446,8 +446,12 @@ def post_official_api_txt2img(self, cb, width, height, has_selection, resized_width, resized_height = calculate_resized_image_dimensions( self.cfg("sd_base_size", int), self.cfg("sd_max_size", int), width, height ) + disable_base_and_max_size = self.cfg("disable_sddebz_highres", bool) params = self.official_api_common_params( - has_selection, resized_width, resized_height, controlnet_src_imgs + has_selection, + resized_width if not disable_base_and_max_size else width, + resized_height if not disable_base_and_max_size else height, + controlnet_src_imgs ) params.update( prompt=fix_prompt(self.cfg("txt2img_prompt", str)), @@ -461,16 +465,15 @@ def post_official_api_txt2img(self, cb, width, height, has_selection, hr_resize_x=width, hr_resize_y=height, denoising_strength=self.cfg("txt2img_denoising_strength", float), - script=ext_name, - script_args=ext_args + script_name=ext_name if ext_name != "None" else None, #Prevent unrecognized "None" script from backend + script_args=ext_args if ext_name != "None" else [] ) url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) - print(params) self.post("txt2img", params, cb, base_url=url) def post_img2img(self, cb, src_img, mask_img, has_selection): - params = dict(is_inpaint=False, src_img=img_to_b64(src_img)) + params = dict(is_inpaint=False, src_img=img_to_b64(src_img)) if not self.cfg("just_use_yaml", bool): seed = ( int(self.cfg("img2img_seed", str)) # Qt casts int as 32-bit int @@ -495,6 +498,43 @@ def post_img2img(self, cb, src_img, mask_img, has_selection): self.post("img2img", params, cb) + def post_official_api_img2img(self, cb, src_img, width, height, has_selection, + controlnet_src_imgs: dict = {}): + """Uses official API. Leave controlnet_src_imgs empty to not use controlnet.""" + params = dict(init_images=[img_to_b64(src_img)]) + if not self.cfg("just_use_yaml", bool): + seed = ( + int(self.cfg("img2img_seed", str)) # Qt casts int as 32-bit int + if not self.cfg("img2img_seed", str).strip() == "" + else -1 + ) + ext_name = self.cfg("img2img_script", str) + ext_args = get_ext_args(self.ext_cfg, "scripts_img2img", ext_name) + resized_width, resized_height = calculate_resized_image_dimensions( + self.cfg("sd_base_size", int), self.cfg("sd_max_size", int), width, height + ) + disable_base_and_max_size = self.cfg("disable_sddebz_highres", bool) + params.update(self.official_api_common_params( + has_selection, + resized_width if not disable_base_and_max_size else width, + resized_height if not disable_base_and_max_size else height, + controlnet_src_imgs + )) + params.update( + prompt=fix_prompt(self.cfg("img2img_prompt", str)), + negative_prompt=fix_prompt(self.cfg("img2img_negative_prompt", str)), + sampler_name=self.cfg("img2img_sampler", str), + steps=self.cfg("img2img_steps", int), + cfg_scale=self.cfg("img2img_cfg_scale", float), + seed=seed, + denoising_strength=self.cfg("img2img_denoising_strength", float), + script_name=ext_name if ext_name != "None" else None, + script_args=ext_args if ext_name != "None" else [] + ) + + url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) + self.post("img2img", params, cb, base_url=url) + def post_inpaint(self, cb, src_img, mask_img, has_selection): assert mask_img, "Inpaint layer is needed for inpainting!" params = dict( @@ -534,6 +574,57 @@ def post_inpaint(self, cb, src_img, mask_img, has_selection): self.post("img2img", params, cb) + def post_official_api_inpaint(self, cb, src_img, mask_img, width, height, has_selection, + controlnet_src_imgs: dict = {}): + """Uses official API. Leave controlnet_src_imgs empty to not use controlnet.""" + assert mask_img, "Inpaint layer is needed for inpainting!" + params = dict( + init_images=[img_to_b64(src_img)], mask=img_to_b64(mask_img) + ) + if not self.cfg("just_use_yaml", bool): + seed = ( + int(self.cfg("inpaint_seed", str)) # Qt casts int as 32-bit int + if not self.cfg("inpaint_seed", str).strip() == "" + else -1 + ) + fill = self.cfg("inpaint_fill_list", "QStringList").index( + self.cfg("inpaint_fill", str) + ) + ext_name = self.cfg("inpaint_script", str) + ext_args = get_ext_args(self.ext_cfg, "scripts_inpaint", ext_name) + resized_width, resized_height = calculate_resized_image_dimensions( + self.cfg("sd_base_size", int), self.cfg("sd_max_size", int), width, height + ) + invert_mask = self.cfg("inpaint_invert_mask", bool) + disable_base_and_max_size = self.cfg("disable_sddebz_highres", bool) + params.update(self.official_api_common_params( + has_selection, + resized_width if not disable_base_and_max_size else width, + resized_height if not disable_base_and_max_size else height, + controlnet_src_imgs + )) + params.update( + prompt=fix_prompt(self.cfg("inpaint_prompt", str)), + negative_prompt=fix_prompt(self.cfg("inpaint_negative_prompt", str)), + sampler_name=self.cfg("inpaint_sampler", str), + steps=self.cfg("inpaint_steps", int), + cfg_scale=self.cfg("inpaint_cfg_scale", float), + seed=seed, + denoising_strength=self.cfg("inpaint_denoising_strength", float), + script_name=ext_name if ext_name != "None" else None, + script_args=ext_args if ext_name != "None" else [], + inpainting_mask_invert=0 if not invert_mask else 1, + inpainting_fill=fill, + mask_blur=0, + inpaint_full_res=False + #not sure what's the equivalent of mask weight for official API + ) + + params["override_settings"]["return_grid"] = False + + url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) + self.post("img2img", params, cb, base_url=url) + def post_upscale(self, cb, src_img): params = ( { diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index 2cc6e0e5..66d44080 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -81,7 +81,8 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): #Tips self.tips = TipsLayout( ["Invert colors if your image has white background.", - "Selection will be used as input if no image has been uploaded or pasted."] + "Selection will be used as input if no image has been uploaded or pasted.", + "Remember to set multi-controlnet in the backend as well if you want to use more than one unit."] ) #Preprocessor list diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index c9fde08e..666ad6c7 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -8,12 +8,14 @@ Krita, Node, QImage, + QColor, + QPainter, QObject, QPixmap, Qt, QTimer, Selection, - pyqtSignal, + pyqtSignal ) from .client import Client @@ -167,20 +169,35 @@ def get_selection_image(self) -> QImage: QImage.Format_RGBA8888, ).rgbSwapped() - def get_mask_image(self) -> Union[QImage, None]: + def get_mask_image(self, using_official_api) -> Union[QImage, None]: """QImage of mask layer for inpainting""" if self.node.type() not in {"paintlayer", "filelayer"}: assert False, "Please select a valid layer to use as inpaint mask!" elif self.node in self._inserted_layers: assert False, "Selected layer was generated. Copy the layer if sure you want to use it as inpaint mask." - return QImage( + mask = QImage( self.node.pixelData(self.x, self.y, self.width, self.height), self.width, self.height, QImage.Format_RGBA8888, ).rgbSwapped() + if using_official_api: + #replace mask's transparency with white color + new_mask = QImage(mask.size(), QImage.Format_RGB888) + new_mask.fill(QColor("white")) + + painter = QPainter(new_mask) + painter.setCompositionMode(QPainter.CompositionMode_SourceOver) + painter.drawImage(0, 0, mask) + painter.end() + + new_mask.invertPixels() + mask = new_mask + + return mask + def img_inserter(self, x, y, width, height, group=False): """Return frozen image inserter to insert images as new layer.""" # Selection may change before callback, so freeze selection region @@ -303,7 +320,7 @@ def cb(response): self.eta_timer.start(ETA_REFRESH_INTERVAL) - if (controlnet_enabled): + if controlnet_enabled: sel_image = self.get_selection_image() self.client.post_official_api_txt2img( cb, self.width, self.height, self.selection is not None, @@ -315,11 +332,13 @@ def cb(response): ) def apply_img2img(self, is_inpaint): + controlnet_enabled = self.check_controlnet_enabled() + insert, glayer = self.img_inserter( self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool) ) mask_trigger = self.transparency_mask_inserter() - mask_image = self.get_mask_image() + mask_image = self.get_mask_image(controlnet_enabled) path = os.path.join(self.cfg("sample_path", str), f"{int(time.time())}.png") mask_path = os.path.join( @@ -341,7 +360,7 @@ def cb(response): self.eta_timer.stop() assert response is not None, "Backend Error, check terminal" - outputs = response["outputs"] + outputs = response["outputs"] if not controlnet_enabled else response["images"] layer_name_prefix = "inpaint" if is_inpaint else "img2img" glayer_name, layer_names = get_desc_from_resp(response, layer_name_prefix) layers = [ @@ -358,14 +377,24 @@ def cb(response): if not is_inpaint: mask_trigger(layers) - method = self.client.post_inpaint if is_inpaint else self.client.post_img2img self.eta_timer.start() - method( - cb, - sel_image, - mask_image, # is unused by backend in img2img mode - self.selection is not None, - ) + if controlnet_enabled: + if is_inpaint: + self.client.post_official_api_inpaint( + cb, sel_image, mask_image, self.width, self.height, self.selection is not None, + self.get_controlnet_input_images(sel_image)) + else: + self.client.post_official_api_img2img( + cb, sel_image, self.width, self.height, self.selection is not None, + self.get_controlnet_input_images(sel_image)) + else: + method = self.client.post_inpaint if is_inpaint else self.client.post_img2img + method( + cb, + sel_image, + mask_image, # is unused by backend in img2img mode + self.selection is not None, + ) def apply_controlnet_preview_annotator(self, preview_label): unit = self.cfg("controlnet_unit") diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py index a97a45cc..321341ce 100644 --- a/frontends/krita/krita_diff/widgets/image_loader.py +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -34,7 +34,6 @@ def load_image(self): self.preview.setPixmap(pixmap) def paste_image(self): - self.clear_image() pixmap = QPixmap(QApplication.clipboard().pixmap()) if pixmap.width() > self.preview.width(): From 3a031ed8524ee2181f88c16862a38a1e74fb01ab Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Wed, 5 Apr 2023 07:13:21 -0600 Subject: [PATCH 12/55] Working im2img --- frontends/krita/krita_diff/client.py | 5 ++- .../krita/krita_diff/pages/controlnet.py | 9 +++- frontends/krita/krita_diff/script.py | 45 ++++++++++++------- frontends/krita/krita_diff/utils.py | 35 +++++++++++++-- 4 files changed, 72 insertions(+), 22 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 84031d87..02d37b5e 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -259,6 +259,7 @@ def options_params(self): code_former_weight=self.cfg("codeformer_weight", float), #Couldn't find filter_nsfw option for official API. img2img_fix_steps=self.cfg("do_exact_steps", bool), #Not sure if this is matched correctly. + img2img_color_correction=self.cfg("img2img_color_correct", bool), return_grid=self.cfg("include_grid", bool) ) return params @@ -276,7 +277,6 @@ def official_api_common_params(self, has_selection, width, height, height=height, tiling=tiling, restore_faces=self.cfg("face_restorer_model", str) != "None", - save_images=self.cfg("save_temp_images", bool), override_settings=self.options_params(), override_settings_restore_afterwards=False, alwayson_scripts={} @@ -540,6 +540,9 @@ def post_inpaint(self, cb, src_img, mask_img, has_selection): params = dict( is_inpaint=True, src_img=img_to_b64(src_img), mask_img=img_to_b64(mask_img) ) + msg = QMessageBox() + msg.setText(params["mask_img"]) + msg.exec() if not self.cfg("just_use_yaml", bool): seed = ( int(self.cfg("inpaint_seed", str)) # Qt casts int as 32-bit int diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index 66d44080..798d68ed 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -25,6 +25,7 @@ def __init__(self, *args, **kwargs): self.units_stacked_layout.addWidget(unit_layout) layout = QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.status_bar) layout.addLayout(self.controlnet_unit) layout.addLayout(self.units_stacked_layout) @@ -143,8 +144,11 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): main_settings_layout = QHBoxLayout() main_settings_layout.addWidget(self.invert_input_color) main_settings_layout.addWidget(self.RGB_to_BGR) - main_settings_layout.addWidget(self.low_vram) - main_settings_layout.addWidget(self.guess_mode) + + + main_settings_layout_2 = QHBoxLayout() + main_settings_layout_2.addWidget(self.low_vram) + main_settings_layout_2.addWidget(self.guess_mode) guidance_layout = QHBoxLayout() guidance_layout.addLayout(self.guidance_start_layout) @@ -160,6 +164,7 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): layout.addLayout(self.image_loader) layout.addLayout(self.tips) layout.addLayout(main_settings_layout) + layout.addLayout(main_settings_layout_2) layout.addLayout(self.preprocessor_layout) layout.addLayout(self.model_layout) layout.addWidget(self.refresh_button) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 666ad6c7..e5fc49a7 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -31,8 +31,8 @@ ) from .utils import ( b64_to_img, - img_to_b64, find_optimal_selection_region, + remove_unmasked_content_for_inpaint, get_desc_from_resp, img_to_ba, save_img, @@ -181,11 +181,13 @@ def get_mask_image(self, using_official_api) -> Union[QImage, None]: self.width, self.height, QImage.Format_RGBA8888, - ).rgbSwapped() + ) if using_official_api: - #replace mask's transparency with white color - new_mask = QImage(mask.size(), QImage.Format_RGB888) + #replace mask's transparency with white color, since official API + #doesn't seem to work with transparency. Assuming masked content + #is black. + new_mask = QImage(mask.size(), QImage.Format_RGBA8888) new_mask.fill(QColor("white")) painter = QPainter(new_mask) @@ -193,12 +195,14 @@ def get_mask_image(self, using_official_api) -> Union[QImage, None]: painter.drawImage(0, 0, mask) painter.end() + #Invert colors so white corresponds to masked area. new_mask.invertPixels() mask = new_mask - return mask + return mask.rgbSwapped() - def img_inserter(self, x, y, width, height, group=False): + def img_inserter(self, x, y, width, height, group=False, + is_official_api_inpaint=False, mask_img=None): """Return frozen image inserter to insert images as new layer.""" # Selection may change before callback, so freeze selection region has_selection = self.selection is not None @@ -239,6 +243,9 @@ def insert(layer_name, enc): width, height, transformMode=Qt.SmoothTransformation ) + if is_official_api_inpaint: + image = remove_unmasked_content_for_inpaint(image, mask_img) + # Resize (not scale!) canvas if image is larger (i.e. outpainting or Upscale was used) if image.width() > self.doc.width() or image.height() > self.doc.height(): # NOTE: @@ -282,9 +289,11 @@ def get_controlnet_input_images(self, selected): input_image = b64_to_img(self.cfg(f"controlnet{i}_input_image", str)) if \ self.cfg(f"controlnet{i}_input_image", str) else selected - if self.cfg(f"controlnet{i}_invert_input_color", bool) or \ - self.cfg(f"controlnet{i}_RGB_to_BGR", bool): - input_image.rgbSwapped() + if self.cfg(f"controlnet{i}_invert_input_color", bool): + input_image.invertPixels() + + if self.cfg(f"controlnet{i}_RGB_to_BGR", bool): + input_image = input_image.rgbSwapped() input_images.update({f"{i}": input_image}) @@ -334,11 +343,13 @@ def cb(response): def apply_img2img(self, is_inpaint): controlnet_enabled = self.check_controlnet_enabled() + mask_trigger = self.transparency_mask_inserter() + mask_image = self.get_mask_image(controlnet_enabled) if is_inpaint else None + insert, glayer = self.img_inserter( - self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool) + self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool), + is_inpaint and controlnet_enabled, mask_image ) - mask_trigger = self.transparency_mask_inserter() - mask_image = self.get_mask_image(controlnet_enabled) path = os.path.join(self.cfg("sample_path", str), f"{int(time.time())}.png") mask_path = os.path.join( @@ -400,13 +411,15 @@ def apply_controlnet_preview_annotator(self, preview_label): unit = self.cfg("controlnet_unit") if self.cfg(f"controlnet{unit}_input_image"): image = b64_to_img(self.cfg(f"controlnet{unit}_input_image")) - - if self.cfg(f"controlnet{unit}_invert_input_color", bool) or \ - self.cfg(f"controlnet{unit}_RGB_to_BGR", bool): - image.rgbSwapped() else: image = self.get_selection_image() + if self.cfg(f"controlnet{unit}_invert_input_color", bool): + image.invertPixels() + + if self.cfg(f"controlnet{unit}_RGB_to_BGR", bool): + image = image.rgbSwapped() + def cb(response): assert response is not None, "Backend Error, check terminal" output = response["images"][0] diff --git a/frontends/krita/krita_diff/utils.py b/frontends/krita/krita_diff/utils.py index 44195742..16143bed 100644 --- a/frontends/krita/krita_diff/utils.py +++ b/frontends/krita/krita_diff/utils.py @@ -3,7 +3,7 @@ from itertools import cycle from math import ceil -from krita import Krita, QBuffer, QByteArray, QImage, QIODevice, Qt +from krita import Krita, QBuffer, QByteArray, QImage, QColor, QIODevice, Qt from .config import Config from .defaults import ( @@ -220,6 +220,7 @@ def reset_docker_layout(): docker_ids = { TAB_SDCOMMON, TAB_CONFIG, + TAB_CONTROLNET, TAB_IMG2IMG, TAB_TXT2IMG, TAB_UPSCALE, @@ -240,11 +241,39 @@ def reset_docker_layout(): qmainwindow.addDockWidget(Qt.LeftDockWidgetArea, d) qmainwindow.tabifyDockWidget(dockers[TAB_SDCOMMON], dockers[TAB_CONFIG]) - qmainwindow.tabifyDockWidget(dockers[TAB_SDCOMMON], dockers[TAB_PREVIEW]) qmainwindow.tabifyDockWidget(dockers[TAB_SDCOMMON], dockers[TAB_CONTROLNET]) + qmainwindow.tabifyDockWidget(dockers[TAB_SDCOMMON], dockers[TAB_PREVIEW]) qmainwindow.tabifyDockWidget(dockers[TAB_TXT2IMG], dockers[TAB_IMG2IMG]) qmainwindow.tabifyDockWidget(dockers[TAB_TXT2IMG], dockers[TAB_INPAINT]) qmainwindow.tabifyDockWidget(dockers[TAB_TXT2IMG], dockers[TAB_UPSCALE]) dockers[TAB_SDCOMMON].raise_() dockers[TAB_INPAINT].raise_() - + +def remove_unmasked_content_for_inpaint(img, mask): + """ + Remove all content from an image that is not masked. + It is recommended that img and mask share the same size. + """ + # Create a new image with the same size as the original image + result = QImage(img.size(), QImage.Format_RGBA8888) + + # Iterate over all pixels in the image + for y in range(img.height()): + for x in range(img.width()): + # Get the color of the pixel in the mask + mask_color = QColor(mask.pixel(x, y)) + + # Calculate the alpha value based on the brightness of the mask pixel + alpha = mask_color.lightness() + + # Get the color of the pixel in the original image + img_color = QColor(img.pixel(x, y)) + + # Set the alpha value of the original pixel based on the mask pixel + img_color.setAlpha(alpha) + + # Set the pixel in the result image + result.setPixelColor(x, y, img_color) + + return result.rgbSwapped() + From 6e6476697965ead086e25c309a3de385f90607c6 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Wed, 5 Apr 2023 07:15:25 -0600 Subject: [PATCH 13/55] Remove QMessageBox for debugging --- frontends/krita/krita_diff/client.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 02d37b5e..50178932 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -5,7 +5,7 @@ from urllib.parse import urljoin, urlparse from urllib.request import Request, urlopen -from krita import QObject, QThread, pyqtSignal, QMessageBox +from krita import QObject, QThread, pyqtSignal from .config import Config from .defaults import ( @@ -540,9 +540,7 @@ def post_inpaint(self, cb, src_img, mask_img, has_selection): params = dict( is_inpaint=True, src_img=img_to_b64(src_img), mask_img=img_to_b64(mask_img) ) - msg = QMessageBox() - msg.setText(params["mask_img"]) - msg.exec() + if not self.cfg("just_use_yaml", bool): seed = ( int(self.cfg("inpaint_seed", str)) # Qt casts int as 32-bit int From 0768ccdd1b259c46042f063a9ee6e57a6c8be6a0 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Mon, 17 Apr 2023 15:32:24 -0600 Subject: [PATCH 14/55] Fixed bug that restarted threshold values every 3 seconds. --- frontends/krita/krita_diff/pages/controlnet.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index 798d68ed..56d0a6b1 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -180,15 +180,13 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): self.setLayout(layout) def set_preprocessor_options(self, selected: str): + self.set_threshold_names(selected) if selected in CONTROLNET_PREPROCESSOR_SETTINGS: self.show_preprocessor_options() - self.annotator_resolution.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["resolution_label"] \ - if "resolution_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else "Preprocessor resolution:") if "threshold_a_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: self.threshold_a.qlabel.show() self.threshold_a.qspin.show() - self.threshold_a.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_label"]) self.threshold_a.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_min_value"] \ if "threshold_a_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) self.threshold_a.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_max_value"] \ @@ -204,7 +202,6 @@ def set_preprocessor_options(self, selected: str): if "threshold_b_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: self.threshold_b.qlabel.show() self.threshold_b.qspin.show() - self.threshold_b.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_label"]) self.threshold_b.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_min_value"] \ if "threshold_b_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) self.threshold_b.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_max_value"] \ @@ -218,6 +215,17 @@ def set_preprocessor_options(self, selected: str): self.threshold_b.qspin.hide() else: self.hide_preprocessor_options(selected) + + def set_threshold_names(self, selected): + if selected in CONTROLNET_PREPROCESSOR_SETTINGS: + self.annotator_resolution.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["resolution_label"] \ + if "resolution_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else "Preprocessor resolution:") + + if "threshold_a_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: + self.threshold_a.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_label"]) + + if "threshold_b_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: + self.threshold_b.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_label"]) def hide_preprocessor_options(self, selected: str): #Hide all annotator settings if no annotator chosen. @@ -263,7 +271,7 @@ def cfg_init(self): self.annotator_resolution.cfg_init() self.threshold_a.cfg_init() self.threshold_b.cfg_init() - self.set_preprocessor_options(self.preprocessor_layout.qcombo.currentText()) + self.set_threshold_names(self.preprocessor_layout.qcombo.currentText()) if (self.preprocessor_layout.qcombo.currentText() == "none"): self.annotator_preview_button.setEnabled(False) From 8eb2736ce1687cdafdc9b54f97103f02c04d8b49 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Mon, 17 Apr 2023 16:43:52 -0600 Subject: [PATCH 15/55] Fixed bug that prevented to run controlnet with img2img --- frontends/krita/krita_diff/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 50178932..5f9be53c 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -290,6 +290,8 @@ def official_api_common_params(self, has_selection, width, height, controlnet_units_param.append( self.controlnet_unit_params(img_to_b64(controlnet_src_imgs[str(i)]), i) ) + else: + controlnet_units_param.append({"enabled": False}) params["alwayson_scripts"].update({ "controlnet": { From de2156b06cbd34462a82d6c9e12a3b45d03424c8 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Sun, 30 Apr 2023 18:59:48 -0600 Subject: [PATCH 16/55] Fixed preprocessor preview bug --- frontends/krita/krita_diff/client.py | 2 +- frontends/krita/krita_diff/defaults.py | 4 ++-- frontends/krita/krita_diff/pages/controlnet.py | 1 - frontends/krita/krita_diff/script.py | 2 +- frontends/krita/krita_diff/utils.py | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 5f9be53c..3336f457 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -641,7 +641,7 @@ def post_upscale(self, cb, src_img): self.post("upscale", params, cb) def post_controlnet_preview(self, cb, src_img): - unit = self.cfg("controlnet_unit") + unit = self.cfg("controlnet_unit", str) params = ( { "controlnet_module": self.cfg(f"controlnet{unit}_preprocessor"), diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index 7a11ebc1..40983944 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -193,8 +193,8 @@ class Defaults: upscale_upscaler_name: str = "None" upscale_downscale_first: bool = False - controlnet_unit: int = 0 - controlnet_unit_list: List[str] = field(default_factory=lambda: list(range(10))) + controlnet_unit: str = "0" + controlnet_unit_list: List[str] = field(default_factory=lambda: list(str(i) for i in range(10))) controlnet_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) controlnet_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index 56d0a6b1..99561e7f 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -15,7 +15,6 @@ def __init__(self, *args, **kwargs): self.controlnet_unit = QComboBoxLayout( script.cfg, "controlnet_unit_list", "controlnet_unit", label="Unit:" ) - self.controlnet_unit.qcombo.setEditable(False) self.controlnet_unit_layout_list = list(ControlNetUnitSettings(i) for i in range(len(script.cfg("controlnet_unit_list")))) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index e5fc49a7..05335b0d 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -408,7 +408,7 @@ def cb(response): ) def apply_controlnet_preview_annotator(self, preview_label): - unit = self.cfg("controlnet_unit") + unit = self.cfg("controlnet_unit", str) if self.cfg(f"controlnet{unit}_input_image"): image = b64_to_img(self.cfg(f"controlnet{unit}_input_image")) else: diff --git a/frontends/krita/krita_diff/utils.py b/frontends/krita/krita_diff/utils.py index 16143bed..5048660d 100644 --- a/frontends/krita/krita_diff/utils.py +++ b/frontends/krita/krita_diff/utils.py @@ -187,7 +187,7 @@ def img_to_b64(img: QImage): def b64_to_img(enc: str): """Converts base64-encoded string to QImage""" ba = QByteArray.fromBase64(enc.encode("utf-8")) - return QImage.fromData(ba, "PNG") + return QImage.fromData(ba) #Removed explicit format to support other image formats. def bytewise_xor(msg: bytes, key: bytes): From b3acf848bc2a0b197c9679bf5bdc8c90c17b98d6 Mon Sep 17 00:00:00 2001 From: Jason Segnini Date: Thu, 30 Mar 2023 07:03:01 -0600 Subject: [PATCH 17/55] UI work in progress --- frontends/krita/krita_diff/__init__.py | 9 ++ frontends/krita/krita_diff/defaults.py | 15 ++ frontends/krita/krita_diff/pages/__init__.py | 1 + .../krita/krita_diff/pages/controlnet_base.py | 129 ++++++++++++++++++ .../krita/krita_diff/widgets/__init__.py | 1 + .../krita/krita_diff/widgets/image_loader.py | 39 ++++++ 6 files changed, 194 insertions(+) create mode 100644 frontends/krita/krita_diff/pages/controlnet_base.py create mode 100644 frontends/krita/krita_diff/widgets/image_loader.py diff --git a/frontends/krita/krita_diff/__init__.py b/frontends/krita/krita_diff/__init__.py index 42af00ed..e47e70cf 100644 --- a/frontends/krita/krita_diff/__init__.py +++ b/frontends/krita/krita_diff/__init__.py @@ -8,6 +8,7 @@ TAB_SDCOMMON, TAB_TXT2IMG, TAB_UPSCALE, + TAB_CONTROLNET ) from .docker import create_docker from .extension import SDPluginExtension @@ -18,6 +19,7 @@ SDCommonPage, Txt2ImgPage, UpscalePage, + ControlNetPageBase ) from .pages.preview import PreviewPage from .script import script @@ -60,6 +62,13 @@ create_docker(UpscalePage), ) ) +instance.addDockWidgetFactory( + DockWidgetFactory( + TAB_CONTROLNET, + DockWidgetFactoryBase.DockLeft, + create_docker(ControlNetPageBase), + ) +) instance.addDockWidgetFactory( DockWidgetFactory( TAB_CONFIG, diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index 9c3f100d..70ee5eb5 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -39,6 +39,7 @@ TAB_IMG2IMG = "krita_diff_img2img" TAB_INPAINT = "krita_diff_inpaint" TAB_UPSCALE = "krita_diff_upscale" +TAB_CONTROLNET = "krita_diff_controlnet" TAB_PREVIEW = "krita_diff_preview" @@ -128,5 +129,19 @@ class Defaults: upscale_upscaler_name: str = "None" upscale_downscale_first: bool = False + controlnet_units: int = 1 + controlnet0_enable: bool = False + controlnet0_use_selection_as_input: bool = True + controlnet0_invert_input_color: bool = False + controlnet0_RGB_to_BGR: bool = False + controlnet0_low_vram: bool = False + controlnet0_guess_mode: bool = False + controlnet0_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet0_preprocessor: str = "None" + controlnet0_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet0_model: str = "None" + controlnet0_weight: float = 1.0 + controlnet0_guidance_start: float = 0 + controlnet0_guidance_end: float = 1 DEFAULTS = Defaults() diff --git a/frontends/krita/krita_diff/pages/__init__.py b/frontends/krita/krita_diff/pages/__init__.py index aba4901a..bc26fa1c 100644 --- a/frontends/krita/krita_diff/pages/__init__.py +++ b/frontends/krita/krita_diff/pages/__init__.py @@ -5,3 +5,4 @@ from .preview import PreviewPage from .txt2img import Txt2ImgPage from .upscale import UpscalePage +from .controlnet_base import ControlNetPageBase diff --git a/frontends/krita/krita_diff/pages/controlnet_base.py b/frontends/krita/krita_diff/pages/controlnet_base.py new file mode 100644 index 00000000..9a88b644 --- /dev/null +++ b/frontends/krita/krita_diff/pages/controlnet_base.py @@ -0,0 +1,129 @@ +from krita import QWidget, QVBoxLayout, QHBoxLayout + +from ..script import script +from ..widgets import StatusBar, ImageLoader, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout + +class ControlNetPageBase(QWidget): + name = "ControlNet" + + def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): + super(ControlNetPageBase, self).__init__(*args, **kwargs) + self.status_bar = StatusBar() + + #Top checkbox + self.enable = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_enable", "Enable ControlNet" + ) + self.use_selection_as_input = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_use_selection_as_input", "Use selection as input" + ) + + self.image_loader = ImageLoader() + + #Main settings + self.invert_input_color = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_invert_input_color", "Invert input color" + ) + self.RGB_to_BGR = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_RGB_to_BGR", "RGB to BGR" + ) + self.low_vram = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_low_vram", "Low VRAM" + ) + self.guess_mode = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_guess_mode", "Guess mode" + ) + + #Tips + self.tips = TipsLayout( + ["Invert colors if your image has white background."] + ) + + #Preprocessor list + self.preprocessor_layout = QComboBoxLayout( + script.cfg, + f"controlnet{cfg_unit_number}_preprocessor_list", + f"controlnet{cfg_unit_number}_preprocessor", + label="Preprocessor:" + ) + + #Model list + self.model_layout = QComboBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" + ) + + self.weight_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_weight", label="Weight:", min=0, max=2, step=0.05 + ) + + self.guidance_start_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_guidance_start", label="Guidance start:", min=0, max=1, step=0.01 + ) + + self.guidance_end_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_guidance_end", label="Guidance end:", min=0, max=1, step=0.01 + ) + + top_checkbox_layout = QHBoxLayout() + top_checkbox_layout.addWidget(self.enable) + top_checkbox_layout.addWidget(self.use_selection_as_input) + + main_settings_layout = QHBoxLayout() + main_settings_layout.addWidget(self.invert_input_color) + main_settings_layout.addWidget(self.RGB_to_BGR) + main_settings_layout.addWidget(self.low_vram) + main_settings_layout.addWidget(self.guess_mode) + + guidance_layout = QHBoxLayout() + guidance_layout.addLayout(self.weight_layout) + guidance_layout.addLayout(self.guidance_start_layout) + guidance_layout.addLayout(self.guidance_end_layout) + + layout = QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.status_bar) + layout.addLayout(top_checkbox_layout) + layout.addWidget(self.image_loader) + layout.addLayout(self.tips) + layout.addLayout(main_settings_layout) + layout.addLayout(self.preprocessor_layout) + layout.addLayout(self.model_layout) + layout.addLayout(guidance_layout) + layout.addStretch() + self.setLayout(layout) + + def change_image_loader_state(self, state): + if state == 1 or state == 2: + self.image_loader.setEnabled(False) + else: + self.image_loader.setEnabled(True) + + def cfg_init(self): + self.enable.cfg_init() + self.use_selection_as_input.cfg_init() + self.invert_input_color.cfg_init() + self.RGB_to_BGR.cfg_init() + self.low_vram.cfg_init() + self.guess_mode.cfg_init() + self.preprocessor_layout.cfg_init() + self.model_layout.cfg_init() + self.weight_layout.cfg_init() + self.guidance_start_layout.cfg_init() + self.guidance_end_layout.cfg_init() + self.tips.setVisible(not script.cfg("minimize_ui", bool)) + self.change_image_loader_state(self.use_selection_as_input.checkState()) + + def cfg_connect(self): + self.enable.cfg_connect() + self.use_selection_as_input.cfg_connect() + self.invert_input_color.cfg_connect() + self.RGB_to_BGR.cfg_connect() + self.low_vram.cfg_connect() + self.guess_mode.cfg_connect() + self.preprocessor_layout.cfg_connect() + self.model_layout.cfg_connect() + self.weight_layout.cfg_connect() + self.guidance_start_layout.cfg_connect() + self.guidance_end_layout.cfg_connect() + self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) + script.status_changed.connect(lambda s: self.status_bar.set_status(s)) \ No newline at end of file diff --git a/frontends/krita/krita_diff/widgets/__init__.py b/frontends/krita/krita_diff/widgets/__init__.py index bb46b96d..ca7138b9 100644 --- a/frontends/krita/krita_diff/widgets/__init__.py +++ b/frontends/krita/krita_diff/widgets/__init__.py @@ -6,3 +6,4 @@ from .spin_box import QSpinBoxLayout from .status_bar import StatusBar from .tips import TipsLayout +from .image_loader import ImageLoader diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py new file mode 100644 index 00000000..96ed2fb7 --- /dev/null +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -0,0 +1,39 @@ +from krita import QWidget, QFileDialog, QPixmap, QPushButton, QVBoxLayout, QHBoxLayout, Qt +from ..widgets import QLabel + +class ImageLoader(QWidget): + def __init__(self, *args, **kwargs): + super(ImageLoader, self).__init__(*args, **kwargs) + + self.preview = QLabel() + self.preview.setAlignment(Qt.AlignCenter) + self.importBtn = QPushButton('Import image') + self.clearBtn = QPushButton('Clear') + + btnLayout = QHBoxLayout() + btnLayout.addWidget(self.importBtn) + btnLayout.addWidget(self.clearBtn) + + layout = QVBoxLayout() + layout.addLayout(btnLayout) + layout.addWidget(self.preview) + self.setLayout(layout) + + self.importBtn.released.connect(self.load_image) + self.clearBtn.released.connect(self.clear_image) + + def load_image(self): + file_name, _ = QFileDialog.getOpenFileName(self, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') + if file_name: + pixmap = QPixmap(file_name) + + if pixmap.width() > self.preview.width(): + pixmap = pixmap.scaledToWidth(self.preview.width(), Qt.SmoothTransformation) + + self.preview.setPixmap(pixmap) + + def clear_image(self): + self.preview.setPixmap(QPixmap()) + + + \ No newline at end of file From eb3617448b1f7066c1207edf39732bb4cee9ddb6 Mon Sep 17 00:00:00 2001 From: Jason Segnini Date: Fri, 31 Mar 2023 05:47:24 -0600 Subject: [PATCH 18/55] Added controlnet preprocessors settings --- frontends/krita/krita_diff/defaults.py | 145 ++++++++- frontends/krita/krita_diff/pages/__init__.py | 2 +- .../krita/krita_diff/pages/controlnet.py | 280 ++++++++++++++++++ .../krita/krita_diff/pages/controlnet_base.py | 129 -------- .../krita/krita_diff/widgets/__init__.py | 2 +- .../krita/krita_diff/widgets/image_loader.py | 22 +- 6 files changed, 439 insertions(+), 141 deletions(-) create mode 100644 frontends/krita/krita_diff/pages/controlnet.py delete mode 100644 frontends/krita/krita_diff/pages/controlnet_base.py diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index 70ee5eb5..43f2309e 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -130,18 +130,157 @@ class Defaults: upscale_downscale_first: bool = False controlnet_units: int = 1 + controlnet_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet0_enable: bool = False controlnet0_use_selection_as_input: bool = True controlnet0_invert_input_color: bool = False controlnet0_RGB_to_BGR: bool = False controlnet0_low_vram: bool = False controlnet0_guess_mode: bool = False - controlnet0_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) - controlnet0_preprocessor: str = "None" - controlnet0_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet0_preprocessor: str = "None" controlnet0_model: str = "None" controlnet0_weight: float = 1.0 controlnet0_guidance_start: float = 0 controlnet0_guidance_end: float = 1 + controlnet0_preprocessor_resolution: int = 512 + controlnet0_treshold_a: float = 0 + controlnet0_treshold_b: float = 0 + + controlnet1_enable: bool = False + controlnet1_use_selection_as_input: bool = True + controlnet1_invert_input_color: bool = False + controlnet1_RGB_to_BGR: bool = False + controlnet1_low_vram: bool = False + controlnet1_guess_mode: bool = False + controlnet1_preprocessor: str = "None" + controlnet1_model: str = "None" + controlnet1_weight: float = 1.0 + controlnet1_guidance_start: float = 0 + controlnet1_guidance_end: float = 1 + controlnet1_preprocessor_resolution: int = 512 + controlnet1_treshold_a: float = 0 + controlnet1_treshold_b: float = 0 + + controlnet2_enable: bool = False + controlnet2_use_selection_as_input: bool = True + controlnet2_invert_input_color: bool = False + controlnet2_RGB_to_BGR: bool = False + controlnet2_low_vram: bool = False + controlnet2_guess_mode: bool = False + controlnet2_preprocessor: str = "None" + controlnet2_model: str = "None" + controlnet2_weight: float = 1.0 + controlnet2_guidance_start: float = 0 + controlnet2_guidance_end: float = 1 + controlnet2_preprocessor_resolution: int = 512 + controlnet2_treshold_a: float = 0 + controlnet2_treshold_b: float = 0 + + controlnet3_enable: bool = False + controlnet3_use_selection_as_input: bool = True + controlnet3_invert_input_color: bool = False + controlnet3_RGB_to_BGR: bool = False + controlnet3_low_vram: bool = False + controlnet3_guess_mode: bool = False + controlnet3_preprocessor: str = "None" + controlnet3_model: str = "None" + controlnet3_weight: float = 1.0 + controlnet3_guidance_start: float = 0 + controlnet3_guidance_end: float = 1 + controlnet3_preprocessor_resolution: int = 512 + controlnet3_treshold_a: float = 0 + controlnet3_treshold_b: float = 0 + + controlnet4_enable: bool = False + controlnet4_use_selection_as_input: bool = True + controlnet4_invert_input_color: bool = False + controlnet4_RGB_to_BGR: bool = False + controlnet4_low_vram: bool = False + controlnet4_guess_mode: bool = False + controlnet4_preprocessor: str = "None" + controlnet4_model: str = "None" + controlnet4_weight: float = 1.0 + controlnet4_guidance_start: float = 0 + controlnet4_guidance_end: float = 1 + controlnet4_preprocessor_resolution: int = 512 + controlnet4_treshold_a: float = 0 + controlnet4_treshold_b: float = 0 + + controlnet5_enable: bool = False + controlnet5_use_selection_as_input: bool = True + controlnet5_invert_input_color: bool = False + controlnet5_RGB_to_BGR: bool = False + controlnet5_low_vram: bool = False + controlnet5_guess_mode: bool = False + controlnet5_preprocessor: str = "None" + controlnet5_model: str = "None" + controlnet5_weight: float = 1.0 + controlnet5_guidance_start: float = 0 + controlnet5_guidance_end: float = 1 + controlnet5_preprocessor_resolution: int = 512 + controlnet5_treshold_a: float = 0 + controlnet5_treshold_b: float = 0 + + controlnet6_enable: bool = False + controlnet6_use_selection_as_input: bool = True + controlnet6_invert_input_color: bool = False + controlnet6_RGB_to_BGR: bool = False + controlnet6_low_vram: bool = False + controlnet6_guess_mode: bool = False + controlnet6_preprocessor: str = "None" + controlnet6_model: str = "None" + controlnet6_weight: float = 1.0 + controlnet6_guidance_start: float = 0 + controlnet6_guidance_end: float = 1 + controlnet6_preprocessor_resolution: int = 512 + controlnet6_treshold_a: float = 0 + controlnet6_treshold_b: float = 0 + + controlnet7_enable: bool = False + controlnet7_use_selection_as_input: bool = True + controlnet7_invert_input_color: bool = False + controlnet7_RGB_to_BGR: bool = False + controlnet7_low_vram: bool = False + controlnet7_guess_mode: bool = False + controlnet7_preprocessor: str = "None" + controlnet7_model: str = "None" + controlnet7_weight: float = 1.0 + controlnet7_guidance_start: float = 0 + controlnet7_guidance_end: float = 1 + controlnet7_preprocessor_resolution: int = 512 + controlnet7_treshold_a: float = 0 + controlnet7_treshold_b: float = 0 + + controlnet8_enable: bool = False + controlnet8_use_selection_as_input: bool = True + controlnet8_invert_input_color: bool = False + controlnet8_RGB_to_BGR: bool = False + controlnet8_low_vram: bool = False + controlnet8_guess_mode: bool = False + controlnet8_preprocessor: str = "None" + controlnet8_model: str = "None" + controlnet8_weight: float = 1.0 + controlnet8_guidance_start: float = 0 + controlnet8_guidance_end: float = 1 + controlnet8_preprocessor_resolution: int = 512 + controlnet8_treshold_a: float = 0 + controlnet8_treshold_b: float = 0 + + controlnet9_enable: bool = False + controlnet9_use_selection_as_input: bool = True + controlnet9_invert_input_color: bool = False + controlnet9_RGB_to_BGR: bool = False + controlnet9_low_vram: bool = False + controlnet9_guess_mode: bool = False + controlnet9_preprocessor: str = "None" + controlnet9_model: str = "None" + controlnet9_weight: float = 1.0 + controlnet9_guidance_start: float = 0 + controlnet9_guidance_end: float = 1 + controlnet9_preprocessor_resolution: int = 512 + controlnet9_treshold_a: float = 0 + controlnet9_treshold_b: float = 0 DEFAULTS = Defaults() diff --git a/frontends/krita/krita_diff/pages/__init__.py b/frontends/krita/krita_diff/pages/__init__.py index bc26fa1c..3c8ccfcb 100644 --- a/frontends/krita/krita_diff/pages/__init__.py +++ b/frontends/krita/krita_diff/pages/__init__.py @@ -5,4 +5,4 @@ from .preview import PreviewPage from .txt2img import Txt2ImgPage from .upscale import UpscalePage -from .controlnet_base import ControlNetPageBase +from .controlnet import ControlNetPageBase diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py new file mode 100644 index 00000000..edc9d5c4 --- /dev/null +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -0,0 +1,280 @@ +from krita import QWidget, QVBoxLayout, QHBoxLayout + +from ..script import script +from ..widgets import StatusBar, ImageLoaderLayout, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout + +class ControlNetPageBase(QWidget): + name = "ControlNet" + + def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): + super(ControlNetPageBase, self).__init__(*args, **kwargs) + self.status_bar = StatusBar() + + #Top checkbox + self.enable = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_enable", "Enable ControlNet" + ) + self.use_selection_as_input = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_use_selection_as_input", "Use selection as input" + ) + + self.image_loader = ImageLoaderLayout() + + #Main settings + self.invert_input_color = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_invert_input_color", "Invert input color" + ) + self.RGB_to_BGR = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_RGB_to_BGR", "RGB to BGR" + ) + self.low_vram = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_low_vram", "Low VRAM" + ) + self.guess_mode = QCheckBox( + script.cfg, f"controlnet{cfg_unit_number}_guess_mode", "Guess mode" + ) + + #Tips + self.tips = TipsLayout( + ["Invert colors if your image has white background."] + ) + + #Preprocessor list + self.preprocessor_layout = QComboBoxLayout( + script.cfg, + f"controlnet_preprocessor_list", f"controlnet{cfg_unit_number}_preprocessor", label="Preprocessor:" + ) + + #Model list + self.model_layout = QComboBoxLayout( + script.cfg, f"controlnet_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" + ) + + self.weight_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_weight", label="Weight:", min=0, max=2, step=0.05 + ) + + self.guidance_start_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_guidance_start", label="Guidance start:", min=0, max=1, step=0.01 + ) + + self.guidance_end_layout = QSpinBoxLayout( + script.cfg, f"controlnet{cfg_unit_number}_guidance_end", label="Guidance end:", min=0, max=1, step=0.01 + ) + + #Preprocessor settings + self.annotator_resolution = QSpinBoxLayout( + script.cfg, + f"controlnet{cfg_unit_number}_preprocessor_resolution", + label="Preprocessor resolution:", + min=64, + max=2048, + step=1 + ) + self.treshold_a = QSpinBoxLayout( + script.cfg, + f"controlnet{cfg_unit_number}_treshold_a", + label="Treshold A:", + min=1, + max=255, + step=1 + ) + self.treshold_b = QSpinBoxLayout( + script.cfg, + f"controlnet{cfg_unit_number}_treshold_b", + label="Treshold B:", + min=1, + max=255, + step=1 + ) + + top_checkbox_layout = QHBoxLayout() + top_checkbox_layout.addWidget(self.enable) + top_checkbox_layout.addWidget(self.use_selection_as_input) + + main_settings_layout = QHBoxLayout() + main_settings_layout.addWidget(self.invert_input_color) + main_settings_layout.addWidget(self.RGB_to_BGR) + main_settings_layout.addWidget(self.low_vram) + main_settings_layout.addWidget(self.guess_mode) + + guidance_layout = QHBoxLayout() + guidance_layout.addLayout(self.weight_layout) + guidance_layout.addLayout(self.guidance_start_layout) + guidance_layout.addLayout(self.guidance_end_layout) + + preprocessor_settings_layout = QHBoxLayout() + preprocessor_settings_layout.addLayout(self.annotator_resolution) + preprocessor_settings_layout.addLayout(self.treshold_a) + preprocessor_settings_layout.addLayout(self.treshold_b) + + layout = QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.status_bar) + layout.addLayout(top_checkbox_layout) + layout.addLayout(self.image_loader) + layout.addLayout(self.tips) + layout.addLayout(main_settings_layout) + layout.addLayout(self.preprocessor_layout) + layout.addLayout(self.model_layout) + layout.addLayout(guidance_layout) + layout.addLayout(preprocessor_settings_layout) + layout.addStretch() + self.setLayout(layout) + + def change_image_loader_state(self, state): + if state == 1 or state == 2: + self.image_loader.disable() + else: + self.image_loader.enable() + + def preprocessor_changed(self, selected: str): + self.init_preprocessor_layouts(selected) + if selected in preprocessor_settings: + self.annotator_resolution.label = preprocessor_settings[selected]["resolution_label"] \ + if "resolution_label" in preprocessor_settings[selected] else "Preprocessor resolution:" + + if "treshold_a_label" in preprocessor_settings[selected]: + self.treshold_a.label = preprocessor_settings[selected]["treshold_a_label"] + self.treshold_a.min = preprocessor_settings[selected]["treshold_a_min_value"] \ + if "treshold_a_min_value" in preprocessor_settings[selected] else 0 + self.treshold_a.max = preprocessor_settings[selected]["treshold_a_max_value"] \ + if "treshold_a_max_value" in preprocessor_settings[selected] else 0 + self.treshold_a.value = preprocessor_settings[selected]["treshold_a_value"] \ + if "treshold_a_value" in preprocessor_settings[selected] else 0 + self.treshold_a.step = preprocessor_settings[selected]["treshold_step"] \ + if "treshold_step" in preprocessor_settings[selected] else 1 + else: + self.treshold_a.hide() + + if "treshold_b_label" in preprocessor_settings[selected]: + self.treshold_b.label = preprocessor_settings[selected]["treshold_b_label"] + self.treshold_b.min = preprocessor_settings[selected]["treshold_b_min_value"] \ + if "treshold_b_min_value" in preprocessor_settings[selected] else 0 + self.treshold_b.max = preprocessor_settings[selected]["treshold_b_max_value"] \ + if "treshold_b_max_value" in preprocessor_settings[selected] else 0 + self.treshold_b.value = preprocessor_settings[selected]["treshold_b_value"] \ + if "treshold_b_value" in preprocessor_settings[selected] else 0 + self.treshold_b.step = preprocessor_settings[selected]["treshold_step"] \ + if "treshold_step" in preprocessor_settings[selected] else 1 + + def init_preprocessor_layouts(self, selected: str): + if selected in preprocessor_settings: + self.show_preprocessor_options() + else: + self.hide_preprocessor_options() + + def hide_preprocessor_options(self): + self.annotator_resolution.qlabel.hide() + self.annotator_resolution.qspin.hide() + self.treshold_a.qlabel.hide() + self.treshold_a.qspin.hide() + self.treshold_b.qlabel.hide() + self.treshold_b.qspin.hide() + + def show_preprocessor_options(self): + self.annotator_resolution.qlabel.show() + self.annotator_resolution.qspin.show() + self.treshold_a.qlabel.show() + self.treshold_a.qspin.show() + self.treshold_b.qlabel.show() + self.treshold_b.qspin.show() + + def cfg_init(self): + self.enable.cfg_init() + self.use_selection_as_input.cfg_init() + self.invert_input_color.cfg_init() + self.RGB_to_BGR.cfg_init() + self.low_vram.cfg_init() + self.guess_mode.cfg_init() + self.preprocessor_layout.cfg_init() + self.model_layout.cfg_init() + self.weight_layout.cfg_init() + self.guidance_start_layout.cfg_init() + self.guidance_end_layout.cfg_init() + self.tips.setVisible(not script.cfg("minimize_ui", bool)) + self.change_image_loader_state(self.use_selection_as_input.checkState()) + self.init_preprocessor_layouts(self.preprocessor_layout.qcombo.currentText()) + + def cfg_connect(self): + self.enable.cfg_connect() + self.use_selection_as_input.cfg_connect() + self.invert_input_color.cfg_connect() + self.RGB_to_BGR.cfg_connect() + self.low_vram.cfg_connect() + self.guess_mode.cfg_connect() + self.preprocessor_layout.cfg_connect() + self.model_layout.cfg_connect() + self.weight_layout.cfg_connect() + self.guidance_start_layout.cfg_connect() + self.guidance_end_layout.cfg_connect() + self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) + self.preprocessor_layout.qcombo.currentTextChanged.connect(self.preprocessor_changed) + script.status_changed.connect(lambda s: self.status_bar.set_status(s)) + +preprocessor_settings = { + "canny": { + "resolution_label": "Annotator resolution", + "treshold_a_label": "Canny low treshold", + "treshold_b_label": "Canny high treshold", + "treshold_a_value": 100, + "treshold_b_value": 200, + "treshold_a_min_value": 1, + "treshold_a_max_value": 255, + "treshold_b_min_value": 1, + "treshold_b_max_value": 255 + }, + "depth": { + "resolution_label": "Midas resolution", + }, + "depth_leres": { + "resolution_label": "LeReS resolution", + "treshold_a_label": "Remove near %", + "treshold_b_label": "Remove background %", + "treshold_a_min_value": 0, + "treshold_a_max_value": 100, + "treshold_b_min_value": 0, + "treshold_b_max_value": 100 + }, + "hed": { + "resolution_label": "HED resolution", + }, + "mlsd": { + "resolution_label": "Hough resolution", + "treshold_a_label": "Hough value threshold (MLSD)", + "treshold_b_label": "Hough distance threshold (MLSD)", + "treshold_a_value": 0.1, + "treshold_b_value": 0.1, + "treshold_a_min_value": 0.01, + "treshold_b_max_value": 2, + "treshold_a_min_value": 0.01, + "treshold_b_max_value": 20, + "treshold_step": 0.01 + }, + "normal_map": { + "treshold_a_label": "Normal background threshold", + "treshold_a_value": 0.4, + "treshold_a_min_value": 0, + "treshold_a_max_value": 1, + "treshold_b_min_value": 0, + "treshold_b_max_value": 1, + "treshold_step": 0.01 + }, + "openpose": {}, + "openpose_hand": {}, + "clip_vision": {}, + "color": {}, + "pidinet": {}, + "scribble": {}, + "fake_scribble": { + "resolution_label": "HED resolution", + }, + "segmentation": {}, + "binary": { + "treshold_a_label": "Binary threshold", + "treshold_a_min_value": 0, + "treshold_a_max_value": 255, + "treshold_b_min_value": 0, + "treshold_b_max_value": 255, + } +} \ No newline at end of file diff --git a/frontends/krita/krita_diff/pages/controlnet_base.py b/frontends/krita/krita_diff/pages/controlnet_base.py deleted file mode 100644 index 9a88b644..00000000 --- a/frontends/krita/krita_diff/pages/controlnet_base.py +++ /dev/null @@ -1,129 +0,0 @@ -from krita import QWidget, QVBoxLayout, QHBoxLayout - -from ..script import script -from ..widgets import StatusBar, ImageLoader, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout - -class ControlNetPageBase(QWidget): - name = "ControlNet" - - def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): - super(ControlNetPageBase, self).__init__(*args, **kwargs) - self.status_bar = StatusBar() - - #Top checkbox - self.enable = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_enable", "Enable ControlNet" - ) - self.use_selection_as_input = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_use_selection_as_input", "Use selection as input" - ) - - self.image_loader = ImageLoader() - - #Main settings - self.invert_input_color = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_invert_input_color", "Invert input color" - ) - self.RGB_to_BGR = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_RGB_to_BGR", "RGB to BGR" - ) - self.low_vram = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_low_vram", "Low VRAM" - ) - self.guess_mode = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_guess_mode", "Guess mode" - ) - - #Tips - self.tips = TipsLayout( - ["Invert colors if your image has white background."] - ) - - #Preprocessor list - self.preprocessor_layout = QComboBoxLayout( - script.cfg, - f"controlnet{cfg_unit_number}_preprocessor_list", - f"controlnet{cfg_unit_number}_preprocessor", - label="Preprocessor:" - ) - - #Model list - self.model_layout = QComboBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" - ) - - self.weight_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_weight", label="Weight:", min=0, max=2, step=0.05 - ) - - self.guidance_start_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_guidance_start", label="Guidance start:", min=0, max=1, step=0.01 - ) - - self.guidance_end_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_guidance_end", label="Guidance end:", min=0, max=1, step=0.01 - ) - - top_checkbox_layout = QHBoxLayout() - top_checkbox_layout.addWidget(self.enable) - top_checkbox_layout.addWidget(self.use_selection_as_input) - - main_settings_layout = QHBoxLayout() - main_settings_layout.addWidget(self.invert_input_color) - main_settings_layout.addWidget(self.RGB_to_BGR) - main_settings_layout.addWidget(self.low_vram) - main_settings_layout.addWidget(self.guess_mode) - - guidance_layout = QHBoxLayout() - guidance_layout.addLayout(self.weight_layout) - guidance_layout.addLayout(self.guidance_start_layout) - guidance_layout.addLayout(self.guidance_end_layout) - - layout = QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.status_bar) - layout.addLayout(top_checkbox_layout) - layout.addWidget(self.image_loader) - layout.addLayout(self.tips) - layout.addLayout(main_settings_layout) - layout.addLayout(self.preprocessor_layout) - layout.addLayout(self.model_layout) - layout.addLayout(guidance_layout) - layout.addStretch() - self.setLayout(layout) - - def change_image_loader_state(self, state): - if state == 1 or state == 2: - self.image_loader.setEnabled(False) - else: - self.image_loader.setEnabled(True) - - def cfg_init(self): - self.enable.cfg_init() - self.use_selection_as_input.cfg_init() - self.invert_input_color.cfg_init() - self.RGB_to_BGR.cfg_init() - self.low_vram.cfg_init() - self.guess_mode.cfg_init() - self.preprocessor_layout.cfg_init() - self.model_layout.cfg_init() - self.weight_layout.cfg_init() - self.guidance_start_layout.cfg_init() - self.guidance_end_layout.cfg_init() - self.tips.setVisible(not script.cfg("minimize_ui", bool)) - self.change_image_loader_state(self.use_selection_as_input.checkState()) - - def cfg_connect(self): - self.enable.cfg_connect() - self.use_selection_as_input.cfg_connect() - self.invert_input_color.cfg_connect() - self.RGB_to_BGR.cfg_connect() - self.low_vram.cfg_connect() - self.guess_mode.cfg_connect() - self.preprocessor_layout.cfg_connect() - self.model_layout.cfg_connect() - self.weight_layout.cfg_connect() - self.guidance_start_layout.cfg_connect() - self.guidance_end_layout.cfg_connect() - self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) - script.status_changed.connect(lambda s: self.status_bar.set_status(s)) \ No newline at end of file diff --git a/frontends/krita/krita_diff/widgets/__init__.py b/frontends/krita/krita_diff/widgets/__init__.py index ca7138b9..5b950669 100644 --- a/frontends/krita/krita_diff/widgets/__init__.py +++ b/frontends/krita/krita_diff/widgets/__init__.py @@ -6,4 +6,4 @@ from .spin_box import QSpinBoxLayout from .status_bar import StatusBar from .tips import TipsLayout -from .image_loader import ImageLoader +from .image_loader import ImageLoaderLayout diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py index 96ed2fb7..c3ce8691 100644 --- a/frontends/krita/krita_diff/widgets/image_loader.py +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -1,9 +1,9 @@ from krita import QWidget, QFileDialog, QPixmap, QPushButton, QVBoxLayout, QHBoxLayout, Qt from ..widgets import QLabel -class ImageLoader(QWidget): +class ImageLoaderLayout(QVBoxLayout): def __init__(self, *args, **kwargs): - super(ImageLoader, self).__init__(*args, **kwargs) + super(ImageLoaderLayout, self).__init__(*args, **kwargs) self.preview = QLabel() self.preview.setAlignment(Qt.AlignCenter) @@ -14,16 +14,24 @@ def __init__(self, *args, **kwargs): btnLayout.addWidget(self.importBtn) btnLayout.addWidget(self.clearBtn) - layout = QVBoxLayout() - layout.addLayout(btnLayout) - layout.addWidget(self.preview) - self.setLayout(layout) + self.addLayout(btnLayout) + self.addWidget(self.preview) self.importBtn.released.connect(self.load_image) self.clearBtn.released.connect(self.clear_image) + def disable(self): + self.preview.setEnabled(False) + self.importBtn.setEnabled(False) + self.clearBtn.setEnabled(False) + + def enable(self): + self.preview.setEnabled(True) + self.importBtn.setEnabled(True) + self.clearBtn.setEnabled(True) + def load_image(self): - file_name, _ = QFileDialog.getOpenFileName(self, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') + file_name, _ = QFileDialog.getOpenFileName(self.importBtn, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') if file_name: pixmap = QPixmap(file_name) From 320489109ca1aacfc18d678949b9c1d3f5b43f84 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Sat, 1 Apr 2023 04:03:34 -0600 Subject: [PATCH 19/55] Added controlnet base layout --- frontends/krita/krita_diff/__init__.py | 4 +- frontends/krita/krita_diff/defaults.py | 3 +- frontends/krita/krita_diff/pages/__init__.py | 2 +- .../krita/krita_diff/pages/controlnet.py | 58 +++++++++++++++---- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/frontends/krita/krita_diff/__init__.py b/frontends/krita/krita_diff/__init__.py index e47e70cf..a087dbf3 100644 --- a/frontends/krita/krita_diff/__init__.py +++ b/frontends/krita/krita_diff/__init__.py @@ -19,7 +19,7 @@ SDCommonPage, Txt2ImgPage, UpscalePage, - ControlNetPageBase + ControlNetPage ) from .pages.preview import PreviewPage from .script import script @@ -66,7 +66,7 @@ DockWidgetFactory( TAB_CONTROLNET, DockWidgetFactoryBase.DockLeft, - create_docker(ControlNetPageBase), + create_docker(ControlNetPage), ) ) instance.addDockWidgetFactory( diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index 43f2309e..a9f421fd 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -129,7 +129,8 @@ class Defaults: upscale_upscaler_name: str = "None" upscale_downscale_first: bool = False - controlnet_units: int = 1 + controlnet_unit: int = 0 + controlnet_unit_list: List[str] = field(default_factory=lambda: list(range(1, 11))) controlnet_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) controlnet_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) diff --git a/frontends/krita/krita_diff/pages/__init__.py b/frontends/krita/krita_diff/pages/__init__.py index 3c8ccfcb..7715bc20 100644 --- a/frontends/krita/krita_diff/pages/__init__.py +++ b/frontends/krita/krita_diff/pages/__init__.py @@ -5,4 +5,4 @@ from .preview import PreviewPage from .txt2img import Txt2ImgPage from .upscale import UpscalePage -from .controlnet import ControlNetPageBase +from .controlnet import ControlNetPage \ No newline at end of file diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index edc9d5c4..a3b8cc88 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -1,18 +1,57 @@ -from krita import QWidget, QVBoxLayout, QHBoxLayout +from krita import QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout from ..script import script from ..widgets import StatusBar, ImageLoaderLayout, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout -class ControlNetPageBase(QWidget): +class ControlNetPage(QWidget): name = "ControlNet" - def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): - super(ControlNetPageBase, self).__init__(*args, **kwargs) + def __init__(self, *args, **kwargs): + super(ControlNetPage, self).__init__(*args, **kwargs) self.status_bar = StatusBar() + self.controlnet_unit = QComboBoxLayout( + script.cfg, "controlnet_unit_list", "controlnet_unit", label="Unit:" + ) + self.controlnet_unit.qcombo.setEditable(False) + self.controlnet_unit_layout_list = list(ControlNetUnitSettings(i) + for i in range(0, len(script.cfg("controlnet_unit_list")))) + + self.units_stacked_layout = QStackedLayout() + + for unit_layout in self.controlnet_unit_layout_list: + self.units_stacked_layout.addWidget(unit_layout) + + layout = QVBoxLayout() + layout.addWidget(self.status_bar) + layout.addLayout(self.controlnet_unit) + layout.addLayout(self.units_stacked_layout) + self.setLayout(layout) + + def controlnet_unit_changed(self, selected: str): + self.units_stacked_layout.setCurrentIndex(int(selected)-1) + + def cfg_init(self): + self.controlnet_unit.cfg_init() + + for controlnet_unit_layout in self.controlnet_unit_layout_list: + controlnet_unit_layout.cfg_init() + + def cfg_connect(self): + self.controlnet_unit.cfg_connect() + + for controlnet_unit_layout in self.controlnet_unit_layout_list: + controlnet_unit_layout.cfg_connect() + + self.controlnet_unit.qcombo.currentTextChanged.connect(self.controlnet_unit_changed) + script.status_changed.connect(lambda s: self.status_bar.set_status(s)) + +class ControlNetUnitSettings(QWidget): + def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): + super(ControlNetUnitSettings, self).__init__(*args, **kwargs) #Top checkbox self.enable = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_enable", "Enable ControlNet" + script.cfg, f"controlnet{cfg_unit_number}_enable", f"Enable ControlNet {cfg_unit_number}" ) self.use_selection_as_input = QCheckBox( script.cfg, f"controlnet{cfg_unit_number}_use_selection_as_input", "Use selection as input" @@ -41,13 +80,12 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): #Preprocessor list self.preprocessor_layout = QComboBoxLayout( - script.cfg, - f"controlnet_preprocessor_list", f"controlnet{cfg_unit_number}_preprocessor", label="Preprocessor:" + script.cfg, "controlnet_preprocessor_list", f"controlnet{cfg_unit_number}_preprocessor", label="Preprocessor:" ) #Model list self.model_layout = QComboBoxLayout( - script.cfg, f"controlnet_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" + script.cfg, "controlnet_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" ) self.weight_layout = QSpinBoxLayout( @@ -110,7 +148,6 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.status_bar) layout.addLayout(top_checkbox_layout) layout.addLayout(self.image_loader) layout.addLayout(self.tips) @@ -120,6 +157,7 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): layout.addLayout(guidance_layout) layout.addLayout(preprocessor_settings_layout) layout.addStretch() + self.setLayout(layout) def change_image_loader_state(self, state): @@ -192,7 +230,6 @@ def cfg_init(self): self.weight_layout.cfg_init() self.guidance_start_layout.cfg_init() self.guidance_end_layout.cfg_init() - self.tips.setVisible(not script.cfg("minimize_ui", bool)) self.change_image_loader_state(self.use_selection_as_input.checkState()) self.init_preprocessor_layouts(self.preprocessor_layout.qcombo.currentText()) @@ -210,7 +247,6 @@ def cfg_connect(self): self.guidance_end_layout.cfg_connect() self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) self.preprocessor_layout.qcombo.currentTextChanged.connect(self.preprocessor_changed) - script.status_changed.connect(lambda s: self.status_bar.set_status(s)) preprocessor_settings = { "canny": { From da1507fe9eb5fa0548789b336d5554730d848649 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Sat, 1 Apr 2023 11:00:16 -0600 Subject: [PATCH 20/55] Added the ability to paste copied image to the image loader layout. --- frontends/krita/krita_diff/widgets/image_loader.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py index c3ce8691..9eeaf87a 100644 --- a/frontends/krita/krita_diff/widgets/image_loader.py +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -1,4 +1,4 @@ -from krita import QWidget, QFileDialog, QPixmap, QPushButton, QVBoxLayout, QHBoxLayout, Qt +from krita import QApplication, QFileDialog, QPixmap, QPushButton, QVBoxLayout, QHBoxLayout, Qt from ..widgets import QLabel class ImageLoaderLayout(QVBoxLayout): @@ -8,29 +8,35 @@ def __init__(self, *args, **kwargs): self.preview = QLabel() self.preview.setAlignment(Qt.AlignCenter) self.importBtn = QPushButton('Import image') + self.pasteBtn = QPushButton('Paste image') self.clearBtn = QPushButton('Clear') btnLayout = QHBoxLayout() btnLayout.addWidget(self.importBtn) - btnLayout.addWidget(self.clearBtn) + btnLayout.addWidget(self.pasteBtn) self.addLayout(btnLayout) + self.addWidget(self.clearBtn) self.addWidget(self.preview) self.importBtn.released.connect(self.load_image) + self.pasteBtn.released.connect(self.paste_image) self.clearBtn.released.connect(self.clear_image) def disable(self): self.preview.setEnabled(False) self.importBtn.setEnabled(False) + self.pasteBtn.setEnabled(False) self.clearBtn.setEnabled(False) def enable(self): self.preview.setEnabled(True) self.importBtn.setEnabled(True) + self.pasteBtn.setEnabled(True) self.clearBtn.setEnabled(True) def load_image(self): + self.clear_image() file_name, _ = QFileDialog.getOpenFileName(self.importBtn, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') if file_name: pixmap = QPixmap(file_name) @@ -40,6 +46,10 @@ def load_image(self): self.preview.setPixmap(pixmap) + def paste_image(self): + self.clear_image() + self.preview.setPixmap(QApplication.clipboard().pixmap()) + def clear_image(self): self.preview.setPixmap(QPixmap()) From 6d4ef4a0bbd93f3c3d284bebfe65dc55e01a6e2f Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Sun, 2 Apr 2023 03:40:30 -0600 Subject: [PATCH 21/55] Get the models and modules from the backend --- frontends/krita/krita_diff/client.py | 28 +++ frontends/krita/krita_diff/defaults.py | 66 ++++++- frontends/krita/krita_diff/pages/config.py | 2 + .../krita/krita_diff/pages/controlnet.py | 180 +++++++----------- frontends/krita/krita_diff/script.py | 4 + .../krita/krita_diff/widgets/image_loader.py | 36 ++-- 6 files changed, 185 insertions(+), 131 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 2f89a393..91d29c57 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -14,6 +14,7 @@ LONG_TIMEOUT, OFFICIAL_ROUTE_PREFIX, ROUTE_PREFIX, + CONTROLNET_ROUTE_PREFIX, SHORT_TIMEOUT, STATE_DONE, STATE_READY, @@ -294,6 +295,33 @@ def cb(obj): self.get("config", cb, ignore_no_connection=True) + #Get config for controlnet + def get_controlnet_config(self): + def check_response(obj, key: str): + try: + assert key in obj + except: + self.status.emit( + f"{STATE_URLERROR}: incompatible response, are you running the right API?" + ) + print("Invalid Response:\n", obj) + return + + def set_model_list(obj): + key = "model_list" + check_response(obj, key) + self.cfg.set("controlnet_model_list", ["None"] + obj[key]) + + def set_preprocessor_list(obj): + key = "module_list" + check_response(obj, key) + self.cfg.set("controlnet_preprocessor_list", obj[key]) + + #Get controlnet API url + url = get_url(self.cfg, prefix=CONTROLNET_ROUTE_PREFIX) + self.get("model_list", set_model_list, base_url=url) + self.get("module_list", set_preprocessor_list, base_url=url) + def post_txt2img(self, cb, width, height, has_selection): params = dict(orig_width=width, orig_height=height) if not self.cfg("just_use_yaml", bool): diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index a9f421fd..eb4a7c62 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -25,6 +25,7 @@ THREADED = True ROUTE_PREFIX = "/sdapi/interpause/" OFFICIAL_ROUTE_PREFIX = "/sdapi/v1/" +CONTROLNET_ROUTE_PREFIX = "/controlnet/" # error messages ERR_MISSING_CONFIG = "Report this bug, developer missed out a config key somewhere." @@ -42,6 +43,69 @@ TAB_CONTROLNET = "krita_diff_controlnet" TAB_PREVIEW = "krita_diff_preview" +# controlnet +CONTROLNET_PREPROCESSOR_SETTINGS = { + "canny": { + "resolution_label": "Annotator resolution", + "treshold_a_label": "Canny low treshold", + "treshold_b_label": "Canny high treshold", + "treshold_a_value": 100, + "treshold_b_value": 200, + "treshold_a_min_value": 1, + "treshold_a_max_value": 255, + "treshold_b_min_value": 1, + "treshold_b_max_value": 255 + }, + "depth": { + "resolution_label": "Midas resolution", + }, + "depth_leres": { + "resolution_label": "LeReS resolution", + "treshold_a_label": "Remove near %", + "treshold_b_label": "Remove background %", + "treshold_a_min_value": 0, + "treshold_a_max_value": 100, + "treshold_b_min_value": 0, + "treshold_b_max_value": 100 + }, + "hed": { + "resolution_label": "HED resolution", + }, + "mlsd": { + "resolution_label": "Hough resolution", + "treshold_a_label": "Hough value threshold (MLSD)", + "treshold_b_label": "Hough distance threshold (MLSD)", + "treshold_a_value": 0.1, + "treshold_b_value": 0.1, + "treshold_a_min_value": 0.01, + "treshold_b_max_value": 2, + "treshold_a_min_value": 0.01, + "treshold_b_max_value": 20, + "treshold_step": 0.01 + }, + "normal_map": { + "treshold_a_label": "Normal background threshold", + "treshold_a_value": 0.4, + "treshold_a_min_value": 0, + "treshold_a_max_value": 1, + "treshold_step": 0.01 + }, + "openpose": {}, + "openpose_hand": {}, + "clip_vision": {}, + "color": {}, + "pidinet": {}, + "scribble": {}, + "fake_scribble": { + "resolution_label": "HED resolution", + }, + "segmentation": {}, + "binary": { + "treshold_a_label": "Binary threshold", + "treshold_a_min_value": 0, + "treshold_a_max_value": 255, + } +} @dataclass(frozen=True) class Defaults: @@ -130,7 +194,7 @@ class Defaults: upscale_downscale_first: bool = False controlnet_unit: int = 0 - controlnet_unit_list: List[str] = field(default_factory=lambda: list(range(1, 11))) + controlnet_unit_list: List[str] = field(default_factory=lambda: list(range(10))) controlnet_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) controlnet_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) diff --git a/frontends/krita/krita_diff/pages/config.py b/frontends/krita/krita_diff/pages/config.py index 9060c58d..a9a813bf 100644 --- a/frontends/krita/krita_diff/pages/config.py +++ b/frontends/krita/krita_diff/pages/config.py @@ -153,6 +153,7 @@ def cfg_connect(self): self.base_url.textChanged.connect(partial(script.cfg.set, "base_url")) # NOTE: this triggers on every keystroke; theres no focus lost signal... self.base_url.textChanged.connect(lambda: script.action_update_config()) + self.base_url.textChanged.connect(lambda: script.action_update_controlnet_config()) self.base_url_reset.released.connect( lambda: self.base_url.setText(DEFAULTS.base_url) ) @@ -178,6 +179,7 @@ def restore_defaults(): script.cfg.set("first_setup", False) # retrieve list of available stuff again script.action_update_config() + script.action_update_controlnet_config() self.refresh_btn.released.connect(lambda: script.action_update_config()) self.restore_defaults.released.connect(restore_defaults) diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index a3b8cc88..f81813eb 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -1,5 +1,6 @@ -from krita import QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout +from krita import QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout +from ..defaults import CONTROLNET_PREPROCESSOR_SETTINGS from ..script import script from ..widgets import StatusBar, ImageLoaderLayout, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout @@ -14,7 +15,7 @@ def __init__(self, *args, **kwargs): ) self.controlnet_unit.qcombo.setEditable(False) self.controlnet_unit_layout_list = list(ControlNetUnitSettings(i) - for i in range(0, len(script.cfg("controlnet_unit_list")))) + for i in range(len(script.cfg("controlnet_unit_list")))) self.units_stacked_layout = QStackedLayout() @@ -28,7 +29,7 @@ def __init__(self, *args, **kwargs): self.setLayout(layout) def controlnet_unit_changed(self, selected: str): - self.units_stacked_layout.setCurrentIndex(int(selected)-1) + self.units_stacked_layout.setCurrentIndex(int(selected)) def cfg_init(self): self.controlnet_unit.cfg_init() @@ -88,6 +89,9 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): script.cfg, "controlnet_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" ) + #Refresh button + self.refresh_button = QPushButton("Refresh") + self.weight_layout = QSpinBoxLayout( script.cfg, f"controlnet{cfg_unit_number}_weight", label="Weight:", min=0, max=2, step=0.05 ) @@ -137,14 +141,12 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): main_settings_layout.addWidget(self.guess_mode) guidance_layout = QHBoxLayout() - guidance_layout.addLayout(self.weight_layout) guidance_layout.addLayout(self.guidance_start_layout) guidance_layout.addLayout(self.guidance_end_layout) - preprocessor_settings_layout = QHBoxLayout() - preprocessor_settings_layout.addLayout(self.annotator_resolution) - preprocessor_settings_layout.addLayout(self.treshold_a) - preprocessor_settings_layout.addLayout(self.treshold_b) + treshold_layout = QHBoxLayout() + treshold_layout.addLayout(self.treshold_a) + treshold_layout.addLayout(self.treshold_b) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) @@ -154,8 +156,11 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): layout.addLayout(main_settings_layout) layout.addLayout(self.preprocessor_layout) layout.addLayout(self.model_layout) + layout.addWidget(self.refresh_button) + layout.addLayout(self.weight_layout) layout.addLayout(guidance_layout) - layout.addLayout(preprocessor_settings_layout) + layout.addLayout(self.annotator_resolution) + layout.addLayout(treshold_layout) layout.addStretch() self.setLayout(layout) @@ -166,45 +171,51 @@ def change_image_loader_state(self, state): else: self.image_loader.enable() - def preprocessor_changed(self, selected: str): - self.init_preprocessor_layouts(selected) - if selected in preprocessor_settings: - self.annotator_resolution.label = preprocessor_settings[selected]["resolution_label"] \ - if "resolution_label" in preprocessor_settings[selected] else "Preprocessor resolution:" + def set_preprocessor_options(self, selected: str): + if selected in CONTROLNET_PREPROCESSOR_SETTINGS: + self.show_preprocessor_options() + self.annotator_resolution.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["resolution_label"] \ + if "resolution_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else "Preprocessor resolution:") - if "treshold_a_label" in preprocessor_settings[selected]: - self.treshold_a.label = preprocessor_settings[selected]["treshold_a_label"] - self.treshold_a.min = preprocessor_settings[selected]["treshold_a_min_value"] \ - if "treshold_a_min_value" in preprocessor_settings[selected] else 0 - self.treshold_a.max = preprocessor_settings[selected]["treshold_a_max_value"] \ - if "treshold_a_max_value" in preprocessor_settings[selected] else 0 - self.treshold_a.value = preprocessor_settings[selected]["treshold_a_value"] \ - if "treshold_a_value" in preprocessor_settings[selected] else 0 - self.treshold_a.step = preprocessor_settings[selected]["treshold_step"] \ - if "treshold_step" in preprocessor_settings[selected] else 1 + if "treshold_a_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: + self.treshold_a.qlabel.show() + self.treshold_a.qspin.show() + self.treshold_a.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_label"]) + self.treshold_a.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_min_value"] \ + if "treshold_a_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.treshold_a.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_max_value"] \ + if "treshold_a_max_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.treshold_a.qspin.setValue(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_value"] \ + if "treshold_a_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else self.treshold_a.qspin.minimum()) + self.treshold_a.qspin.setSingleStep(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_step"] \ + if "treshold_step" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 1) else: - self.treshold_a.hide() + self.treshold_a.qlabel.hide() + self.treshold_a.qspin.hide() - if "treshold_b_label" in preprocessor_settings[selected]: - self.treshold_b.label = preprocessor_settings[selected]["treshold_b_label"] - self.treshold_b.min = preprocessor_settings[selected]["treshold_b_min_value"] \ - if "treshold_b_min_value" in preprocessor_settings[selected] else 0 - self.treshold_b.max = preprocessor_settings[selected]["treshold_b_max_value"] \ - if "treshold_b_max_value" in preprocessor_settings[selected] else 0 - self.treshold_b.value = preprocessor_settings[selected]["treshold_b_value"] \ - if "treshold_b_value" in preprocessor_settings[selected] else 0 - self.treshold_b.step = preprocessor_settings[selected]["treshold_step"] \ - if "treshold_step" in preprocessor_settings[selected] else 1 - - def init_preprocessor_layouts(self, selected: str): - if selected in preprocessor_settings: - self.show_preprocessor_options() + if "treshold_b_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: + self.treshold_b.qlabel.show() + self.treshold_b.qspin.show() + self.treshold_b.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_label"]) + self.treshold_b.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_min_value"] \ + if "treshold_b_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.treshold_b.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_max_value"] \ + if "treshold_b_max_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.treshold_b.qspin.setValue(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_value"] \ + if "treshold_b_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else self.treshold_b.qspin.minimum()) + self.treshold_b.qspin.setSingleStep(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_step"] \ + if "treshold_b_step" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 1) + else: + self.treshold_b.qlabel.hide() + self.treshold_b.qspin.hide() else: - self.hide_preprocessor_options() + self.hide_preprocessor_options(selected) - def hide_preprocessor_options(self): - self.annotator_resolution.qlabel.hide() - self.annotator_resolution.qspin.hide() + def hide_preprocessor_options(self, selected: str): + if selected == "none": + self.annotator_resolution.qlabel.hide() + self.annotator_resolution.qspin.hide() + self.treshold_a.qlabel.hide() self.treshold_a.qspin.hide() self.treshold_b.qlabel.hide() @@ -218,6 +229,10 @@ def show_preprocessor_options(self): self.treshold_b.qlabel.show() self.treshold_b.qspin.show() + def enabled_changed(self, state): + if state == 1 or state == 2: + script.action_update_controlnet_config() + def cfg_init(self): self.enable.cfg_init() self.use_selection_as_input.cfg_init() @@ -230,8 +245,11 @@ def cfg_init(self): self.weight_layout.cfg_init() self.guidance_start_layout.cfg_init() self.guidance_end_layout.cfg_init() + self.annotator_resolution.cfg_init() + self.treshold_a.cfg_init() + self.treshold_b.cfg_init() self.change_image_loader_state(self.use_selection_as_input.checkState()) - self.init_preprocessor_layouts(self.preprocessor_layout.qcombo.currentText()) + self.set_preprocessor_options(self.preprocessor_layout.qcombo.currentText()) def cfg_connect(self): self.enable.cfg_connect() @@ -245,72 +263,10 @@ def cfg_connect(self): self.weight_layout.cfg_connect() self.guidance_start_layout.cfg_connect() self.guidance_end_layout.cfg_connect() + self.annotator_resolution.cfg_connect() + self.treshold_a.cfg_connect() + self.treshold_b.cfg_connect() + self.enable.stateChanged.connect(self.enabled_changed) self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) - self.preprocessor_layout.qcombo.currentTextChanged.connect(self.preprocessor_changed) - -preprocessor_settings = { - "canny": { - "resolution_label": "Annotator resolution", - "treshold_a_label": "Canny low treshold", - "treshold_b_label": "Canny high treshold", - "treshold_a_value": 100, - "treshold_b_value": 200, - "treshold_a_min_value": 1, - "treshold_a_max_value": 255, - "treshold_b_min_value": 1, - "treshold_b_max_value": 255 - }, - "depth": { - "resolution_label": "Midas resolution", - }, - "depth_leres": { - "resolution_label": "LeReS resolution", - "treshold_a_label": "Remove near %", - "treshold_b_label": "Remove background %", - "treshold_a_min_value": 0, - "treshold_a_max_value": 100, - "treshold_b_min_value": 0, - "treshold_b_max_value": 100 - }, - "hed": { - "resolution_label": "HED resolution", - }, - "mlsd": { - "resolution_label": "Hough resolution", - "treshold_a_label": "Hough value threshold (MLSD)", - "treshold_b_label": "Hough distance threshold (MLSD)", - "treshold_a_value": 0.1, - "treshold_b_value": 0.1, - "treshold_a_min_value": 0.01, - "treshold_b_max_value": 2, - "treshold_a_min_value": 0.01, - "treshold_b_max_value": 20, - "treshold_step": 0.01 - }, - "normal_map": { - "treshold_a_label": "Normal background threshold", - "treshold_a_value": 0.4, - "treshold_a_min_value": 0, - "treshold_a_max_value": 1, - "treshold_b_min_value": 0, - "treshold_b_max_value": 1, - "treshold_step": 0.01 - }, - "openpose": {}, - "openpose_hand": {}, - "clip_vision": {}, - "color": {}, - "pidinet": {}, - "scribble": {}, - "fake_scribble": { - "resolution_label": "HED resolution", - }, - "segmentation": {}, - "binary": { - "treshold_a_label": "Binary threshold", - "treshold_a_min_value": 0, - "treshold_a_max_value": 255, - "treshold_b_min_value": 0, - "treshold_b_max_value": 255, - } -} \ No newline at end of file + self.preprocessor_layout.qcombo.currentTextChanged.connect(self.set_preprocessor_options) + self.refresh_button.released.connect(lambda: script.action_update_controlnet_config()) \ No newline at end of file diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 069eb27f..2ad33ed5 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -444,6 +444,10 @@ def action_simple_upscale(self): def action_update_config(self): """Update certain config/state from the backend.""" self.client.get_config() + + def action_update_controlnet_config(self): + """Update controlnet config from the backend.""" + self.client.get_controlnet_config() def action_interrupt(self): def cb(resp=None): diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py index 9eeaf87a..78ff05ac 100644 --- a/frontends/krita/krita_diff/widgets/image_loader.py +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -7,37 +7,37 @@ def __init__(self, *args, **kwargs): self.preview = QLabel() self.preview.setAlignment(Qt.AlignCenter) - self.importBtn = QPushButton('Import image') - self.pasteBtn = QPushButton('Paste image') - self.clearBtn = QPushButton('Clear') + self.import_button = QPushButton('Import image') + self.paste_button = QPushButton('Paste image') + self.clear_button = QPushButton('Clear') - btnLayout = QHBoxLayout() - btnLayout.addWidget(self.importBtn) - btnLayout.addWidget(self.pasteBtn) + button_layout = QHBoxLayout() + button_layout.addWidget(self.import_button) + button_layout.addWidget(self.paste_button) - self.addLayout(btnLayout) - self.addWidget(self.clearBtn) + self.addLayout(button_layout) + self.addWidget(self.clear_button) self.addWidget(self.preview) - self.importBtn.released.connect(self.load_image) - self.pasteBtn.released.connect(self.paste_image) - self.clearBtn.released.connect(self.clear_image) + self.import_button.released.connect(self.load_image) + self.paste_button.released.connect(self.paste_image) + self.clear_button.released.connect(self.clear_image) def disable(self): self.preview.setEnabled(False) - self.importBtn.setEnabled(False) - self.pasteBtn.setEnabled(False) - self.clearBtn.setEnabled(False) + self.import_button.setEnabled(False) + self.paste_button.setEnabled(False) + self.clear_button.setEnabled(False) def enable(self): self.preview.setEnabled(True) - self.importBtn.setEnabled(True) - self.pasteBtn.setEnabled(True) - self.clearBtn.setEnabled(True) + self.import_button.setEnabled(True) + self.paste_button.setEnabled(True) + self.clear_button.setEnabled(True) def load_image(self): self.clear_image() - file_name, _ = QFileDialog.getOpenFileName(self.importBtn, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') + file_name, _ = QFileDialog.getOpenFileName(self.import_button, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') if file_name: pixmap = QPixmap(file_name) From e223b8676171f43a9b7529f87eeee05a5858c0de Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Sun, 2 Apr 2023 10:44:20 -0600 Subject: [PATCH 22/55] Added the ability to preview annotators --- frontends/krita/krita_diff/client.py | 13 ++ frontends/krita/krita_diff/defaults.py | 112 ++++++------ .../krita/krita_diff/pages/controlnet.py | 171 +++++++++--------- frontends/krita/krita_diff/script.py | 23 +++ .../krita/krita_diff/widgets/image_loader.py | 12 -- 5 files changed, 174 insertions(+), 157 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 91d29c57..2d2f32f9 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -425,6 +425,19 @@ def post_upscale(self, cb, src_img): ) self.post("upscale", params, cb) + def post_controlnet_preview(self, cb, src_img, unit): + params = ( + { + "controlnet_module": self.cfg(f"controlnet{unit}_preprocessor"), + "controlnet_input_images": [img_to_b64(src_img)], + "controlnet_processor_res": self.cfg(f"controlnet{unit}_preprocessor_resolution"), + "controlnet_threshold_a": self.cfg(f"controlnet{unit}_threshold_a"), + "controlnet_threshold_b": self.cfg(f"controlnet{unit}_threshold_b") + } #Not sure if it's necessary to make the just_use_yaml validation here + ) + url = get_url(self.cfg, prefix=CONTROLNET_ROUTE_PREFIX) + self.post("detect", params, cb, url) + def post_interrupt(self, cb): # get official API url url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index eb4a7c62..13ce4427 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -47,48 +47,48 @@ CONTROLNET_PREPROCESSOR_SETTINGS = { "canny": { "resolution_label": "Annotator resolution", - "treshold_a_label": "Canny low treshold", - "treshold_b_label": "Canny high treshold", - "treshold_a_value": 100, - "treshold_b_value": 200, - "treshold_a_min_value": 1, - "treshold_a_max_value": 255, - "treshold_b_min_value": 1, - "treshold_b_max_value": 255 + "threshold_a_label": "Canny low threshold", + "threshold_b_label": "Canny high threshold", + "threshold_a_value": 100, + "threshold_b_value": 200, + "threshold_a_min_value": 1, + "threshold_a_max_value": 255, + "threshold_b_min_value": 1, + "threshold_b_max_value": 255 }, "depth": { "resolution_label": "Midas resolution", }, "depth_leres": { "resolution_label": "LeReS resolution", - "treshold_a_label": "Remove near %", - "treshold_b_label": "Remove background %", - "treshold_a_min_value": 0, - "treshold_a_max_value": 100, - "treshold_b_min_value": 0, - "treshold_b_max_value": 100 + "threshold_a_label": "Remove near %", + "threshold_b_label": "Remove background %", + "threshold_a_min_value": 0, + "threshold_a_max_value": 100, + "threshold_b_min_value": 0, + "threshold_b_max_value": 100 }, "hed": { "resolution_label": "HED resolution", }, "mlsd": { "resolution_label": "Hough resolution", - "treshold_a_label": "Hough value threshold (MLSD)", - "treshold_b_label": "Hough distance threshold (MLSD)", - "treshold_a_value": 0.1, - "treshold_b_value": 0.1, - "treshold_a_min_value": 0.01, - "treshold_b_max_value": 2, - "treshold_a_min_value": 0.01, - "treshold_b_max_value": 20, - "treshold_step": 0.01 + "threshold_a_label": "Hough value threshold (MLSD)", + "threshold_b_label": "Hough distance threshold (MLSD)", + "threshold_a_value": 0.1, + "threshold_b_value": 0.1, + "threshold_a_min_value": 0.01, + "threshold_b_max_value": 2, + "threshold_a_min_value": 0.01, + "threshold_b_max_value": 20, + "threshold_step": 0.01 }, "normal_map": { - "treshold_a_label": "Normal background threshold", - "treshold_a_value": 0.4, - "treshold_a_min_value": 0, - "treshold_a_max_value": 1, - "treshold_step": 0.01 + "threshold_a_label": "Normal background threshold", + "threshold_a_value": 0.4, + "threshold_a_min_value": 0, + "threshold_a_max_value": 1, + "threshold_step": 0.01 }, "openpose": {}, "openpose_hand": {}, @@ -101,9 +101,9 @@ }, "segmentation": {}, "binary": { - "treshold_a_label": "Binary threshold", - "treshold_a_min_value": 0, - "treshold_a_max_value": 255, + "threshold_a_label": "Binary threshold", + "threshold_a_min_value": 0, + "threshold_a_max_value": 255, } } @@ -199,7 +199,6 @@ class Defaults: controlnet_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) controlnet0_enable: bool = False - controlnet0_use_selection_as_input: bool = True controlnet0_invert_input_color: bool = False controlnet0_RGB_to_BGR: bool = False controlnet0_low_vram: bool = False @@ -210,11 +209,10 @@ class Defaults: controlnet0_guidance_start: float = 0 controlnet0_guidance_end: float = 1 controlnet0_preprocessor_resolution: int = 512 - controlnet0_treshold_a: float = 0 - controlnet0_treshold_b: float = 0 + controlnet0_threshold_a: float = 0 + controlnet0_threshold_b: float = 0 controlnet1_enable: bool = False - controlnet1_use_selection_as_input: bool = True controlnet1_invert_input_color: bool = False controlnet1_RGB_to_BGR: bool = False controlnet1_low_vram: bool = False @@ -225,11 +223,10 @@ class Defaults: controlnet1_guidance_start: float = 0 controlnet1_guidance_end: float = 1 controlnet1_preprocessor_resolution: int = 512 - controlnet1_treshold_a: float = 0 - controlnet1_treshold_b: float = 0 + controlnet1_threshold_a: float = 0 + controlnet1_threshold_b: float = 0 controlnet2_enable: bool = False - controlnet2_use_selection_as_input: bool = True controlnet2_invert_input_color: bool = False controlnet2_RGB_to_BGR: bool = False controlnet2_low_vram: bool = False @@ -240,11 +237,10 @@ class Defaults: controlnet2_guidance_start: float = 0 controlnet2_guidance_end: float = 1 controlnet2_preprocessor_resolution: int = 512 - controlnet2_treshold_a: float = 0 - controlnet2_treshold_b: float = 0 + controlnet2_threshold_a: float = 0 + controlnet2_threshold_b: float = 0 controlnet3_enable: bool = False - controlnet3_use_selection_as_input: bool = True controlnet3_invert_input_color: bool = False controlnet3_RGB_to_BGR: bool = False controlnet3_low_vram: bool = False @@ -255,11 +251,10 @@ class Defaults: controlnet3_guidance_start: float = 0 controlnet3_guidance_end: float = 1 controlnet3_preprocessor_resolution: int = 512 - controlnet3_treshold_a: float = 0 - controlnet3_treshold_b: float = 0 + controlnet3_threshold_a: float = 0 + controlnet3_threshold_b: float = 0 controlnet4_enable: bool = False - controlnet4_use_selection_as_input: bool = True controlnet4_invert_input_color: bool = False controlnet4_RGB_to_BGR: bool = False controlnet4_low_vram: bool = False @@ -270,11 +265,10 @@ class Defaults: controlnet4_guidance_start: float = 0 controlnet4_guidance_end: float = 1 controlnet4_preprocessor_resolution: int = 512 - controlnet4_treshold_a: float = 0 - controlnet4_treshold_b: float = 0 + controlnet4_threshold_a: float = 0 + controlnet4_threshold_b: float = 0 controlnet5_enable: bool = False - controlnet5_use_selection_as_input: bool = True controlnet5_invert_input_color: bool = False controlnet5_RGB_to_BGR: bool = False controlnet5_low_vram: bool = False @@ -285,11 +279,10 @@ class Defaults: controlnet5_guidance_start: float = 0 controlnet5_guidance_end: float = 1 controlnet5_preprocessor_resolution: int = 512 - controlnet5_treshold_a: float = 0 - controlnet5_treshold_b: float = 0 + controlnet5_threshold_a: float = 0 + controlnet5_threshold_b: float = 0 controlnet6_enable: bool = False - controlnet6_use_selection_as_input: bool = True controlnet6_invert_input_color: bool = False controlnet6_RGB_to_BGR: bool = False controlnet6_low_vram: bool = False @@ -300,11 +293,10 @@ class Defaults: controlnet6_guidance_start: float = 0 controlnet6_guidance_end: float = 1 controlnet6_preprocessor_resolution: int = 512 - controlnet6_treshold_a: float = 0 - controlnet6_treshold_b: float = 0 + controlnet6_threshold_a: float = 0 + controlnet6_threshold_b: float = 0 controlnet7_enable: bool = False - controlnet7_use_selection_as_input: bool = True controlnet7_invert_input_color: bool = False controlnet7_RGB_to_BGR: bool = False controlnet7_low_vram: bool = False @@ -315,11 +307,10 @@ class Defaults: controlnet7_guidance_start: float = 0 controlnet7_guidance_end: float = 1 controlnet7_preprocessor_resolution: int = 512 - controlnet7_treshold_a: float = 0 - controlnet7_treshold_b: float = 0 + controlnet7_threshold_a: float = 0 + controlnet7_threshold_b: float = 0 controlnet8_enable: bool = False - controlnet8_use_selection_as_input: bool = True controlnet8_invert_input_color: bool = False controlnet8_RGB_to_BGR: bool = False controlnet8_low_vram: bool = False @@ -330,11 +321,10 @@ class Defaults: controlnet8_guidance_start: float = 0 controlnet8_guidance_end: float = 1 controlnet8_preprocessor_resolution: int = 512 - controlnet8_treshold_a: float = 0 - controlnet8_treshold_b: float = 0 + controlnet8_threshold_a: float = 0 + controlnet8_threshold_b: float = 0 controlnet9_enable: bool = False - controlnet9_use_selection_as_input: bool = True controlnet9_invert_input_color: bool = False controlnet9_RGB_to_BGR: bool = False controlnet9_low_vram: bool = False @@ -345,7 +335,7 @@ class Defaults: controlnet9_guidance_start: float = 0 controlnet9_guidance_end: float = 1 controlnet9_preprocessor_resolution: int = 512 - controlnet9_treshold_a: float = 0 - controlnet9_treshold_b: float = 0 + controlnet9_threshold_a: float = 0 + controlnet9_threshold_b: float = 0 DEFAULTS = Defaults() diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index f81813eb..142cd7ed 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -1,8 +1,8 @@ -from krita import QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout +from krita import QPixmap, QImage, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout, Qt from ..defaults import CONTROLNET_PREPROCESSOR_SETTINGS from ..script import script -from ..widgets import StatusBar, ImageLoaderLayout, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout +from ..widgets import QLabel, StatusBar, ImageLoaderLayout, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout class ControlNetPage(QWidget): name = "ControlNet" @@ -49,90 +49,89 @@ def cfg_connect(self): class ControlNetUnitSettings(QWidget): def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): super(ControlNetUnitSettings, self).__init__(*args, **kwargs) + self.unit = cfg_unit_number #Top checkbox self.enable = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_enable", f"Enable ControlNet {cfg_unit_number}" - ) - self.use_selection_as_input = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_use_selection_as_input", "Use selection as input" + script.cfg, f"controlnet{self.unit}_enable", f"Enable ControlNet {self.unit}" ) self.image_loader = ImageLoaderLayout() #Main settings self.invert_input_color = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_invert_input_color", "Invert input color" + script.cfg, f"controlnet{self.unit}_invert_input_color", "Invert input color" ) self.RGB_to_BGR = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_RGB_to_BGR", "RGB to BGR" + script.cfg, f"controlnet{self.unit}_RGB_to_BGR", "RGB to BGR" ) self.low_vram = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_low_vram", "Low VRAM" + script.cfg, f"controlnet{self.unit}_low_vram", "Low VRAM" ) self.guess_mode = QCheckBox( - script.cfg, f"controlnet{cfg_unit_number}_guess_mode", "Guess mode" + script.cfg, f"controlnet{self.unit}_guess_mode", "Guess mode" ) #Tips self.tips = TipsLayout( - ["Invert colors if your image has white background."] + ["Invert colors if your image has white background.", + "Selection will be used as input if no image has been uploaded or pasted."] ) #Preprocessor list self.preprocessor_layout = QComboBoxLayout( - script.cfg, "controlnet_preprocessor_list", f"controlnet{cfg_unit_number}_preprocessor", label="Preprocessor:" + script.cfg, "controlnet_preprocessor_list", f"controlnet{self.unit}_preprocessor", label="Preprocessor:" ) #Model list self.model_layout = QComboBoxLayout( - script.cfg, "controlnet_model_list", f"controlnet{cfg_unit_number}_model", label="Model:" + script.cfg, "controlnet_model_list", f"controlnet{self.unit}_model", label="Model:" ) #Refresh button self.refresh_button = QPushButton("Refresh") self.weight_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_weight", label="Weight:", min=0, max=2, step=0.05 + script.cfg, f"controlnet{self.unit}_weight", label="Weight:", min=0, max=2, step=0.05 ) - self.guidance_start_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_guidance_start", label="Guidance start:", min=0, max=1, step=0.01 + script.cfg, f"controlnet{self.unit}_guidance_start", label="Guidance start:", min=0, max=1, step=0.01 ) - self.guidance_end_layout = QSpinBoxLayout( - script.cfg, f"controlnet{cfg_unit_number}_guidance_end", label="Guidance end:", min=0, max=1, step=0.01 + script.cfg, f"controlnet{self.unit}_guidance_end", label="Guidance end:", min=0, max=1, step=0.01 ) #Preprocessor settings self.annotator_resolution = QSpinBoxLayout( script.cfg, - f"controlnet{cfg_unit_number}_preprocessor_resolution", + f"controlnet{self.unit}_preprocessor_resolution", label="Preprocessor resolution:", min=64, max=2048, step=1 ) - self.treshold_a = QSpinBoxLayout( + self.threshold_a = QSpinBoxLayout( script.cfg, - f"controlnet{cfg_unit_number}_treshold_a", - label="Treshold A:", + f"controlnet{self.unit}_threshold_a", + label="Threshold A:", min=1, max=255, step=1 ) - self.treshold_b = QSpinBoxLayout( + self.threshold_b = QSpinBoxLayout( script.cfg, - f"controlnet{cfg_unit_number}_treshold_b", - label="Treshold B:", + f"controlnet{self.unit}_threshold_b", + label="Threshold B:", min=1, max=255, step=1 ) - top_checkbox_layout = QHBoxLayout() - top_checkbox_layout.addWidget(self.enable) - top_checkbox_layout.addWidget(self.use_selection_as_input) + #Preview annotator + self.annotator_preview = QLabel() + self.annotator_preview.setAlignment(Qt.AlignCenter) + self.annotator_preview_button = QPushButton("Preview annotator") + self.annotator_clear_button = QPushButton("Clear preview") main_settings_layout = QHBoxLayout() main_settings_layout.addWidget(self.invert_input_color) @@ -144,13 +143,13 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): guidance_layout.addLayout(self.guidance_start_layout) guidance_layout.addLayout(self.guidance_end_layout) - treshold_layout = QHBoxLayout() - treshold_layout.addLayout(self.treshold_a) - treshold_layout.addLayout(self.treshold_b) + threshold_layout = QHBoxLayout() + threshold_layout.addLayout(self.threshold_a) + threshold_layout.addLayout(self.threshold_b) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) - layout.addLayout(top_checkbox_layout) + layout.addWidget(self.enable) layout.addLayout(self.image_loader) layout.addLayout(self.tips) layout.addLayout(main_settings_layout) @@ -160,74 +159,75 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): layout.addLayout(self.weight_layout) layout.addLayout(guidance_layout) layout.addLayout(self.annotator_resolution) - layout.addLayout(treshold_layout) + layout.addLayout(threshold_layout) + layout.addWidget(self.annotator_preview) + layout.addWidget(self.annotator_preview_button) + layout.addWidget(self.annotator_clear_button) layout.addStretch() self.setLayout(layout) - def change_image_loader_state(self, state): - if state == 1 or state == 2: - self.image_loader.disable() - else: - self.image_loader.enable() - def set_preprocessor_options(self, selected: str): if selected in CONTROLNET_PREPROCESSOR_SETTINGS: self.show_preprocessor_options() self.annotator_resolution.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["resolution_label"] \ if "resolution_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else "Preprocessor resolution:") - if "treshold_a_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: - self.treshold_a.qlabel.show() - self.treshold_a.qspin.show() - self.treshold_a.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_label"]) - self.treshold_a.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_min_value"] \ - if "treshold_a_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) - self.treshold_a.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_max_value"] \ - if "treshold_a_max_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) - self.treshold_a.qspin.setValue(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_a_value"] \ - if "treshold_a_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else self.treshold_a.qspin.minimum()) - self.treshold_a.qspin.setSingleStep(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_step"] \ - if "treshold_step" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 1) + if "threshold_a_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: + self.threshold_a.qlabel.show() + self.threshold_a.qspin.show() + self.threshold_a.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_label"]) + self.threshold_a.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_min_value"] \ + if "threshold_a_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.threshold_a.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_max_value"] \ + if "threshold_a_max_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.threshold_a.qspin.setValue(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_value"] \ + if "threshold_a_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else self.threshold_a.qspin.minimum()) + self.threshold_a.qspin.setSingleStep(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_step"] \ + if "threshold_step" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 1) else: - self.treshold_a.qlabel.hide() - self.treshold_a.qspin.hide() + self.threshold_a.qlabel.hide() + self.threshold_a.qspin.hide() - if "treshold_b_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: - self.treshold_b.qlabel.show() - self.treshold_b.qspin.show() - self.treshold_b.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_label"]) - self.treshold_b.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_min_value"] \ - if "treshold_b_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) - self.treshold_b.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_max_value"] \ - if "treshold_b_max_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) - self.treshold_b.qspin.setValue(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_value"] \ - if "treshold_b_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else self.treshold_b.qspin.minimum()) - self.treshold_b.qspin.setSingleStep(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["treshold_b_step"] \ - if "treshold_b_step" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 1) + if "threshold_b_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: + self.threshold_b.qlabel.show() + self.threshold_b.qspin.show() + self.threshold_b.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_label"]) + self.threshold_b.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_min_value"] \ + if "threshold_b_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.threshold_b.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_max_value"] \ + if "threshold_b_max_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) + self.threshold_b.qspin.setValue(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_value"] \ + if "threshold_b_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else self.threshold_b.qspin.minimum()) + self.threshold_b.qspin.setSingleStep(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_step"] \ + if "threshold_b_step" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 1) else: - self.treshold_b.qlabel.hide() - self.treshold_b.qspin.hide() + self.threshold_b.qlabel.hide() + self.threshold_b.qspin.hide() else: self.hide_preprocessor_options(selected) def hide_preprocessor_options(self, selected: str): + #Hide all annotator settings if no annotator chosen. + #if there is an annotator that hasn't been listed in defaults, + #just show resolution option. Users may be able to play + #with new unsupported annotators, but they may or not work. if selected == "none": self.annotator_resolution.qlabel.hide() self.annotator_resolution.qspin.hide() - self.treshold_a.qlabel.hide() - self.treshold_a.qspin.hide() - self.treshold_b.qlabel.hide() - self.treshold_b.qspin.hide() + self.threshold_a.qlabel.hide() + self.threshold_a.qspin.hide() + self.threshold_b.qlabel.hide() + self.threshold_b.qspin.hide() def show_preprocessor_options(self): self.annotator_resolution.qlabel.show() self.annotator_resolution.qspin.show() - self.treshold_a.qlabel.show() - self.treshold_a.qspin.show() - self.treshold_b.qlabel.show() - self.treshold_b.qspin.show() + self.threshold_a.qlabel.show() + self.threshold_a.qspin.show() + self.threshold_b.qlabel.show() + self.threshold_b.qspin.show() def enabled_changed(self, state): if state == 1 or state == 2: @@ -235,7 +235,6 @@ def enabled_changed(self, state): def cfg_init(self): self.enable.cfg_init() - self.use_selection_as_input.cfg_init() self.invert_input_color.cfg_init() self.RGB_to_BGR.cfg_init() self.low_vram.cfg_init() @@ -246,14 +245,12 @@ def cfg_init(self): self.guidance_start_layout.cfg_init() self.guidance_end_layout.cfg_init() self.annotator_resolution.cfg_init() - self.treshold_a.cfg_init() - self.treshold_b.cfg_init() - self.change_image_loader_state(self.use_selection_as_input.checkState()) + self.threshold_a.cfg_init() + self.threshold_b.cfg_init() self.set_preprocessor_options(self.preprocessor_layout.qcombo.currentText()) def cfg_connect(self): self.enable.cfg_connect() - self.use_selection_as_input.cfg_connect() self.invert_input_color.cfg_connect() self.RGB_to_BGR.cfg_connect() self.low_vram.cfg_connect() @@ -264,9 +261,15 @@ def cfg_connect(self): self.guidance_start_layout.cfg_connect() self.guidance_end_layout.cfg_connect() self.annotator_resolution.cfg_connect() - self.treshold_a.cfg_connect() - self.treshold_b.cfg_connect() + self.threshold_a.cfg_connect() + self.threshold_b.cfg_connect() self.enable.stateChanged.connect(self.enabled_changed) - self.use_selection_as_input.stateChanged.connect(self.change_image_loader_state) self.preprocessor_layout.qcombo.currentTextChanged.connect(self.set_preprocessor_options) - self.refresh_button.released.connect(lambda: script.action_update_controlnet_config()) \ No newline at end of file + self.refresh_button.released.connect(lambda: script.action_update_controlnet_config()) + self.annotator_preview_button.released.connect(lambda: script.action_preview_controlnet_annotator( + self.image_loader.preview.pixmap().toImage().convertToFormat(QImage.Format_RGBA8888).rgbSwapped() + if self.image_loader.preview.pixmap() is not None else None, + self.annotator_preview, + self.unit + )) + self.annotator_clear_button.released.connect(lambda: self.annotator_preview.setPixmap(QPixmap())) \ No newline at end of file diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 2ad33ed5..c18ea9ed 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -9,6 +9,7 @@ Node, QImage, QObject, + QPixmap, Qt, QTimer, Selection, @@ -333,6 +334,20 @@ def cb(response): self.selection is not None, ) + def apply_controlnet_preview_annotator(self, image, preview_label, unit: int): + image = self.get_selection_image() if image is None else image + + def cb(response): + assert response is not None, "Backend Error, check terminal" + output = response["images"][0] + pixmap = QPixmap.fromImage(b64_to_img(output)) + + if pixmap.width() > preview_label.width(): + pixmap = pixmap.scaledToWidth(preview_label.width(), Qt.SmoothTransformation) + preview_label.setPixmap(pixmap) + + self.client.post_controlnet_preview(cb, image, unit) + def apply_simple_upscale(self): insert, _ = self.img_inserter(self.x, self.y, self.width, self.height) sel_image = self.get_selection_image() @@ -449,6 +464,14 @@ def action_update_controlnet_config(self): """Update controlnet config from the backend.""" self.client.get_controlnet_config() + def action_preview_controlnet_annotator(self, image, label, unit: int): + self.status_changed.emit(STATE_WAIT) + self.update_selection() + if not self.doc: + return + self.adjust_selection() + self.apply_controlnet_preview_annotator(image, label, unit) + def action_interrupt(self): def cb(resp=None): self.status_changed.emit(STATE_INTERRUPT) diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py index 78ff05ac..7b6dc728 100644 --- a/frontends/krita/krita_diff/widgets/image_loader.py +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -23,18 +23,6 @@ def __init__(self, *args, **kwargs): self.paste_button.released.connect(self.paste_image) self.clear_button.released.connect(self.clear_image) - def disable(self): - self.preview.setEnabled(False) - self.import_button.setEnabled(False) - self.paste_button.setEnabled(False) - self.clear_button.setEnabled(False) - - def enable(self): - self.preview.setEnabled(True) - self.import_button.setEnabled(True) - self.paste_button.setEnabled(True) - self.clear_button.setEnabled(True) - def load_image(self): self.clear_image() file_name, _ = QFileDialog.getOpenFileName(self.import_button, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') From 91d08cbe7fc8f9d41e02341f981abf57752129ae Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Mon, 3 Apr 2023 11:03:40 -0600 Subject: [PATCH 23/55] txt2img work in progress --- frontends/krita/krita_diff/client.py | 116 +++++++++++++++++- frontends/krita/krita_diff/defaults.py | 10 ++ .../krita/krita_diff/pages/controlnet.py | 30 +++-- frontends/krita/krita_diff/script.py | 58 +++++++-- frontends/krita/krita_diff/utils.py | 24 ++-- 5 files changed, 211 insertions(+), 27 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 2d2f32f9..bdb83257 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -21,7 +21,14 @@ STATE_URLERROR, THREADED, ) -from .utils import bytewise_xor, fix_prompt, get_ext_args, get_ext_key, img_to_b64 +from .utils import ( + bytewise_xor, + fix_prompt, + get_ext_args, + get_ext_key, + img_to_b64, + calculate_resized_image_dimensions +) # NOTE: backend queues up responses, so no explicit need to block multiple requests # except to prevent user from spamming themselves @@ -239,6 +246,53 @@ def common_params(self, has_selection): save_samples=self.cfg("save_temp_images", bool), ) return params + + def options_params(self): + """Parameters that are specific for the official API options endpoint.""" + params = dict( + sd_model_checkpoint=self.cfg("sd_model", str), + sd_vae=self.cfg("sd_vae", str), + CLIP_stop_at_last_layers=self.cfg("clip_skip", int), + upscaler_for_img2img=self.cfg("upscaler_name", str), + face_restoration_model=self.cfg("face_restorer_model", str), + code_former_weight=self.cfg("codeformer_weight", float), + #Couldn't find filter_nsfw option for official API. + img2img_fix_steps=self.cfg("do_exact_steps", bool), #Not sure if this is matched correctly. + return_grid=self.cfg("include_grid", bool) + ) + return params + + def official_api_common_params(self, has_selection, width, height): + """Parameters used by most official API endpoints.""" + tiling = self.cfg("sd_tiling", bool) and not ( + self.cfg("only_full_img_tiling", bool) and has_selection + ) + + params = dict( + batch_size=self.cfg("sd_batch_size", int), + width=width, + height=height, + tiling=tiling, + restore_faces=self.cfg("face_restorer_model", str) != "None", + save_images=self.cfg("save_temp_images", bool), + ) + return params + + def controlnet_unit_params(self, image: str, unit: int): + params = dict( + input_image=image, + module=self.cfg(f"controlnet{unit}_preprocessor"), + model=self.cfg(f"controlnet{unit}_model"), + weight=self.cfg(f"controlnet{unit}_weight"), + lowvram=self.cfg(f"controlnet{unit}_low_vram"), + processor_res=self.cfg(f"controlnet{unit}_preprocessor_resolution"), + threshold_a=self.cfg(f"controlnet{unit}_threshold_a"), + threshold_b=self.cfg(f"controlnet{unit}_threshold_b"), + guidance_start=self.cfg(f"controlnet{unit}_guidance_start"), + guidance_end=self.cfg(f"controlnet{unit}_guidance_end"), + guessmode=self.cfg(f"controlnet{unit}_guess_mode") + ) + return params def get_config(self): def cb(obj): @@ -295,8 +349,8 @@ def cb(obj): self.get("config", cb, ignore_no_connection=True) - #Get config for controlnet def get_controlnet_config(self): + '''Get models and modules for ControlNet''' def check_response(obj, key: str): try: assert key in obj @@ -322,6 +376,15 @@ def set_preprocessor_list(obj): self.get("model_list", set_model_list, base_url=url) self.get("module_list", set_preprocessor_list, base_url=url) + def post_options(self): + """Sets the options for the backend, using the official API""" + def cb(response): + assert response is not None, "Backend Error, check terminal" + + params = self.options_params() + url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) + self.post("options", params, cb, base_url=url) + def post_txt2img(self, cb, width, height, has_selection): params = dict(orig_width=width, orig_height=height) if not self.cfg("just_use_yaml", bool): @@ -348,6 +411,52 @@ def post_txt2img(self, cb, width, height, has_selection): self.post("txt2img", params, cb) + def post_controlnet_txt2image(self, cb, width, height, has_selection, src_imgs: dict): + """Uses official API""" + if not self.cfg("just_use_yaml", bool): + seed = ( + int(self.cfg("txt2img_seed", str)) # Qt casts int as 32-bit int + if not self.cfg("txt2img_seed", str).strip() == "" + else -1 + ) + ext_name = self.cfg("txt2img_script", str) + ext_args = get_ext_args(self.ext_cfg, "scripts_txt2img", ext_name) + resized_width, resized_height = calculate_resized_image_dimensions( + self.cfg("sd_base_size", int), self.cfg("sd_max_size", int), width, height + ) + params = self.official_api_common_params( + has_selection, resized_width, resized_height + ) + params.update( + prompt=fix_prompt(self.cfg("txt2img_prompt", str)), + negative_prompt=fix_prompt(self.cfg("txt2img_negative_prompt", str)), + sampler_name=self.cfg("txt2img_sampler", str), + steps=self.cfg("txt2img_steps", int), + cfg_scale=self.cfg("txt2img_cfg_scale", float), + seed=seed, + enable_hr=self.cfg("txt2img_highres", bool), + hr_upscaler=self.cfg("upscaler_name", str), + hr_resize_x=width, + hr_resize_y=height, + denoising_strength=self.cfg("txt2img_denoising_strength", float), + script=ext_name, + script_args=ext_args, + ) + + controlnet_units_param = list() + + for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))): + if self.cfg(f"controlnet{i}_enable", bool): + controlnet_units_param.append( + self.controlnet_unit_params(img_to_b64(src_imgs[str(i)]), i) + ) + + params.update(controlnet_units=controlnet_units_param) + + self.post_options() + url = get_url(self.cfg, prefix=CONTROLNET_ROUTE_PREFIX) + self.post("txt2img", params, cb, base_url=url) + def post_img2img(self, cb, src_img, mask_img, has_selection): params = dict(is_inpaint=False, src_img=img_to_b64(src_img)) if not self.cfg("just_use_yaml", bool): @@ -425,7 +534,8 @@ def post_upscale(self, cb, src_img): ) self.post("upscale", params, cb) - def post_controlnet_preview(self, cb, src_img, unit): + def post_controlnet_preview(self, cb, src_img): + unit = self.cfg("controlnet_unit") params = ( { "controlnet_module": self.cfg(f"controlnet{unit}_preprocessor"), diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index 13ce4427..7a11ebc1 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -211,6 +211,7 @@ class Defaults: controlnet0_preprocessor_resolution: int = 512 controlnet0_threshold_a: float = 0 controlnet0_threshold_b: float = 0 + controlnet0_input_image: str = "" controlnet1_enable: bool = False controlnet1_invert_input_color: bool = False @@ -225,6 +226,7 @@ class Defaults: controlnet1_preprocessor_resolution: int = 512 controlnet1_threshold_a: float = 0 controlnet1_threshold_b: float = 0 + controlnet1_input_image: str = "" controlnet2_enable: bool = False controlnet2_invert_input_color: bool = False @@ -239,6 +241,7 @@ class Defaults: controlnet2_preprocessor_resolution: int = 512 controlnet2_threshold_a: float = 0 controlnet2_threshold_b: float = 0 + controlnet2_input_image: str = "" controlnet3_enable: bool = False controlnet3_invert_input_color: bool = False @@ -253,6 +256,7 @@ class Defaults: controlnet3_preprocessor_resolution: int = 512 controlnet3_threshold_a: float = 0 controlnet3_threshold_b: float = 0 + controlnet3_input_image: str = "" controlnet4_enable: bool = False controlnet4_invert_input_color: bool = False @@ -267,6 +271,7 @@ class Defaults: controlnet4_preprocessor_resolution: int = 512 controlnet4_threshold_a: float = 0 controlnet4_threshold_b: float = 0 + controlnet4_input_image: str = "" controlnet5_enable: bool = False controlnet5_invert_input_color: bool = False @@ -281,6 +286,7 @@ class Defaults: controlnet5_preprocessor_resolution: int = 512 controlnet5_threshold_a: float = 0 controlnet5_threshold_b: float = 0 + controlnet5_input_image: str = "" controlnet6_enable: bool = False controlnet6_invert_input_color: bool = False @@ -295,6 +301,7 @@ class Defaults: controlnet6_preprocessor_resolution: int = 512 controlnet6_threshold_a: float = 0 controlnet6_threshold_b: float = 0 + controlnet6_input_image: str = "" controlnet7_enable: bool = False controlnet7_invert_input_color: bool = False @@ -309,6 +316,7 @@ class Defaults: controlnet7_preprocessor_resolution: int = 512 controlnet7_threshold_a: float = 0 controlnet7_threshold_b: float = 0 + controlnet7_input_image: str = "" controlnet8_enable: bool = False controlnet8_invert_input_color: bool = False @@ -323,6 +331,7 @@ class Defaults: controlnet8_preprocessor_resolution: int = 512 controlnet8_threshold_a: float = 0 controlnet8_threshold_b: float = 0 + controlnet8_input_image: str = "" controlnet9_enable: bool = False controlnet9_invert_input_color: bool = False @@ -337,5 +346,6 @@ class Defaults: controlnet9_preprocessor_resolution: int = 512 controlnet9_threshold_a: float = 0 controlnet9_threshold_b: float = 0 + controlnet9_input_image: str = "" DEFAULTS = Defaults() diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index 142cd7ed..c07ae10a 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -1,8 +1,10 @@ from krita import QPixmap, QImage, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout, Qt +from functools import partial from ..defaults import CONTROLNET_PREPROCESSOR_SETTINGS from ..script import script from ..widgets import QLabel, StatusBar, ImageLoaderLayout, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout +from ..utils import img_to_b64, b64_to_img class ControlNetPage(QWidget): name = "ControlNet" @@ -48,7 +50,7 @@ def cfg_connect(self): class ControlNetUnitSettings(QWidget): def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): - super(ControlNetUnitSettings, self).__init__(*args, **kwargs) + super(ControlNetUnitSettings, self).__init__(*args, **kwargs) self.unit = cfg_unit_number #Top checkbox @@ -57,6 +59,10 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): ) self.image_loader = ImageLoaderLayout() + input_image = script.cfg(f"controlnet{self.unit}_input_image", str) + self.image_loader.preview.setPixmap( + QPixmap.fromImage(b64_to_img(input_image) if input_image else QImage()) + ) #Main settings self.invert_input_color = QCheckBox( @@ -229,10 +235,14 @@ def show_preprocessor_options(self): self.threshold_b.qlabel.show() self.threshold_b.qspin.show() - def enabled_changed(self, state): + def enable_changed(self, state): if state == 1 or state == 2: script.action_update_controlnet_config() + def image_loaded(self): + image = self.image_loader.preview.pixmap().toImage().convertToFormat(QImage.Format_RGBA8888) + script.cfg.set(f"controlnet{self.unit}_input_image", img_to_b64(image)) + def cfg_init(self): self.enable.cfg_init() self.invert_input_color.cfg_init() @@ -263,13 +273,15 @@ def cfg_connect(self): self.annotator_resolution.cfg_connect() self.threshold_a.cfg_connect() self.threshold_b.cfg_connect() - self.enable.stateChanged.connect(self.enabled_changed) + self.enable.stateChanged.connect(self.enable_changed) + self.image_loader.import_button.released.connect(self.image_loaded) + self.image_loader.paste_button.released.connect(self.image_loaded) + self.image_loader.clear_button.released.connect( + partial(script.cfg.set, f"controlnet{self.unit}_input_image", "") + ) self.preprocessor_layout.qcombo.currentTextChanged.connect(self.set_preprocessor_options) self.refresh_button.released.connect(lambda: script.action_update_controlnet_config()) - self.annotator_preview_button.released.connect(lambda: script.action_preview_controlnet_annotator( - self.image_loader.preview.pixmap().toImage().convertToFormat(QImage.Format_RGBA8888).rgbSwapped() - if self.image_loader.preview.pixmap() is not None else None, - self.annotator_preview, - self.unit - )) + self.annotator_preview_button.released.connect( + lambda: script.action_preview_controlnet_annotator(self.annotator_preview) + ) self.annotator_clear_button.released.connect(lambda: self.annotator_preview.setPixmap(QPixmap())) \ No newline at end of file diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index c18ea9ed..2a9ab7c0 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -29,6 +29,7 @@ ) from .utils import ( b64_to_img, + img_to_b64, find_optimal_selection_region, get_desc_from_resp, img_to_ba, @@ -250,6 +251,29 @@ def insert(layer_name, enc): return layer return insert, glayer + + def check_controlnet_enabled(self): + for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))): + if self.cfg(f"controlnet{i}_enable", bool): + return True + + def get_controlnet_input_images(self, selected): + input_images = dict() + + for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))): + if self.cfg(f"controlnet{i}_enable", bool): + input_image = b64_to_img(self.cfg(f"controlnet{i}_input_image", str)) + + if input_image: + if self.cfg(f"controlnet{i}_invert_input_color", bool) or \ + self.cfg(f"controlnet{i}_RGB_to_BGR", bool): + input_image.rgbSwapped() + + input_images.update({f"{i}": input_image}) + else: + input_images.update({f"{i}": selected}) + + return input_images def apply_txt2img(self): # freeze selection region @@ -277,9 +301,17 @@ def cb(response): mask_trigger(layers) self.eta_timer.start(ETA_REFRESH_INTERVAL) - self.client.post_txt2img( - cb, self.width, self.height, self.selection is not None - ) + + if (self.check_controlnet_enabled()): + sel_image = self.get_selection_image() + self.client.post_controlnet_txt2image( + cb, self.width, self.height, self.selection is not None, + self.get_controlnet_input_images(sel_image) + ) + else: + self.client.post_txt2img( + cb, self.width, self.height, self.selection is not None + ) def apply_img2img(self, is_inpaint): insert, glayer = self.img_inserter( @@ -334,8 +366,18 @@ def cb(response): self.selection is not None, ) - def apply_controlnet_preview_annotator(self, image, preview_label, unit: int): - image = self.get_selection_image() if image is None else image + def apply_controlnet_preview_annotator(self, preview_label): + unit = self.cfg("controlnet_unit") + if self.cfg(f"controlnet{unit}_input_image"): + image = b64_to_img(self.cfg(f"controlnet{unit}_input_image")) + + #self.get_selection_image() already performs a image.rgbSwapped() + #so, I have decided not to play with it. + if self.cfg(f"controlnet{unit}_invert_input_color", bool) or \ + self.cfg(f"controlnet{unit}_RGB_to_BGR", bool): + image.rgbSwapped() + else: + image = self.get_selection_image() def cb(response): assert response is not None, "Backend Error, check terminal" @@ -346,7 +388,7 @@ def cb(response): pixmap = pixmap.scaledToWidth(preview_label.width(), Qt.SmoothTransformation) preview_label.setPixmap(pixmap) - self.client.post_controlnet_preview(cb, image, unit) + self.client.post_controlnet_preview(cb, image) def apply_simple_upscale(self): insert, _ = self.img_inserter(self.x, self.y, self.width, self.height) @@ -464,13 +506,13 @@ def action_update_controlnet_config(self): """Update controlnet config from the backend.""" self.client.get_controlnet_config() - def action_preview_controlnet_annotator(self, image, label, unit: int): + def action_preview_controlnet_annotator(self, preview_label): self.status_changed.emit(STATE_WAIT) self.update_selection() if not self.doc: return self.adjust_selection() - self.apply_controlnet_preview_annotator(image, label, unit) + self.apply_controlnet_preview_annotator(preview_label) def action_interrupt(self): def cb(resp=None): diff --git a/frontends/krita/krita_diff/utils.py b/frontends/krita/krita_diff/utils.py index ff948be1..44195742 100644 --- a/frontends/krita/krita_diff/utils.py +++ b/frontends/krita/krita_diff/utils.py @@ -14,6 +14,7 @@ TAB_SDCOMMON, TAB_TXT2IMG, TAB_UPSCALE, + TAB_CONTROLNET ) @@ -50,14 +51,11 @@ def get_ext_args(ext_cfg: Config, ext_type: str, ext_name: str): args.append(val) return args - -def find_fixed_aspect_ratio( - base_size: int, max_size: int, orig_width: int, orig_height: int +def calculate_resized_image_dimensions( + base_size: int, max_size: int, orig_width: int, orig_height: int ): - """Copy of `krita_server.utils.sddebz_highres_fix()`. - - This is used by `find_optimal_selection_region()` below to adjust the selected region. - """ + """Finds the dimensions of the resized images based on base_size and max_size. + See https://github.com/Interpause/auto-sd-paint-ext#faq for more details.""" def rnd(r, x, z=64): """Scale dimension x with stride z while attempting to preserve aspect ratio r.""" @@ -75,7 +73,17 @@ def rnd(r, x, z=64): width, height = base_size, rnd(1 / ratio, base_size) if height > max_size: width, height = rnd(ratio, max_size), max_size + + return width, height + +def find_fixed_aspect_ratio( + base_size: int, max_size: int, orig_width: int, orig_height: int +): + """Copy of `krita_server.utils.sddebz_highres_fix()`. + This is used by `find_optimal_selection_region()` below to adjust the selected region. + """ + width, height = calculate_resized_image_dimensions(base_size, max_size, orig_width, orig_height) return width / height @@ -233,8 +241,10 @@ def reset_docker_layout(): qmainwindow.tabifyDockWidget(dockers[TAB_SDCOMMON], dockers[TAB_CONFIG]) qmainwindow.tabifyDockWidget(dockers[TAB_SDCOMMON], dockers[TAB_PREVIEW]) + qmainwindow.tabifyDockWidget(dockers[TAB_SDCOMMON], dockers[TAB_CONTROLNET]) qmainwindow.tabifyDockWidget(dockers[TAB_TXT2IMG], dockers[TAB_IMG2IMG]) qmainwindow.tabifyDockWidget(dockers[TAB_TXT2IMG], dockers[TAB_INPAINT]) qmainwindow.tabifyDockWidget(dockers[TAB_TXT2IMG], dockers[TAB_UPSCALE]) dockers[TAB_SDCOMMON].raise_() dockers[TAB_INPAINT].raise_() + From c906cdedc2f1bc43732890006cca40f236222159 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Tue, 4 Apr 2023 00:18:26 -0600 Subject: [PATCH 24/55] Working txt2img --- frontends/krita/krita_diff/client.py | 82 +++++++++++-------- .../krita/krita_diff/pages/controlnet.py | 9 ++ frontends/krita/krita_diff/script.py | 27 +++--- .../krita/krita_diff/widgets/image_loader.py | 8 +- 4 files changed, 75 insertions(+), 51 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index bdb83257..6c1f72ab 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -248,7 +248,8 @@ def common_params(self, has_selection): return params def options_params(self): - """Parameters that are specific for the official API options endpoint.""" + """Parameters that are specific for the official API options endpoint + or overriding settings.""" params = dict( sd_model_checkpoint=self.cfg("sd_model", str), sd_vae=self.cfg("sd_vae", str), @@ -262,7 +263,8 @@ def options_params(self): ) return params - def official_api_common_params(self, has_selection, width, height): + def official_api_common_params(self, has_selection, width, height, + controlnet_src_imgs): """Parameters used by most official API endpoints.""" tiling = self.cfg("sd_tiling", bool) and not ( self.cfg("only_full_img_tiling", bool) and has_selection @@ -275,22 +277,41 @@ def official_api_common_params(self, has_selection, width, height): tiling=tiling, restore_faces=self.cfg("face_restorer_model", str) != "None", save_images=self.cfg("save_temp_images", bool), + override_settings=self.options_params(), + override_settings_restore_afterwards=False, + alwayson_scripts={} ) + + if controlnet_src_imgs: + controlnet_units_param = list() + + for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))): + if self.cfg(f"controlnet{i}_enable", bool): + controlnet_units_param.append( + self.controlnet_unit_params(img_to_b64(controlnet_src_imgs[str(i)]), i) + ) + + params["alwayson_scripts"].update({ + "controlnet": { + "args": controlnet_units_param + } + }) + return params def controlnet_unit_params(self, image: str, unit: int): params = dict( input_image=image, - module=self.cfg(f"controlnet{unit}_preprocessor"), - model=self.cfg(f"controlnet{unit}_model"), - weight=self.cfg(f"controlnet{unit}_weight"), - lowvram=self.cfg(f"controlnet{unit}_low_vram"), - processor_res=self.cfg(f"controlnet{unit}_preprocessor_resolution"), - threshold_a=self.cfg(f"controlnet{unit}_threshold_a"), - threshold_b=self.cfg(f"controlnet{unit}_threshold_b"), - guidance_start=self.cfg(f"controlnet{unit}_guidance_start"), - guidance_end=self.cfg(f"controlnet{unit}_guidance_end"), - guessmode=self.cfg(f"controlnet{unit}_guess_mode") + module=self.cfg(f"controlnet{unit}_preprocessor", str), + model=self.cfg(f"controlnet{unit}_model", str), + weight=self.cfg(f"controlnet{unit}_weight", float), + lowvram=self.cfg(f"controlnet{unit}_low_vram", bool), + processor_res=self.cfg(f"controlnet{unit}_preprocessor_resolution", int), + threshold_a=self.cfg(f"controlnet{unit}_threshold_a", float), + threshold_b=self.cfg(f"controlnet{unit}_threshold_b", float), + guidance_start=self.cfg(f"controlnet{unit}_guidance_start", float), + guidance_end=self.cfg(f"controlnet{unit}_guidance_end", float), + guessmode=self.cfg(f"controlnet{unit}_guess_mode", bool) ) return params @@ -376,14 +397,14 @@ def set_preprocessor_list(obj): self.get("model_list", set_model_list, base_url=url) self.get("module_list", set_preprocessor_list, base_url=url) - def post_options(self): - """Sets the options for the backend, using the official API""" - def cb(response): - assert response is not None, "Backend Error, check terminal" + # def post_options(self): + # """Sets the options for the backend, using the official API""" + # def cb(response): + # assert response is not None, "Backend Error, check terminal" - params = self.options_params() - url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) - self.post("options", params, cb, base_url=url) + # params = self.options_params() + # url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) + # self.post("options", params, cb, base_url=url) def post_txt2img(self, cb, width, height, has_selection): params = dict(orig_width=width, orig_height=height) @@ -411,8 +432,9 @@ def post_txt2img(self, cb, width, height, has_selection): self.post("txt2img", params, cb) - def post_controlnet_txt2image(self, cb, width, height, has_selection, src_imgs: dict): - """Uses official API""" + def post_official_api_txt2img(self, cb, width, height, has_selection, + controlnet_src_imgs: dict = {}): + """Uses official API. Leave controlnet_src_imgs empty to not use controlnet.""" if not self.cfg("just_use_yaml", bool): seed = ( int(self.cfg("txt2img_seed", str)) # Qt casts int as 32-bit int @@ -425,7 +447,7 @@ def post_controlnet_txt2image(self, cb, width, height, has_selection, src_imgs: self.cfg("sd_base_size", int), self.cfg("sd_max_size", int), width, height ) params = self.official_api_common_params( - has_selection, resized_width, resized_height + has_selection, resized_width, resized_height, controlnet_src_imgs ) params.update( prompt=fix_prompt(self.cfg("txt2img_prompt", str)), @@ -440,21 +462,11 @@ def post_controlnet_txt2image(self, cb, width, height, has_selection, src_imgs: hr_resize_y=height, denoising_strength=self.cfg("txt2img_denoising_strength", float), script=ext_name, - script_args=ext_args, + script_args=ext_args ) - controlnet_units_param = list() - - for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))): - if self.cfg(f"controlnet{i}_enable", bool): - controlnet_units_param.append( - self.controlnet_unit_params(img_to_b64(src_imgs[str(i)]), i) - ) - - params.update(controlnet_units=controlnet_units_param) - - self.post_options() - url = get_url(self.cfg, prefix=CONTROLNET_ROUTE_PREFIX) + url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) + print(params) self.post("txt2img", params, cb, base_url=url) def post_img2img(self, cb, src_img, mask_img, has_selection): diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index c07ae10a..2cc6e0e5 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -259,6 +259,11 @@ def cfg_init(self): self.threshold_b.cfg_init() self.set_preprocessor_options(self.preprocessor_layout.qcombo.currentText()) + if (self.preprocessor_layout.qcombo.currentText() == "none"): + self.annotator_preview_button.setEnabled(False) + else: + self.annotator_preview_button.setEnabled(True) + def cfg_connect(self): self.enable.cfg_connect() self.invert_input_color.cfg_connect() @@ -280,6 +285,10 @@ def cfg_connect(self): partial(script.cfg.set, f"controlnet{self.unit}_input_image", "") ) self.preprocessor_layout.qcombo.currentTextChanged.connect(self.set_preprocessor_options) + self.preprocessor_layout.qcombo.currentTextChanged.connect( + lambda: self.annotator_preview_button.setEnabled(False) if + self.preprocessor_layout.qcombo.currentText() == "none" else self.annotator_preview_button.setEnabled(True) + ) self.refresh_button.released.connect(lambda: script.action_update_controlnet_config()) self.annotator_preview_button.released.connect( lambda: script.action_preview_controlnet_annotator(self.annotator_preview) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 2a9ab7c0..c9fde08e 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -262,21 +262,21 @@ def get_controlnet_input_images(self, selected): for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))): if self.cfg(f"controlnet{i}_enable", bool): - input_image = b64_to_img(self.cfg(f"controlnet{i}_input_image", str)) - - if input_image: - if self.cfg(f"controlnet{i}_invert_input_color", bool) or \ - self.cfg(f"controlnet{i}_RGB_to_BGR", bool): - input_image.rgbSwapped() + input_image = b64_to_img(self.cfg(f"controlnet{i}_input_image", str)) if \ + self.cfg(f"controlnet{i}_input_image", str) else selected + + if self.cfg(f"controlnet{i}_invert_input_color", bool) or \ + self.cfg(f"controlnet{i}_RGB_to_BGR", bool): + input_image.rgbSwapped() - input_images.update({f"{i}": input_image}) - else: - input_images.update({f"{i}": selected}) + input_images.update({f"{i}": input_image}) return input_images def apply_txt2img(self): # freeze selection region + controlnet_enabled = self.check_controlnet_enabled() + insert, glayer = self.img_inserter( self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool) ) @@ -286,7 +286,8 @@ def cb(response): if len(self.client.long_reqs) == 1: # last request self.eta_timer.stop() assert response is not None, "Backend Error, check terminal" - outputs = response["outputs"] + #response key varies for official api used for controlnet + outputs = response["outputs"] if not controlnet_enabled else response["images"] glayer_name, layer_names = get_desc_from_resp(response, "txt2img") layers = [ insert(name if name else f"txt2img {i + 1}", output) @@ -302,9 +303,9 @@ def cb(response): self.eta_timer.start(ETA_REFRESH_INTERVAL) - if (self.check_controlnet_enabled()): + if (controlnet_enabled): sel_image = self.get_selection_image() - self.client.post_controlnet_txt2image( + self.client.post_official_api_txt2img( cb, self.width, self.height, self.selection is not None, self.get_controlnet_input_images(sel_image) ) @@ -371,8 +372,6 @@ def apply_controlnet_preview_annotator(self, preview_label): if self.cfg(f"controlnet{unit}_input_image"): image = b64_to_img(self.cfg(f"controlnet{unit}_input_image")) - #self.get_selection_image() already performs a image.rgbSwapped() - #so, I have decided not to play with it. if self.cfg(f"controlnet{unit}_invert_input_color", bool) or \ self.cfg(f"controlnet{unit}_RGB_to_BGR", bool): image.rgbSwapped() diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py index 7b6dc728..a97a45cc 100644 --- a/frontends/krita/krita_diff/widgets/image_loader.py +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -24,7 +24,6 @@ def __init__(self, *args, **kwargs): self.clear_button.released.connect(self.clear_image) def load_image(self): - self.clear_image() file_name, _ = QFileDialog.getOpenFileName(self.import_button, 'Open File', '', 'Image Files (*.png *.jpg *.bmp)') if file_name: pixmap = QPixmap(file_name) @@ -36,7 +35,12 @@ def load_image(self): def paste_image(self): self.clear_image() - self.preview.setPixmap(QApplication.clipboard().pixmap()) + pixmap = QPixmap(QApplication.clipboard().pixmap()) + + if pixmap.width() > self.preview.width(): + pixmap = pixmap.scaledToWidth(self.preview.width(), Qt.SmoothTransformation) + + self.preview.setPixmap(pixmap) def clear_image(self): self.preview.setPixmap(QPixmap()) From e00f8d7d0deb3e06a877a5abc630aab86dff962d Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Tue, 4 Apr 2023 07:49:11 -0600 Subject: [PATCH 25/55] Img2img work in progress --- frontends/krita/krita_diff/client.py | 103 +++++++++++++++++- .../krita/krita_diff/pages/controlnet.py | 3 +- frontends/krita/krita_diff/script.py | 55 +++++++--- .../krita/krita_diff/widgets/image_loader.py | 1 - 4 files changed, 141 insertions(+), 21 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 6c1f72ab..84031d87 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -5,7 +5,7 @@ from urllib.parse import urljoin, urlparse from urllib.request import Request, urlopen -from krita import QObject, QThread, pyqtSignal +from krita import QObject, QThread, pyqtSignal, QMessageBox from .config import Config from .defaults import ( @@ -446,8 +446,12 @@ def post_official_api_txt2img(self, cb, width, height, has_selection, resized_width, resized_height = calculate_resized_image_dimensions( self.cfg("sd_base_size", int), self.cfg("sd_max_size", int), width, height ) + disable_base_and_max_size = self.cfg("disable_sddebz_highres", bool) params = self.official_api_common_params( - has_selection, resized_width, resized_height, controlnet_src_imgs + has_selection, + resized_width if not disable_base_and_max_size else width, + resized_height if not disable_base_and_max_size else height, + controlnet_src_imgs ) params.update( prompt=fix_prompt(self.cfg("txt2img_prompt", str)), @@ -461,16 +465,15 @@ def post_official_api_txt2img(self, cb, width, height, has_selection, hr_resize_x=width, hr_resize_y=height, denoising_strength=self.cfg("txt2img_denoising_strength", float), - script=ext_name, - script_args=ext_args + script_name=ext_name if ext_name != "None" else None, #Prevent unrecognized "None" script from backend + script_args=ext_args if ext_name != "None" else [] ) url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) - print(params) self.post("txt2img", params, cb, base_url=url) def post_img2img(self, cb, src_img, mask_img, has_selection): - params = dict(is_inpaint=False, src_img=img_to_b64(src_img)) + params = dict(is_inpaint=False, src_img=img_to_b64(src_img)) if not self.cfg("just_use_yaml", bool): seed = ( int(self.cfg("img2img_seed", str)) # Qt casts int as 32-bit int @@ -495,6 +498,43 @@ def post_img2img(self, cb, src_img, mask_img, has_selection): self.post("img2img", params, cb) + def post_official_api_img2img(self, cb, src_img, width, height, has_selection, + controlnet_src_imgs: dict = {}): + """Uses official API. Leave controlnet_src_imgs empty to not use controlnet.""" + params = dict(init_images=[img_to_b64(src_img)]) + if not self.cfg("just_use_yaml", bool): + seed = ( + int(self.cfg("img2img_seed", str)) # Qt casts int as 32-bit int + if not self.cfg("img2img_seed", str).strip() == "" + else -1 + ) + ext_name = self.cfg("img2img_script", str) + ext_args = get_ext_args(self.ext_cfg, "scripts_img2img", ext_name) + resized_width, resized_height = calculate_resized_image_dimensions( + self.cfg("sd_base_size", int), self.cfg("sd_max_size", int), width, height + ) + disable_base_and_max_size = self.cfg("disable_sddebz_highres", bool) + params.update(self.official_api_common_params( + has_selection, + resized_width if not disable_base_and_max_size else width, + resized_height if not disable_base_and_max_size else height, + controlnet_src_imgs + )) + params.update( + prompt=fix_prompt(self.cfg("img2img_prompt", str)), + negative_prompt=fix_prompt(self.cfg("img2img_negative_prompt", str)), + sampler_name=self.cfg("img2img_sampler", str), + steps=self.cfg("img2img_steps", int), + cfg_scale=self.cfg("img2img_cfg_scale", float), + seed=seed, + denoising_strength=self.cfg("img2img_denoising_strength", float), + script_name=ext_name if ext_name != "None" else None, + script_args=ext_args if ext_name != "None" else [] + ) + + url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) + self.post("img2img", params, cb, base_url=url) + def post_inpaint(self, cb, src_img, mask_img, has_selection): assert mask_img, "Inpaint layer is needed for inpainting!" params = dict( @@ -534,6 +574,57 @@ def post_inpaint(self, cb, src_img, mask_img, has_selection): self.post("img2img", params, cb) + def post_official_api_inpaint(self, cb, src_img, mask_img, width, height, has_selection, + controlnet_src_imgs: dict = {}): + """Uses official API. Leave controlnet_src_imgs empty to not use controlnet.""" + assert mask_img, "Inpaint layer is needed for inpainting!" + params = dict( + init_images=[img_to_b64(src_img)], mask=img_to_b64(mask_img) + ) + if not self.cfg("just_use_yaml", bool): + seed = ( + int(self.cfg("inpaint_seed", str)) # Qt casts int as 32-bit int + if not self.cfg("inpaint_seed", str).strip() == "" + else -1 + ) + fill = self.cfg("inpaint_fill_list", "QStringList").index( + self.cfg("inpaint_fill", str) + ) + ext_name = self.cfg("inpaint_script", str) + ext_args = get_ext_args(self.ext_cfg, "scripts_inpaint", ext_name) + resized_width, resized_height = calculate_resized_image_dimensions( + self.cfg("sd_base_size", int), self.cfg("sd_max_size", int), width, height + ) + invert_mask = self.cfg("inpaint_invert_mask", bool) + disable_base_and_max_size = self.cfg("disable_sddebz_highres", bool) + params.update(self.official_api_common_params( + has_selection, + resized_width if not disable_base_and_max_size else width, + resized_height if not disable_base_and_max_size else height, + controlnet_src_imgs + )) + params.update( + prompt=fix_prompt(self.cfg("inpaint_prompt", str)), + negative_prompt=fix_prompt(self.cfg("inpaint_negative_prompt", str)), + sampler_name=self.cfg("inpaint_sampler", str), + steps=self.cfg("inpaint_steps", int), + cfg_scale=self.cfg("inpaint_cfg_scale", float), + seed=seed, + denoising_strength=self.cfg("inpaint_denoising_strength", float), + script_name=ext_name if ext_name != "None" else None, + script_args=ext_args if ext_name != "None" else [], + inpainting_mask_invert=0 if not invert_mask else 1, + inpainting_fill=fill, + mask_blur=0, + inpaint_full_res=False + #not sure what's the equivalent of mask weight for official API + ) + + params["override_settings"]["return_grid"] = False + + url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) + self.post("img2img", params, cb, base_url=url) + def post_upscale(self, cb, src_img): params = ( { diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index 2cc6e0e5..66d44080 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -81,7 +81,8 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): #Tips self.tips = TipsLayout( ["Invert colors if your image has white background.", - "Selection will be used as input if no image has been uploaded or pasted."] + "Selection will be used as input if no image has been uploaded or pasted.", + "Remember to set multi-controlnet in the backend as well if you want to use more than one unit."] ) #Preprocessor list diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index c9fde08e..666ad6c7 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -8,12 +8,14 @@ Krita, Node, QImage, + QColor, + QPainter, QObject, QPixmap, Qt, QTimer, Selection, - pyqtSignal, + pyqtSignal ) from .client import Client @@ -167,20 +169,35 @@ def get_selection_image(self) -> QImage: QImage.Format_RGBA8888, ).rgbSwapped() - def get_mask_image(self) -> Union[QImage, None]: + def get_mask_image(self, using_official_api) -> Union[QImage, None]: """QImage of mask layer for inpainting""" if self.node.type() not in {"paintlayer", "filelayer"}: assert False, "Please select a valid layer to use as inpaint mask!" elif self.node in self._inserted_layers: assert False, "Selected layer was generated. Copy the layer if sure you want to use it as inpaint mask." - return QImage( + mask = QImage( self.node.pixelData(self.x, self.y, self.width, self.height), self.width, self.height, QImage.Format_RGBA8888, ).rgbSwapped() + if using_official_api: + #replace mask's transparency with white color + new_mask = QImage(mask.size(), QImage.Format_RGB888) + new_mask.fill(QColor("white")) + + painter = QPainter(new_mask) + painter.setCompositionMode(QPainter.CompositionMode_SourceOver) + painter.drawImage(0, 0, mask) + painter.end() + + new_mask.invertPixels() + mask = new_mask + + return mask + def img_inserter(self, x, y, width, height, group=False): """Return frozen image inserter to insert images as new layer.""" # Selection may change before callback, so freeze selection region @@ -303,7 +320,7 @@ def cb(response): self.eta_timer.start(ETA_REFRESH_INTERVAL) - if (controlnet_enabled): + if controlnet_enabled: sel_image = self.get_selection_image() self.client.post_official_api_txt2img( cb, self.width, self.height, self.selection is not None, @@ -315,11 +332,13 @@ def cb(response): ) def apply_img2img(self, is_inpaint): + controlnet_enabled = self.check_controlnet_enabled() + insert, glayer = self.img_inserter( self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool) ) mask_trigger = self.transparency_mask_inserter() - mask_image = self.get_mask_image() + mask_image = self.get_mask_image(controlnet_enabled) path = os.path.join(self.cfg("sample_path", str), f"{int(time.time())}.png") mask_path = os.path.join( @@ -341,7 +360,7 @@ def cb(response): self.eta_timer.stop() assert response is not None, "Backend Error, check terminal" - outputs = response["outputs"] + outputs = response["outputs"] if not controlnet_enabled else response["images"] layer_name_prefix = "inpaint" if is_inpaint else "img2img" glayer_name, layer_names = get_desc_from_resp(response, layer_name_prefix) layers = [ @@ -358,14 +377,24 @@ def cb(response): if not is_inpaint: mask_trigger(layers) - method = self.client.post_inpaint if is_inpaint else self.client.post_img2img self.eta_timer.start() - method( - cb, - sel_image, - mask_image, # is unused by backend in img2img mode - self.selection is not None, - ) + if controlnet_enabled: + if is_inpaint: + self.client.post_official_api_inpaint( + cb, sel_image, mask_image, self.width, self.height, self.selection is not None, + self.get_controlnet_input_images(sel_image)) + else: + self.client.post_official_api_img2img( + cb, sel_image, self.width, self.height, self.selection is not None, + self.get_controlnet_input_images(sel_image)) + else: + method = self.client.post_inpaint if is_inpaint else self.client.post_img2img + method( + cb, + sel_image, + mask_image, # is unused by backend in img2img mode + self.selection is not None, + ) def apply_controlnet_preview_annotator(self, preview_label): unit = self.cfg("controlnet_unit") diff --git a/frontends/krita/krita_diff/widgets/image_loader.py b/frontends/krita/krita_diff/widgets/image_loader.py index a97a45cc..321341ce 100644 --- a/frontends/krita/krita_diff/widgets/image_loader.py +++ b/frontends/krita/krita_diff/widgets/image_loader.py @@ -34,7 +34,6 @@ def load_image(self): self.preview.setPixmap(pixmap) def paste_image(self): - self.clear_image() pixmap = QPixmap(QApplication.clipboard().pixmap()) if pixmap.width() > self.preview.width(): From 2e0d344f6d0ae77ed9ae0b89747c08346b3497f0 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Wed, 5 Apr 2023 07:13:21 -0600 Subject: [PATCH 26/55] Working im2img --- frontends/krita/krita_diff/client.py | 5 ++- .../krita/krita_diff/pages/controlnet.py | 9 +++- frontends/krita/krita_diff/script.py | 45 ++++++++++++------- frontends/krita/krita_diff/utils.py | 35 +++++++++++++-- 4 files changed, 72 insertions(+), 22 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 84031d87..02d37b5e 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -259,6 +259,7 @@ def options_params(self): code_former_weight=self.cfg("codeformer_weight", float), #Couldn't find filter_nsfw option for official API. img2img_fix_steps=self.cfg("do_exact_steps", bool), #Not sure if this is matched correctly. + img2img_color_correction=self.cfg("img2img_color_correct", bool), return_grid=self.cfg("include_grid", bool) ) return params @@ -276,7 +277,6 @@ def official_api_common_params(self, has_selection, width, height, height=height, tiling=tiling, restore_faces=self.cfg("face_restorer_model", str) != "None", - save_images=self.cfg("save_temp_images", bool), override_settings=self.options_params(), override_settings_restore_afterwards=False, alwayson_scripts={} @@ -540,6 +540,9 @@ def post_inpaint(self, cb, src_img, mask_img, has_selection): params = dict( is_inpaint=True, src_img=img_to_b64(src_img), mask_img=img_to_b64(mask_img) ) + msg = QMessageBox() + msg.setText(params["mask_img"]) + msg.exec() if not self.cfg("just_use_yaml", bool): seed = ( int(self.cfg("inpaint_seed", str)) # Qt casts int as 32-bit int diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index 66d44080..798d68ed 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -25,6 +25,7 @@ def __init__(self, *args, **kwargs): self.units_stacked_layout.addWidget(unit_layout) layout = QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.status_bar) layout.addLayout(self.controlnet_unit) layout.addLayout(self.units_stacked_layout) @@ -143,8 +144,11 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): main_settings_layout = QHBoxLayout() main_settings_layout.addWidget(self.invert_input_color) main_settings_layout.addWidget(self.RGB_to_BGR) - main_settings_layout.addWidget(self.low_vram) - main_settings_layout.addWidget(self.guess_mode) + + + main_settings_layout_2 = QHBoxLayout() + main_settings_layout_2.addWidget(self.low_vram) + main_settings_layout_2.addWidget(self.guess_mode) guidance_layout = QHBoxLayout() guidance_layout.addLayout(self.guidance_start_layout) @@ -160,6 +164,7 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): layout.addLayout(self.image_loader) layout.addLayout(self.tips) layout.addLayout(main_settings_layout) + layout.addLayout(main_settings_layout_2) layout.addLayout(self.preprocessor_layout) layout.addLayout(self.model_layout) layout.addWidget(self.refresh_button) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 666ad6c7..e5fc49a7 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -31,8 +31,8 @@ ) from .utils import ( b64_to_img, - img_to_b64, find_optimal_selection_region, + remove_unmasked_content_for_inpaint, get_desc_from_resp, img_to_ba, save_img, @@ -181,11 +181,13 @@ def get_mask_image(self, using_official_api) -> Union[QImage, None]: self.width, self.height, QImage.Format_RGBA8888, - ).rgbSwapped() + ) if using_official_api: - #replace mask's transparency with white color - new_mask = QImage(mask.size(), QImage.Format_RGB888) + #replace mask's transparency with white color, since official API + #doesn't seem to work with transparency. Assuming masked content + #is black. + new_mask = QImage(mask.size(), QImage.Format_RGBA8888) new_mask.fill(QColor("white")) painter = QPainter(new_mask) @@ -193,12 +195,14 @@ def get_mask_image(self, using_official_api) -> Union[QImage, None]: painter.drawImage(0, 0, mask) painter.end() + #Invert colors so white corresponds to masked area. new_mask.invertPixels() mask = new_mask - return mask + return mask.rgbSwapped() - def img_inserter(self, x, y, width, height, group=False): + def img_inserter(self, x, y, width, height, group=False, + is_official_api_inpaint=False, mask_img=None): """Return frozen image inserter to insert images as new layer.""" # Selection may change before callback, so freeze selection region has_selection = self.selection is not None @@ -239,6 +243,9 @@ def insert(layer_name, enc): width, height, transformMode=Qt.SmoothTransformation ) + if is_official_api_inpaint: + image = remove_unmasked_content_for_inpaint(image, mask_img) + # Resize (not scale!) canvas if image is larger (i.e. outpainting or Upscale was used) if image.width() > self.doc.width() or image.height() > self.doc.height(): # NOTE: @@ -282,9 +289,11 @@ def get_controlnet_input_images(self, selected): input_image = b64_to_img(self.cfg(f"controlnet{i}_input_image", str)) if \ self.cfg(f"controlnet{i}_input_image", str) else selected - if self.cfg(f"controlnet{i}_invert_input_color", bool) or \ - self.cfg(f"controlnet{i}_RGB_to_BGR", bool): - input_image.rgbSwapped() + if self.cfg(f"controlnet{i}_invert_input_color", bool): + input_image.invertPixels() + + if self.cfg(f"controlnet{i}_RGB_to_BGR", bool): + input_image = input_image.rgbSwapped() input_images.update({f"{i}": input_image}) @@ -334,11 +343,13 @@ def cb(response): def apply_img2img(self, is_inpaint): controlnet_enabled = self.check_controlnet_enabled() + mask_trigger = self.transparency_mask_inserter() + mask_image = self.get_mask_image(controlnet_enabled) if is_inpaint else None + insert, glayer = self.img_inserter( - self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool) + self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool), + is_inpaint and controlnet_enabled, mask_image ) - mask_trigger = self.transparency_mask_inserter() - mask_image = self.get_mask_image(controlnet_enabled) path = os.path.join(self.cfg("sample_path", str), f"{int(time.time())}.png") mask_path = os.path.join( @@ -400,13 +411,15 @@ def apply_controlnet_preview_annotator(self, preview_label): unit = self.cfg("controlnet_unit") if self.cfg(f"controlnet{unit}_input_image"): image = b64_to_img(self.cfg(f"controlnet{unit}_input_image")) - - if self.cfg(f"controlnet{unit}_invert_input_color", bool) or \ - self.cfg(f"controlnet{unit}_RGB_to_BGR", bool): - image.rgbSwapped() else: image = self.get_selection_image() + if self.cfg(f"controlnet{unit}_invert_input_color", bool): + image.invertPixels() + + if self.cfg(f"controlnet{unit}_RGB_to_BGR", bool): + image = image.rgbSwapped() + def cb(response): assert response is not None, "Backend Error, check terminal" output = response["images"][0] diff --git a/frontends/krita/krita_diff/utils.py b/frontends/krita/krita_diff/utils.py index 44195742..16143bed 100644 --- a/frontends/krita/krita_diff/utils.py +++ b/frontends/krita/krita_diff/utils.py @@ -3,7 +3,7 @@ from itertools import cycle from math import ceil -from krita import Krita, QBuffer, QByteArray, QImage, QIODevice, Qt +from krita import Krita, QBuffer, QByteArray, QImage, QColor, QIODevice, Qt from .config import Config from .defaults import ( @@ -220,6 +220,7 @@ def reset_docker_layout(): docker_ids = { TAB_SDCOMMON, TAB_CONFIG, + TAB_CONTROLNET, TAB_IMG2IMG, TAB_TXT2IMG, TAB_UPSCALE, @@ -240,11 +241,39 @@ def reset_docker_layout(): qmainwindow.addDockWidget(Qt.LeftDockWidgetArea, d) qmainwindow.tabifyDockWidget(dockers[TAB_SDCOMMON], dockers[TAB_CONFIG]) - qmainwindow.tabifyDockWidget(dockers[TAB_SDCOMMON], dockers[TAB_PREVIEW]) qmainwindow.tabifyDockWidget(dockers[TAB_SDCOMMON], dockers[TAB_CONTROLNET]) + qmainwindow.tabifyDockWidget(dockers[TAB_SDCOMMON], dockers[TAB_PREVIEW]) qmainwindow.tabifyDockWidget(dockers[TAB_TXT2IMG], dockers[TAB_IMG2IMG]) qmainwindow.tabifyDockWidget(dockers[TAB_TXT2IMG], dockers[TAB_INPAINT]) qmainwindow.tabifyDockWidget(dockers[TAB_TXT2IMG], dockers[TAB_UPSCALE]) dockers[TAB_SDCOMMON].raise_() dockers[TAB_INPAINT].raise_() - + +def remove_unmasked_content_for_inpaint(img, mask): + """ + Remove all content from an image that is not masked. + It is recommended that img and mask share the same size. + """ + # Create a new image with the same size as the original image + result = QImage(img.size(), QImage.Format_RGBA8888) + + # Iterate over all pixels in the image + for y in range(img.height()): + for x in range(img.width()): + # Get the color of the pixel in the mask + mask_color = QColor(mask.pixel(x, y)) + + # Calculate the alpha value based on the brightness of the mask pixel + alpha = mask_color.lightness() + + # Get the color of the pixel in the original image + img_color = QColor(img.pixel(x, y)) + + # Set the alpha value of the original pixel based on the mask pixel + img_color.setAlpha(alpha) + + # Set the pixel in the result image + result.setPixelColor(x, y, img_color) + + return result.rgbSwapped() + From f1c7e0ad40b1ad40fa9581b0dfc6863912164f0d Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Wed, 5 Apr 2023 07:15:25 -0600 Subject: [PATCH 27/55] Remove QMessageBox for debugging --- frontends/krita/krita_diff/client.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 02d37b5e..50178932 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -5,7 +5,7 @@ from urllib.parse import urljoin, urlparse from urllib.request import Request, urlopen -from krita import QObject, QThread, pyqtSignal, QMessageBox +from krita import QObject, QThread, pyqtSignal from .config import Config from .defaults import ( @@ -540,9 +540,7 @@ def post_inpaint(self, cb, src_img, mask_img, has_selection): params = dict( is_inpaint=True, src_img=img_to_b64(src_img), mask_img=img_to_b64(mask_img) ) - msg = QMessageBox() - msg.setText(params["mask_img"]) - msg.exec() + if not self.cfg("just_use_yaml", bool): seed = ( int(self.cfg("inpaint_seed", str)) # Qt casts int as 32-bit int From 54e262b4b4fd7242b5d0ec22bbb7cb6e9230dc05 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Mon, 17 Apr 2023 15:32:24 -0600 Subject: [PATCH 28/55] Fixed bug that restarted threshold values every 3 seconds. --- frontends/krita/krita_diff/pages/controlnet.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index 798d68ed..56d0a6b1 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -180,15 +180,13 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): self.setLayout(layout) def set_preprocessor_options(self, selected: str): + self.set_threshold_names(selected) if selected in CONTROLNET_PREPROCESSOR_SETTINGS: self.show_preprocessor_options() - self.annotator_resolution.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["resolution_label"] \ - if "resolution_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else "Preprocessor resolution:") if "threshold_a_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: self.threshold_a.qlabel.show() self.threshold_a.qspin.show() - self.threshold_a.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_label"]) self.threshold_a.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_min_value"] \ if "threshold_a_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) self.threshold_a.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_max_value"] \ @@ -204,7 +202,6 @@ def set_preprocessor_options(self, selected: str): if "threshold_b_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: self.threshold_b.qlabel.show() self.threshold_b.qspin.show() - self.threshold_b.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_label"]) self.threshold_b.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_min_value"] \ if "threshold_b_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) self.threshold_b.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_max_value"] \ @@ -218,6 +215,17 @@ def set_preprocessor_options(self, selected: str): self.threshold_b.qspin.hide() else: self.hide_preprocessor_options(selected) + + def set_threshold_names(self, selected): + if selected in CONTROLNET_PREPROCESSOR_SETTINGS: + self.annotator_resolution.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["resolution_label"] \ + if "resolution_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else "Preprocessor resolution:") + + if "threshold_a_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: + self.threshold_a.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_label"]) + + if "threshold_b_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: + self.threshold_b.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_label"]) def hide_preprocessor_options(self, selected: str): #Hide all annotator settings if no annotator chosen. @@ -263,7 +271,7 @@ def cfg_init(self): self.annotator_resolution.cfg_init() self.threshold_a.cfg_init() self.threshold_b.cfg_init() - self.set_preprocessor_options(self.preprocessor_layout.qcombo.currentText()) + self.set_threshold_names(self.preprocessor_layout.qcombo.currentText()) if (self.preprocessor_layout.qcombo.currentText() == "none"): self.annotator_preview_button.setEnabled(False) From 50277eb5c2dd075068019b0b042141f789ec951c Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Mon, 17 Apr 2023 16:43:52 -0600 Subject: [PATCH 29/55] Fixed bug that prevented to run controlnet with img2img --- frontends/krita/krita_diff/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 50178932..5f9be53c 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -290,6 +290,8 @@ def official_api_common_params(self, has_selection, width, height, controlnet_units_param.append( self.controlnet_unit_params(img_to_b64(controlnet_src_imgs[str(i)]), i) ) + else: + controlnet_units_param.append({"enabled": False}) params["alwayson_scripts"].update({ "controlnet": { From 3af1846c18203902b414bf8af36dc8951e89a10b Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Sun, 30 Apr 2023 18:59:48 -0600 Subject: [PATCH 30/55] Fixed preprocessor preview bug --- frontends/krita/krita_diff/client.py | 2 +- frontends/krita/krita_diff/defaults.py | 4 ++-- frontends/krita/krita_diff/pages/controlnet.py | 1 - frontends/krita/krita_diff/script.py | 2 +- frontends/krita/krita_diff/utils.py | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 5f9be53c..3336f457 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -641,7 +641,7 @@ def post_upscale(self, cb, src_img): self.post("upscale", params, cb) def post_controlnet_preview(self, cb, src_img): - unit = self.cfg("controlnet_unit") + unit = self.cfg("controlnet_unit", str) params = ( { "controlnet_module": self.cfg(f"controlnet{unit}_preprocessor"), diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index 7a11ebc1..40983944 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -193,8 +193,8 @@ class Defaults: upscale_upscaler_name: str = "None" upscale_downscale_first: bool = False - controlnet_unit: int = 0 - controlnet_unit_list: List[str] = field(default_factory=lambda: list(range(10))) + controlnet_unit: str = "0" + controlnet_unit_list: List[str] = field(default_factory=lambda: list(str(i) for i in range(10))) controlnet_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) controlnet_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index 56d0a6b1..99561e7f 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -15,7 +15,6 @@ def __init__(self, *args, **kwargs): self.controlnet_unit = QComboBoxLayout( script.cfg, "controlnet_unit_list", "controlnet_unit", label="Unit:" ) - self.controlnet_unit.qcombo.setEditable(False) self.controlnet_unit_layout_list = list(ControlNetUnitSettings(i) for i in range(len(script.cfg("controlnet_unit_list")))) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index e5fc49a7..05335b0d 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -408,7 +408,7 @@ def cb(response): ) def apply_controlnet_preview_annotator(self, preview_label): - unit = self.cfg("controlnet_unit") + unit = self.cfg("controlnet_unit", str) if self.cfg(f"controlnet{unit}_input_image"): image = b64_to_img(self.cfg(f"controlnet{unit}_input_image")) else: diff --git a/frontends/krita/krita_diff/utils.py b/frontends/krita/krita_diff/utils.py index 16143bed..5048660d 100644 --- a/frontends/krita/krita_diff/utils.py +++ b/frontends/krita/krita_diff/utils.py @@ -187,7 +187,7 @@ def img_to_b64(img: QImage): def b64_to_img(enc: str): """Converts base64-encoded string to QImage""" ba = QByteArray.fromBase64(enc.encode("utf-8")) - return QImage.fromData(ba, "PNG") + return QImage.fromData(ba) #Removed explicit format to support other image formats. def bytewise_xor(msg: bytes, key: bytes): From 90d134409e70a0e4d897030763441782ad6249d5 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Mon, 1 May 2023 16:14:02 -0600 Subject: [PATCH 31/55] Transparency mask for inpainting WIP --- frontends/krita/krita_diff/script.py | 15 +++++++-------- frontends/krita/krita_diff/utils.py | 28 ---------------------------- 2 files changed, 7 insertions(+), 36 deletions(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 05335b0d..72750ac3 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -32,7 +32,6 @@ from .utils import ( b64_to_img, find_optimal_selection_region, - remove_unmasked_content_for_inpaint, get_desc_from_resp, img_to_ba, save_img, @@ -201,8 +200,7 @@ def get_mask_image(self, using_official_api) -> Union[QImage, None]: return mask.rgbSwapped() - def img_inserter(self, x, y, width, height, group=False, - is_official_api_inpaint=False, mask_img=None): + def img_inserter(self, x, y, width, height, group=False, mask_img=None): """Return frozen image inserter to insert images as new layer.""" # Selection may change before callback, so freeze selection region has_selection = self.selection is not None @@ -218,6 +216,11 @@ def create_layer(name: str): else: parent.addChildNode(layer, None) return layer + + def insert_transparency_layer_for_inpaint(): + ba = img_to_ba(mask_img) + mask_layer = self.doc.createNode("Transparency Mask", "paintLayer") + mask_layer.setPixelData(ba, x, y, width, height) def insert(layer_name, enc): nonlocal x, y, width, height, has_selection @@ -243,9 +246,6 @@ def insert(layer_name, enc): width, height, transformMode=Qt.SmoothTransformation ) - if is_official_api_inpaint: - image = remove_unmasked_content_for_inpaint(image, mask_img) - # Resize (not scale!) canvas if image is larger (i.e. outpainting or Upscale was used) if image.width() > self.doc.width() or image.height() > self.doc.height(): # NOTE: @@ -347,8 +347,7 @@ def apply_img2img(self, is_inpaint): mask_image = self.get_mask_image(controlnet_enabled) if is_inpaint else None insert, glayer = self.img_inserter( - self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool), - is_inpaint and controlnet_enabled, mask_image + self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool), mask_image ) path = os.path.join(self.cfg("sample_path", str), f"{int(time.time())}.png") diff --git a/frontends/krita/krita_diff/utils.py b/frontends/krita/krita_diff/utils.py index 5048660d..6a98838a 100644 --- a/frontends/krita/krita_diff/utils.py +++ b/frontends/krita/krita_diff/utils.py @@ -249,31 +249,3 @@ def reset_docker_layout(): dockers[TAB_SDCOMMON].raise_() dockers[TAB_INPAINT].raise_() -def remove_unmasked_content_for_inpaint(img, mask): - """ - Remove all content from an image that is not masked. - It is recommended that img and mask share the same size. - """ - # Create a new image with the same size as the original image - result = QImage(img.size(), QImage.Format_RGBA8888) - - # Iterate over all pixels in the image - for y in range(img.height()): - for x in range(img.width()): - # Get the color of the pixel in the mask - mask_color = QColor(mask.pixel(x, y)) - - # Calculate the alpha value based on the brightness of the mask pixel - alpha = mask_color.lightness() - - # Get the color of the pixel in the original image - img_color = QColor(img.pixel(x, y)) - - # Set the alpha value of the original pixel based on the mask pixel - img_color.setAlpha(alpha) - - # Set the pixel in the result image - result.setPixelColor(x, y, img_color) - - return result.rgbSwapped() - From b9accbb325d6fd6a795d9e38dcfc9e10a61c92f5 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Mon, 15 May 2023 09:35:44 -0600 Subject: [PATCH 32/55] Update controlnet --- frontends/krita/krita_diff/client.py | 2 +- frontends/krita/krita_diff/defaults.py | 109 ++++++++++++------ .../krita/krita_diff/pages/controlnet.py | 29 ++--- frontends/krita/krita_diff/script.py | 26 +---- 4 files changed, 89 insertions(+), 77 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index 3336f457..f12ba5e9 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -313,7 +313,7 @@ def controlnet_unit_params(self, image: str, unit: int): threshold_b=self.cfg(f"controlnet{unit}_threshold_b", float), guidance_start=self.cfg(f"controlnet{unit}_guidance_start", float), guidance_end=self.cfg(f"controlnet{unit}_guidance_end", float), - guessmode=self.cfg(f"controlnet{unit}_guess_mode", bool) + control_mode=self.cfg(f"controlnet{unit}_control_mode", str) ) return params diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index 40983944..d5f1cba4 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -56,9 +56,6 @@ "threshold_b_min_value": 1, "threshold_b_max_value": 255 }, - "depth": { - "resolution_label": "Midas resolution", - }, "depth_leres": { "resolution_label": "LeReS resolution", "threshold_a_label": "Remove near %", @@ -68,6 +65,71 @@ "threshold_b_min_value": 0, "threshold_b_max_value": 100 }, + "depth_leres++": { + "resolution_label": "LeReS resolution", + "threshold_a_label": "Remove near %", + "threshold_b_label": "Remove background %", + "threshold_a_min_value": 0, + "threshold_a_max_value": 100, + "threshold_b_min_value": 0, + "threshold_b_max_value": 100 + }, + "mediapipe_face": { + "threshold_a_label": "Max Faces", + "threshold_b_label": "Min Face Confidence", + "threshold_a_min_value": 1, + "threshold_a_max_value": 10, + "threshold_b_min_value": 0.01, + "threshold_b_max_value": 1, + "threshold_b_step": 0.01 + }, + "normal_midas": { + "threshold_a_label": "Normal background threshold", + "threshold_a_value": 0.4, + "threshold_a_min_value": 0, + "threshold_a_max_value": 1, + "threshold_step": 0.01 + }, + "reference_adain": { + "threshold_a_label": "Style Fidelity (only for \"Balanced\" mode)", + "threshold_a_value": 0.5, + "threshold_a_min_value": 0, + "threshold_a_max_value": 1, + "threshold_step": 0.01 + }, + "reference_adain+attn": { + "threshold_a_label": "Style Fidelity (only for \"Balanced\" mode)", + "threshold_a_value": 0.5, + "threshold_a_min_value": 0, + "threshold_a_max_value": 1, + "threshold_step": 0.01 + }, + "reference_only": { + "threshold_a_label": "Style Fidelity (only for \"Balanced\" mode)", + "threshold_a_value": 0.5, + "threshold_a_min_value": 0, + "threshold_a_max_value": 1, + "threshold_step": 0.01 + }, + "scribble_xdog": { + "threshold_a_label": "XDoG Threshold", + "threshold_a_value": 32, + "threshold_a_min_value": 1, + "threshold_a_max_value": 64 + }, + "threshold": { + "threshold_a_label": "Binarization Threshold", + "threshold_a_value": 127, + "threshold_a_min_value": 0, + "threshold_a_max_value": 255 + }, + "tile_resample": { + "threshold_a_label": "Down Sampling Rate", + "threshold_a_value": 1, + "threshold_a_min_value": 1, + "threshold_a_max_value": 8, + "threshold_step": 0.01 + }, "hed": { "resolution_label": "HED resolution", }, @@ -197,12 +259,10 @@ class Defaults: controlnet_unit_list: List[str] = field(default_factory=lambda: list(str(i) for i in range(10))) controlnet_preprocessor_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) controlnet_model_list: List[str] = field(default_factory=lambda: [ERROR_MSG]) + controlnet_control_mode_list: List[str] = field(default_factory=lambda: ["Balanced", "My prompt is more important", "ControlNet is more important"]) controlnet0_enable: bool = False - controlnet0_invert_input_color: bool = False - controlnet0_RGB_to_BGR: bool = False controlnet0_low_vram: bool = False - controlnet0_guess_mode: bool = False controlnet0_preprocessor: str = "None" controlnet0_model: str = "None" controlnet0_weight: float = 1.0 @@ -212,12 +272,10 @@ class Defaults: controlnet0_threshold_a: float = 0 controlnet0_threshold_b: float = 0 controlnet0_input_image: str = "" + controlnet0_control_mode: str = "Balanced" controlnet1_enable: bool = False - controlnet1_invert_input_color: bool = False - controlnet1_RGB_to_BGR: bool = False controlnet1_low_vram: bool = False - controlnet1_guess_mode: bool = False controlnet1_preprocessor: str = "None" controlnet1_model: str = "None" controlnet1_weight: float = 1.0 @@ -227,12 +285,10 @@ class Defaults: controlnet1_threshold_a: float = 0 controlnet1_threshold_b: float = 0 controlnet1_input_image: str = "" + controlnet1_control_mode: str = "Balanced" controlnet2_enable: bool = False - controlnet2_invert_input_color: bool = False - controlnet2_RGB_to_BGR: bool = False controlnet2_low_vram: bool = False - controlnet2_guess_mode: bool = False controlnet2_preprocessor: str = "None" controlnet2_model: str = "None" controlnet2_weight: float = 1.0 @@ -242,12 +298,10 @@ class Defaults: controlnet2_threshold_a: float = 0 controlnet2_threshold_b: float = 0 controlnet2_input_image: str = "" + controlnet2_control_mode: str = "Balanced" controlnet3_enable: bool = False - controlnet3_invert_input_color: bool = False - controlnet3_RGB_to_BGR: bool = False controlnet3_low_vram: bool = False - controlnet3_guess_mode: bool = False controlnet3_preprocessor: str = "None" controlnet3_model: str = "None" controlnet3_weight: float = 1.0 @@ -257,12 +311,10 @@ class Defaults: controlnet3_threshold_a: float = 0 controlnet3_threshold_b: float = 0 controlnet3_input_image: str = "" + controlnet3_control_mode: str = "Balanced" controlnet4_enable: bool = False - controlnet4_invert_input_color: bool = False - controlnet4_RGB_to_BGR: bool = False controlnet4_low_vram: bool = False - controlnet4_guess_mode: bool = False controlnet4_preprocessor: str = "None" controlnet4_model: str = "None" controlnet4_weight: float = 1.0 @@ -272,12 +324,10 @@ class Defaults: controlnet4_threshold_a: float = 0 controlnet4_threshold_b: float = 0 controlnet4_input_image: str = "" + controlnet4_control_mode: str = "Balanced" controlnet5_enable: bool = False - controlnet5_invert_input_color: bool = False - controlnet5_RGB_to_BGR: bool = False controlnet5_low_vram: bool = False - controlnet5_guess_mode: bool = False controlnet5_preprocessor: str = "None" controlnet5_model: str = "None" controlnet5_weight: float = 1.0 @@ -287,12 +337,10 @@ class Defaults: controlnet5_threshold_a: float = 0 controlnet5_threshold_b: float = 0 controlnet5_input_image: str = "" + controlnet5_control_mode: str = "Balanced" controlnet6_enable: bool = False - controlnet6_invert_input_color: bool = False - controlnet6_RGB_to_BGR: bool = False controlnet6_low_vram: bool = False - controlnet6_guess_mode: bool = False controlnet6_preprocessor: str = "None" controlnet6_model: str = "None" controlnet6_weight: float = 1.0 @@ -302,12 +350,10 @@ class Defaults: controlnet6_threshold_a: float = 0 controlnet6_threshold_b: float = 0 controlnet6_input_image: str = "" + controlnet6_control_mode: str = "Balanced" controlnet7_enable: bool = False - controlnet7_invert_input_color: bool = False - controlnet7_RGB_to_BGR: bool = False controlnet7_low_vram: bool = False - controlnet7_guess_mode: bool = False controlnet7_preprocessor: str = "None" controlnet7_model: str = "None" controlnet7_weight: float = 1.0 @@ -317,12 +363,10 @@ class Defaults: controlnet7_threshold_a: float = 0 controlnet7_threshold_b: float = 0 controlnet7_input_image: str = "" + controlnet7_control_mode: str = "Balanced" controlnet8_enable: bool = False - controlnet8_invert_input_color: bool = False - controlnet8_RGB_to_BGR: bool = False controlnet8_low_vram: bool = False - controlnet8_guess_mode: bool = False controlnet8_preprocessor: str = "None" controlnet8_model: str = "None" controlnet8_weight: float = 1.0 @@ -332,12 +376,10 @@ class Defaults: controlnet8_threshold_a: float = 0 controlnet8_threshold_b: float = 0 controlnet8_input_image: str = "" + controlnet8_control_mode: str = "Balanced" controlnet9_enable: bool = False - controlnet9_invert_input_color: bool = False - controlnet9_RGB_to_BGR: bool = False controlnet9_low_vram: bool = False - controlnet9_guess_mode: bool = False controlnet9_preprocessor: str = "None" controlnet9_model: str = "None" controlnet9_weight: float = 1.0 @@ -347,5 +389,6 @@ class Defaults: controlnet9_threshold_a: float = 0 controlnet9_threshold_b: float = 0 controlnet9_input_image: str = "" + controlnet9_control_mode: str = "Balanced" DEFAULTS = Defaults() diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index 99561e7f..b0ea5f73 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -65,18 +65,9 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): ) #Main settings - self.invert_input_color = QCheckBox( - script.cfg, f"controlnet{self.unit}_invert_input_color", "Invert input color" - ) - self.RGB_to_BGR = QCheckBox( - script.cfg, f"controlnet{self.unit}_RGB_to_BGR", "RGB to BGR" - ) self.low_vram = QCheckBox( script.cfg, f"controlnet{self.unit}_low_vram", "Low VRAM" ) - self.guess_mode = QCheckBox( - script.cfg, f"controlnet{self.unit}_guess_mode", "Guess mode" - ) #Tips self.tips = TipsLayout( @@ -108,6 +99,10 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): script.cfg, f"controlnet{self.unit}_guidance_end", label="Guidance end:", min=0, max=1, step=0.01 ) + self.control_mode = QComboBoxLayout( + script.cfg, "controlnet_control_mode_list", f"controlnet{self.unit}_control_mode", label="Control mode:" + ) + #Preprocessor settings self.annotator_resolution = QSpinBoxLayout( script.cfg, @@ -140,14 +135,8 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): self.annotator_preview_button = QPushButton("Preview annotator") self.annotator_clear_button = QPushButton("Clear preview") - main_settings_layout = QHBoxLayout() - main_settings_layout.addWidget(self.invert_input_color) - main_settings_layout.addWidget(self.RGB_to_BGR) - - main_settings_layout_2 = QHBoxLayout() main_settings_layout_2.addWidget(self.low_vram) - main_settings_layout_2.addWidget(self.guess_mode) guidance_layout = QHBoxLayout() guidance_layout.addLayout(self.guidance_start_layout) @@ -162,13 +151,13 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): layout.addWidget(self.enable) layout.addLayout(self.image_loader) layout.addLayout(self.tips) - layout.addLayout(main_settings_layout) layout.addLayout(main_settings_layout_2) layout.addLayout(self.preprocessor_layout) layout.addLayout(self.model_layout) layout.addWidget(self.refresh_button) layout.addLayout(self.weight_layout) layout.addLayout(guidance_layout) + layout.addLayout(self.control_mode) layout.addLayout(self.annotator_resolution) layout.addLayout(threshold_layout) layout.addWidget(self.annotator_preview) @@ -258,15 +247,13 @@ def image_loaded(self): def cfg_init(self): self.enable.cfg_init() - self.invert_input_color.cfg_init() - self.RGB_to_BGR.cfg_init() self.low_vram.cfg_init() - self.guess_mode.cfg_init() self.preprocessor_layout.cfg_init() self.model_layout.cfg_init() self.weight_layout.cfg_init() self.guidance_start_layout.cfg_init() self.guidance_end_layout.cfg_init() + self.control_mode.cfg_init() self.annotator_resolution.cfg_init() self.threshold_a.cfg_init() self.threshold_b.cfg_init() @@ -279,15 +266,13 @@ def cfg_init(self): def cfg_connect(self): self.enable.cfg_connect() - self.invert_input_color.cfg_connect() - self.RGB_to_BGR.cfg_connect() self.low_vram.cfg_connect() - self.guess_mode.cfg_connect() self.preprocessor_layout.cfg_connect() self.model_layout.cfg_connect() self.weight_layout.cfg_connect() self.guidance_start_layout.cfg_connect() self.guidance_end_layout.cfg_connect() + self.control_mode.cfg_connect() self.annotator_resolution.cfg_connect() self.threshold_a.cfg_connect() self.threshold_b.cfg_connect() diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 72750ac3..28843309 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -200,7 +200,7 @@ def get_mask_image(self, using_official_api) -> Union[QImage, None]: return mask.rgbSwapped() - def img_inserter(self, x, y, width, height, group=False, mask_img=None): + def img_inserter(self, x, y, width, height, group=False): """Return frozen image inserter to insert images as new layer.""" # Selection may change before callback, so freeze selection region has_selection = self.selection is not None @@ -216,12 +216,7 @@ def create_layer(name: str): else: parent.addChildNode(layer, None) return layer - - def insert_transparency_layer_for_inpaint(): - ba = img_to_ba(mask_img) - mask_layer = self.doc.createNode("Transparency Mask", "paintLayer") - mask_layer.setPixelData(ba, x, y, width, height) - + def insert(layer_name, enc): nonlocal x, y, width, height, has_selection print(f"inserting layer {layer_name}") @@ -272,6 +267,7 @@ def insert(layer_name, enc): print(f"inserting at x: {x}, y: {y}, w: {width}, h: {height}") layer.setPixelData(ba, x, y, width, height) self._inserted_layers.append(layer) + return layer return insert, glayer @@ -288,12 +284,6 @@ def get_controlnet_input_images(self, selected): if self.cfg(f"controlnet{i}_enable", bool): input_image = b64_to_img(self.cfg(f"controlnet{i}_input_image", str)) if \ self.cfg(f"controlnet{i}_input_image", str) else selected - - if self.cfg(f"controlnet{i}_invert_input_color", bool): - input_image.invertPixels() - - if self.cfg(f"controlnet{i}_RGB_to_BGR", bool): - input_image = input_image.rgbSwapped() input_images.update({f"{i}": input_image}) @@ -347,7 +337,7 @@ def apply_img2img(self, is_inpaint): mask_image = self.get_mask_image(controlnet_enabled) if is_inpaint else None insert, glayer = self.img_inserter( - self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool), mask_image + self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool) ) path = os.path.join(self.cfg("sample_path", str), f"{int(time.time())}.png") @@ -411,13 +401,7 @@ def apply_controlnet_preview_annotator(self, preview_label): if self.cfg(f"controlnet{unit}_input_image"): image = b64_to_img(self.cfg(f"controlnet{unit}_input_image")) else: - image = self.get_selection_image() - - if self.cfg(f"controlnet{unit}_invert_input_color", bool): - image.invertPixels() - - if self.cfg(f"controlnet{unit}_RGB_to_BGR", bool): - image = image.rgbSwapped() + image = self.get_selection_image() def cb(response): assert response is not None, "Backend Error, check terminal" From 0bce1b86862fdc292c6ae8ea091c30e2b1da058e Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Mon, 15 May 2023 09:50:47 -0600 Subject: [PATCH 33/55] Fixed errors --- frontends/krita/krita_diff/script.py | 12 ++---------- frontends/krita/krita_diff/utils.py | 16 +--------------- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index f53b93b8..475ca511 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -35,7 +35,6 @@ from .utils import ( b64_to_img, find_optimal_selection_region, - remove_unmasked_content_for_inpaint, get_desc_from_resp, img_to_ba, save_img, @@ -204,8 +203,7 @@ def get_mask_image(self, using_official_api) -> Union[QImage, None]: return mask.rgbSwapped() - def img_inserter(self, x, y, width, height, group=False, - is_official_api_inpaint=False, mask_img=None): + def img_inserter(self, x, y, width, height, group=False): """Return frozen image inserter to insert images as new layer.""" # Selection may change before callback, so freeze selection region has_selection = self.selection is not None @@ -246,9 +244,6 @@ def insert(layer_name, enc): width, height, transformMode=Qt.SmoothTransformation ) - if is_official_api_inpaint: - image = remove_unmasked_content_for_inpaint(image, mask_img) - # Resize (not scale!) canvas if image is larger (i.e. outpainting or Upscale was used) if image.width() > self.doc.width() or image.height() > self.doc.height(): # NOTE: @@ -301,8 +296,6 @@ def apply_txt2img(self): # freeze selection region controlnet_enabled = self.check_controlnet_enabled() - controlnet_enabled = self.check_controlnet_enabled() - insert, glayer = self.img_inserter( self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool) ) @@ -347,8 +340,7 @@ def apply_img2img(self, is_inpaint): mask_image = self.get_mask_image(controlnet_enabled) if is_inpaint else None insert, glayer = self.img_inserter( - self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool), - is_inpaint and controlnet_enabled, mask_image + self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool) ) path = os.path.join(self.cfg("sample_path", str), f"{int(time.time())}.png") diff --git a/frontends/krita/krita_diff/utils.py b/frontends/krita/krita_diff/utils.py index 5ce5ee43..f0b9381a 100644 --- a/frontends/krita/krita_diff/utils.py +++ b/frontends/krita/krita_diff/utils.py @@ -3,8 +3,7 @@ from itertools import cycle from math import ceil -from krita import Krita, QBuffer, QByteArray, QImage, QColor, QIODevice, Qt -from krita import Krita, QBuffer, QByteArray, QImage, QColor, QIODevice, Qt +from krita import Krita, QBuffer, QByteArray, QImage, QIODevice, Qt from .config import Config from .defaults import ( @@ -52,8 +51,6 @@ def get_ext_args(ext_cfg: Config, ext_type: str, ext_name: str): args.append(val) return args -def calculate_resized_image_dimensions( - base_size: int, max_size: int, orig_width: int, orig_height: int def calculate_resized_image_dimensions( base_size: int, max_size: int, orig_width: int, orig_height: int ): @@ -89,17 +86,6 @@ def find_fixed_aspect_ratio( width, height = calculate_resized_image_dimensions(base_size, max_size, orig_width, orig_height) return width, height - -def find_fixed_aspect_ratio( - base_size: int, max_size: int, orig_width: int, orig_height: int -): - """Copy of `krita_server.utils.sddebz_highres_fix()`. - - This is used by `find_optimal_selection_region()` below to adjust the selected region. - """ - width, height = calculate_resized_image_dimensions(base_size, max_size, orig_width, orig_height) - return width / height - def find_optimal_selection_region( base_size: int, From 852041893241717c2bc68aa91cd4f9324bbad06d Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Tue, 16 May 2023 00:02:09 -0600 Subject: [PATCH 34/55] Fixed issue with controlnet threshold spin boxes --- frontends/krita/krita_diff/pages/controlnet.py | 6 ++++-- frontends/krita/krita_diff/widgets/spin_box.py | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index b0ea5f73..14a0b015 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -118,7 +118,8 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): label="Threshold A:", min=1, max=255, - step=1 + step=1, + always_float=True ) self.threshold_b = QSpinBoxLayout( script.cfg, @@ -126,7 +127,8 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): label="Threshold B:", min=1, max=255, - step=1 + step=1, + always_float=True ) #Preview annotator diff --git a/frontends/krita/krita_diff/widgets/spin_box.py b/frontends/krita/krita_diff/widgets/spin_box.py index 89eadf15..88e6f818 100644 --- a/frontends/krita/krita_diff/widgets/spin_box.py +++ b/frontends/krita/krita_diff/widgets/spin_box.py @@ -17,6 +17,7 @@ def __init__( min: Union[int, float] = 0.0, max: Union[int, float] = 1.0, step: Union[int, float] = 0.1, + always_float: bool = False, #Workaround for controlnet threshold spin boxes *args, **kwargs ): @@ -40,6 +41,7 @@ def __init__( self.qlabel = QLabel(field_cfg if label is None else label) is_integer = ( + not always_float and float(step).is_integer() and float(min).is_integer() and float(max).is_integer() From 94fd0dcff9ee902c949b7591515f960345c76704 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Tue, 16 May 2023 09:22:10 -0600 Subject: [PATCH 35/55] More bug fixes --- frontends/krita/krita_diff/script.py | 17 ----------------- frontends/krita/krita_diff/utils.py | 2 +- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 475ca511..76b9e946 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -398,23 +398,6 @@ def cb(response): mask_image, # is unused by backend in img2img mode self.selection is not None, ) - if controlnet_enabled: - if is_inpaint: - self.client.post_official_api_inpaint( - cb, sel_image, mask_image, self.width, self.height, self.selection is not None, - self.get_controlnet_input_images(sel_image)) - else: - self.client.post_official_api_img2img( - cb, sel_image, self.width, self.height, self.selection is not None, - self.get_controlnet_input_images(sel_image)) - else: - method = self.client.post_inpaint if is_inpaint else self.client.post_img2img - method( - cb, - sel_image, - mask_image, # is unused by backend in img2img mode - self.selection is not None, - ) def apply_controlnet_preview_annotator(self, preview_label): unit = self.cfg("controlnet_unit", str) diff --git a/frontends/krita/krita_diff/utils.py b/frontends/krita/krita_diff/utils.py index f0b9381a..d12b2a62 100644 --- a/frontends/krita/krita_diff/utils.py +++ b/frontends/krita/krita_diff/utils.py @@ -85,7 +85,7 @@ def find_fixed_aspect_ratio( """ width, height = calculate_resized_image_dimensions(base_size, max_size, orig_width, orig_height) - return width, height + return width/height def find_optimal_selection_region( base_size: int, From f2eef084ca6ccfe3981034d7719fef40faf35c47 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Tue, 6 Jun 2023 02:59:10 -0400 Subject: [PATCH 36/55] horrible, broken transparency mask for inpainting --- frontends/krita/krita_diff/script.py | 42 ++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 76b9e946..bbd9a478 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -10,11 +10,9 @@ QImage, QColor, QPainter, - QColor, - QPainter, QObject, QPixmap, - QPixmap, + QRect, Qt, QTimer, Selection, @@ -379,6 +377,9 @@ def cb(response): # dont need transparency mask for inpaint mode if not is_inpaint: mask_trigger(layers) + # ...unless we're using the official API + elif controlnet_enabled: + mask_trigger(layers, mask_image) self.eta_timer.start() if controlnet_enabled: @@ -442,7 +443,7 @@ def transparency_mask_inserter(self): merge_mask_action = self.app.action("flatten_layer") # This function is recursive to workaround race conditions when calling Krita's actions - def add_mask(layers: list, cur_selection): + def add_mask(layers: list, cur_selection, mask_image): if len(layers) < 1: self.doc.setSelection(cur_selection) # reset to current selection return @@ -451,39 +452,56 @@ def add_mask(layers: list, cur_selection): orig_visible = layer.visible() orig_name = layer.name() - def restore(): + def restore(mask_image): # assume newly flattened layer is active result = self.doc.activeNode() result.setVisible(orig_visible) result.setName(orig_name) - add_mask(layers, cur_selection) + add_mask(layers, cur_selection, mask_image) layer.setVisible(True) self.doc.setActiveNode(layer) self.doc.setSelection(orig_selection) add_mask_action.trigger() + if (mask_image): + gray_img = mask_image.convertToFormat(QImage.Format_Grayscale8) + + x = orig_selection.x() + y = orig_selection.y() + sel_w = orig_selection.width() + sel_h = orig_selection.height() + w = gray_img.width() + h = gray_img.height() + crop_rect = QRect((w - sel_w)/2,(h - sel_h)/2, sel_w, sel_h) + + ba = img_to_ba(gray_img.copy(crop_rect)) + wait = 0 + while (len(layer.childNodes())==0) and (wait < 100): + time.sleep(0.1) + wait += 1 + layer.childNodes()[0].setPixelData(ba, x, y, sel_w, sel_h) if create_mask: # collapse transparency mask by default layer.setCollapsed(True) layer.setVisible(orig_visible) QTimer.singleShot( - ADD_MASK_TIMEOUT, lambda: add_mask(layers, cur_selection) + ADD_MASK_TIMEOUT, lambda: add_mask(layers, cur_selection, mask_image) ) else: # flatten transparency mask into layer merge_mask_action.trigger() - QTimer.singleShot(ADD_MASK_TIMEOUT, lambda: restore()) + QTimer.singleShot(ADD_MASK_TIMEOUT, lambda: restore(mask_image)) - def trigger_mask_adding(layers: list): + def trigger_mask_adding(layers: list, mask_image = None): layers = layers[::-1] # causes final active layer to be the top one - def handle_mask(): + def handle_mask(mask_image): cur_selection = self.selection.duplicate() if self.selection else None - add_mask(layers, cur_selection) + add_mask(layers, cur_selection, mask_image) - QTimer.singleShot(ADD_MASK_TIMEOUT, lambda: handle_mask()) + QTimer.singleShot(ADD_MASK_TIMEOUT, lambda: handle_mask(mask_image)) return trigger_mask_adding From 489538141de218b2631875b7049a37279a8ce715 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Tue, 6 Jun 2023 06:17:44 -0600 Subject: [PATCH 37/55] Fixed preprocessor options bug at start. --- frontends/krita/krita_diff/pages/controlnet.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index 14a0b015..e3d68b5d 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -170,13 +170,15 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): self.setLayout(layout) def set_preprocessor_options(self, selected: str): - self.set_threshold_names(selected) if selected in CONTROLNET_PREPROCESSOR_SETTINGS: self.show_preprocessor_options() + self.annotator_resolution.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["resolution_label"] \ + if "resolution_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else "Preprocessor resolution:") if "threshold_a_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: self.threshold_a.qlabel.show() self.threshold_a.qspin.show() + self.threshold_a.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_label"]) self.threshold_a.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_min_value"] \ if "threshold_a_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) self.threshold_a.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_max_value"] \ @@ -192,6 +194,7 @@ def set_preprocessor_options(self, selected: str): if "threshold_b_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: self.threshold_b.qlabel.show() self.threshold_b.qspin.show() + self.threshold_b.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_label"]) self.threshold_b.qspin.setMinimum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_min_value"] \ if "threshold_b_min_value" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else 0) self.threshold_b.qspin.setMaximum(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_max_value"] \ @@ -205,17 +208,6 @@ def set_preprocessor_options(self, selected: str): self.threshold_b.qspin.hide() else: self.hide_preprocessor_options(selected) - - def set_threshold_names(self, selected): - if selected in CONTROLNET_PREPROCESSOR_SETTINGS: - self.annotator_resolution.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["resolution_label"] \ - if "resolution_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected] else "Preprocessor resolution:") - - if "threshold_a_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: - self.threshold_a.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_a_label"]) - - if "threshold_b_label" in CONTROLNET_PREPROCESSOR_SETTINGS[selected]: - self.threshold_b.qlabel.setText(CONTROLNET_PREPROCESSOR_SETTINGS[selected]["threshold_b_label"]) def hide_preprocessor_options(self, selected: str): #Hide all annotator settings if no annotator chosen. @@ -259,7 +251,7 @@ def cfg_init(self): self.annotator_resolution.cfg_init() self.threshold_a.cfg_init() self.threshold_b.cfg_init() - self.set_threshold_names(self.preprocessor_layout.qcombo.currentText()) + self.set_preprocessor_options(self.preprocessor_layout.qcombo.currentText()) if (self.preprocessor_layout.qcombo.currentText() == "none"): self.annotator_preview_button.setEnabled(False) From 9105bbe52071deadc1ee4abd9973a4ad97f98d8f Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Tue, 6 Jun 2023 06:23:18 -0600 Subject: [PATCH 38/55] Switch controlnet unit at start. --- frontends/krita/krita_diff/pages/controlnet.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index e3d68b5d..e5094419 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -38,6 +38,8 @@ def cfg_init(self): for controlnet_unit_layout in self.controlnet_unit_layout_list: controlnet_unit_layout.cfg_init() + + self.controlnet_unit_changed(self.controlnet_unit.qcombo.currentText()) def cfg_connect(self): self.controlnet_unit.cfg_connect() From 14f839d6fb022ffd9ef0393fc6d023c6116aa09a Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Tue, 6 Jun 2023 19:52:20 -0400 Subject: [PATCH 39/55] Working implementation of transparency mask --- frontends/krita/krita_diff/script.py | 82 +++++++++++++++++++--------- 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index bbd9a478..1e546183 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -379,7 +379,7 @@ def cb(response): mask_trigger(layers) # ...unless we're using the official API elif controlnet_enabled: - mask_trigger(layers, mask_image) + self.controlnet_transparency_mask_inserter(layers, mask_image) self.eta_timer.start() if controlnet_enabled: @@ -399,6 +399,51 @@ def cb(response): mask_image, # is unused by backend in img2img mode self.selection is not None, ) + + def controlnet_transparency_mask_inserter(self, layers: list, mask_image): + orig_selection = self.selection.duplicate() if self.selection else None + create_mask = self.cfg("create_mask_layer", bool) + add_mask_action = self.app.action("add_new_transparency_mask") + merge_mask_action = self.app.action("flatten_layer") + + sx = orig_selection.x() + sy = orig_selection.y() + sw = orig_selection.width() + sh = orig_selection.height() + + # must convert mask to single channel format + gray_mask = mask_image.convertToFormat(QImage.Format_Grayscale8) + + w = gray_mask.width() + h = gray_mask.height() + + # int division should end up on the right side of rounding here + crop_rect = QRect((w - sw)/2,(h - sh)/2, sw, sh) + crop_mask = gray_mask.copy(crop_rect) + + mask_ba = img_to_ba(crop_mask) + + mask_selection = Selection() + mask_selection.setPixelData(mask_ba, sx, sy, sw, sh) + + for layer in layers: + layer.setVisible(True) + self.doc.setActiveNode(layer) + self.doc.setSelection(mask_selection) + add_mask_action.trigger() + + self.doc.setSelection(orig_selection) + + for layer in layers: + wait = 0 # wait for the mask to show up + while (len(layer.childNodes())==0) and (wait < 100): + time.sleep(0.1) + wait += 1 + if create_mask: + layer.setCollapsed(True) + else: + self.doc.setActiveNode(layer) + merge_mask_action.trigger() def apply_controlnet_preview_annotator(self, preview_label): unit = self.cfg("controlnet_unit", str) @@ -443,7 +488,7 @@ def transparency_mask_inserter(self): merge_mask_action = self.app.action("flatten_layer") # This function is recursive to workaround race conditions when calling Krita's actions - def add_mask(layers: list, cur_selection, mask_image): + def add_mask(layers: list, cur_selection): if len(layers) < 1: self.doc.setSelection(cur_selection) # reset to current selection return @@ -452,56 +497,39 @@ def add_mask(layers: list, cur_selection, mask_image): orig_visible = layer.visible() orig_name = layer.name() - def restore(mask_image): + def restore(): # assume newly flattened layer is active result = self.doc.activeNode() result.setVisible(orig_visible) result.setName(orig_name) - add_mask(layers, cur_selection, mask_image) + add_mask(layers, cur_selection) layer.setVisible(True) self.doc.setActiveNode(layer) self.doc.setSelection(orig_selection) add_mask_action.trigger() - if (mask_image): - gray_img = mask_image.convertToFormat(QImage.Format_Grayscale8) - x = orig_selection.x() - y = orig_selection.y() - sel_w = orig_selection.width() - sel_h = orig_selection.height() - w = gray_img.width() - h = gray_img.height() - crop_rect = QRect((w - sel_w)/2,(h - sel_h)/2, sel_w, sel_h) - - ba = img_to_ba(gray_img.copy(crop_rect)) - wait = 0 - while (len(layer.childNodes())==0) and (wait < 100): - time.sleep(0.1) - wait += 1 - layer.childNodes()[0].setPixelData(ba, x, y, sel_w, sel_h) - if create_mask: # collapse transparency mask by default layer.setCollapsed(True) layer.setVisible(orig_visible) QTimer.singleShot( - ADD_MASK_TIMEOUT, lambda: add_mask(layers, cur_selection, mask_image) + ADD_MASK_TIMEOUT, lambda: add_mask(layers, cur_selection) ) else: # flatten transparency mask into layer merge_mask_action.trigger() - QTimer.singleShot(ADD_MASK_TIMEOUT, lambda: restore(mask_image)) + QTimer.singleShot(ADD_MASK_TIMEOUT, lambda: restore()) - def trigger_mask_adding(layers: list, mask_image = None): + def trigger_mask_adding(layers: list): layers = layers[::-1] # causes final active layer to be the top one - def handle_mask(mask_image): + def handle_mask(): cur_selection = self.selection.duplicate() if self.selection else None - add_mask(layers, cur_selection, mask_image) + add_mask(layers, cur_selection) - QTimer.singleShot(ADD_MASK_TIMEOUT, lambda: handle_mask(mask_image)) + QTimer.singleShot(ADD_MASK_TIMEOUT, lambda: handle_mask()) return trigger_mask_adding From d670f7c6c3fe504fe6a39b90478ebed11276e2a2 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Tue, 6 Jun 2023 22:08:42 -0400 Subject: [PATCH 40/55] Better race condition handling --- frontends/krita/krita_diff/script.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 1e546183..933ab7f5 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -430,20 +430,20 @@ def controlnet_transparency_mask_inserter(self, layers: list, mask_image): layer.setVisible(True) self.doc.setActiveNode(layer) self.doc.setSelection(mask_selection) + self.doc.waitForDone() # deals with race condition? add_mask_action.trigger() self.doc.setSelection(orig_selection) - + i = 0 for layer in layers: - wait = 0 # wait for the mask to show up - while (len(layer.childNodes())==0) and (wait < 100): - time.sleep(0.1) - wait += 1 + i += 1 + self.doc.waitForDone() # deals with race condition if create_mask: layer.setCollapsed(True) else: self.doc.setActiveNode(layer) merge_mask_action.trigger() + if i != len(layers): layer.setVisible(False) def apply_controlnet_preview_annotator(self, preview_label): unit = self.cfg("controlnet_unit", str) From e7e4b252eec511e7ab1ab98c6b24c7551228577b Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Wed, 7 Jun 2023 19:24:41 -0400 Subject: [PATCH 41/55] Implement img2img upscale and update mask converter ControlNet img2img pipeline will now route images through the upscaler API endpoint before inserting them. Also changes mask code to use QImage format conversions to isolate the alpha channel. --- frontends/krita/krita_diff/script.py | 49 ++++++++++++++++++---------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 933ab7f5..4b84a1c7 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -184,20 +184,12 @@ def get_mask_image(self, using_official_api) -> Union[QImage, None]: ) if using_official_api: - #replace mask's transparency with white color, since official API - #doesn't seem to work with transparency. Assuming masked content - #is black. - new_mask = QImage(mask.size(), QImage.Format_RGBA8888) - new_mask.fill(QColor("white")) - - painter = QPainter(new_mask) - painter.setCompositionMode(QPainter.CompositionMode_SourceOver) - painter.drawImage(0, 0, mask) - painter.end() - - #Invert colors so white corresponds to masked area. - new_mask.invertPixels() - mask = new_mask + # Official API requires a black and white mask. + # Fastest way to do this: Convert to 1 channel alpha, tell it that + # it's grayscale, convert that to RGBA. + mask = mask.convertToFormat(QImage.Format_Alpha8) + mask.reinterpretAsFormat(QImage.Format_Grayscale8) + mask = mask.convertToFormat(QImage.Format_RGBA8888) return mask.rgbSwapped() @@ -357,11 +349,37 @@ def apply_img2img(self, is_inpaint): save_img(sel_image, path) def cb(response): + def cb_upscale(upscale_response): + if len(self.client.long_reqs) == 1: # last request + self.eta_timer.stop() + assert response is not None, "Backend Error, check terminal" + + outputs = upscale_response["images"] + layer_name_prefix = "inpaint" if is_inpaint else "img2img" + glayer_name, layer_names = get_desc_from_resp(response, layer_name_prefix) + layers = [ + insert(name if name else f"{layer_name_prefix} {i + 1}", output) + for output, name, i in zip(outputs, layer_names, itertools.count()) + ] + if self.cfg("hide_layers", bool): + for layer in layers[:-1]: + layer.setVisible(False) + if glayer: + glayer.setName(glayer_name) + self.doc.refreshProjection() + self.controlnet_transparency_mask_inserter(layers, mask_image) + if len(self.client.long_reqs) == 1: # last request self.eta_timer.stop() assert response is not None, "Backend Error, check terminal" outputs = response["outputs"] if not controlnet_enabled else response["images"] + + if controlnet_enabled: + self.client.post_official_api_upscale_postprocess( + cb_upscale, outputs, self.width, self.height) + return + layer_name_prefix = "inpaint" if is_inpaint else "img2img" glayer_name, layer_names = get_desc_from_resp(response, layer_name_prefix) layers = [ @@ -377,9 +395,6 @@ def cb(response): # dont need transparency mask for inpaint mode if not is_inpaint: mask_trigger(layers) - # ...unless we're using the official API - elif controlnet_enabled: - self.controlnet_transparency_mask_inserter(layers, mask_image) self.eta_timer.start() if controlnet_enabled: From cdc2029cd6da13d523087dd937be4d178fd27e09 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Wed, 7 Jun 2023 19:26:01 -0400 Subject: [PATCH 42/55] Implement upscale postprocess API call function --- frontends/krita/krita_diff/client.py | 31 +++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index f12ba5e9..4739bb14 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -278,7 +278,7 @@ def official_api_common_params(self, has_selection, width, height, tiling=tiling, restore_faces=self.cfg("face_restorer_model", str) != "None", override_settings=self.options_params(), - override_settings_restore_afterwards=False, + override_settings_restore_afterwards=True, alwayson_scripts={} ) @@ -640,6 +640,35 @@ def post_upscale(self, cb, src_img): ) self.post("upscale", params, cb) + def post_official_api_upscale_postprocess(self, cb, src_imgs: list, width, height): + """Uses official API. Intended for finalizing img2img pipeline.""" + + params = dict( + resize_mode=1, + show_extras_results=False, + gfpgan_visibility=0, + codeformer_visibility=0, + codeformer_weight=0, + upscaling_resize=1, + upscaling_resize_w=width, + upscaling_resize_h=height, + upscaling_crop=True, + upscaler_1=self.cfg("upscaler_name", str), + upscaler_2="None", # Todo: would be nice to support blended upscalers + extras_upscaler_2_visibility=0, + upscale_first=False, + imageList=[] + ) + + for img in src_imgs: + params["imageList"].append({ + "data": img, + "name": "example_image" + }) + + url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) + self.post("extra-batch-images", params, cb, base_url=url) + def post_controlnet_preview(self, cb, src_img): unit = self.cfg("controlnet_unit", str) params = ( From eafff05701e6acfbd6978201667e8d908bae2109 Mon Sep 17 00:00:00 2001 From: drhead <1313496+drhead@users.noreply.github.com> Date: Wed, 7 Jun 2023 21:41:00 -0400 Subject: [PATCH 43/55] fix inpainting without selection --- frontends/krita/krita_diff/script.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 4b84a1c7..3d44f038 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -421,11 +421,16 @@ def controlnet_transparency_mask_inserter(self, layers: list, mask_image): add_mask_action = self.app.action("add_new_transparency_mask") merge_mask_action = self.app.action("flatten_layer") - sx = orig_selection.x() - sy = orig_selection.y() - sw = orig_selection.width() - sh = orig_selection.height() - + if orig_selection: + sx = orig_selection.x() + sy = orig_selection.y() + sw = orig_selection.width() + sh = orig_selection.height() + else: + sx = 0 + sy = 0 + sw = self.doc.width() + sh = self.doc.height() # must convert mask to single channel format gray_mask = mask_image.convertToFormat(QImage.Format_Grayscale8) From d1a0826ff520eb519d1b938545a5eb28da4dbd70 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Wed, 7 Jun 2023 23:18:53 -0600 Subject: [PATCH 44/55] Regression bug fix --- frontends/krita/krita_diff/pages/controlnet.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index e5094419..aebc0a89 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -171,6 +171,9 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): self.setLayout(layout) + self.cfg_init() + self.set_preprocessor_options(self.preprocessor_layout.qcombo.currentText()) + def set_preprocessor_options(self, selected: str): if selected in CONTROLNET_PREPROCESSOR_SETTINGS: self.show_preprocessor_options() @@ -239,7 +242,7 @@ def enable_changed(self, state): def image_loaded(self): image = self.image_loader.preview.pixmap().toImage().convertToFormat(QImage.Format_RGBA8888) - script.cfg.set(f"controlnet{self.unit}_input_image", img_to_b64(image)) + script.cfg.set(f"controlnet{self.unit}_input_image", img_to_b64(image)) def cfg_init(self): self.enable.cfg_init() @@ -253,7 +256,6 @@ def cfg_init(self): self.annotator_resolution.cfg_init() self.threshold_a.cfg_init() self.threshold_b.cfg_init() - self.set_preprocessor_options(self.preprocessor_layout.qcombo.currentText()) if (self.preprocessor_layout.qcombo.currentText() == "none"): self.annotator_preview_button.setEnabled(False) From a99774b9bdf7da901632c99696d0cd784d5ac31a Mon Sep 17 00:00:00 2001 From: drhead Date: Thu, 8 Jun 2023 14:13:47 -0400 Subject: [PATCH 45/55] Bypass upscaling call when it is not needed --- frontends/krita/krita_diff/script.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 3d44f038..442a8312 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -376,8 +376,14 @@ def cb_upscale(upscale_response): outputs = response["outputs"] if not controlnet_enabled else response["images"] if controlnet_enabled: - self.client.post_official_api_upscale_postprocess( - cb_upscale, outputs, self.width, self.height) + if min(self.width,self.height) > self.cfg("sd_base_size", int) \ + or max(self.width,self.height) > self.cfg("sd_max_size", int): + # this only handles with base/max size enabled + self.client.post_official_api_upscale_postprocess( + cb_upscale, outputs, self.width, self.height) + else: + # passing response directly to the callback works fine + cb_upscale(response) return layer_name_prefix = "inpaint" if is_inpaint else "img2img" From 14e776be25c54093ccd78b23a0b8227377d8d04f Mon Sep 17 00:00:00 2001 From: drhead Date: Fri, 9 Jun 2023 13:44:37 -0400 Subject: [PATCH 46/55] changed mask generation to only affect group (may break old api) --- frontends/krita/krita_diff/script.py | 49 +++++++++++----------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 442a8312..29daf0f2 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -193,11 +193,10 @@ def get_mask_image(self, using_official_api) -> Union[QImage, None]: return mask.rgbSwapped() - def img_inserter(self, x, y, width, height, group=False): + def img_inserter(self, x, y, width, height, glayer=None): """Return frozen image inserter to insert images as new layer.""" # Selection may change before callback, so freeze selection region has_selection = self.selection is not None - glayer = self.doc.createGroupLayer("Unnamed Group") if group else None def create_layer(name: str): """Create new layer in document or group""" @@ -205,7 +204,6 @@ def create_layer(name: str): parent = self.doc.rootNode() if glayer: glayer.addChildNode(layer, None) - parent.addChildNode(glayer, None) else: parent.addChildNode(layer, None) return layer @@ -263,7 +261,7 @@ def insert(layer_name, enc): return layer - return insert, glayer + return insert def check_controlnet_enabled(self): for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))): @@ -285,9 +283,9 @@ def get_controlnet_input_images(self, selected): def apply_txt2img(self): # freeze selection region controlnet_enabled = self.check_controlnet_enabled() - - insert, glayer = self.img_inserter( - self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool) + glayer = self.doc.createGroupLayer("Unnamed Group") + insert = self.img_inserter( + self.x, self.y, self.width, self.height, glayer ) mask_trigger = self.transparency_mask_inserter() @@ -328,9 +326,11 @@ def apply_img2img(self, is_inpaint): mask_trigger = self.transparency_mask_inserter() mask_image = self.get_mask_image(controlnet_enabled) if is_inpaint else None - - insert, glayer = self.img_inserter( - self.x, self.y, self.width, self.height, not self.cfg("no_groups", bool) + glayer = self.doc.createGroupLayer("Unnamed Group") + parent = self.doc.rootNode() + parent.addChildNode(glayer, None) + insert = self.img_inserter( + self.x, self.y, self.width, self.height, glayer ) path = os.path.join(self.cfg("sample_path", str), f"{int(time.time())}.png") @@ -342,6 +342,7 @@ def apply_img2img(self, is_inpaint): save_img(mask_image, mask_path) # auto-hide mask layer before getting selection image self.node.setVisible(False) + self.controlnet_transparency_mask_inserter(glayer, mask_image) self.doc.refreshProjection() sel_image = self.get_selection_image() @@ -367,7 +368,6 @@ def cb_upscale(upscale_response): if glayer: glayer.setName(glayer_name) self.doc.refreshProjection() - self.controlnet_transparency_mask_inserter(layers, mask_image) if len(self.client.long_reqs) == 1: # last request self.eta_timer.stop() @@ -421,7 +421,7 @@ def cb_upscale(upscale_response): self.selection is not None, ) - def controlnet_transparency_mask_inserter(self, layers: list, mask_image): + def controlnet_transparency_mask_inserter(self, glayer, mask_image): orig_selection = self.selection.duplicate() if self.selection else None create_mask = self.cfg("create_mask_layer", bool) add_mask_action = self.app.action("add_new_transparency_mask") @@ -443,6 +443,7 @@ def controlnet_transparency_mask_inserter(self, layers: list, mask_image): w = gray_mask.width() h = gray_mask.height() + # crop mask to the actual selection side # int division should end up on the right side of rounding here crop_rect = QRect((w - sw)/2,(h - sh)/2, sw, sh) crop_mask = gray_mask.copy(crop_rect) @@ -452,24 +453,12 @@ def controlnet_transparency_mask_inserter(self, layers: list, mask_image): mask_selection = Selection() mask_selection.setPixelData(mask_ba, sx, sy, sw, sh) - for layer in layers: - layer.setVisible(True) - self.doc.setActiveNode(layer) - self.doc.setSelection(mask_selection) - self.doc.waitForDone() # deals with race condition? - add_mask_action.trigger() - + self.doc.setActiveNode(glayer) + self.doc.setSelection(mask_selection) + self.doc.waitForDone() # deals with race condition? + add_mask_action.trigger() + self.doc.waitForDone() self.doc.setSelection(orig_selection) - i = 0 - for layer in layers: - i += 1 - self.doc.waitForDone() # deals with race condition - if create_mask: - layer.setCollapsed(True) - else: - self.doc.setActiveNode(layer) - merge_mask_action.trigger() - if i != len(layers): layer.setVisible(False) def apply_controlnet_preview_annotator(self, preview_label): unit = self.cfg("controlnet_unit", str) @@ -490,7 +479,7 @@ def cb(response): self.client.post_controlnet_preview(cb, image) def apply_simple_upscale(self): - insert, _ = self.img_inserter(self.x, self.y, self.width, self.height) + insert = self.img_inserter(self.x, self.y, self.width, self.height) sel_image = self.get_selection_image() path = os.path.join(self.cfg("sample_path", str), f"{int(time.time())}.png") From 2859a7572504974303c92ddc1f2ba701ba337899 Mon Sep 17 00:00:00 2001 From: drhead Date: Fri, 9 Jun 2023 13:49:55 -0400 Subject: [PATCH 47/55] fix txt2img and simplify glayer creation --- frontends/krita/krita_diff/script.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 29daf0f2..c6e340aa 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -284,6 +284,7 @@ def apply_txt2img(self): # freeze selection region controlnet_enabled = self.check_controlnet_enabled() glayer = self.doc.createGroupLayer("Unnamed Group") + self.doc.rootNode().addChildNode(glayer, None) insert = self.img_inserter( self.x, self.y, self.width, self.height, glayer ) @@ -327,8 +328,7 @@ def apply_img2img(self, is_inpaint): mask_trigger = self.transparency_mask_inserter() mask_image = self.get_mask_image(controlnet_enabled) if is_inpaint else None glayer = self.doc.createGroupLayer("Unnamed Group") - parent = self.doc.rootNode() - parent.addChildNode(glayer, None) + self.doc.rootNode().addChildNode(glayer, None) insert = self.img_inserter( self.x, self.y, self.width, self.height, glayer ) From bc6fa516ce8d30b1f87f53aa92558498cf1aca2a Mon Sep 17 00:00:00 2001 From: drhead Date: Fri, 9 Jun 2023 17:55:31 -0400 Subject: [PATCH 48/55] desperation --- frontends/krita/krita_diff/script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index c6e340aa..f4b726a1 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -455,7 +455,7 @@ def controlnet_transparency_mask_inserter(self, glayer, mask_image): self.doc.setActiveNode(glayer) self.doc.setSelection(mask_selection) - self.doc.waitForDone() # deals with race condition? + self.doc.refreshProjection() # deals with race condition? add_mask_action.trigger() self.doc.waitForDone() self.doc.setSelection(orig_selection) From 5391954a09e0de40adab5eea003e7729fe4f67ab Mon Sep 17 00:00:00 2001 From: drhead Date: Mon, 12 Jun 2023 20:39:17 -0400 Subject: [PATCH 49/55] QTimer-based approach to avoiding mask race condition --- frontends/krita/krita_diff/script.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index f4b726a1..b5ad0dc9 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -453,12 +453,17 @@ def controlnet_transparency_mask_inserter(self, glayer, mask_image): mask_selection = Selection() mask_selection.setPixelData(mask_ba, sx, sy, sw, sh) - self.doc.setActiveNode(glayer) - self.doc.setSelection(mask_selection) - self.doc.refreshProjection() # deals with race condition? - add_mask_action.trigger() - self.doc.waitForDone() - self.doc.setSelection(orig_selection) + def apply_mask_when_ready(): + # glayer will be selected when it is done being created + if self.doc.activeNode() == glayer: + self.doc.setSelection(mask_selection) + add_mask_action.trigger() + self.doc.setSelection(orig_selection) + timer.stop() + + timer = QTimer() + timer.timeout.connect(apply_mask_when_ready) + timer.start(0.05) def apply_controlnet_preview_annotator(self, preview_label): unit = self.cfg("controlnet_unit", str) @@ -625,4 +630,4 @@ def action_update_eta(self): self.client.get_progress(self.progress_update.emit) -script = Script() +script = Script() \ No newline at end of file From 91f5ad8c889dd66d5c7a622dc837aedcd650503c Mon Sep 17 00:00:00 2001 From: drhead Date: Tue, 13 Jun 2023 02:22:37 -0400 Subject: [PATCH 50/55] Fixed mask shredding bug. --- frontends/krita/krita_diff/script.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index b5ad0dc9..e4e313e7 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -437,21 +437,22 @@ def controlnet_transparency_mask_inserter(self, glayer, mask_image): sy = 0 sw = self.doc.width() sh = self.doc.height() + # must convert mask to single channel format gray_mask = mask_image.convertToFormat(QImage.Format_Grayscale8) - w = gray_mask.width() - h = gray_mask.height() - - # crop mask to the actual selection side - # int division should end up on the right side of rounding here - crop_rect = QRect((w - sw)/2,(h - sh)/2, sw, sh) + # crop mask to the actual selection size + crop_rect = QRect(0, 0, sw, sh) crop_mask = gray_mask.copy(crop_rect) mask_ba = img_to_ba(crop_mask) + # Why is sizeInBytes() different from width * height? Just... why? + w = crop_mask.bytesPerLine() + h = crop_mask.sizeInBytes()/w + mask_selection = Selection() - mask_selection.setPixelData(mask_ba, sx, sy, sw, sh) + mask_selection.setPixelData(mask_ba, sx, sy, w, h) def apply_mask_when_ready(): # glayer will be selected when it is done being created From c72b9a892f15d375de68e7b69d3721ae77412f9b Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Wed, 7 Jun 2023 23:20:18 -0600 Subject: [PATCH 51/55] Regression bug fix --- .../krita/krita_diff/pages/controlnet.py | 48 +++++++++++++++++-- frontends/krita/krita_diff/script.py | 16 +++---- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index aebc0a89..f265fafa 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -1,9 +1,27 @@ -from krita import QPixmap, QImage, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout, Qt +from krita import ( + QApplication, + QPixmap, + QImage, + QPushButton, + QWidget, + QVBoxLayout, + QHBoxLayout, + QStackedLayout, + Qt +) from functools import partial from ..defaults import CONTROLNET_PREPROCESSOR_SETTINGS from ..script import script -from ..widgets import QLabel, StatusBar, ImageLoaderLayout, QCheckBox, TipsLayout, QComboBoxLayout, QSpinBoxLayout +from ..widgets import ( + QLabel, + StatusBar, + ImageLoaderLayout, + QCheckBox, + TipsLayout, + QComboBoxLayout, + QSpinBoxLayout +) from ..utils import img_to_b64, b64_to_img class ControlNetPage(QWidget): @@ -54,6 +72,7 @@ class ControlNetUnitSettings(QWidget): def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): super(ControlNetUnitSettings, self).__init__(*args, **kwargs) self.unit = cfg_unit_number + self.preview_result = QPixmap() #This will help us to copy to clipboard the image with original dimensions. #Top checkbox self.enable = QCheckBox( @@ -138,6 +157,7 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): self.annotator_preview.setAlignment(Qt.AlignCenter) self.annotator_preview_button = QPushButton("Preview annotator") self.annotator_clear_button = QPushButton("Clear preview") + self.copy_result_button = QPushButton("Copy result to clipboard") main_settings_layout_2 = QHBoxLayout() main_settings_layout_2.addWidget(self.low_vram) @@ -166,6 +186,7 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): layout.addLayout(threshold_layout) layout.addWidget(self.annotator_preview) layout.addWidget(self.annotator_preview_button) + layout.addWidget(self.copy_result_button) layout.addWidget(self.annotator_clear_button) layout.addStretch() @@ -243,6 +264,21 @@ def enable_changed(self, state): def image_loaded(self): image = self.image_loader.preview.pixmap().toImage().convertToFormat(QImage.Format_RGBA8888) script.cfg.set(f"controlnet{self.unit}_input_image", img_to_b64(image)) + + def annotator_preview_received(self, pixmap): + self.preview_result = pixmap + if pixmap.width() > self.annotator_preview.width(): + pixmap = pixmap.scaledToWidth(self.annotator_preview.width(), Qt.SmoothTransformation) + self.annotator_preview.setPixmap(pixmap) + + def annotator_clear_button_released(self): + self.annotator_preview.setPixmap(QPixmap()) + self.preview_result = QPixmap() + + def copy_result_released(self): + if self.preview_result: + clipboard = QApplication.clipboard() + clipboard.setImage(self.preview_result.toImage()) def cfg_init(self): self.enable.cfg_init() @@ -257,7 +293,7 @@ def cfg_init(self): self.threshold_a.cfg_init() self.threshold_b.cfg_init() - if (self.preprocessor_layout.qcombo.currentText() == "none"): + if self.preprocessor_layout.qcombo.currentText() == "none": self.annotator_preview_button.setEnabled(False) else: self.annotator_preview_button.setEnabled(True) @@ -287,6 +323,8 @@ def cfg_connect(self): ) self.refresh_button.released.connect(lambda: script.action_update_controlnet_config()) self.annotator_preview_button.released.connect( - lambda: script.action_preview_controlnet_annotator(self.annotator_preview) + lambda: script.action_preview_controlnet_annotator() ) - self.annotator_clear_button.released.connect(lambda: self.annotator_preview.setPixmap(QPixmap())) \ No newline at end of file + self.copy_result_button.released.connect(self.copy_result_released) + self.annotator_clear_button.released.connect(lambda: self.annotator_preview.setPixmap(QPixmap())) + script.controlnet_preview_annotator_received.connect(self.annotator_preview_received) \ No newline at end of file diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 933ab7f5..b5465447 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -67,6 +67,7 @@ class Script(QObject): status_changed = pyqtSignal(str) config_updated = pyqtSignal() progress_update = pyqtSignal(object) + controlnet_preview_annotator_received = pyqtSignal(QPixmap) def __init__(self): super(Script, self).__init__() @@ -445,7 +446,7 @@ def controlnet_transparency_mask_inserter(self, layers: list, mask_image): merge_mask_action.trigger() if i != len(layers): layer.setVisible(False) - def apply_controlnet_preview_annotator(self, preview_label): + def apply_controlnet_preview_annotator(self): unit = self.cfg("controlnet_unit", str) if self.cfg(f"controlnet{unit}_input_image"): image = b64_to_img(self.cfg(f"controlnet{unit}_input_image")) @@ -456,10 +457,7 @@ def cb(response): assert response is not None, "Backend Error, check terminal" output = response["images"][0] pixmap = QPixmap.fromImage(b64_to_img(output)) - - if pixmap.width() > preview_label.width(): - pixmap = pixmap.scaledToWidth(preview_label.width(), Qt.SmoothTransformation) - preview_label.setPixmap(pixmap) + self.controlnet_preview_annotator_received.emit(pixmap) self.client.post_controlnet_preview(cb, image) @@ -579,26 +577,26 @@ def action_update_controlnet_config(self): """Update controlnet config from the backend.""" self.client.get_controlnet_config() - def action_preview_controlnet_annotator(self, preview_label): + def action_preview_controlnet_annotator(self): self.status_changed.emit(STATE_WAIT) self.update_selection() if not self.doc: return self.adjust_selection() - self.apply_controlnet_preview_annotator(preview_label) + self.apply_controlnet_preview_annotator() def action_update_controlnet_config(self): """Update controlnet config from the backend.""" self.client.get_controlnet_config() - def action_preview_controlnet_annotator(self, preview_label): + def action_preview_controlnet_annotator(self): self.status_changed.emit(STATE_WAIT) self.update_selection() if not self.doc: return self.adjust_selection() - self.apply_controlnet_preview_annotator(preview_label) + self.apply_controlnet_preview_annotator() def action_interrupt(self): def cb(resp=None): From d8ac8765e300eaac27633ef99af1bdf2cc6fe157 Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Wed, 7 Jun 2023 23:20:18 -0600 Subject: [PATCH 52/55] Regression bug fix --- frontends/krita/krita_diff/client.py | 24 ++++++++++++++----- frontends/krita/krita_diff/defaults.py | 10 ++++++++ .../krita/krita_diff/pages/controlnet.py | 23 +++++++++++++++++- frontends/krita/krita_diff/script.py | 2 +- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index f12ba5e9..a7f14e6a 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -640,15 +640,27 @@ def post_upscale(self, cb, src_img): ) self.post("upscale", params, cb) - def post_controlnet_preview(self, cb, src_img): - unit = self.cfg("controlnet_unit", str) + def post_controlnet_preview(self, cb, src_img, width, height): + def get_pixel_perfect_preprocessor_resolution(): + if self.cfg("disable_sddebz_highres", bool): + return width if width <= height else height + + resized_width, resized_height = calculate_resized_image_dimensions( + self.cfg("sd_base_size", int), self.cfg("sd_max_size", int), width, height + ) + return resized_width if resized_width <= resized_height else resized_height + + unit = self.cfg("controlnet_unit", str) + preprocessor_resolution = get_pixel_perfect_preprocessor_resolution() if self.cfg(f"controlnet{unit}_pixel_perfect", bool) \ + else self.cfg(f"controlnet{unit}_preprocessor_resolution", int) + params = ( { - "controlnet_module": self.cfg(f"controlnet{unit}_preprocessor"), + "controlnet_module": self.cfg(f"controlnet{unit}_preprocessor", str), "controlnet_input_images": [img_to_b64(src_img)], - "controlnet_processor_res": self.cfg(f"controlnet{unit}_preprocessor_resolution"), - "controlnet_threshold_a": self.cfg(f"controlnet{unit}_threshold_a"), - "controlnet_threshold_b": self.cfg(f"controlnet{unit}_threshold_b") + "controlnet_processor_res": preprocessor_resolution, + "controlnet_threshold_a": self.cfg(f"controlnet{unit}_threshold_a", float), + "controlnet_threshold_b": self.cfg(f"controlnet{unit}_threshold_b", float) } #Not sure if it's necessary to make the just_use_yaml validation here ) url = get_url(self.cfg, prefix=CONTROLNET_ROUTE_PREFIX) diff --git a/frontends/krita/krita_diff/defaults.py b/frontends/krita/krita_diff/defaults.py index 1067a748..5d5597d0 100644 --- a/frontends/krita/krita_diff/defaults.py +++ b/frontends/krita/krita_diff/defaults.py @@ -265,6 +265,7 @@ class Defaults: controlnet0_enable: bool = False controlnet0_low_vram: bool = False + controlnet0_pixel_perfect: bool = False controlnet0_preprocessor: str = "None" controlnet0_model: str = "None" controlnet0_weight: float = 1.0 @@ -278,6 +279,7 @@ class Defaults: controlnet1_enable: bool = False controlnet1_low_vram: bool = False + controlnet1_pixel_perfect: bool = False controlnet1_preprocessor: str = "None" controlnet1_model: str = "None" controlnet1_weight: float = 1.0 @@ -291,6 +293,7 @@ class Defaults: controlnet2_enable: bool = False controlnet2_low_vram: bool = False + controlnet2_pixel_perfect: bool = False controlnet2_preprocessor: str = "None" controlnet2_model: str = "None" controlnet2_weight: float = 1.0 @@ -304,6 +307,7 @@ class Defaults: controlnet3_enable: bool = False controlnet3_low_vram: bool = False + controlnet3_pixel_perfect: bool = False controlnet3_preprocessor: str = "None" controlnet3_model: str = "None" controlnet3_weight: float = 1.0 @@ -317,6 +321,7 @@ class Defaults: controlnet4_enable: bool = False controlnet4_low_vram: bool = False + controlnet4_pixel_perfect: bool = False controlnet4_preprocessor: str = "None" controlnet4_model: str = "None" controlnet4_weight: float = 1.0 @@ -330,6 +335,7 @@ class Defaults: controlnet5_enable: bool = False controlnet5_low_vram: bool = False + controlnet5_pixel_perfect: bool = False controlnet5_preprocessor: str = "None" controlnet5_model: str = "None" controlnet5_weight: float = 1.0 @@ -343,6 +349,7 @@ class Defaults: controlnet6_enable: bool = False controlnet6_low_vram: bool = False + controlnet6_pixel_perfect: bool = False controlnet6_preprocessor: str = "None" controlnet6_model: str = "None" controlnet6_weight: float = 1.0 @@ -356,6 +363,7 @@ class Defaults: controlnet7_enable: bool = False controlnet7_low_vram: bool = False + controlnet7_pixel_perfect: bool = False controlnet7_preprocessor: str = "None" controlnet7_model: str = "None" controlnet7_weight: float = 1.0 @@ -369,6 +377,7 @@ class Defaults: controlnet8_enable: bool = False controlnet8_low_vram: bool = False + controlnet8_pixel_perfect: bool = False controlnet8_preprocessor: str = "None" controlnet8_model: str = "None" controlnet8_weight: float = 1.0 @@ -382,6 +391,7 @@ class Defaults: controlnet9_enable: bool = False controlnet9_low_vram: bool = False + controlnet9_pixel_perfect: bool = False controlnet9_preprocessor: str = "None" controlnet9_model: str = "None" controlnet9_weight: float = 1.0 diff --git a/frontends/krita/krita_diff/pages/controlnet.py b/frontends/krita/krita_diff/pages/controlnet.py index f265fafa..b5c570b2 100644 --- a/frontends/krita/krita_diff/pages/controlnet.py +++ b/frontends/krita/krita_diff/pages/controlnet.py @@ -90,11 +90,16 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): script.cfg, f"controlnet{self.unit}_low_vram", "Low VRAM" ) + self.pixel_perfect = QCheckBox( + script.cfg, f"controlnet{self.unit}_pixel_perfect", "Pixel Perfect" + ) + #Tips self.tips = TipsLayout( ["Invert colors if your image has white background.", "Selection will be used as input if no image has been uploaded or pasted.", - "Remember to set multi-controlnet in the backend as well if you want to use more than one unit."] + "Remember to set multi-controlnet in the backend as well if you want to use more than one unit.", + "Enable pixel perfect if you want the preprocessor to automatically adjust to the selection size (respects base/max size)"] ) #Preprocessor list @@ -161,6 +166,7 @@ def __init__(self, cfg_unit_number: int = 0, *args, **kwargs): main_settings_layout_2 = QHBoxLayout() main_settings_layout_2.addWidget(self.low_vram) + main_settings_layout_2.addWidget(self.pixel_perfect) guidance_layout = QHBoxLayout() guidance_layout.addLayout(self.guidance_start_layout) @@ -279,10 +285,19 @@ def copy_result_released(self): if self.preview_result: clipboard = QApplication.clipboard() clipboard.setImage(self.preview_result.toImage()) + + def hide_or_show_preprocessor_resolution(self, pixel_perfect): + if pixel_perfect: + self.annotator_resolution.qlabel.hide() + self.annotator_resolution.qspin.hide() + else: + self.annotator_resolution.qlabel.show() + self.annotator_resolution.qspin.show() def cfg_init(self): self.enable.cfg_init() self.low_vram.cfg_init() + self.pixel_perfect.cfg_init() self.preprocessor_layout.cfg_init() self.model_layout.cfg_init() self.weight_layout.cfg_init() @@ -293,6 +308,8 @@ def cfg_init(self): self.threshold_a.cfg_init() self.threshold_b.cfg_init() + self.hide_or_show_preprocessor_resolution(self.pixel_perfect.isChecked()) + if self.preprocessor_layout.qcombo.currentText() == "none": self.annotator_preview_button.setEnabled(False) else: @@ -301,6 +318,7 @@ def cfg_init(self): def cfg_connect(self): self.enable.cfg_connect() self.low_vram.cfg_connect() + self.pixel_perfect.cfg_connect() self.preprocessor_layout.cfg_connect() self.model_layout.cfg_connect() self.weight_layout.cfg_connect() @@ -316,6 +334,9 @@ def cfg_connect(self): self.image_loader.clear_button.released.connect( partial(script.cfg.set, f"controlnet{self.unit}_input_image", "") ) + self.pixel_perfect.stateChanged.connect( + lambda: self.hide_or_show_preprocessor_resolution(self.pixel_perfect.isChecked()) + ) self.preprocessor_layout.qcombo.currentTextChanged.connect(self.set_preprocessor_options) self.preprocessor_layout.qcombo.currentTextChanged.connect( lambda: self.annotator_preview_button.setEnabled(False) if diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index b5465447..84beec1e 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -459,7 +459,7 @@ def cb(response): pixmap = QPixmap.fromImage(b64_to_img(output)) self.controlnet_preview_annotator_received.emit(pixmap) - self.client.post_controlnet_preview(cb, image) + self.client.post_controlnet_preview(cb, image, self.width, self.height) def apply_simple_upscale(self): insert, _ = self.img_inserter(self.x, self.y, self.width, self.height) From 72a4af52f99b1b2defa8fdc517d062d03b42d765 Mon Sep 17 00:00:00 2001 From: drhead Date: Tue, 13 Jun 2023 15:10:16 -0400 Subject: [PATCH 53/55] Fix handling of no selection in inpaint --- frontends/krita/krita_diff/script.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index e4e313e7..81546a63 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -193,7 +193,7 @@ def get_mask_image(self, using_official_api) -> Union[QImage, None]: return mask.rgbSwapped() - def img_inserter(self, x, y, width, height, glayer=None): + def img_inserter(self, x, y, width, height, inpaint=False, glayer=None): """Return frozen image inserter to insert images as new layer.""" # Selection may change before callback, so freeze selection region has_selection = self.selection is not None @@ -222,11 +222,11 @@ def insert(layer_name, enc): f"image created: {image}, {image.width()}x{image.height()}, depth: {image.depth()}, format: {image.format()}" ) - # NOTE: Scaling is usually done by backend (although I am reconsidering this) - # The scaling here is for SD Upscale or Upscale on a selection region rather than whole image + # NOTE: Scaling must be done by the frontend when using the official API. + # The scaling here is for SD Upscale, Upscale on a selection region, or inpainting. # Image won't be scaled down ONLY if there is no selection; i.e. selecting whole image will scale down, # not selecting anything won't scale down, leading to the canvas being resized afterwards - if has_selection and (image.width() != width or image.height() != height): + if (has_selection or inpaint) and (image.width() != width or image.height() != height): print(f"Rescaling image to selection: {width}x{height}") image = image.scaled( width, height, transformMode=Qt.SmoothTransformation @@ -286,7 +286,7 @@ def apply_txt2img(self): glayer = self.doc.createGroupLayer("Unnamed Group") self.doc.rootNode().addChildNode(glayer, None) insert = self.img_inserter( - self.x, self.y, self.width, self.height, glayer + self.x, self.y, self.width, self.height, False, glayer ) mask_trigger = self.transparency_mask_inserter() @@ -330,7 +330,7 @@ def apply_img2img(self, is_inpaint): glayer = self.doc.createGroupLayer("Unnamed Group") self.doc.rootNode().addChildNode(glayer, None) insert = self.img_inserter( - self.x, self.y, self.width, self.height, glayer + self.x, self.y, self.width, self.height, is_inpaint, glayer ) path = os.path.join(self.cfg("sample_path", str), f"{int(time.time())}.png") From cc142c2bc84f08beae6df32cbbc9229d8a97316e Mon Sep 17 00:00:00 2001 From: JasonS09 Date: Wed, 14 Jun 2023 19:10:33 -0600 Subject: [PATCH 54/55] Fixed pixel perfect bug --- frontends/krita/krita_diff/client.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frontends/krita/krita_diff/client.py b/frontends/krita/krita_diff/client.py index a7f14e6a..02682117 100644 --- a/frontends/krita/krita_diff/client.py +++ b/frontends/krita/krita_diff/client.py @@ -288,7 +288,7 @@ def official_api_common_params(self, has_selection, width, height, for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))): if self.cfg(f"controlnet{i}_enable", bool): controlnet_units_param.append( - self.controlnet_unit_params(img_to_b64(controlnet_src_imgs[str(i)]), i) + self.controlnet_unit_params(img_to_b64(controlnet_src_imgs[str(i)]), i, width, height) ) else: controlnet_units_param.append({"enabled": False}) @@ -301,14 +301,16 @@ def official_api_common_params(self, has_selection, width, height, return params - def controlnet_unit_params(self, image: str, unit: int): + def controlnet_unit_params(self, image: str, unit: int, width: int, height: int): + preprocessor_resolution = min(width, height) if self.cfg(f"controlnet{unit}_pixel_perfect", bool) \ + else self.cfg(f"controlnet{unit}_preprocessor_resolution", int) params = dict( input_image=image, module=self.cfg(f"controlnet{unit}_preprocessor", str), model=self.cfg(f"controlnet{unit}_model", str), weight=self.cfg(f"controlnet{unit}_weight", float), lowvram=self.cfg(f"controlnet{unit}_low_vram", bool), - processor_res=self.cfg(f"controlnet{unit}_preprocessor_resolution", int), + processor_res=preprocessor_resolution, threshold_a=self.cfg(f"controlnet{unit}_threshold_a", float), threshold_b=self.cfg(f"controlnet{unit}_threshold_b", float), guidance_start=self.cfg(f"controlnet{unit}_guidance_start", float), @@ -674,4 +676,4 @@ def post_interrupt(self, cb): def get_progress(self, cb): # get official API url url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX) - self.get("progress", cb, base_url=url) + self.get("progress", cb, base_url=url) \ No newline at end of file From 0d0e4767e342d08061afe84f2933871c73561f7b Mon Sep 17 00:00:00 2001 From: drhead Date: Wed, 21 Jun 2023 13:17:55 -0400 Subject: [PATCH 55/55] fixed mask positioning bug --- frontends/krita/krita_diff/script.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontends/krita/krita_diff/script.py b/frontends/krita/krita_diff/script.py index 81546a63..9c1c751c 100644 --- a/frontends/krita/krita_diff/script.py +++ b/frontends/krita/krita_diff/script.py @@ -441,8 +441,10 @@ def controlnet_transparency_mask_inserter(self, glayer, mask_image): # must convert mask to single channel format gray_mask = mask_image.convertToFormat(QImage.Format_Grayscale8) + mw = gray_mask.width() + mh = gray_mask.height() # crop mask to the actual selection size - crop_rect = QRect(0, 0, sw, sh) + crop_rect = QRect((mw - sw)/2,(mh - sh)/2, sw, sh) crop_mask = gray_mask.copy(crop_rect) mask_ba = img_to_ba(crop_mask)