diff --git a/GifImporter.cs b/GifImporter.cs index 4f7fc48..16028e8 100644 --- a/GifImporter.cs +++ b/GifImporter.cs @@ -18,7 +18,7 @@ public class GifImporter : ResoniteMod { public override string Name => "GifImporter"; public override string Author => "astral"; - public override string Version => "1.1.6"; + public override string Version => "1.1.7"; public override string Link => "https://github.com/astralchan/GifImporter"; [AutoRegisterConfigKey] @@ -43,156 +43,160 @@ public static bool Prefix(string path, ref Task __result, Slot targetSlot, float Image? image = null; bool validGif = false; - LocalDB localDB = targetSlot.World.Engine.LocalDB; + LocalDB localDB = targetSlot.World.Engine.LocalDB; - // Local file import vs URL import - if (uri.Scheme == "file") { - // Check file header - using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read)) { - byte[] headerBytes = new byte[6]; // GIF header is 6 bytes - int bytesRead = fs.Read(headerBytes, 0, headerBytes.Length); + // Local file import vs URL import + if (uri.Scheme == "file") { + // Check file header + using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read)) { + byte[] headerBytes = new byte[6]; // GIF header is 6 bytes + int bytesRead = fs.Read(headerBytes, 0, headerBytes.Length); - if (bytesRead != headerBytes.Length) - throw new Exception("File too short to be a gif"); - - string header = Encoding.ASCII.GetString(headerBytes); - - if (header != "GIF87a" && header != "GIF89a") - throw new Exception("Magic number doesn't match GIF magic number"); - - validGif = true; + if (bytesRead != headerBytes.Length) { + Debug("File is too short to be a gif"); + return true; } - image = Image.FromStream(File.OpenRead(path)); - } else if (uri.Scheme == "http" || uri.Scheme == "https" || uri.Scheme == "resdb") { - var client = new System.Net.WebClient(); - image = Image.FromStream(client.OpenRead(uri)); - var type = client.ResponseHeaders.Get("content-type"); - validGif = type == "image/gif"; - } else if (uri.Scheme == "resdb") { - validGif = true; - } - - if (!validGif) { - Debug($"{path} is not a gif, returning true"); - image?.Dispose(); - return true; - } - __result = targetSlot.StartTask(async delegate () { - await default(ToBackground); - - // Load the image - if (uri.Scheme == "resdb") { - Debug($"Awaiting asset from resdb uri..."); - image = Image.FromStream(await localDB.TryOpenAsset(uri)); - } - - int frameCount = 0; - float frameDelay = 0; - var frameWidth = 0; - var frameHeight = 0; - int gifRows = 0; - int gifCols = 0; - // https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.propertyitem.id PropertyTagFrameDelay - const int PropertyTagFrameDelay = 0x5100; - Bitmap? spriteSheet = null; - string spritePath = Path.Combine(localDB.TemporaryPath, Path.GetFileName(path)); - - try { - frameCount = image!.GetFrameCount(FrameDimension.Time); - - FrameDimension frameDimension = new FrameDimension(image.FrameDimensionsList[0]); - frameWidth = image.Width; - frameHeight = image.Height; - - // Get the times stored in the image - var times = image.GetPropertyItem(PropertyTagFrameDelay).Value; - - if (config!.GetValue(KEY_SQUARE)) { - // Calculate amount of cols and rows - float ratio = (float)frameWidth / frameHeight; - var cols = MathX.Sqrt(frameCount / ratio); - gifCols = MathX.RoundToInt(cols); - gifRows = frameCount / gifCols + ((frameCount % gifCols != 0) ? 1 : 0); - } else { - gifCols = frameCount; - gifRows = 1; - } - - // Create a new image - spriteSheet = new Bitmap(frameWidth * gifCols, frameHeight * gifRows); - int delay = 0; - using (Graphics g = Graphics.FromImage(spriteSheet)) { - for (int i = 0; i < gifRows; i++) - for (int j = 0; j < gifCols; j++) { - if (i * gifCols + j >= frameCount) - break; - // Convert 4-bit value to integer - var duration = BitConverter.ToInt32(times, 4 * ((i * gifCols) + j)); - // Set the write frame before we save it - image.SelectActiveFrame(FrameDimension.Time, i * gifCols + j); - g.DrawImage(image, frameWidth * j, frameHeight * i); - delay += duration; - } - frameDelay = 100 * frameCount / delay; - } + string header = Encoding.ASCII.GetString(headerBytes); - // Save the image - spriteSheet.Save(spritePath); - } - finally { - image!.Dispose(); + if (header != "GIF87a" && header != "GIF89a") { + Debug("Magic number doesn't match GIF magic number"); + return true; } + + validGif = true; + } + image = Image.FromStream(File.OpenRead(path)); + } else if (uri.Scheme == "http" || uri.Scheme == "https" || uri.Scheme == "resdb") { + var client = new System.Net.WebClient(); + image = Image.FromStream(client.OpenRead(uri)); + var type = client.ResponseHeaders.Get("content-type"); + validGif = type == "image/gif"; + } else if (uri.Scheme == "resdb") { + validGif = true; + } - Debug($"Image saved as {spritePath}"); - - Uri localUri = await localDB.ImportLocalAssetAsync(spritePath, - LocalDB.ImportLocation.Copy).ConfigureAwait(continueOnCapturedContext: false); + if (!validGif) { + Debug($"{path} is not a gif, returning true"); + image?.Dispose(); + return true; + } - File.Delete(spritePath); + __result = targetSlot.StartTask(async delegate () { + await default(ToBackground); - await default(ToWorld); + // Load the image + if (uri.Scheme == "resdb") { + Debug($"Awaiting asset from resdb uri..."); + image = Image.FromStream(await localDB.TryOpenAsset(uri)); + } - targetSlot.Name = Path.GetFileNameWithoutExtension(spritePath); - if (forward.HasValue) { - float3 from = forward.Value; - float3 to = float3.Forward; - targetSlot.LocalRotation = floatQ.FromToRotation(in from, in to); + int frameCount = 0; + float frameDelay = 0; + var frameWidth = 0; + var frameHeight = 0; + int gifRows = 0; + int gifCols = 0; + // https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.propertyitem.id PropertyTagFrameDelay + const int PropertyTagFrameDelay = 0x5100; + Bitmap? spriteSheet = null; + string spritePath = Path.Combine(localDB.TemporaryPath, Path.GetFileName(path)); + + try { + frameCount = image!.GetFrameCount(FrameDimension.Time); + + FrameDimension frameDimension = new FrameDimension(image.FrameDimensionsList[0]); + frameWidth = image.Width; + frameHeight = image.Height; + + // Get the times stored in the image + var times = image.GetPropertyItem(PropertyTagFrameDelay).Value; + + if (config!.GetValue(KEY_SQUARE)) { + // Calculate amount of cols and rows + float ratio = (float)frameWidth / frameHeight; + var cols = MathX.Sqrt(frameCount / ratio); + gifCols = MathX.RoundToInt(cols); + gifRows = frameCount / gifCols + ((frameCount % gifCols != 0) ? 1 : 0); + } else { + gifCols = frameCount; + gifRows = 1; } - StaticTexture2D tex = targetSlot.AttachComponent(); - tex.URL.Value = localUri; - ImageImporter.SetupTextureProxyComponents(targetSlot, tex, stereoLayout, projection, - setupScreenshotMetadata); - if (projection != 0) - ImageImporter.Create360Sphere(targetSlot, tex, stereoLayout, projection, addCollider); - else { - while (!tex.IsAssetAvailable) await default(NextUpdate); - ImageImporter.CreateQuad(targetSlot, tex, stereoLayout, addCollider); + // Create a new image + spriteSheet = new Bitmap(frameWidth * gifCols, frameHeight * gifRows); + int delay = 0; + using (Graphics g = Graphics.FromImage(spriteSheet)) { + for (int i = 0; i < gifRows; i++) + for (int j = 0; j < gifCols; j++) { + if (i * gifCols + j >= frameCount) + break; + // Convert 4-bit value to integer + var duration = BitConverter.ToInt32(times, 4 * ((i * gifCols) + j)); + // Set the write frame before we save it + image.SelectActiveFrame(FrameDimension.Time, i * gifCols + j); + g.DrawImage(image, frameWidth * j, frameHeight * i); + delay += duration; + } + frameDelay = 100 * frameCount / delay; } - if (setupScreenshotMetadata) targetSlot.GetComponentInChildren()?.NotifyOfScreenshot(); + // Save the image + spriteSheet.Save(spritePath); + } finally { + image!.Dispose(); + } + + Debug($"Image saved as {spritePath}"); + + Uri localUri = await localDB.ImportLocalAssetAsync(spritePath, + LocalDB.ImportLocation.Copy).ConfigureAwait(continueOnCapturedContext: false); - AtlasInfo _AtlasInfo = targetSlot.AttachComponent(); - UVAtlasAnimator _UVAtlasAnimator = targetSlot.AttachComponent(); - TimeIntDriver _TimeIntDriver = targetSlot.AttachComponent(); - _AtlasInfo.GridFrames.Value = frameCount; - _AtlasInfo.GridSize.Value = new int2(gifCols, gifRows); - _TimeIntDriver.Scale.Value = frameDelay; - _TimeIntDriver.Repeat.Value = _AtlasInfo.GridFrames.Value; - _TimeIntDriver.Target.Target = _UVAtlasAnimator.Frame; - _UVAtlasAnimator.AtlasInfo.Target = _AtlasInfo; + File.Delete(spritePath); - QuadMesh _QuadMesh = targetSlot.GetComponent(); - _QuadMesh.Size.Value = new float2(frameWidth, frameHeight).Normalized; + await default(ToWorld); - UnlitMaterial _UnlitMaterial = targetSlot.GetComponent(); - _UVAtlasAnimator.ScaleField.Target = _UnlitMaterial.TextureScale; - _UVAtlasAnimator.OffsetField.Target = _UnlitMaterial.TextureOffset; + targetSlot.Name = Path.GetFileNameWithoutExtension(spritePath); + if (forward.HasValue) { + float3 from = forward.Value; + float3 to = float3.Forward; + targetSlot.LocalRotation = floatQ.FromToRotation(in from, in to); + } + + StaticTexture2D tex = targetSlot.AttachComponent(); + tex.URL.Value = localUri; + ImageImporter.SetupTextureProxyComponents(targetSlot, tex, stereoLayout, projection, + setupScreenshotMetadata); + if (projection != 0) + ImageImporter.Create360Sphere(targetSlot, tex, stereoLayout, projection, addCollider); + else { + while (!tex.IsAssetAvailable) await default(NextUpdate); + ImageImporter.CreateQuad(targetSlot, tex, stereoLayout, addCollider); + } - // Set inventory preview to first frame - ItemTextureThumbnailSource _inventoryPreview = targetSlot.GetComponent(); - _inventoryPreview.Crop.Value = new Rect(0, 0, 1f / (float)gifCols, 1f / (float)gifRows); + if (setupScreenshotMetadata) + targetSlot.GetComponentInChildren()?.NotifyOfScreenshot(); + + AtlasInfo _AtlasInfo = targetSlot.AttachComponent(); + UVAtlasAnimator _UVAtlasAnimator = targetSlot.AttachComponent(); + TimeIntDriver _TimeIntDriver = targetSlot.AttachComponent(); + _AtlasInfo.GridFrames.Value = frameCount; + _AtlasInfo.GridSize.Value = new int2(gifCols, gifRows); + _TimeIntDriver.Scale.Value = frameDelay; + _TimeIntDriver.Repeat.Value = _AtlasInfo.GridFrames.Value; + _TimeIntDriver.Target.Target = _UVAtlasAnimator.Frame; + _UVAtlasAnimator.AtlasInfo.Target = _AtlasInfo; + + QuadMesh _QuadMesh = targetSlot.GetComponent(); + _QuadMesh.Size.Value = new float2(frameWidth, frameHeight).Normalized; + + UnlitMaterial _UnlitMaterial = targetSlot.GetComponent(); + _UVAtlasAnimator.ScaleField.Target = _UnlitMaterial.TextureScale; + _UVAtlasAnimator.OffsetField.Target = _UnlitMaterial.TextureOffset; + + // Set inventory preview to first frame + ItemTextureThumbnailSource _inventoryPreview = targetSlot.GetComponent(); + _inventoryPreview.Crop.Value = new Rect(0, 0, 1f / (float)gifCols, 1f / (float)gifRows); }); return false; diff --git a/GifImporter.csproj b/GifImporter.csproj index 91c9eaa..51c448d 100644 --- a/GifImporter.csproj +++ b/GifImporter.csproj @@ -2,7 +2,7 @@ - net462 + net472 LatestMajor enable true