From 167618411846b8ed41ab789628868851f6d66139 Mon Sep 17 00:00:00 2001 From: "H. Utku Maden" Date: Sun, 2 Jul 2023 11:22:52 +0300 Subject: [PATCH] Add image buffers and color conversion. --- Quik/Media/Color/FormatConvert.cs | 59 ++++++++++++ Quik/Media/Color/ImageBuffer.cs | 60 ++++++++++++ Quik/Media/Color/LockIO.cs | 150 ++++++++++++++++++++++++++++++ Quik/Media/Extensions.cs | 71 ++++++++++++++ Quik/Media/QImage.cs | 40 +++++++- 5 files changed, 377 insertions(+), 3 deletions(-) create mode 100644 Quik/Media/Color/FormatConvert.cs create mode 100644 Quik/Media/Color/ImageBuffer.cs create mode 100644 Quik/Media/Color/LockIO.cs create mode 100644 Quik/Media/Extensions.cs diff --git a/Quik/Media/Color/FormatConvert.cs b/Quik/Media/Color/FormatConvert.cs new file mode 100644 index 0000000..66f1dbf --- /dev/null +++ b/Quik/Media/Color/FormatConvert.cs @@ -0,0 +1,59 @@ +using System; + +namespace Quik.Media.Color +{ + public static class FormatConvert + { + public static void Convert(QImageLock dst, QImageLock src) + { + if (dst.Format.IsU8() && src.Format.IsU8()) + { + LockIO dstIO = new LockIO(dst); + LockIO srcIO = new LockIO(src); + + int count = dst.Width * dst.Height * dst.Depth; + for (int i = 0; i < count; i++) + { + dstIO[i] = srcIO[i]; + } + } + else if (dst.Format.IsU8() && src.Format.IsFloat()) + { + LockIO dstIO = new LockIO(dst); + LockIOF srcIO = new LockIOF(src); + + int count = dst.Width * dst.Height * dst.Depth; + for (int i = 0; i < count; i++) + { + dstIO[i] = (QColor)srcIO[i]; + } + } + else if (dst.Format.IsFloat() && src.Format.IsU8()) + { + LockIOF dstIO = new LockIOF(dst); + LockIO srcIO = new LockIO(src); + + int count = dst.Width * dst.Height * dst.Depth; + for (int i = 0; i < count; i++) + { + dstIO[i] = (QColorF)srcIO[i]; + } + } + else if (dst.Format.IsFloat() && src.Format.IsFloat()) + { + LockIOF dstIO = new LockIOF(dst); + LockIOF srcIO = new LockIOF(src); + + int count = dst.Width * dst.Height * dst.Depth; + for (int i = 0; i < count; i++) + { + dstIO[i] = srcIO[i]; + } + } + else + { + throw new Exception("Congratulations you have broken image formats!"); + } + } + } +} \ No newline at end of file diff --git a/Quik/Media/Color/ImageBuffer.cs b/Quik/Media/Color/ImageBuffer.cs new file mode 100644 index 0000000..9c9dfb0 --- /dev/null +++ b/Quik/Media/Color/ImageBuffer.cs @@ -0,0 +1,60 @@ +using System; +using System.Runtime.InteropServices; + +namespace Quik.Media.Color +{ + public class ImageBuffer : IDisposable + { + private byte[] buffer; + GCHandle handle; + + public QImageFormat Format { get; } + public int Width { get; } + public int Height { get; } + public int Depth { get; } + + public ImageBuffer(QImageFormat format, int width, int height, int depth = 1) + { + Format = format; + Width = width; + Height = height; + Depth = depth; + + buffer = new byte[width * height * depth]; + } + ~ImageBuffer() + { + Dispose(false); + } + + public QImageLock Lock() + { + handle.Free(); + handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); + IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0); + return new QImageLock(Format, Width, Height, Depth, ptr); + } + + public void Unlock() + { + handle.Free(); + } + + private bool isDiposed = false; + private void Dispose(bool disposing) + { + if (isDiposed) return; + + buffer = null; + handle.Free(); + + isDiposed = true; + GC.SuppressFinalize(this); + } + + public void Dispose() + { + Dispose(true); + } + } +} \ No newline at end of file diff --git a/Quik/Media/Color/LockIO.cs b/Quik/Media/Color/LockIO.cs new file mode 100644 index 0000000..e27d34f --- /dev/null +++ b/Quik/Media/Color/LockIO.cs @@ -0,0 +1,150 @@ +using System; + +namespace Quik.Media.Color +{ + public unsafe struct LockIO + { + public QImageLock Lock { get; } + public int Width => Lock.Width; + public int Height => Lock.Height; + public int Depth => Depth; + public QImageFormat Format => Lock.Format; + + public LockIO(QImageLock imageLock) + { + if (!imageLock.Format.IsU8()) + throw new Exception("Can only read/write U8 format images"); + + Lock = imageLock; + } + + public QColor this[int index] + { + get + { + int chan = Format.Channels(); + byte *ptr = (byte*)Lock.ImagePtr + chan * index; + + switch (Format) + { + default: + case QImageFormat.RedU8: return new QColor(ptr[0], 0, 0, 255); + case QImageFormat.AlphaU8: return new QColor(0, 0, 0, ptr[0]); + case QImageFormat.RaU8: return new QColor(ptr[0], 0, 0, ptr[1]); + case QImageFormat.RgbU8: return new QColor(ptr[0], ptr[1], ptr[2], 255); + case QImageFormat.RgbaU8: return new QColor(ptr[0], ptr[1], ptr[2], ptr[3]); + } + } + + set + { + int chan = Format.Channels(); + byte *ptr = (byte*)Lock.ImagePtr + chan * index; + + switch (Format) + { + default: + case QImageFormat.RedU8: + ptr[0] = value.R; + break; + case QImageFormat.AlphaU8: + ptr[0] = value.A; + break; + case QImageFormat.RaU8: + ptr[0] = value.R; + ptr[1] = value.A; + break; + case QImageFormat.RgbU8: + ptr[0] = value.R; + ptr[1] = value.G; + ptr[2] = value.B; + break; + case QImageFormat.RgbaU8: + ptr[0] = value.R; + ptr[1] = value.G; + ptr[2] = value.B; + ptr[3] = value.A; + break; + } + } + } + public QColor this[int x, int y, int z = 0] + { + get => this[x + y * Width + z * Width * Height]; + set => this[x + y * Width + z * Width * Height] = value; + } + } + + public unsafe struct LockIOF + { + public QImageLock Lock { get; } + public int Width => Lock.Width; + public int Height => Lock.Height; + public int Depth => Depth; + public QImageFormat Format => Lock.Format; + + public LockIOF(QImageLock imageLock) + { + if (!imageLock.Format.IsFloat()) + throw new Exception("Can only read/write U8 format images"); + + Lock = imageLock; + } + + public QColorF this[int index] + { + get + { + int chan = Format.Channels(); + float *ptr = (float*)Lock.ImagePtr + chan * index; + + switch (Format) + { + default: + case QImageFormat.RedU8: return new QColorF(ptr[0], 0, 0, 255); + case QImageFormat.AlphaU8: return new QColorF(0, 0, 0, ptr[0]); + case QImageFormat.RaU8: return new QColorF(ptr[0], 0, 0, ptr[1]); + case QImageFormat.RgbU8: return new QColorF(ptr[0], ptr[1], ptr[2], 255); + case QImageFormat.RgbaU8: return new QColorF(ptr[0], ptr[1], ptr[2], ptr[3]); + } + } + + set + { + int chan = Format.Channels(); + float *ptr = (float*)Lock.ImagePtr + chan * index; + + switch (Format) + { + default: + case QImageFormat.RedU8: + ptr[0] = value.R; + break; + case QImageFormat.AlphaU8: + ptr[0] = value.A; + break; + case QImageFormat.RaU8: + ptr[0] = value.R; + ptr[1] = value.A; + break; + case QImageFormat.RgbU8: + ptr[0] = value.R; + ptr[1] = value.G; + ptr[2] = value.B; + break; + case QImageFormat.RgbaU8: + ptr[0] = value.R; + ptr[1] = value.G; + ptr[2] = value.B; + ptr[3] = value.A; + break; + } + } + } + public QColorF this[int x, int y, int z = 0] + { + get => this[x + y * Width + z * Width * Height]; + set => this[x + y * Width + z * Width * Height] = value; + } + } +} \ No newline at end of file diff --git a/Quik/Media/Extensions.cs b/Quik/Media/Extensions.cs new file mode 100644 index 0000000..64cff5f --- /dev/null +++ b/Quik/Media/Extensions.cs @@ -0,0 +1,71 @@ +namespace Quik.Media +{ + public static class Extensions + { + public static bool IsU8(this QImageFormat format) + { + switch (format) + { + case QImageFormat.AlphaU8: + case QImageFormat.RedU8: + case QImageFormat.RaU8: + case QImageFormat.RgbU8: + case QImageFormat.RgbaU8: + return true; + default: + return false; + } + } + + public static bool IsFloat(this QImageFormat format) + { + switch (format) + { + case QImageFormat.AlphaF: + case QImageFormat.RedF: + case QImageFormat.RaF: + case QImageFormat.RgbF: + case QImageFormat.RgbaF: + return true; + default: + return false; + } + } + + public static int BitsPerPixel(this QImageFormat format) + { + switch (format) + { + case QImageFormat.AlphaU8: return sizeof(byte); + case QImageFormat.RedU8: return sizeof(byte); + case QImageFormat.RaU8: return 2 * sizeof(byte); + case QImageFormat.RgbU8: return 3 * sizeof(byte); + case QImageFormat.RgbaU8: return 4 * sizeof(byte); + case QImageFormat.AlphaF: return sizeof(float); + case QImageFormat.RedF: return sizeof(float); + case QImageFormat.RaF: return 2 * sizeof(float); + case QImageFormat.RgbF: return 3 * sizeof(float); + case QImageFormat.RgbaF: return 4 * sizeof(float); + default: return 0; + } + } + + public static int Channels(this QImageFormat format) + { + switch (format) + { + case QImageFormat.AlphaU8: return 1; + case QImageFormat.RedU8: return 1; + case QImageFormat.RaU8: return 2; + case QImageFormat.RgbU8: return 3; + case QImageFormat.RgbaU8: return 4; + case QImageFormat.AlphaF: return 1; + case QImageFormat.RedF: return 1; + case QImageFormat.RaF: return 2; + case QImageFormat.RgbF: return 3; + case QImageFormat.RgbaF: return 4; + default: return 0; + } + } + } +} \ No newline at end of file diff --git a/Quik/Media/QImage.cs b/Quik/Media/QImage.cs index 5c17114..8732f27 100644 --- a/Quik/Media/QImage.cs +++ b/Quik/Media/QImage.cs @@ -7,12 +7,13 @@ namespace Quik.Media public abstract int Width { get; } public abstract int Height { get; } public abstract int Depth { get; } + public abstract QImageFormat InternalFormat { get; } public virtual int MipMapLevels => 0; public virtual bool Premultiplied => false; - public abstract IntPtr LockBits2d(QImageFormat format, int level = 0); - public abstract IntPtr LockBits3d(QImageFormat format, int level = 0); - public abstract IntPtr LockBits3d(QImageFormat format, int depth, int level = 0); + public abstract void LockBits2d(out QImageLock imageLock, QImageLockOptions options); + public abstract void LockBits3d(out QImageLock imageLock, QImageLockOptions options); + public abstract void LockBits3d(out QImageLock imageLock, QImageLockOptions options, int depth); public abstract void UnlockBits(); // IDisposable @@ -28,4 +29,37 @@ namespace Quik.Media protected virtual void Dispose(bool disposing) { } public void Dispose() => DisposePrivate(true); } + + public struct QImageLockOptions + { + public QImageFormat Format { get; } + public bool Premultiply { get; } + public int MipLevel { get; } + public static QImageLockOptions Default { get; } = new QImageLockOptions(QImageFormat.RgbaU8, true, 0); + + public QImageLockOptions(QImageFormat format, bool premultiply, int level) + { + Format = format; + Premultiply = premultiply; + MipLevel = level; + } + } + + public struct QImageLock + { + public QImageFormat Format { get; } + public int Width { get; } + public int Height { get; } + public int Depth { get; } + public IntPtr ImagePtr { get; } + + public QImageLock(QImageFormat format, int width, int height, int depth, IntPtr ptr) + { + Format = format; + Width = width; + Height = height; + Depth = depth; + ImagePtr = ptr; + } + } } \ No newline at end of file