Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ME] WebP support #261

Merged
merged 10 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/MaterialEditor.Base/MaterialEditor.Base.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,8 @@
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
<None Include="$(MSBuildThisFileDirectory)normal_convert_opengl.shader" />
<None Include="$(MSBuildThisFileDirectory)Resources\libwebp.lib">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
2 changes: 0 additions & 2 deletions src/MaterialEditor.Base/PluginBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ public partial class MaterialEditorPluginBase : BaseUnityPlugin
public static new ManualLogSource Logger;
public static MaterialEditorPluginBase Instance;

internal const string FileFilter = "Images (*.png;.jpg;.apng;.gif)|*.png;*.jpg;*.apng;*.gif|All files|*.*";

/// <summary>
/// Path where textures will be exported
/// </summary>
Expand Down
Binary file added src/MaterialEditor.Base/Resources/libwebp.lib
Binary file not shown.
7 changes: 6 additions & 1 deletion src/MaterialEditor.Base/UI/UI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,12 @@ void PopulateListMaterial(Material mat, Projector projector = null)
};
textureItem.TextureOnImport = () =>
{
OpenFileDialog.Show(OnFileAccept, "Open image", Application.dataPath, FileFilter);
#if !API
string fileFilter = KK_Plugins.ImageHelper.FileFilter;
#else
string fileFilter = "Images (*.png;.jpg)|*.png;*.jpg|All files|*.*";
#endif
OpenFileDialog.Show(OnFileAccept, "Open image", Application.dataPath, fileFilter);

void OnFileAccept(string[] strings)
{
Expand Down
3 changes: 3 additions & 0 deletions src/MaterialEditor.Core/Core.MaterialEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ public override void Awake()
{
base.Awake();

//Load any image loading dependencies before any images are actually ever loaded
ImageHelper.LoadDependencies(typeof(MaterialEditorPlugin));

#if KK || EC || KKS
RimRemover = Config.Bind("Config", "Remove Rim Lighting", false, new ConfigDescription("Remove rim lighting for all characters clothes, hair, accessories, etc. Will save modified values to the card.\n\nUse with caution as it cannot be undone except by manually resetting all the changes.", null, new ConfigurationManagerAttributes { Order = 0, IsAdvanced = true }));
#endif
Expand Down
127 changes: 127 additions & 0 deletions src/Shared.TextureContainer/Shared.ImageHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using UnityEngine;

namespace KK_Plugins
{
//Shamelessly stolen from https://stackoverflow.com/a/1246008
public static class ImageHelper
{
/// <summary>
/// File filter for all the supported images
/// </summary>
public const string FileFilter = "Images (*.png;.jpg;.apng;.gif;.webp)|*.png;*.jpg;*.apng;*.gif;*.webp|All files|*.*";

/// <summary>
/// Dict containing the dll and dll file for all external dependencies needed to load all supported file formats
/// </summary>
private static readonly Dictionary<string, string> dllDependencies = new Dictionary<string, string>
{
{ "webp", "libwebp.lib" }
};

/// <summary>
/// Read the file signature from a byte array to deterimine its file format.
/// https://www.garykessler.net/library/file_sigs.html
/// </summary>
/// <param name="imageBytes">Byte array containing the image</param>
/// <returns>ImageFormat</returns>
public static ImageFormat GetContentType(byte[] imageBytes)
{
foreach (var kvPair in imageFormatDecoders.OrderByDescending(x => x.Key.Length))
if (kvPair.Key.Length <= imageBytes.Length && imageBytes.StartsWith(kvPair.Key))
return kvPair.Value;
return ImageFormat.Unrecognized;
}

public static Texture2D LoadTexture2DFromBytes(byte[] texBytes, TextureFormat format, bool mipmaps)
{
Texture2D tex = null;
var imageFormat = GetContentType(texBytes);

//Only use magic numbers for custom supported image formats. Let LoadImage handle png/jpg/unknown
if (imageFormat == ImageFormat.WebP)
{
tex = WebP.Texture2DExt.CreateTexture2DFromWebP(texBytes, mipmaps, false, out var error);
if (error != WebP.Error.Success) tex = null;
}

//Always fall back to default load method if all others were skipped/failed
if (tex == null)
{
//LoadImage automatically resizes the texture so the texture size doesn't matter here
tex = new Texture2D(2, 2, format, mipmaps);
tex.LoadImage(texBytes);
RikkiBalboa marked this conversation as resolved.
Show resolved Hide resolved
}
return tex;
}

/// <summary>
/// Load all needed dependencies for loading all the supported file formats.
/// Needs to be to be run before any classes using these dependencies are loaded!
/// </summary>
internal static void LoadDependencies(Type pluginType)
{
foreach(var dependency in dllDependencies)
try
{
LoadDependency(dependency.Key, dependency.Value, pluginType);
}
catch
{
System.Console.WriteLine($"Failed to load {dependency.Value}");
}
}

private static bool StartsWith(this byte[] thisBytes, byte[] thatBytes)
{
for (int i = 0; i < thatBytes.Length; i += 1)
{
if (thisBytes[i] != thatBytes[i])
{
return false;
}
}
return true;
}

//Site that lists magic numbers for file formats https://www.garykessler.net/library/file_sigs.html
private static Dictionary<byte[], ImageFormat> imageFormatDecoders = new Dictionary<byte[], ImageFormat>()
{
{ new byte[]{ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, ImageFormat.Png },
{ new byte[]{ 0xff, 0xd8 }, ImageFormat.Jpeg },
{ new byte[]{ 0x52, 0x49, 0x46, 0x46 }, ImageFormat.WebP },
{ new byte[]{ 0x00, 0x00, 0x00 }, ImageFormat.Avif },
{ new byte[]{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, ImageFormat.Unrecognized},
};

public enum ImageFormat
{
Png,
Jpeg,
WebP,
Avif,
Unrecognized
}


[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr LoadLibrary(string fileName);
[DllImport("__Internal", CharSet = CharSet.Ansi)]
private static extern void mono_dllmap_insert(IntPtr assembly, string dll, string func, string tdll, string tfunc);

private static void LoadDependency(string dllName, string dllFileName, Type pluginType)
{
var assemblyPath = Path.GetDirectoryName(pluginType.Assembly.Location);
var nativeDllPath = Path.Combine(assemblyPath, dllFileName);
if (LoadLibrary(nativeDllPath) == IntPtr.Zero)
throw new IOException($"Failed to load {nativeDllPath}, verify that the file exists and is not corrupted.");
// Needed to let the non-standard extension to work with dllimport
mono_dllmap_insert(IntPtr.Zero, dllName, null, dllFileName, null);
}
}
}
5 changes: 5 additions & 0 deletions src/Shared.TextureContainer/Shared.TextureContainer.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
<Import_RootNamespace>Shared.TextureContainer</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Shared.ImageHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Shared.TextureContainer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Shared.TextureContainerManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)WebP\Error.cs" />
<Compile Include="$(MSBuildThisFileDirectory)WebP\Info.cs" />
<Compile Include="$(MSBuildThisFileDirectory)WebP\NativeBindings.cs" />
<Compile Include="$(MSBuildThisFileDirectory)WebP\Texture2DExt.cs" />
</ItemGroup>
</Project>
6 changes: 2 additions & 4 deletions src/Shared.TextureContainer/Shared.TextureContainerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,11 @@ public static void Release( Token holder )
private static Texture TextureFromBytes(byte[] texBytes, TextureFormat format, bool mipmaps)
{
if (texBytes == null || texBytes.Length == 0) return null;

//LoadImage automatically resizes the texture so the texture size doesn't matter here
Texture2D tex = new Texture2D(2, 2, format, mipmaps);
Texture2D tex = null;

try
{
tex.LoadImage(texBytes);
tex = ImageHelper.LoadTexture2DFromBytes(texBytes, format, mipmaps);

//Transfer to GPU memory and delete data in normal memory
RenderTexture rt = new RenderTexture(tex.width, tex.height, 0);
Expand Down
16 changes: 16 additions & 0 deletions src/Shared.TextureContainer/WebP/Error.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//https://github.com/octo-code/webp-unity3d
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WebP
{
public enum Error
{
Success = 0,

InvalidHeader = 20,
DecodingError = 30,
}
}
28 changes: 28 additions & 0 deletions src/Shared.TextureContainer/WebP/Info.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//https://github.com/octo-code/webp-unity3d
using System;
using System.Text;
using System.Collections.Generic;

namespace WebP
{
public class Info
{
public static string GetDecoderVersion()
{
uint v = (uint)WebP.Extern.NativeBindings.WebPGetDecoderVersion();
var revision = v % 256;
var minor = (v >> 8) % 256;
var major = (v >> 16) % 256;
return major + "." + minor + "." + revision;
}

public static string GetEncoderVersion()
{
uint v = (uint)WebP.Extern.NativeBindings.WebPGetEncoderVersion();
var revision = v % 256;
var minor = (v >> 8) % 256;
var major = (v >> 16) % 256;
return major + "." + minor + "." + revision;
}
}
}
Loading