Files
PCK-Studio/PckStudio.Core/Extensions/ImageExtensions.cs
MayNL dcb7624ccc Create ImageCollectionExtension
-Added basic Insert method for ImageCollections
2026-04-14 19:09:56 -04:00

285 lines
13 KiB
C#

/* Copyright (c) 2023-present miku-666
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1.The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace PckStudio.Core.Extensions
{
public static class ImageExtensions
{
public static Image ReleaseFromFile(this Image image)
{
Image img = new Bitmap(image);
image.Dispose();
return img;
}
public static Image GetArea(this Image source, Rectangle area)
{
Image tileImage = new Bitmap(area.Width, area.Height);
using (Graphics gfx = Graphics.FromImage(tileImage))
{
gfx.SmoothingMode = SmoothingMode.None;
gfx.InterpolationMode = InterpolationMode.NearestNeighbor;
gfx.PixelOffsetMode = PixelOffsetMode.HighQuality;
gfx.DrawImage(source, new Rectangle(Point.Empty, area.Size), area, GraphicsUnit.Pixel);
}
return tileImage;
}
/// <summary>
/// Creates an IEnumerable by reading in horizontal order
/// </summary>
/// <param name="source">this image</param>
/// <param name="scalar">Indecates width and height of image sub section</param>
/// <returns><see cref="IEnumerable{Image}"/> of type <see cref="Image"/></returns>
public static IEnumerable<Image> SplitHorizontal(this Image source, int scalar)
{
return source.Split(scalar, ImageLayoutDirection.Horizontal);
}
public static IEnumerable<Image> Split(this Image source, int scalar, ImageLayoutDirection layoutDirection)
{
return Split(source, new Size(scalar, scalar), layoutDirection);
}
public static IEnumerable<Image> Split(this Image source, Size size, ImageLayoutDirection imageLayout)
{
int rowCount = source.Width / size.Width;
int columnCount = source.Height / size.Height;
Debug.WriteLine($"Image size: {source.Size}, Area size: {size}, col num: {columnCount}, row num: {rowCount}");
bool vertical = imageLayout == ImageLayoutDirection.Vertical;
for (int i = 0; i < columnCount * rowCount; i++)
{
int column = Math.DivRem(i, rowCount, out int row);
if (vertical)
row = Math.DivRem(i, columnCount, out column);
Rectangle tileArea = new Rectangle(new Point(row * size.Width, column * size.Height), size);
yield return source.GetArea(tileArea);
}
yield break;
}
public static IEnumerable<Image> Split(this Image source, ImageLayoutDirection layoutDirection)
{
for (int i = 0; i < source.Height / source.Width; i++)
{
ImageSection locationInfo = new ImageSection(source.Size, i, layoutDirection);
yield return source.GetArea(locationInfo.Area);
}
yield break;
}
public static Image Combine(this IEnumerable<Image> sources, ImageLayoutDirection layoutDirection)
{
bool horizontal = layoutDirection == ImageLayoutDirection.Horizontal;
int imgCount = sources.Count();
int rows = horizontal ? imgCount : 1;
int columns = horizontal ? 1 : imgCount;
return sources.Combine(rows, columns, layoutDirection);
}
public static Image Combine(this IEnumerable<Image> sources, int rows, int columns, ImageLayoutDirection layoutDirection)
{
Size imageSize = CalculateImageSize(sources, rows, columns);
var image = new Bitmap(imageSize.Width, imageSize.Height);
bool horizontal = layoutDirection == ImageLayoutDirection.Horizontal;
using (var graphic = Graphics.FromImage(image))
{
foreach ((int i, Image texture) in sources.enumerate())
{
int x = Math.DivRem(i, columns, out int y);
if (horizontal)
y = Math.DivRem(i, rows, out x);
graphic.DrawImage(texture, new Point(x * texture.Width, y * texture.Height));
}
}
return image;
}
private static Size CalculateImageSize(IEnumerable<Image> sources, int rows, int columns)
{
if (sources == null)
return Size.Empty;
Image[] imgs = sources.ToArray();
if (imgs.Length < rows * columns)
throw new ArgumentOutOfRangeException("Insufficent soure images provided.");
Size size = imgs[0].Size;
if (!imgs.All(img => img.Size == size))
throw new InvalidOperationException("Images must have the same width and height.");
size.Width *= rows;
size.Height *= columns;
return size;
}
public static Image Resize(this Image image, Size size, GraphicsConfig graphicsConfig)
{
return image.Resize(size.Width, size.Height, graphicsConfig);
}
public static Image Resize(this Image image, int width, int height, GraphicsConfig graphicsConfig)
{
var destRect = new Rectangle(0, 0, width, height);
var destImage = new Bitmap(width, height);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.ApplyConfig(graphicsConfig);
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}
public static Image Blend(this Image image, Color overlayColor, BlendMode mode)
{
if (image is not Bitmap baseImage)
return image;
Bitmap bitmapResult = new Bitmap(baseImage.Width, baseImage.Height, PixelFormat.Format32bppArgb);
BitmapData baseImageData = baseImage.LockBits(new Rectangle(Point.Empty, baseImage.Size),
ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
BitmapData resultImageData = bitmapResult.LockBits(new Rectangle(Point.Empty, bitmapResult.Size),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Parallel.For(0, baseImageData.Stride * baseImageData.Height / 4, (i) =>
{
int k = i * 4;
unsafe
{
int color = Unsafe.Read<int>((baseImageData.Scan0 + k).ToPointer());
byte a = (byte)(color >> 24 & 0xff);
if (a == 0)
{
Unsafe.Write((resultImageData.Scan0 + k).ToPointer(), 0);
return;
}
var b = ColorExtensions.BlendValues((byte)(color >> 0 & 0xff), overlayColor.B, mode);
var g = ColorExtensions.BlendValues((byte)(color >> 8 & 0xff), overlayColor.G, mode);
var r = ColorExtensions.BlendValues((byte)(color >> 16 & 0xff), overlayColor.R, mode);
int blendedValue = a << 24 | r << 16 | g << 8 | b;
Unsafe.Write((resultImageData.Scan0 + k).ToPointer(), blendedValue);
}
});
bitmapResult.UnlockBits(resultImageData);
baseImage.UnlockBits(baseImageData);
return bitmapResult;
}
public static Image Blend(this Image image, Image overlay, BlendMode mode)
{
if (image is not Bitmap baseImage || overlay is not Bitmap overlayImage ||
image.Width != overlay.Width || image.Height != overlay.Height)
return image;
BitmapData baseImageData = baseImage.LockBits(new Rectangle(Point.Empty, baseImage.Size),
ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
byte[] baseImageBuffer = new byte[baseImageData.Stride * baseImageData.Height];
Marshal.Copy(baseImageData.Scan0, baseImageBuffer, 0, baseImageBuffer.Length);
BitmapData overlayImageData = overlayImage.LockBits(new Rectangle(Point.Empty, overlayImage.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] overlayImageBuffer = new byte[overlayImageData.Stride * overlayImageData.Height];
Marshal.Copy(overlayImageData.Scan0, overlayImageBuffer, 0, overlayImageBuffer.Length);
for (int k = 0; k < baseImageBuffer.Length && k < overlayImageBuffer.Length; k += 4)
{
baseImageBuffer[k + 0] = ColorExtensions.BlendValues(baseImageBuffer[k + 0], overlayImageBuffer[k + 0], mode);
baseImageBuffer[k + 1] = ColorExtensions.BlendValues(baseImageBuffer[k + 1], overlayImageBuffer[k + 1], mode);
baseImageBuffer[k + 2] = ColorExtensions.BlendValues(baseImageBuffer[k + 2], overlayImageBuffer[k + 2], mode);
}
Bitmap bitmapResult = new Bitmap(baseImage.Width, baseImage.Height, PixelFormat.Format32bppArgb);
BitmapData resultImageData = bitmapResult.LockBits(new Rectangle(Point.Empty, bitmapResult.Size),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(baseImageBuffer, 0, resultImageData.Scan0, baseImageBuffer.Length);
bitmapResult.UnlockBits(resultImageData);
baseImage.UnlockBits(baseImageData);
overlayImage.UnlockBits(overlayImageData);
return bitmapResult;
}
public static Image Interpolate(this Image image1, Image image2, double delta)
{
delta = MathExtensions.Clamp(delta, 0.0, 1.0);
if (image1 is not Bitmap baseImage || image2 is not Bitmap overlayImage ||
image1.Width != image2.Width || image1.Height != image2.Height)
return image1;
BitmapData baseImageData = baseImage.LockBits(new Rectangle(Point.Empty, baseImage.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] baseImageBuffer = new byte[baseImageData.Stride * baseImageData.Height];
Marshal.Copy(baseImageData.Scan0, baseImageBuffer, 0, baseImageBuffer.Length);
baseImage.UnlockBits(baseImageData);
BitmapData overlayImageData = overlayImage.LockBits(new Rectangle(Point.Empty, overlayImage.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] overlayImageBuffer = new byte[overlayImageData.Stride * overlayImageData.Height];
Marshal.Copy(overlayImageData.Scan0, overlayImageBuffer, 0, overlayImageBuffer.Length);
overlayImage.UnlockBits(overlayImageData);
byte[] finalBuffer = new byte[baseImageData.Stride * baseImageData.Height];
for (int k = 0; k < baseImageBuffer.Length && k < overlayImageBuffer.Length; k += 4)
{
finalBuffer[k + 0] = ColorExtensions.Mix(delta, baseImageBuffer[k + 0], overlayImageBuffer[k + 0]);
finalBuffer[k + 1] = ColorExtensions.Mix(delta, baseImageBuffer[k + 1], overlayImageBuffer[k + 1]);
finalBuffer[k + 2] = ColorExtensions.Mix(delta, baseImageBuffer[k + 2], overlayImageBuffer[k + 2]);
finalBuffer[k + 3] = ColorExtensions.Mix(delta, baseImageBuffer[k + 3], overlayImageBuffer[k + 3]);
}
Bitmap bitmapResult = new Bitmap(baseImage.Width, baseImage.Height, PixelFormat.Format32bppArgb);
BitmapData resultImageData = bitmapResult.LockBits(new Rectangle(Point.Empty, bitmapResult.Size),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(finalBuffer, 0, resultImageData.Scan0, finalBuffer.Length);
bitmapResult.UnlockBits(resultImageData);
return bitmapResult;
}
}
}