Created
December 14, 2020 11:05
-
-
Save yakumo-proj/3f729319e734293e0b073d2139a82ace to your computer and use it in GitHub Desktop.
UniVRMを使わずにC#でVRMの画像リサイズを実装してみたやつ
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Json; | |
using System.IO; | |
using System.Collections.Generic; | |
using System.Linq; | |
using SixLabors.ImageSharp; | |
using SixLabors.ImageSharp.Processing; | |
using SixLabors.ImageSharp.Processing.Processors.Transforms; | |
namespace VRMOptimizer4Cluster | |
{ | |
/// <summary> | |
/// 縮小アルゴリズム選択用enum値 | |
/// </summary> | |
public enum ResamplerType | |
{ | |
BiCubic = 0, | |
Lanczos2, | |
Lanczos3, | |
Lanczos5, | |
BiLinear, //triangle | |
Welch, | |
} | |
/// <summary> | |
/// ビューに渡す用データ | |
/// </summary> | |
public struct TextureInfo | |
{ | |
public readonly byte[] Buf; | |
public readonly int BufferIndex; | |
public readonly int ImageIndex; | |
public readonly string Name; | |
public readonly int Width; | |
public readonly int Height; | |
public TextureInfo(byte[] buf, int bufferIndex, int imageIndex, string name) | |
{ | |
Buf = buf; | |
BufferIndex = bufferIndex; | |
ImageIndex = imageIndex; | |
Name = name; | |
using var image = Image.Load(buf); | |
Width = image.Width; | |
Height = image.Height; | |
} | |
} | |
public class TinyVRMFileUtil | |
{ | |
/// <summary> | |
/// VRM/GLBのヘッダ | |
/// </summary> | |
public class GlbHeader | |
{ | |
public UInt32 Magic { get; set; } | |
public UInt32 Version { get; set; } | |
public UInt32 Length { get; set; } | |
public GlbHeader(UInt32 magic, UInt32 version, UInt32 length) | |
{ | |
Magic = magic; | |
Version = version; | |
Length = length; | |
} | |
public void Write(StreamWriter stream) | |
{ | |
stream.Write(Magic); | |
stream.Write(Version); | |
stream.Write(Length); | |
} | |
} | |
/// <summary> | |
/// VRM/GLBのチャンク(JSON/BINARY) | |
/// </summary> | |
public class GlbChunk | |
{ | |
public UInt32 Length { get; set; } | |
public UInt32 ChunkType { get; set; } | |
public byte[] Data { get; set; } | |
public GlbChunk(UInt32 length, UInt32 chunkType, byte[] data) | |
{ | |
Length = length; | |
ChunkType = chunkType; | |
Data = data; | |
} | |
public void Write(StreamWriter stream) | |
{ | |
stream.Write(Length); | |
stream.Write(ChunkType); | |
stream.Write(Data); | |
} | |
} | |
/// <summary> | |
/// View用表示データ | |
/// </summary> | |
public List<TextureInfo> TextureList => | |
ImageIndexes.Select((x, imgIndex) => new TextureInfo( | |
BinaryBuffers[x.index], x.index, imgIndex, x.name) | |
).ToList(); | |
/// <summary> | |
/// GLBデータのインスタンス用 | |
/// </summary> | |
public GlbHeader Header { get; set; } | |
public List<GlbChunk> Chunks { get; set; } | |
public List<byte[]> BinaryBuffers { get; set; } | |
JsonValue GlTFPart { get; set; } = null; | |
//JsonArray BufferViews { get; set; } = null; | |
/// <summary> | |
/// GLTFのimagesを解析して一時格納用 | |
/// </summary> | |
public struct ImageIndex | |
{ | |
public int index; | |
public string name; | |
} | |
List<ImageIndex> ImageIndexes { get; set; } = null; | |
/// <summary> | |
/// VRMファイルのヘッダ・チャンクを分割 | |
/// </summary> | |
/// <param name="fileName">開くVRMファイル</param> | |
/// <returns></returns> | |
bool ChunkParser(string fileName) | |
{ | |
using BinaryReader br = new BinaryReader(File.OpenRead(fileName)); | |
Header = new GlbHeader( | |
br.ReadUInt32(), | |
br.ReadUInt32(), | |
br.ReadUInt32() | |
); | |
Chunks = new List<GlbChunk>(); | |
for (int i = 0; i < 2; i++) | |
{ | |
UInt32 length = br.ReadUInt32(); | |
UInt32 chunkType = br.ReadUInt32(); | |
var chunk = new GlbChunk( | |
length, | |
chunkType, | |
br.ReadBytes((int)length) | |
); | |
Chunks.Add(chunk); | |
} | |
return true; | |
} | |
/// <summary> | |
/// GLTF解析 | |
/// </summary> | |
void GltfParser() | |
{ | |
using MemoryStream memoryStream = new MemoryStream(Chunks[0].Data); | |
GlTFPart = JsonValue.Load(memoryStream); | |
BinaryBuffers = new List<byte[]>(); | |
foreach (var bv in GlTFPart["bufferViews"] as JsonArray) | |
{ | |
int offset = bv["byteOffset"]; | |
int length = bv["byteLength"]; | |
var buffer = new byte[length]; | |
Array.Copy(Chunks[1].Data, offset, buffer, 0, length); | |
BinaryBuffers.Add(buffer); | |
} | |
Chunks[1].Data = null; //NOTE 使わないので開放しておく。 | |
ImageIndexes = (GlTFPart["images"] as JsonArray) | |
.Select(x => new ImageIndex { | |
index = x["bufferView"], | |
name = x["name"] | |
}).ToList(); | |
} | |
/// <summary> | |
/// コンストラクタ | |
/// </summary> | |
/// <param name="fileName"></param> | |
public TinyVRMFileUtil(string fileName) | |
{ | |
ChunkParser(fileName); //ヘッダ・チャンクの解析 | |
GltfParser(); //テクスチャ画像情報の解析 | |
} | |
/// <summary> | |
/// 規定の最大サイズで縮小+保存 | |
/// </summary> | |
/// <param name="filePath"></param> | |
/// <param name="maxWidth"></param> | |
/// <param name="type"></param> | |
public void ChangeAndSave(string filePath, int maxWidth, ResamplerType type) | |
{ | |
// 画像のリサイズ | |
IResampler resampler = GetResampler(type); | |
foreach (var texture in TextureList.Where(x => x.Width > maxWidth)) | |
{ | |
using var image = Image.Load(BinaryBuffers[texture.BufferIndex]); | |
image.Mutate(x => x.Resize(maxWidth, maxWidth, resampler)); | |
using MemoryStream ms = new MemoryStream(); | |
image.SaveAsPng(ms); | |
BinaryBuffers[texture.BufferIndex] = ms.ToArray(); | |
} | |
//bufferViewオフセットを再計算 | |
int nOffset = 0; | |
for (int i = 0; i < BinaryBuffers.Count(); i++) | |
{ | |
var buffer = BinaryBuffers[i]; | |
GlTFPart["bufferViews"][i]["byteOffset"] = nOffset; | |
GlTFPart["bufferViews"][i]["byteLength"] = buffer.Count(); | |
nOffset += buffer.Count(); | |
} | |
//長さ情報の再計算など | |
Chunks[1].Length = (uint)nOffset; | |
Chunks[0].Data = System.Text.Encoding.UTF8.GetBytes(GlTFPart.ToString()); | |
Chunks[0].Length = (UInt32)Chunks[0].Data.Count(); | |
Header.Length = 12 + 8 + Chunks[0].Length + 8 + Chunks[1].Length; | |
// ファイルに書き出し | |
using var stream = File.OpenWrite(filePath); | |
stream.Write(BitConverter.GetBytes(Header.Magic)); | |
stream.Write(BitConverter.GetBytes(Header.Version)); | |
stream.Write(BitConverter.GetBytes(Header.Length)); | |
stream.Write(BitConverter.GetBytes(Chunks[0].Length)); | |
stream.Write(BitConverter.GetBytes(Chunks[0].ChunkType)); | |
stream.Write(Chunks[0].Data); | |
stream.Write(BitConverter.GetBytes(Chunks[1].Length)); | |
stream.Write(BitConverter.GetBytes(Chunks[1].ChunkType)); | |
foreach (var buf in BinaryBuffers) { stream.Write(buf); } | |
} | |
/// <summary> | |
/// 縮小アルゴリズムの選択 | |
/// </summary> | |
/// <param name="type"></param> | |
/// <returns></returns> | |
protected IResampler GetResampler(ResamplerType type) | |
{ | |
switch (type) | |
{ | |
case ResamplerType.Lanczos2: return new LanczosResampler(2); | |
case ResamplerType.Lanczos3: return new LanczosResampler(3); | |
case ResamplerType.Lanczos5: return new LanczosResampler(5); | |
case ResamplerType.BiLinear: return new TriangleResampler(); | |
case ResamplerType.Welch: return new WelchResampler(); | |
/* case ResamplerType.BiCubic: */ | |
default: return new BicubicResampler(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment