Add image buffers and color conversion.
This commit is contained in:
parent
995943d83b
commit
1676184118
59
Quik/Media/Color/FormatConvert.cs
Normal file
59
Quik/Media/Color/FormatConvert.cs
Normal file
@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
60
Quik/Media/Color/ImageBuffer.cs
Normal file
60
Quik/Media/Color/ImageBuffer.cs
Normal file
@ -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
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
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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user