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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ This project:
- can automatically install MIT/BSD/Apache2/PythonSoftwareFoundation pip packages: [spandrel](https://pypi.org/project/spandrel/), [dill](https://pypi.org/project/dill/), [imageio-ffmpeg](https://pypi.org/project/imageio-ffmpeg/), [opencv-python-headless](https://pypi.org/project/opencv-python-headless/), [matplotlib](https://pypi.org/project/matplotlib/), [rembg](https://pypi.org/project/rembg/), [kornia](https://pypi.org/project/kornia/), [Cython](https://pypi.org/project/Cython/)
- can automatically install [ultralytics](https://github.com/ultralytics/ultralytics) (AGPL) for `YOLOv8` face detection (ie `SwarmYoloDetection` node or `<segment:yolo-...>` syntax usage may become subject to AGPL terms),
- can automatically install [insightface](https://github.com/deepinsight/insightface) (MIT) for `IP Adapter - Face` support
- uses [JSON.NET](https://github.com/JamesNK/Newtonsoft.Json) (MIT), [FreneticUtilities](https://github.com/FreneticLLC/FreneticUtilities) (MIT), [LiteDB](https://github.com/mbdavid/LiteDB) (MIT), [ImageSharp](https://github.com/SixLabors/ImageSharp/) (Apache2 under open-source Split License), [ATL.NET](https://github.com/Zeugma440/atldotnet) (MIT)
- uses [JSON.NET](https://github.com/JamesNK/Newtonsoft.Json) (MIT), [FreneticUtilities](https://github.com/FreneticLLC/FreneticUtilities) (MIT), [LiteDB](https://github.com/mbdavid/LiteDB) (MIT), [Magick.NET](https://github.com/dlemstra/Magick.NET) (Apache2), [ATL.NET](https://github.com/Zeugma440/atldotnet) (MIT)
- embeds copies of web assets from [BootStrap](https://getbootstrap.com/) (MIT), [Select2](https://select2.org/) (MIT), [JQuery](https://jquery.com/) (MIT), [exif-reader](https://github.com/mattiasw/ExifReader) (MPL-2.0), [music-metadata](https://github.com/Borewit/music-metadata) (MIT).
- contains some icons from [Cristian Munoz](https://www.figma.com/community/file/1311159026125960259/7000-free-ui-icons) (CC-BY-4.0), the font [inter by rsms](https://github.com/rsms/inter) (OFL), [Unifont by GNU](https://unifoundry.com/unifont/) (OFL), [Material Symbols Outlined by Google](https://fonts.google.com/icons) (Apache2).
- can be used to install some custom node packs, which have individual license notices for any non-pure-FOSS licenses before install.
Expand Down
2 changes: 1 addition & 1 deletion docs/Troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ That is the build dependency service, connections to it are required for the fir

If you see errors like:
- `The type or namespace name 'Hardware' could not be found (are you missing a using directive or an assembly reference?) [src/SwarmUI.csproj]`, noting the mention of `Hardware` specifically (this is a NuGet package)
- `The type or namespace name 'Image<>' does not exist in the namespace 'SixLabors.ImageSharp'`
- `The type or namespace name 'MagickImage' does not exist in the namespace 'ImageMagick'`

that might mean your NuGet cache is corrupt. This is rare, but can happen. In that case, you must reset your NuGet cache.

Expand Down
2 changes: 1 addition & 1 deletion src/Accounts/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ public void Dispose()
File.WriteAllBytes(fullPath, actualFile.RawData);
if ((User.Settings.FileFormat.SaveTextFileMetadata || extension == "webp" || !OutputMetadataTracker.ExtensionsWithMetadata.Contains(extension)) && !string.IsNullOrWhiteSpace(metadata))
{
if (extension == "webp" && actualFile is ImageFile imageFile && imageFile.ToIS.Frames.Count == 1)
if (extension == "webp" && actualFile is ImageFile imageFile && imageFile.ToMagickFrames.Count == 1)
{
// no .json write for still-image webps
}
Expand Down
36 changes: 14 additions & 22 deletions src/Accounts/UserImageHistoryHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
using ATL;
using FreneticUtilities.FreneticExtensions;
using FreneticUtilities.FreneticToolkit;
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.Processing;
using ImageMagick;
using ImageMagick.Drawing;
using SwarmUI.Core;
using SwarmUI.Media;
using SwarmUI.Utils;
Expand Down Expand Up @@ -91,29 +89,23 @@ public static async Task DoFfmpegPreviewGeneration(string file)
double durationMs = new Track(file).DurationMs;
long totalSeconds = double.IsFinite(durationMs) ? Math.Max(0, (long)(durationMs / 1000)) : 0;
string duration = totalSeconds >= 3600 ? $"{totalSeconds / 3600:00}:{totalSeconds / 60 % 60:00}:{totalSeconds % 60:00}" : $"{totalSeconds / 60:00}:{totalSeconds % 60:00}";
FontCollection fonts = new();
Font font = fonts.Add("src/wwwroot/fonts/Inter.woff2").CreateFont(32, FontStyle.Bold);
using SixLabors.ImageSharp.Image preview = SixLabors.ImageSharp.Image.Load(tempPreviewPath);
RichTextOptions textOptions = new(font) { Origin = new(128, 128), HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center };
Brush outlineBrush = Brushes.Solid(Color.FromRgb(39, 39, 42));
Brush textBrush = Brushes.Solid(Color.FromRgb(228, 228, 228));
preview.Mutate(m =>
using MagickImage preview = new(tempPreviewPath);
IDrawables<ushort> text = new Drawables().Font("src/wwwroot/fonts/Inter.woff2", FontStyleType.Normal, FontWeight.Bold, FontStretch.Normal).FontPointSize(32).TextAlignment(TextAlignment.Center);
ITypeMetric metrics = text.FontTypeMetrics($"Audio {duration}");
double baseline = 128 + (metrics.Ascent + metrics.Descent) / 2;
text.FillColor(new MagickColor("#27272a"));
for (int x = -2; x <= 2; x += 2)
{
for (int x = -2; x <= 2; x += 2)
for (int y = -2; y <= 2; y += 2)
{
for (int y = -2; y <= 2; y += 2)
if (x != 0 || y != 0)
{
if (x != 0 || y != 0)
{
textOptions.Origin = new(128 + x, 128 + y);
m.DrawText(textOptions, $"Audio {duration}", outlineBrush);
}
text.Text(128 + x, baseline + y, $"Audio {duration}");
}
}
textOptions.Origin = new(128, 128);
m.DrawText(textOptions, $"Audio {duration}", textBrush);
});
preview.SaveAsJpeg(previewPath);
}
text.FillColor(new MagickColor("#e4e4e4")).Text(128, baseline, $"Audio {duration}").Draw(preview);
preview.Write(previewPath, MagickFormat.Jpeg);
}
finally
{
Expand Down
181 changes: 97 additions & 84 deletions src/BuiltinExtensions/GridGenerator/GridGeneratorExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,10 @@
using SwarmUI.Utils;
using SwarmUI.WebAPI;
using System.Net.WebSockets;
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Drawing.Processing;
using ImageMagick;
using ImageMagick.Drawing;

using static SwarmUI.Builtin_GridGeneratorExtension.GridGenCore;
using Image = SwarmUI.Utils.Image;
using ISImage = SixLabors.ImageSharp.Image;
using ISImageRGBA = SixLabors.ImageSharp.Image<SixLabors.ImageSharp.PixelFormats.Rgba32>;

namespace SwarmUI.Builtin_GridGeneratorExtension;

Expand Down Expand Up @@ -444,17 +439,9 @@ public async Task<JObject> GridGenDoesExist(Session session, string folderName)
return new JObject() { ["exists"] = exists };
}

public static FontCollection MainFontCollection;
public static ConcurrentDictionary<float, Font> Fonts = new();

public static Font GetFont(float sizeMult)
public static IDrawables<ushort> GetFont(float sizeMult)
{
if (MainFontCollection is null)
{
MainFontCollection = new();
MainFontCollection.Add("src/wwwroot/fonts/unifont-12.0.01.woff2");
}
return Fonts.GetOrCreate(sizeMult, () => MainFontCollection.Families.First().CreateFont(16 * sizeMult, FontStyle.Bold));
return new Drawables().Font("src/wwwroot/fonts/unifont-12.0.01.woff2", FontStyleType.Normal, FontWeight.Bold, FontStretch.Normal).FontPointSize(16 * sizeMult);
}

public async Task<JObject> GridGenRun(WebSocket socket, Session session, JObject raw, string outputFolderName, bool doOverwrite, bool fastSkip, bool generatePage, bool publishGenMetadata, bool dryRun, bool weightOrder, string outputType, bool continueOnError, bool showOutputs)
Expand Down Expand Up @@ -516,103 +503,129 @@ async Task sendStatus()
List<(string, string)> xAxis = [.. grid.Axes[0].Values.Where(v => !v.Skip).Select(proc)];
List<(string, string)> yAxis = grid.Axes.Count > 1 ? [.. grid.Axes[1].Values.Where(v => !v.Skip).Select(proc)] : [(null, null)];
List<(string, string)> y2Axis = grid.Axes.Count > 2 ? [.. grid.Axes[2].Values.Where(v => !v.Skip).Select(proc)] : [(null, null)];
int maxWidth = images.Max(x => x.ToIS.Width);
int maxHeight = images.Max(x => x.ToIS.Height);
int maxWidth = (int)images.Max(x => x.ToMagick.Width);
int maxHeight = (int)images.Max(x => x.ToMagick.Height);
float extraSizeMult = 1;
if (maxWidth > 800 || maxHeight > 800)
{
extraSizeMult = 2;
}
Font font = GetFont(extraSizeMult);
TextOptions options = new(font);
FontRectangle rect = TextMeasurer.MeasureSize("ABCdefg Word Prefix", options);
int textWidth = (int)Math.Ceiling(rect.Width);
int rawTextHeight = (int)Math.Ceiling(rect.Height * 1.1);
IDrawables<ushort> font = GetFont(extraSizeMult);
ITypeMetric rect = font.FontTypeMetrics("ABCdefg Word Prefix");
int textWidth = (int)Math.Ceiling(rect.TextWidth);
int rawTextHeight = (int)Math.Ceiling(rect.TextHeight * 1.1);
int textHeight = rawTextHeight * 2;
int totalWidth = maxWidth * xAxis.Count + textWidth;
int totalHeight = maxHeight * (yAxis.Count * y2Axis.Count) + textHeight * y2Axis.Count;
Logs.Info($"Will generate grid image of size {totalWidth}x{totalHeight}");
ISImageRGBA gridImg = new(totalWidth, totalHeight);
gridImg.Mutate(m =>
using MagickImage gridImg = new(MagickColors.White, (uint)totalWidth, (uint)totalHeight) { Depth = 8 };
void DrawTextAutoScale(string text, float x, float y, float width, float height)
{
Brush brush = new SolidBrush(Color.Black);
void DrawTextAutoScale(string text, float x, float y, float width, float height)
if (text.Length == 0)
{
if (text.Length > 500)
{
text = $"{text[..500]}...";
}
RichTextOptions rto = new(font) { WrappingLength = width, Origin = new(x, y) };
float lines = height / rawTextHeight;
FontRectangle measured = TextMeasurer.MeasureSize(text, options);
Logs.Verbose($"Measured text '{text}' as {measured.Width}x{measured.Height} in {width}x{height} with {lines} lines");
if (measured.Width < width * lines * 0.5)
{
rto.Font = GetFont(2 * extraSizeMult);
}
else if (measured.Width > width * lines * 2)
{
rto.Font = GetFont(0.5f * extraSizeMult);
}
else if (measured.Width > width * lines)
{
rto.Font = GetFont(0.75f * extraSizeMult);
}
m.DrawText(rto, text, brush);
return;
}
m.Fill(Color.White);
int xIndex = 0;
float yIndex = 0;
foreach ((string x, _) in xAxis)
if (text.Length > 500)
{
DrawTextAutoScale(x, xIndex * maxWidth + textWidth, 0, maxWidth, textHeight);
xIndex++;
text = $"{text[..500]}...";
}
foreach ((string y2, _) in y2Axis)
float lines = height / rawTextHeight;
ITypeMetric measured = font.FontTypeMetrics(text);
Logs.Verbose($"Measured text '{text}' as {measured.TextWidth}x{measured.TextHeight} in {width}x{height} with {lines} lines");
float sizeMult = extraSizeMult;
if (measured.TextWidth < width * lines * 0.5)
{
if (y2 is not null)
{
DrawTextAutoScale(y2, 0, yIndex * maxHeight + textHeight, textWidth, maxHeight);
yIndex += textHeight / (float)maxHeight;
}
foreach ((string y, _) in yAxis)
sizeMult *= 2;
}
else if (measured.TextWidth > width * lines * 2)
{
sizeMult *= 0.5f;
}
else if (measured.TextWidth > width * lines)
{
sizeMult *= 0.75f;
}
IDrawables<ushort> labelFont = GetFont(sizeMult);
IDrawables<ushort> label = GetFont(sizeMult).FillColor(MagickColors.Black);
ITypeMetric lineMetrics = labelFont.FontTypeMetrics("Ag");
double baseline = y + lineMetrics.Ascent;
foreach (string paragraph in text.Replace("\r", "").Split('\n'))
{
string line = "";
foreach (Rune character in paragraph.EnumerateRunes())
{
if (y is not null)
string candidate = line + character;
if (line.Length > 0 && labelFont.FontTypeMetrics(candidate).TextWidth > width)
{
int space = line.LastIndexOf(' ');
label.Text(x, baseline, space > 0 ? line[..space] : line);
baseline += lineMetrics.TextHeight;
line = (space > 0 ? line[(space + 1)..] : "") + character;
}
else
{
DrawTextAutoScale(y, 0, yIndex * maxHeight + textHeight * 2, textWidth, maxHeight);
line = candidate;
}
yIndex++;
}
if (line.Length > 0)
{
label.Text(x, baseline, line);
}
baseline += lineMetrics.TextHeight;
}
yIndex = 0;
foreach ((_, string y2) in y2Axis)
label.Draw(gridImg);
}
int xIndex = 0;
float yIndex = 0;
foreach ((string x, _) in xAxis)
{
DrawTextAutoScale(x, xIndex * maxWidth + textWidth, 0, maxWidth, textHeight);
xIndex++;
}
foreach ((string y2, _) in y2Axis)
{
if (y2 is not null)
{
if (y2 is not null)
DrawTextAutoScale(y2, 0, yIndex * maxHeight + textHeight, textWidth, maxHeight);
yIndex += textHeight / (float)maxHeight;
}
foreach ((string y, _) in yAxis)
{
if (y is not null)
{
yIndex += textHeight / (float)maxHeight;
DrawTextAutoScale(y, 0, yIndex * maxHeight + textHeight * 2, textWidth, maxHeight);
}
foreach ((_, string y) in yAxis)
yIndex++;
}
}
yIndex = 0;
foreach ((_, string y2) in y2Axis)
{
if (y2 is not null)
{
yIndex += textHeight / (float)maxHeight;
}
foreach ((_, string y) in yAxis)
{
xIndex = 0;
foreach ((_, string x) in xAxis)
{
xIndex = 0;
foreach ((_, string x) in xAxis)
string imgPath = x;
if (y is not null)
{
string imgPath = x;
if (y is not null)
imgPath = $"{imgPath}/{y}";
if (y2 is not null)
{
imgPath = $"{imgPath}/{y}";
if (y2 is not null)
{
imgPath = $"{imgPath}/{y2}";
}
imgPath = $"{imgPath}/{y2}";
}
ISImage img = (data.GeneratedOutputs[imgPath] as ImageFile).ToIS;
m.DrawImage(img, new Point(xIndex * maxWidth + textWidth, (int)(yIndex * maxHeight + textHeight)), 1);
xIndex++;
}
yIndex++;
IMagickImage<ushort> img = (data.GeneratedOutputs[imgPath] as ImageFile).ToMagick;
gridImg.Composite(img, xIndex * maxWidth + textWidth, (int)(yIndex * maxHeight + textHeight), CompositeOperator.Over);
xIndex++;
}
yIndex++;
}
});
}
Logs.Info("Generated, saving...");
Image outImg = new(gridImg);
int batchId = (xAxis.Count * yAxis.Count * y2Axis.Count) + 1;
Expand Down
Loading
Loading