mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-19 14:53:50 +02:00
feat: Port ImageSaving to ImageSharp to fix Linux image uploads (#1131)
This commit is contained in:
@@ -1,7 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
|
using SixLabors.ImageSharp.Formats.Png;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using Color = SixLabors.ImageSharp.Color;
|
||||||
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
|
using Point = SixLabors.ImageSharp.Point;
|
||||||
|
using Rectangle = SixLabors.ImageSharp.Rectangle;
|
||||||
|
using Size = SixLabors.ImageSharp.Size;
|
||||||
|
|
||||||
namespace VRCX
|
namespace VRCX
|
||||||
{
|
{
|
||||||
@@ -17,14 +26,15 @@ namespace VRCX
|
|||||||
return Convert.ToBase64String(ResizeImageToFitLimits(Convert.FromBase64String(base64data), false));
|
return Convert.ToBase64String(ResizeImageToFitLimits(Convert.FromBase64String(base64data), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] ResizeImageToFitLimits(byte[] imageData, bool matchingDimensions, int maxWidth = 2000, int maxHeight = 2000, long maxSize = 10_000_000)
|
public byte[] ResizeImageToFitLimits(byte[] imageData, bool matchingDimensions, int maxWidth = 2000,
|
||||||
|
int maxHeight = 2000, long maxSize = 10_000_000)
|
||||||
{
|
{
|
||||||
using var fileMemoryStream = new MemoryStream(imageData);
|
using var fileMemoryStream = new MemoryStream(imageData);
|
||||||
var image = new Bitmap(fileMemoryStream);
|
var image = Image.Load(fileMemoryStream);
|
||||||
|
|
||||||
// for APNG, check if image is png format and less than maxSize
|
// for APNG, check if image is png format and less than maxSize
|
||||||
if ((!matchingDimensions || image.Width == image.Height) &&
|
if ((!matchingDimensions || image.Width == image.Height) &&
|
||||||
image.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Png) &&
|
image.Metadata.DecodedImageFormat == PngFormat.Instance &&
|
||||||
imageData.Length < maxSize &&
|
imageData.Length < maxSize &&
|
||||||
image.Width <= maxWidth &&
|
image.Width <= maxWidth &&
|
||||||
image.Height <= maxHeight)
|
image.Height <= maxHeight)
|
||||||
@@ -32,31 +42,38 @@ namespace VRCX
|
|||||||
return imageData;
|
return imageData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: I think these are aspect ratio preserving calcs, but we can ask ImageSharp nicely to do this by
|
||||||
|
// passing 0, see docs for Resize()
|
||||||
if (image.Width > maxWidth)
|
if (image.Width > maxWidth)
|
||||||
{
|
{
|
||||||
var sizingFactor = image.Width / (double)maxWidth;
|
var sizingFactor = image.Width / (double)maxWidth;
|
||||||
var newHeight = (int)Math.Round(image.Height / sizingFactor);
|
var newHeight = (int)Math.Round(image.Height / sizingFactor);
|
||||||
image = new Bitmap(image, maxWidth, newHeight);
|
image.Mutate(x => x.Resize(maxWidth, newHeight));
|
||||||
}
|
}
|
||||||
if (image.Height > maxHeight)
|
if (image.Height > maxHeight)
|
||||||
{
|
{
|
||||||
var sizingFactor = image.Height / (double)maxHeight;
|
var sizingFactor = image.Height / (double)maxHeight;
|
||||||
var newWidth = (int)Math.Round(image.Width / sizingFactor);
|
var newWidth = (int)Math.Round(image.Width / sizingFactor);
|
||||||
image = new Bitmap(image, newWidth, maxHeight);
|
image.Mutate(x => x.Resize(newWidth, maxHeight));
|
||||||
}
|
}
|
||||||
if (matchingDimensions && image.Width != image.Height)
|
if (matchingDimensions && image.Width != image.Height)
|
||||||
{
|
{
|
||||||
var newSize = Math.Max(image.Width, image.Height);
|
var newSize = Math.Max(image.Width, image.Height);
|
||||||
var newImage = new Bitmap(newSize, newSize);
|
using Image<Rgba32> resizedImage = new(newSize, newSize);
|
||||||
using var graphics = Graphics.FromImage(newImage);
|
// regalialong: i think the access should be safe
|
||||||
graphics.Clear(Color.Transparent);
|
// ReSharper disable AccessToModifiedClosure
|
||||||
graphics.DrawImage(image, new Rectangle((newSize - image.Width) / 2, (newSize - image.Height) / 2, image.Width, image.Height));
|
// ReSharper disable AccessToDisposedClosure
|
||||||
|
resizedImage.Mutate(x => x.DrawImage(image,
|
||||||
|
new Rectangle((newSize - image.Width) / 2, (newSize - image.Height) / 2, image.Width, image.Height),
|
||||||
|
0));
|
||||||
|
// ReSharper restore AccessToDisposedClosure
|
||||||
|
// ReSharper restore AccessToModifiedClosure
|
||||||
image.Dispose();
|
image.Dispose();
|
||||||
image = newImage;
|
image = resizedImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveToFileToUpload();
|
SaveToFileToUpload();
|
||||||
for (int i = 0; i < 250 && imageData.Length > maxSize; i++)
|
for (var i = 0; i < 250 && imageData.Length > maxSize; i++)
|
||||||
{
|
{
|
||||||
SaveToFileToUpload();
|
SaveToFileToUpload();
|
||||||
if (imageData.Length < maxSize)
|
if (imageData.Length < maxSize)
|
||||||
@@ -74,7 +91,8 @@ namespace VRCX
|
|||||||
newHeight = image.Height - 25;
|
newHeight = image.Height - 25;
|
||||||
newWidth = (int)Math.Round(image.Width / (image.Height / (double)newHeight));
|
newWidth = (int)Math.Round(image.Width / (image.Height / (double)newHeight));
|
||||||
}
|
}
|
||||||
image = new Bitmap(image, newWidth, newHeight);
|
|
||||||
|
image.Mutate(x => x.Resize(newWidth, newHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageData.Length > maxSize)
|
if (imageData.Length > maxSize)
|
||||||
@@ -82,12 +100,13 @@ namespace VRCX
|
|||||||
throw new Exception("Failed to get image into target filesize.");
|
throw new Exception("Failed to get image into target filesize.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
image.Dispose();
|
||||||
return imageData;
|
return imageData;
|
||||||
|
|
||||||
void SaveToFileToUpload()
|
void SaveToFileToUpload()
|
||||||
{
|
{
|
||||||
using var imageSaveMemoryStream = new MemoryStream();
|
using var imageSaveMemoryStream = new MemoryStream();
|
||||||
image.Save(imageSaveMemoryStream, System.Drawing.Imaging.ImageFormat.Png);
|
image.SaveAsPng(imageSaveMemoryStream);
|
||||||
imageData = imageSaveMemoryStream.ToArray();
|
imageData = imageSaveMemoryStream.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,10 +117,9 @@ namespace VRCX
|
|||||||
const int desiredHeight = 1080;
|
const int desiredHeight = 1080;
|
||||||
|
|
||||||
using var fileMemoryStream = new MemoryStream(imageData);
|
using var fileMemoryStream = new MemoryStream(imageData);
|
||||||
var image = new Bitmap(fileMemoryStream);
|
var image = Image.Load(fileMemoryStream);
|
||||||
|
|
||||||
if (image.Height > image.Width)
|
if (image.Height > image.Width) image.Mutate(x => x.Rotate(RotateMode.Rotate90));
|
||||||
image.RotateFlip(RotateFlipType.Rotate90FlipNone);
|
|
||||||
|
|
||||||
// increase size to 1920x1080
|
// increase size to 1920x1080
|
||||||
if (image.Width < desiredWidth || image.Height < desiredHeight)
|
if (image.Width < desiredWidth || image.Height < desiredHeight)
|
||||||
@@ -126,12 +144,16 @@ namespace VRCX
|
|||||||
newWidth = testWidth;
|
newWidth = testWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var resizedImage = new Bitmap(desiredWidth, desiredHeight);
|
|
||||||
using var graphics1 = Graphics.FromImage(resizedImage);
|
|
||||||
graphics1.Clear(Color.White);
|
using Image<Rgba32> resizedImage = new(desiredWidth, desiredHeight);
|
||||||
var x = (desiredWidth - newWidth) / 2;
|
resizedImage.Mutate(x
|
||||||
var y = (desiredHeight - newHeight) / 2;
|
// ReSharper disable once AccessToModifiedClosure
|
||||||
graphics1.DrawImage(image, new Rectangle(x, y, newWidth, newHeight));
|
// ReSharper disable once AccessToDisposedClosure
|
||||||
|
=> x.Fill(Color.White).DrawImage(image,
|
||||||
|
new Rectangle((desiredWidth - newWidth) / 2, (desiredHeight - newHeight) / 2, newWidth,
|
||||||
|
newHeight), 0)
|
||||||
|
);
|
||||||
image.Dispose();
|
image.Dispose();
|
||||||
image = resizedImage;
|
image = resizedImage;
|
||||||
}
|
}
|
||||||
@@ -141,31 +163,26 @@ namespace VRCX
|
|||||||
{
|
{
|
||||||
var sizingFactor = image.Width / (double)desiredWidth;
|
var sizingFactor = image.Width / (double)desiredWidth;
|
||||||
var newHeight = (int)Math.Round(image.Height / sizingFactor);
|
var newHeight = (int)Math.Round(image.Height / sizingFactor);
|
||||||
image = new Bitmap(image, desiredWidth, newHeight);
|
image.Mutate(x => x.Resize(desiredWidth, newHeight));
|
||||||
}
|
}
|
||||||
if (image.Height > desiredHeight)
|
if (image.Height > desiredHeight)
|
||||||
{
|
{
|
||||||
var sizingFactor = image.Height / (double)desiredHeight;
|
var sizingFactor = image.Height / (double)desiredHeight;
|
||||||
var newWidth = (int)Math.Round(image.Width / sizingFactor);
|
var newWidth = (int)Math.Round(image.Width / sizingFactor);
|
||||||
image = new Bitmap(image, newWidth, desiredHeight);
|
image.Mutate(x => x.Resize(newWidth, desiredHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
// add white border
|
// add white border
|
||||||
// wtf are these magic numbers
|
// wtf are these magic numbers
|
||||||
const int xOffset = 64; // 2048 / 32
|
const int xOffset = 64; // 2048 / 32
|
||||||
const int yOffset = 69; // 1440 / 20.869
|
const int yOffset = 69; // 1440 / 20.869
|
||||||
var newImage = new Bitmap(2048, 1440);
|
using Image<Rgba32> newImage = new(2048, 1440);
|
||||||
using var graphics = Graphics.FromImage(newImage);
|
newImage.Mutate(x => x.Fill(Color.White));
|
||||||
graphics.Clear(Color.White);
|
|
||||||
// graphics.DrawImage(image, new Rectangle(xOffset, yOffset, image.Width, image.Height));
|
// graphics.DrawImage(image, new Rectangle(xOffset, yOffset, image.Width, image.Height));
|
||||||
var newX = (2048 - image.Width) / 2;
|
var newX = (2048 - image.Width) / 2;
|
||||||
var newY = yOffset;
|
newImage.Mutate(x => x.DrawImage(image, new Rectangle(newX, yOffset, image.Width, image.Height), 0));
|
||||||
graphics.DrawImage(image, new Rectangle(newX, newY, image.Width, image.Height));
|
|
||||||
image.Dispose();
|
|
||||||
image = newImage;
|
|
||||||
|
|
||||||
using var imageSaveMemoryStream = new MemoryStream();
|
using var imageSaveMemoryStream = new MemoryStream();
|
||||||
image.Save(imageSaveMemoryStream, System.Drawing.Imaging.ImageFormat.Png);
|
newImage.SaveAsPng(imageSaveMemoryStream);
|
||||||
return imageSaveMemoryStream.ToArray();
|
return imageSaveMemoryStream.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,17 +201,15 @@ namespace VRCX
|
|||||||
var tempPath = path + ".temp";
|
var tempPath = path + ".temp";
|
||||||
var bytes = await File.ReadAllBytesAsync(path);
|
var bytes = await File.ReadAllBytesAsync(path);
|
||||||
var ms = new MemoryStream(bytes);
|
var ms = new MemoryStream(bytes);
|
||||||
Bitmap print = new Bitmap(ms);
|
var print = await Image.LoadAsync(ms);
|
||||||
// validation step to ensure image is actually a print
|
// validation step to ensure image is actually a print
|
||||||
if (print.Width != 2048 || print.Height != 1440)
|
if (print.Width != 2048 || print.Height != 1440) return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var point = new Point(64, 69);
|
var point = new Point(64, 69);
|
||||||
var size = new Size(1920, 1080);
|
var size = new Size(1920, 1080);
|
||||||
var rectangle = new Rectangle(point, size);
|
var rectangle = new Rectangle(point, size);
|
||||||
Bitmap cropped = print.Clone(rectangle, print.PixelFormat);
|
print.Mutate(x => x.Crop(rectangle));
|
||||||
cropped.Save(tempPath);
|
await print.SaveAsPngAsync(tempPath);
|
||||||
if (ScreenshotHelper.HasTXt(path))
|
if (ScreenshotHelper.HasTXt(path))
|
||||||
{
|
{
|
||||||
var success = ScreenshotHelper.CopyTXt(path, tempPath);
|
var success = ScreenshotHelper.CopyTXt(path, tempPath);
|
||||||
|
|||||||
@@ -94,6 +94,8 @@
|
|||||||
<PackageReference Include="NLog" Version="5.4.0" />
|
<PackageReference Include="NLog" Version="5.4.0" />
|
||||||
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
|
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
|
||||||
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
|
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
|
||||||
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
|
||||||
|
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.5" />
|
||||||
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
||||||
<PackageReference Include="System.Data.SQLite" Version="1.0.119" />
|
<PackageReference Include="System.Data.SQLite" Version="1.0.119" />
|
||||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
||||||
|
|||||||
@@ -100,6 +100,8 @@
|
|||||||
<PackageReference Include="NLog" Version="5.4.0" />
|
<PackageReference Include="NLog" Version="5.4.0" />
|
||||||
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
|
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
|
||||||
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
|
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
|
||||||
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
|
||||||
|
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.5" />
|
||||||
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
||||||
<PackageReference Include="System.Data.SQLite" Version="1.0.119" />
|
<PackageReference Include="System.Data.SQLite" Version="1.0.119" />
|
||||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
||||||
|
|||||||
Reference in New Issue
Block a user