Add image buffers and color conversion.

This commit is contained in:
H. Utku Maden 2023-07-02 11:22:52 +03:00
parent 995943d83b
commit 1676184118
5 changed files with 377 additions and 3 deletions

@ -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!");
}
}
}
}

@ -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);
}
}
}

150
Quik/Media/Color/LockIO.cs Normal file

@ -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;
}
}
}

71
Quik/Media/Extensions.cs Normal file

@ -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;
}
}
}
}

@ -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;
}
}
}