Skip to content

Instantly share code, notes, and snippets.

@elliotwoods
Created June 14, 2016 07:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save elliotwoods/2e1228201451d63d1765135da44c2782 to your computer and use it in GitHub Desktop.
Save elliotwoods/2e1228201451d63d1765135da44c2782 to your computer and use it in GitHub Desktop.
FreeImage version of FileTexture for DX11
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VVVV.PluginInterfaces.V2;
using VVVV.DX11;
using FeralTic.DX11.Resources;
using VVVV.PluginInterfaces.V1;
using System.ComponentModel.Composition;
using FeralTic.DX11;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;
using FreeImageAPI;
using System.IO;
using SlimDX.DXGI;
namespace VVVV.Nodes.Ximea
{
#region PluginInfo
[PluginInfo(Name = "FileTexture",
Category = "DX11",
Version = "FreeImage",
Help = "Load FreeImage files",
Tags = "")]
#endregion PluginInfo
public class VideoInDX11Node : IPluginEvaluate, IDisposable, IDX11ResourceProvider
{
#region fields & pins
IPluginHost FHost;
[Input("Filename", StringType = StringType.Filename)]
IDiffSpread<string> FInFilename;
[Input("Reload", IsBang = true)]
ISpread<bool> FInReload;
[Output("Texture Out", Order = 0)]
protected Pin<DX11Resource<DX11DynamicTexture2D>> FTextureOut;
[Output("Size")]
ISpread<int> FOutSize;
[Output("Format")]
ISpread<SlimDX.DXGI.Format> FOutFormat;
[Output("Valid")]
ISpread<bool> FOutValid;
[Output("Status")]
ISpread<string> FOutStatus;
Dictionary<string, Image> FLoadedImages = new Dictionary<string, Image>();
Dictionary<int, string> FLoadedTextureFiles = new Dictionary<int, string>();
#endregion fields & pins
[ImportingConstructor()]
public VideoInDX11Node(IPluginHost Host)
{
this.FHost = Host;
}
class Image
{
public System.Type Type;
public uint channelCount;
public byte[] data;
public ulong width;
public ulong pitch;
public ulong height;
public ulong size;
public Format format;
}
bool FFirstRun = true;
public void Evaluate(int SpreadMax)
{
if(FFirstRun)
{
this.FTextureOut.SliceCount = 0;
FFirstRun = false;
}
//resize output
while (this.FTextureOut.SliceCount > SpreadMax)
{
var resource = FTextureOut[FTextureOut.SliceCount - 1];
if (resource != null)
{
resource.Dispose();
resource = null;
}
}
while (this.FTextureOut.SliceCount < SpreadMax)
{
this.FTextureOut.Add(new DX11Resource<DX11DynamicTexture2D>());
}
this.FTextureOut.SliceCount = SpreadMax;
//unload anything with a reload flat
for(int i=0; i<SpreadMax; i++)
{
if(FInReload[i])
{
var filename = FInFilename[i];
if(FLoadedImages.ContainsKey(filename))
{
//unload the texture
FLoadedImages.Remove(filename);
//remove texture from loaded texture flags
var keys = new List<int>(FLoadedTextureFiles.Keys);
foreach(var key in keys)
{
if(FLoadedTextureFiles[key] == filename)
{
FLoadedTextureFiles.Remove(key);
}
}
}
}
}
FOutSize.SliceCount = SpreadMax * 2;
FOutFormat.SliceCount = SpreadMax;
FOutStatus.SliceCount = SpreadMax;
FOutValid.SliceCount = SpreadMax;
for (int i = 0; i < SpreadMax; i++)
{
try
{
var filename = FInFilename[i];
if (!File.Exists(filename))
{
throw (new Exception("File not found"));
}
if (!FLoadedImages.ContainsKey(filename))
{
FLoadedImages.Add(filename, LoadImage(filename));
}
var image = FLoadedImages[filename];
FOutSize[i * 2 + 0] = (int) image.width;
FOutSize[i * 2 + 1] = (int)image.height;
FOutFormat[i] = image.format;
FOutValid[i] = true;
FOutStatus[i] = "OK";
}
catch (Exception e)
{
FOutSize[i * 2 + 0] = 0;
FOutSize[i * 2 + 1] = 0;
FOutValid[i] = false;
FOutStatus[i] = e.Message;
}
}
//unload any unused images
{
var filenames = new List<string>(FLoadedImages.Keys);
foreach(var filename in filenames)
{
//double check we didn't already remove
if(!FInFilename.Contains(filename) && FLoadedImages.ContainsKey(filename))
{
FLoadedImages.Remove(filename);
}
}
}
}
Image LoadImage(string filename)
{
FREE_IMAGE_FORMAT format = FreeImage.GetFileType(filename, 0);
var bmp = FreeImage.Load(format, filename, FREE_IMAGE_LOAD_FLAGS.JPEG_ACCURATE);
try
{
if (bmp.IsNull == true)
{
throw (new Exception("Couldn't load file"));
}
//we need some conversion from strange palettes
if (FreeImage.GetColorType(bmp) == FREE_IMAGE_COLOR_TYPE.FIC_PALETTE || FreeImage.GetBPP(bmp) < 8)
{
FIBITMAP converted;
if (FreeImage.IsTransparent(bmp))
converted = FreeImage.ConvertTo32Bits(bmp);
else if (FreeImage.IsGreyscaleImage(bmp))
converted = FreeImage.ConvertTo8Bits(bmp);
else
converted = FreeImage.ConvertTo24Bits(bmp);
FreeImage.Unload(bmp);
bmp = converted;
}
//now we should have a fairly sensible 8, 24 or 32bit (uchar) image
//or a float / hdr image
uint width = FreeImage.GetWidth(bmp);
uint height = FreeImage.GetHeight(bmp);
uint bpp = FreeImage.GetBPP(bmp);
uint pitch = FreeImage.GetPitch(bmp);
FREE_IMAGE_TYPE type = FreeImage.GetImageType(bmp);
var dataType = typeof(byte);
uint channelCount = 1;
Format DX11Format = Format.Unknown;
if (type == FREE_IMAGE_TYPE.FIT_BITMAP)
{
//standard image (8bbp)
dataType = typeof(byte);
channelCount = bpp / 8;
switch(channelCount)
{
case 1:
DX11Format = Format.R8_UNorm;
break;
case 2:
DX11Format = Format.R8G8_UNorm;
break;
case 4:
DX11Format = Format.R8G8B8A8_UNorm;
break;
default:
throw (new Exception("Channel count is not supported : " + channelCount.ToString()));
}
}
else
{
switch (type)
{
case (FREE_IMAGE_TYPE.FIT_UINT16):
dataType = typeof(UInt16);
channelCount = 1;
DX11Format = Format.R16_UNorm;
break;
case (FREE_IMAGE_TYPE.FIT_INT16):
dataType = typeof(Int16);
channelCount = 1;
DX11Format = Format.R16_SNorm;
break;
case (FREE_IMAGE_TYPE.FIT_UINT32):
dataType = typeof(UInt32);
channelCount = 1;
DX11Format = Format.R32_UInt;
break;
case (FREE_IMAGE_TYPE.FIT_INT32):
dataType = typeof(Int32);
channelCount = 1;
DX11Format = Format.R32_SInt;
break;
case (FREE_IMAGE_TYPE.FIT_FLOAT):
dataType = typeof(Single);
channelCount = 1;
DX11Format = Format.R32_Float;
break;
case (FREE_IMAGE_TYPE.FIT_RGBF):
dataType = typeof(Single);
channelCount = 3;
DX11Format = Format.R32G32B32_Float;
break;
case (FREE_IMAGE_TYPE.FIT_RGBAF):
dataType = typeof(Single);
channelCount = 4;
DX11Format = Format.R32G32B32A32_Float;
break;
default:
throw (new Exception("Image of unknown format : " + type.ToString()));
}
}
//flip the image vertically
FreeImage.FlipVertical(bmp);
//load the data out
var size = (ulong)Marshal.SizeOf(dataType) * width * height * channelCount;
var data = new byte[size];
Marshal.Copy(FreeImage.GetBits(bmp), data, 0, (int)size);
//unload the bmp
FreeImage.Unload(bmp);
//keep image in memory
var newImage = new Image
{
Type = dataType,
channelCount = channelCount,
width = width,
pitch = pitch,
height = height,
size = size,
format = DX11Format,
data = data
};
return newImage;
}
catch (Exception e)
{
if (!bmp.IsNull)
{
FreeImage.Unload(bmp);
}
throw (e);
}
}
public void Dispose()
{
foreach(var textureSlice in FTextureOut)
{
if(textureSlice != null)
{
textureSlice.Dispose();
}
}
}
public void Update(DX11RenderContext context)
{
for(int i=0; i<FTextureOut.SliceCount; i++)
{
if(i > FInFilename.SliceCount)
{
continue;
}
var filename = FInFilename[i];
if(!FLoadedImages.ContainsKey(filename))
{
continue;
}
var image = FLoadedImages[filename];
var textureSlice = FTextureOut[i];
DX11DynamicTexture2D texture;
bool needsTexture = false;
bool needsUpdate = false;
if(textureSlice.Contains(context))
{
if (textureSlice[context].Width != (int)image.width
|| textureSlice[context].Height != (int)image.height
|| textureSlice[context].Format != image.format)
{
textureSlice[context].Dispose();
needsTexture = true;
}
} else
{
needsTexture = true;
}
if(needsTexture)
{
texture = new DX11DynamicTexture2D(context, (int)image.width, (int)image.height, image.format);
textureSlice[context] = texture;
needsUpdate = true;
} else
{
texture = textureSlice[context];
}
if (FLoadedTextureFiles.ContainsKey(i))
{
needsUpdate |= FLoadedTextureFiles[i] != filename;
} else
{
needsUpdate = true;
}
if(needsUpdate)
{
texture.WriteData(image.data);
FLoadedTextureFiles[i] = filename;
}
}
}
public void Destroy(DX11RenderContext context, bool force)
{
foreach(var textureSlice in FTextureOut)
{
if(textureSlice != null)
{
if(textureSlice.Contains(context))
{
textureSlice[context].Dispose();
}
}
}
}
public void Update(IPluginIO pin, DX11RenderContext context)
{
this.Update(context);
}
public void Destroy(IPluginIO pin, DX11RenderContext context, bool force)
{
this.Destroy(context, force);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment