Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions src/BuiltinExtensions/ImageBatchTool/Assets/image_batcher.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@

class ImageBatcherClass {

updateSideLengthModeVisibility() {
let isSideLength = getRequiredElementById('ext_image_batcher_res_mode').value == 'Scale Input To Side Length';
let useSameSideLength = getRequiredElementById('ext_image_batcher_use_same_side_length').checked;
getRequiredElementById('ext_image_batcher_side_length_wrap').style.display = isSideLength ? 'flex' : 'none';
getRequiredElementById('ext_image_batcher_output_side_length_wrap').style.display = isSideLength && !useSameSideLength ? 'block' : 'none';
}

doGenerate() {
resetBatchIfNeeded();
let batch_id = mainGenHandler.getBatchId();
Expand All @@ -12,7 +19,10 @@ class ImageBatcherClass {
'revision': getRequiredElementById('ext_image_batcher_use_as_revision').checked,
'controlnet': getRequiredElementById('ext_image_batcher_use_as_controlnet').checked,
'append_filename_to_prompt': getRequiredElementById('ext_image_batcher_append_filename_to_prompt').checked,
'resMode': getRequiredElementById('ext_image_batcher_res_mode').value
'resMode': getRequiredElementById('ext_image_batcher_res_mode').value,
'use_same_side_length': getRequiredElementById('ext_image_batcher_use_same_side_length').checked,
'input_side_length': parseInt(getRequiredElementById('ext_image_batcher_input_side_length').value) || 1024,
'output_side_length': parseInt(getRequiredElementById('ext_image_batcher_output_side_length').value) || 1024
};
let timeLastGenHit = [Date.now()];
let images = {};
Expand Down Expand Up @@ -40,7 +50,24 @@ class ImageBatcherClass {
+ makeCheckboxInput(null, 'ext_image_batcher_use_as_controlnet', '', 'Use As ControlNet Input', 'Whether to use the image as input to ControlNet (only applies if a ControlNet model is enabled).', true, false, true, true)
+ makeCheckboxInput(null, 'ext_image_batcher_use_as_revision', '', 'Use As Image Prompt', 'Whether to use the image as an Image Prompting input.', false, false, true, true)
+ makeCheckboxInput(null, 'ext_image_batcher_append_filename_to_prompt', '', 'Append Filename to Prompt', 'Whether to append the filename to the prompt.', false, false, true, true)
+ `Resolution: <select id="ext_image_batcher_res_mode"><option>From Parameter</option><option>From Image</option><option>Scale To Model</option><option>Scale To Model Or Above</option></select>`;
+ makeGenericPopover('ext_image_batcher_res_mode', 'Resolution', 'Dropdown', `Choose how the batcher sets generation resolution.<ul><li><b>From Parameter:</b> Keep the current width and height from the main parameter panel.</li><li><b>From Image:</b> Use each input image's current resolution directly.</li><li><b>Scale To Model:</b> Resize the output resolution to fit the selected model's preferred pixel count while keeping aspect ratio.</li><li><b>Scale To Model Or Above:</b> Like Scale To Model, but never shrink below the input image's current size.</li><li><b>Scale Input To Side Length:</b> Resize the input image so its total pixel count approximates side length squared (e.g. ~1024x1024 pixels at side length 1024), maintaining the original aspect ratio.</li></ul>`, '')
+ makeDropdownInput(null, 'ext_image_batcher_res_mode', '', 'Resolution', '', ['From Parameter', 'From Image', 'Scale To Model', 'Scale To Model Or Above', 'Scale Input To Side Length'], 'From Parameter', false, true)
+ `<span id="ext_image_batcher_side_length_wrap" style="display:none;flex-direction:column;align-items:flex-start;">`
+ makeCheckboxInput(null, 'ext_image_batcher_use_same_side_length', '', 'Use same side length for input and output', 'When checked, the output resolution matches the scaled input resolution exactly. When unchecked, the output resolution is independently scaled to the Output Side Length squared, maintaining the same aspect ratio.', true, false, true, true)
+ `<div style="width:100%;max-width:512px;">`
+ makeSliderInput(null, 'ext_image_batcher_input_side_length', '', 'Input Side Length', '', 1024, 64, 4096, 64, 4096, 64, false, false, false)
+ `</div>`
+ `<span id="ext_image_batcher_output_side_length_wrap" style="display:none;width:100%;max-width:512px;">`
+ makeSliderInput(null, 'ext_image_batcher_output_side_length', '', 'Output Side Length', '', 1024, 64, 4096, 64, 4096, 64, false, false, false)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This value makes no sense to have

+ `</span></span>`;
document.getElementById('ext_image_batcher_res_mode').addEventListener('change', () => {
this.updateSideLengthModeVisibility();
});
document.getElementById('ext_image_batcher_use_same_side_length').addEventListener('change', () => {
this.updateSideLengthModeVisibility();
});
enableSlidersIn(this.mainDiv);
this.updateSideLengthModeVisibility();
toolSelector.addEventListener('change', () => {
if (toolSelector.value == 'image_batcher') {
showRevisionInputs();
Expand Down
41 changes: 37 additions & 4 deletions src/BuiltinExtensions/ImageBatchTool/ImageBatchToolExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using System;
using System.IO;
using System.Net.WebSockets;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Processing;
using ISImage = SixLabors.ImageSharp.Image;

namespace SwarmUI.Builtin_ImageBatchToolExtension;
Expand All @@ -30,7 +32,7 @@ public override void OnInit()
}

/// <summary>API route to generate images with WebSocket updates.</summary>
public static async Task<JObject> ImageBatchRun(WebSocket socket, Session session, JObject rawInput, string input_folder, string output_folder, bool init_image, bool revision, bool controlnet, string resMode, bool append_filename_to_prompt)
public static async Task<JObject> ImageBatchRun(WebSocket socket, Session session, JObject rawInput, string input_folder, string output_folder, bool init_image, bool revision, bool controlnet, string resMode, bool append_filename_to_prompt, bool use_same_side_length = true, int input_side_length = 1024, int output_side_length = 1024)
{
// TODO: Strict path validation / user permission confirmation.
if (input_folder.Length < 5 || output_folder.Length < 5)
Expand Down Expand Up @@ -61,17 +63,23 @@ public static async Task<JObject> ImageBatchRun(WebSocket socket, Session sessio
await socket.SendAndReportError($"ImageBatchRun request from {session.User.UserID}, for folder '{input_folder}'", "Image batch needs to supply the images to at least one parameter.", API.WebsocketTimeout);
return null;
}
// In case someone tries to leverage the websocket API directly, not possible from UI
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment shouldn't be here

if (input_side_length <= 0 || output_side_length <= 0)
{
await socket.SendAndReportError($"ImageBatchRun request from {session.User.UserID}", "Side lengths must be positive values.", API.WebsocketTimeout);
return null;
}
Directory.CreateDirectory(output_folder);
await API.RunWebsocketHandlerCallWS(GenBatchRun_Internal, session, (rawInput, input_folder, output_folder, init_image, revision, controlnet, imageFiles, resMode, append_filename_to_prompt), socket);
await API.RunWebsocketHandlerCallWS(GenBatchRun_Internal, session, (rawInput, input_folder, output_folder, init_image, revision, controlnet, imageFiles, resMode, append_filename_to_prompt, use_same_side_length, input_side_length, output_side_length), socket);
Logs.Info("Image Batcher completed successfully");
await socket.SendJson(new JObject() { ["success"] = "complete" }, API.WebsocketTimeout);
return null;
}

public static async Task GenBatchRun_Internal(Session session, (JObject, string, string, bool, bool, bool, string[], string, bool) input, Action<JObject> output, bool isWS)
public static async Task GenBatchRun_Internal(Session session, (JObject, string, string, bool, bool, bool, string[], string, bool, bool, int, int) input, Action<JObject> output, bool isWS)
{
// TODO: This is a silly way of passing data, time for a struct?
(JObject rawInput, string input_folder, string output_folder, bool init_image, bool revision, bool controlnet, string[] imageFiles, string resMode, bool appendFilenameToPrompt) = input;
(JObject rawInput, string input_folder, string output_folder, bool init_image, bool revision, bool controlnet, string[] imageFiles, string resMode, bool appendFilenameToPrompt, bool useSameSideLength, int inputSideLength, int outputSideLength) = input;
using Session.GenClaim claim = session.Claim(gens: imageFiles.Length);
async Task sendStatus()
{
Expand Down Expand Up @@ -129,6 +137,17 @@ void removeDoneTasks()
}
Image image = new(File.ReadAllBytes(file), MediaType.GetByExtension(file.AfterLast('.')));
ISImage imgData = image.ToIS;
// Check EXIF to make sure we have the correct orientation
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, comment should be here

if (imgData.Metadata?.ExifProfile?.TryGetValue(ExifTag.Orientation, out IExifValue<ushort> orientationValue) ?? false)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems out of place to be in this PR

{
ushort orientation = orientationValue.Value;
if (orientation != 1)
{
using ISImage oriented = imgData.Clone(x => x.AutoOrient());
image = new Image(ImageFile.ISImgToPngBytes(oriented), MediaType.ImagePng);
imgData = image.ToIS;
}
}
T2IParamInput param = baseParams.Clone();
void setRes(int width, int height)
{
Expand Down Expand Up @@ -164,6 +183,20 @@ void setRes(int width, int height)
setRes(width, height);
}
break;
case "Scale Input To Side Length":
(int scaledInputWidth, int scaledInputHeight) = Utilities.ResToModelFit(imgData.Width, imgData.Height, inputSideLength * inputSideLength, 16);
image = (Image)((ImageFile)image).Resize(scaledInputWidth, scaledInputHeight);
imgData = image.ToIS;
if (useSameSideLength)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this boolean and code around it make no sense

{
setRes(scaledInputWidth, scaledInputHeight);
}
else
{
(int scaledOutputWidth, int scaledOutputHeight) = Utilities.ResToModelFit(imgData.Width, imgData.Height, outputSideLength * outputSideLength, 16);
setRes(scaledOutputWidth, scaledOutputHeight);
}
break;
default:
throw new SwarmUserErrorException("Invalid resolution mode");
}
Expand Down
Loading