diff --git a/invokeai/app/invocations/image.py b/invokeai/app/invocations/image.py
index 1d5ba44b24c..d4a1977319f 100644
--- a/invokeai/app/invocations/image.py
+++ b/invokeai/app/invocations/image.py
@@ -21,7 +21,7 @@
WithBoard,
WithMetadata,
)
-from invokeai.app.invocations.primitives import ImageOutput
+from invokeai.app.invocations.primitives import ImageOutput, StringOutput
from invokeai.app.services.image_records.image_records_common import ImageCategory
from invokeai.app.services.shared.invocation_context import InvocationContext
from invokeai.app.util.misc import SEED_MAX
@@ -581,6 +581,25 @@ def invoke(self, context: InvocationContext) -> ImageOutput:
return ImageOutput.build(image_dto)
+@invocation(
+ "decode_watermark",
+ title="Decode Invisible Watermark",
+ tags=["image", "watermark"],
+ category="image",
+ version="1.0.0",
+)
+class DecodeInvisibleWatermarkInvocation(BaseInvocation):
+ """Decode an invisible watermark from an image."""
+
+ image: ImageField = InputField(description="The image to decode the watermark from")
+ length: int = InputField(default=8, description="The expected watermark length in bytes")
+
+ def invoke(self, context: InvocationContext) -> StringOutput:
+ image = context.images.get_pil(self.image.image_name)
+ watermark = InvisibleWatermark.decode_watermark(image, self.length)
+ return StringOutput(value=watermark)
+
+
@invocation(
"mask_edge",
title="Mask Edge",
diff --git a/invokeai/backend/image_util/invisible_watermark.py b/invokeai/backend/image_util/invisible_watermark.py
index 5b0b2dbb5b1..95c483848cc 100644
--- a/invokeai/backend/image_util/invisible_watermark.py
+++ b/invokeai/backend/image_util/invisible_watermark.py
@@ -9,7 +9,7 @@
from PIL import Image
import invokeai.backend.util.logging as logger
-from invokeai.backend.image_util.imwatermark.vendor import WatermarkEncoder
+from invokeai.backend.image_util.imwatermark.vendor import WatermarkDecoder, WatermarkEncoder
class InvisibleWatermark:
@@ -25,3 +25,25 @@ def add_watermark(cls, image: Image.Image, watermark_text: str) -> Image.Image:
encoder.set_watermark("bytes", watermark_text.encode("utf-8"))
bgr_encoded = encoder.encode(bgr, "dwtDct")
return Image.fromarray(cv2.cvtColor(bgr_encoded, cv2.COLOR_BGR2RGB)).convert("RGBA")
+
+ @classmethod
+ def decode_watermark(cls, image: Image.Image, length: int = 8) -> str:
+ """Attempt to decode an invisible watermark from an image.
+
+ Args:
+ image: The PIL Image to decode the watermark from.
+ length: The expected watermark length in bytes. Must match the length used when encoding.
+ The WatermarkDecoder requires the length in bits; this value is multiplied by 8 internally.
+
+ Returns:
+ The decoded watermark text, or an empty string if no watermark is detected or decoding fails.
+ """
+ logger.debug("Attempting to decode invisible watermark")
+ try:
+ bgr = cv2.cvtColor(np.array(image.convert("RGB")), cv2.COLOR_RGB2BGR)
+ decoder = WatermarkDecoder("bytes", length * 8)
+ watermark_bytes = decoder.decode(bgr, "dwtDct")
+ return watermark_bytes.decode("utf-8", errors="ignore").rstrip("\x00")
+ except Exception:
+ logger.debug("Failed to decode invisible watermark")
+ return ""
diff --git a/invokeai/frontend/web/public/locales/fi.json b/invokeai/frontend/web/public/locales/fi.json
index f03c6f1aa1e..54e5a666605 100644
--- a/invokeai/frontend/web/public/locales/fi.json
+++ b/invokeai/frontend/web/public/locales/fi.json
@@ -4,7 +4,8 @@
"uploadImage": "Lataa kuva",
"invokeProgressBar": "Invoken edistymispalkki",
"nextImage": "Seuraava kuva",
- "previousImage": "Edellinen kuva"
+ "previousImage": "Edellinen kuva",
+ "uploadImages": "Lähetä Kuva(t)"
},
"common": {
"languagePickerLabel": "Kielen valinta",
@@ -29,5 +30,28 @@
"galleryImageSize": "Kuvan koko",
"gallerySettings": "Gallerian asetukset",
"autoSwitchNewImages": "Vaihda uusiin kuviin automaattisesti"
+ },
+ "modelManager": {
+ "t5Encoder": "T5-kooderi",
+ "qwen3Encoder": "Qwen3-kooderi",
+ "zImageVae": "VAE (valinnainen)",
+ "zImageQwen3Encoder": "Qwen3-kooderi (valinnainen)",
+ "zImageQwen3SourcePlaceholder": "Pakollinen, jos VAE/Enkooderi on tyhjä",
+ "flux2KleinVae": "VAE (valinnainen)",
+ "flux2KleinQwen3Encoder": "Qwen3-kooderi (valinnainen)"
+ },
+ "auth": {
+ "login": {
+ "title": "Kirjaudu sisään InvokeAI:hin",
+ "password": "Salasana",
+ "passwordPlaceholder": "Salasana",
+ "signIn": "Kirjaudu sisään",
+ "signingIn": "Kirjaudutaan sisään...",
+ "loginFailed": "Kirjautuminen epäonnistui. Tarkista käyttäjätunnuksesi tiedot."
+ },
+ "setup": {
+ "title": "Tervetuloa InvokeAI:hin",
+ "subtitle": "Määritä ensimmäiseksi järjestelmänvalvojan tili"
+ }
}
}
diff --git a/invokeai/frontend/web/public/locales/it.json b/invokeai/frontend/web/public/locales/it.json
index d17d36d5c0b..7a6dafe4c7f 100644
--- a/invokeai/frontend/web/public/locales/it.json
+++ b/invokeai/frontend/web/public/locales/it.json
@@ -3139,6 +3139,11 @@
"back": "Indietro",
"cannotDeleteSelf": "Non puoi eliminare il tuo account",
"cannotDeactivateSelf": "Non puoi disattivare il tuo account"
+ },
+ "passwordStrength": {
+ "weak": "Password debole",
+ "moderate": "Password moderata",
+ "strong": "Password forte"
}
}
}
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsBooleanSubMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsBooleanSubMenu.tsx
index c321317a34e..19b02783532 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsBooleanSubMenu.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsBooleanSubMenu.tsx
@@ -9,7 +9,8 @@ import { rasterLayerGlobalCompositeOperationChanged } from 'features/controlLaye
import type { CanvasEntityIdentifier, CompositeOperation } from 'features/controlLayers/store/types';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
-import { CgPathBack, CgPathCrop, CgPathExclude, CgPathFront, CgPathIntersect } from 'react-icons/cg';
+import { CgPathBack, CgPathExclude, CgPathFront, CgPathIntersect } from 'react-icons/cg';
+import { PiIntersectSquareBold } from 'react-icons/pi';
export const RasterLayerMenuItemsBooleanSubMenu = memo(() => {
const { t } = useTranslation();
@@ -48,7 +49,7 @@ export const RasterLayerMenuItemsBooleanSubMenu = memo(() => {
const disabled = isBusy || !entityIdentifierBelowThisOne;
return (
- }>
+ }>