using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; public class AnimationClipFrameChecker : EditorWindow { private List clips = new List(); private Vector2 scrollPos; private Vector2 clipListScrollPos; private Vector2 resultListScrollPos; private bool hasRunCheck = false; // 检测结果缓存 private struct ClipResult { public string name; public string assetPath; public int frameCount; public float length; public float frameRate; } private List results = new List(); // 大背景图片 private Texture2D bgImageGlobal; private const string BgImagePrefKey = "ClipFrameChecker_BgImagePath"; // GIF动画背景 private Texture2D[] gifFrames; private int[] gifDelays; private int gifTotalDuration; private bool isGif; private string gifSourcePath = ""; private double gifStartTime; private double lastGifRepaintTime; [MenuItem("Tools/动画帧数检测工具")] public static void ShowWindow() { var win = GetWindow("动画帧数检测工具"); win.minSize = new Vector2(500, 400); win.LoadBgImage(); } private void OnEnable() { EditorApplication.update -= OnEditorUpdate; EditorApplication.update += OnEditorUpdate; } private void OnDestroy() { EditorApplication.update -= OnEditorUpdate; ClearGif(); } private void OnEditorUpdate() { if (isGif && gifFrames != null && gifFrames.Length > 1) { if (EditorApplication.timeSinceStartup - lastGifRepaintTime > 0.033) { Repaint(); lastGifRepaintTime = EditorApplication.timeSinceStartup; } } } private void OnGUI() { // 配色方案 Color headerBg = new Color(0.3f, 0.5f, 0.8f, 1f); Color section1Bg = new Color(0.3f, 0.65f, 0.35f, 0.6f); // 绿 - 动画列表 Color section2Bg = new Color(0.65f, 0.35f, 0.7f, 0.6f); // 紫 - 检测按钮 Color section3Bg = new Color(0.2f, 0.6f, 0.55f, 0.6f); // 青 - 检测结果 Color section4Bg = new Color(0.7f, 0.45f, 0.2f, 0.6f); // 橙 - 背景设置 // 绘制大背景 Texture2D bgToDraw = null; if (isGif && gifFrames != null && gifFrames.Length > 0) { int elapsed = (int)((EditorApplication.timeSinceStartup - gifStartTime) * 1000) % gifTotalDuration; int accum = 0; for (int i = 0; i < gifFrames.Length; i++) { accum += gifDelays[i]; if (elapsed < accum) { bgToDraw = gifFrames[i]; break; } } if (bgToDraw == null) bgToDraw = gifFrames[0]; } else if (bgImageGlobal != null) { bgToDraw = bgImageGlobal; } if (bgToDraw != null) { Rect fullRect = new Rect(0, 0, position.width, position.height); GUI.DrawTexture(fullRect, bgToDraw, ScaleMode.StretchToFill); } // 整体滚动 scrollPos = EditorGUILayout.BeginScrollView(scrollPos); // 大标题 EditorGUILayout.Space(5); EditorGUILayout.LabelField("动画帧数检测工具", new GUIStyle(EditorStyles.boldLabel) { fontSize = 18, alignment = TextAnchor.MiddleCenter, fontStyle = FontStyle.Bold }); GUILayout.Space(8); // 1. 动画列表 DrawSectionHeader("1. 拖入 Animation Clip", headerBg); DrawSectionContent(DrawClipListInner, section1Bg); GUILayout.Space(6); // 2. 检测按钮 DrawSectionHeader("2. 检测动画", headerBg); DrawSectionContent(DrawCheckSection, section2Bg); GUILayout.Space(6); // 3. 检测结果 if (hasRunCheck) { DrawSectionHeader("3. 检测结果", headerBg); DrawSectionContent(DrawResultListInner, section3Bg); } GUILayout.Space(6); // 4. 背景设置 showBgSettings = EditorGUILayout.Foldout(showBgSettings, "🎨 界面背景设置", true); if (showBgSettings) { DrawSectionContent(DrawBgSettingsInner, section4Bg); } GUILayout.Space(8); EditorGUILayout.EndScrollView(); } private bool showBgSettings = false; // ===================== 动画列表 ===================== private void DrawClipListInner() { EditorGUILayout.HelpBox("将 Animation Clip 拖入下方列表,或点击按钮选择文件。支持批量操作。", MessageType.Info); GUILayout.Space(4); // 拖拽区域 Rect dropArea = GUILayoutUtility.GetRect(0, 40, GUILayout.ExpandWidth(true)); GUIStyle dropStyle = new GUIStyle("box") { alignment = TextAnchor.MiddleCenter, fontSize = 12, fontStyle = FontStyle.Italic }; GUI.Box(dropArea, "📂 拖拽 Animation Clip 到此处", dropStyle); // 处理拖拽 if (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform) { if (dropArea.Contains(Event.current.mousePosition)) { DragAndDrop.visualMode = DragAndDropVisualMode.Copy; if (Event.current.type == EventType.DragPerform) { DragAndDrop.AcceptDrag(); foreach (var obj in DragAndDrop.objectReferences) { if (obj is AnimationClip clip && !clips.Contains(clip)) clips.Add(clip); } } } } GUILayout.Space(6); // 按钮行 EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("➕ 添加动画", GUILayout.Height(28))) { string path = EditorUtility.OpenFilePanelWithFilters("选择 Animation Clip", "", new string[] { "Animation Files", "anim", "All Files", "*" }); if (!string.IsNullOrEmpty(path)) { string assetPath = GetAssetPath(path); if (string.IsNullOrEmpty(assetPath)) assetPath = path; AnimationClip clip = AssetDatabase.LoadAssetAtPath(assetPath); if (clip != null && !clips.Contains(clip)) clips.Add(clip); } } if (GUILayout.Button("📂 从文件夹批量导入", GUILayout.Height(28))) { string folder = EditorUtility.OpenFolderPanel("选择包含动画的文件夹", "", ""); if (!string.IsNullOrEmpty(folder)) { string assetFolder = GetAssetPath(folder); if (string.IsNullOrEmpty(assetFolder)) { // 用户选了项目外目录,尝试直接加载 string[] files = System.IO.Directory.GetFiles(folder, "*.anim", System.IO.SearchOption.AllDirectories); foreach (string f in files) { string ap = GetAssetPath(f); if (!string.IsNullOrEmpty(ap)) { AnimationClip clip = AssetDatabase.LoadAssetAtPath(ap); if (clip != null && !clips.Contains(clip)) clips.Add(clip); } } } else { string[] guids = AssetDatabase.FindAssets("t:AnimationClip", new string[] { assetFolder }); foreach (string guid in guids) { string ap = AssetDatabase.GUIDToAssetPath(guid); AnimationClip clip = AssetDatabase.LoadAssetAtPath(ap); if (clip != null && !clips.Contains(clip)) clips.Add(clip); } } Repaint(); } } if (GUILayout.Button("🗑️ 清空列表", GUILayout.Height(28))) { clips.Clear(); results.Clear(); hasRunCheck = false; } EditorGUILayout.EndHorizontal(); GUILayout.Space(6); // 显示当前列表 if (clips.Count > 0) { EditorGUILayout.LabelField($"已添加 {clips.Count} 个动画", EditorStyles.boldLabel); GUILayout.Space(4); clipListScrollPos = EditorGUILayout.BeginScrollView(clipListScrollPos, GUILayout.MaxHeight(150)); for (int i = 0; i < clips.Count; i++) { EditorGUILayout.BeginHorizontal(); GUIContent clipIcon = EditorGUIUtility.IconContent("d_AnimationClip Icon"); GUILayout.Label(clipIcon, GUILayout.Width(20), GUILayout.Height(20)); GUILayout.Label(clips[i].name, GUILayout.Width(200)); GUILayout.FlexibleSpace(); GUILayout.Label(AssetDatabase.GetAssetPath(clips[i]), EditorStyles.miniLabel); if (GUILayout.Button("✕", GUILayout.Width(30))) { clips.RemoveAt(i); i--; if (hasRunCheck) { hasRunCheck = false; results.Clear(); } } EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndScrollView(); } } // ===================== 检测按钮 ===================== private void DrawCheckSection() { EditorGUILayout.BeginHorizontal(); GUIStyle btnStyle = new GUIStyle(GUI.skin.button) { fontSize = 14, fontStyle = FontStyle.Bold, fixedHeight = 36 }; if (GUILayout.Button("🔍 开始检测动画帧数", btnStyle, GUILayout.ExpandWidth(true))) { RunCheck(); } EditorGUILayout.EndHorizontal(); if (hasRunCheck && results.Count > 0) { EditorGUILayout.Space(4); int totalFrames = 0; foreach (var r in results) totalFrames += r.frameCount; EditorGUILayout.HelpBox($"检测完成:共 {results.Count} 个动画,总计 {totalFrames} 帧", MessageType.Info); } } // ===================== 检测结果 ===================== private void DrawResultListInner() { if (results.Count == 0) { EditorGUILayout.HelpBox("没有检测结果。", MessageType.Warning); return; } GUILayout.Space(4); // 表头 EditorGUILayout.BeginHorizontal(); GUILayout.Space(28); GUIStyle colStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 11 }; GUILayout.Label("动画名称", colStyle, GUILayout.Width(200)); GUILayout.Label("帧数", colStyle, GUILayout.Width(70)); GUILayout.Label("时长(秒)", colStyle, GUILayout.Width(70)); GUILayout.Label("帧率(FPS)", colStyle, GUILayout.Width(70)); GUILayout.Label("工程路径", colStyle); EditorGUILayout.EndHorizontal(); GUI.Box(GUILayoutUtility.GetLastRect(), ""); GUILayout.Space(2); // 使用单独的滚动区域 resultListScrollPos = EditorGUILayout.BeginScrollView(resultListScrollPos, GUILayout.MaxHeight(400)); for (int i = 0; i < results.Count; i++) { var r = results[i]; // 交替行背景 Color rowColor = (i % 2 == 0) ? new Color(0, 0, 0, 0.1f) : new Color(0, 0, 0, 0.05f); GUIStyle rowStyle = new GUIStyle(GUI.skin.box); Texture2D rowTex = new Texture2D(1, 1); rowTex.SetPixel(0, 0, rowColor); rowTex.Apply(); rowStyle.normal.background = rowTex; EditorGUILayout.BeginVertical(rowStyle); EditorGUILayout.BeginHorizontal(); GUILayout.Space(5); GUILayout.Label((i + 1).ToString(), GUILayout.Width(22)); // 动画名称 GUIContent clipIcon = EditorGUIUtility.IconContent("d_AnimationClip Icon"); GUILayout.Label(clipIcon, GUILayout.Width(20), GUILayout.Height(20)); GUILayout.Label(r.name, GUILayout.Width(200)); // 帧数 GUIStyle frameStyle = new GUIStyle(GUI.skin.label) { fontSize = 13, fontStyle = FontStyle.Bold, alignment = TextAnchor.MiddleCenter }; GUILayout.Label(r.frameCount.ToString(), frameStyle, GUILayout.Width(70)); // 时长 GUILayout.Label(r.length.ToString("F3"), GUILayout.Width(70)); // 帧率 GUILayout.Label(r.frameRate.ToString("F1"), GUILayout.Width(70)); // 路径(可点击定位) GUILayout.FlexibleSpace(); string displayPath = r.assetPath; if (GUILayout.Button("📍 定位", GUILayout.Width(50), GUILayout.Height(20))) { // 在Project窗口中高亮选中该资源 var obj = AssetDatabase.LoadAssetAtPath(r.assetPath); if (obj != null) { EditorGUIUtility.PingObject(obj); Selection.activeObject = obj; } } GUIStyle pathStyle = new GUIStyle(EditorStyles.miniLabel) { fontSize = 10 }; GUILayout.Label(displayPath, pathStyle); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); } EditorGUILayout.EndScrollView(); } // ===================== 背景设置 ===================== private void DrawBgSettingsInner() { EditorGUILayout.HelpBox("拖入背景图片(Texture2D)或选择GIF文件,拉伸填充整个工具窗口。", MessageType.Info); GUILayout.Space(4); Texture2D newBg = (Texture2D)EditorGUILayout.ObjectField("背景图片", bgImageGlobal, typeof(Texture2D), false); if (newBg != bgImageGlobal) { bgImageGlobal = newBg; ClearGif(); SaveBgImage(); } string gifPath = EditorGUILayout.TextField("GIF文件路径", isGif ? gifSourcePath : ""); if (GUILayout.Button("选择GIF文件")) { string selected = EditorUtility.OpenFilePanel("选择GIF图片", "", "gif"); if (!string.IsNullOrEmpty(selected)) { LoadGifFromFile(selected); gifPath = selected; } } if (GUILayout.Button("清除GIF")) { ClearGif(); gifPath = ""; } GUILayout.Space(4); } // ===================== 核心检测逻辑 ===================== /// /// 将绝对路径转换为 Unity AssetDatabase 可用的 Assets 相对路径 /// private static string GetAssetPath(string absolutePath) { if (string.IsNullOrEmpty(absolutePath)) return ""; try { string dataPath = System.IO.Path.GetFullPath(Application.dataPath).Replace('\\', '/').TrimEnd('/'); string fullPath = System.IO.Path.GetFullPath(absolutePath).Replace('\\', '/').TrimEnd('/'); if (fullPath.StartsWith(dataPath, System.StringComparison.OrdinalIgnoreCase)) return "Assets" + fullPath.Substring(dataPath.Length); } catch { } return ""; } private void RunCheck() { if (clips.Count == 0) { EditorUtility.DisplayDialog("提示", "请先添加 Animation Clip!", "确定"); return; } results.Clear(); foreach (var clip in clips) { string assetPath = AssetDatabase.GetAssetPath(clip); float length = clip.length; float frameRate = 0f; int frameCount = 0; // 获取帧率:优先读取AnimationClip的帧率设置 // Unity中AnimationClip没有直接的frameRate属性, // 通过遍历所有curve的最后一个keyframe的时间推算帧率 frameRate = GetClipFrameRate(clip); if (frameRate > 0f) frameCount = Mathf.RoundToInt(length * frameRate); else frameCount = GetClipFrameCountFromCurves(clip); results.Add(new ClipResult { name = clip.name, assetPath = assetPath, frameCount = frameCount, length = length, frameRate = frameRate > 0f ? frameRate : (frameCount > 0 ? frameCount / length : 0f) }); } // 按时长(秒)从小到大排序 results.Sort((a, b) => a.length.CompareTo(b.length)); hasRunCheck = true; Repaint(); Debug.Log($"[帧数检测] 完成,共检测 {results.Count} 个动画"); } /// /// 通过分析动画曲线推算帧率。 /// 取所有绑定中最大的 keyframe 时间差,近似帧间隔,帧率 = 1/间隔。 /// private float GetClipFrameRate(AnimationClip clip) { if (clip == null) return 0f; // 优先使用 EditorCurveBinding 读取曲线 var bindings = AnimationUtility.GetCurveBindings(clip); if (bindings.Length == 0) return 0f; float minInterval = float.MaxValue; foreach (var binding in bindings) { var curve = AnimationUtility.GetEditorCurve(clip, binding); if (curve == null || curve.length == 0) continue; float prevTime = curve.keys[0].time; for (int i = 1; i < curve.length; i++) { float diff = curve.keys[i].time - prevTime; if (diff > 0.001f && diff < minInterval) minInterval = diff; prevTime = curve.keys[i].time; } } if (minInterval < float.MaxValue && minInterval > 0f) return 1f / minInterval; return 0f; } /// /// 备用方案:直接计算所有 curve 中不同时间点的数量作为帧数。 /// private int GetClipFrameCountFromCurves(AnimationClip clip) { var bindings = AnimationUtility.GetCurveBindings(clip); var frameTimes = new HashSet(); foreach (var binding in bindings) { var curve = AnimationUtility.GetEditorCurve(clip, binding); if (curve == null) continue; foreach (var key in curve.keys) frameTimes.Add((float)System.Math.Round(key.time, 4)); } return frameTimes.Count; } // ===================== 通用 UI 辅助 ===================== private delegate void GUIAction(); private void DrawSectionHeader(string title, Color bgColor) { EditorGUILayout.Space(3); var prevBg = GUI.backgroundColor; GUI.backgroundColor = bgColor; EditorGUILayout.BeginHorizontal("box"); GUI.backgroundColor = prevBg; GUIStyle headerStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 13, fontStyle = FontStyle.Bold, alignment = TextAnchor.MiddleLeft, padding = new RectOffset(4, 4, 2, 2) }; GUIContent iconContent = EditorGUIUtility.IconContent("d_SettingsIcon"); GUILayout.Label(iconContent, GUILayout.Width(20), GUILayout.Height(20)); GUILayout.Label(title, headerStyle); GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); } private static readonly Dictionary bgTextures = new Dictionary(); private Texture2D GetOrCreateBgTexture(string key, Color color) { if (!bgTextures.TryGetValue(key, out var tex)) { tex = new Texture2D(1, 1); tex.SetPixel(0, 0, color); tex.Apply(); bgTextures[key] = tex; } return tex; } private void DrawSectionContent(GUIAction drawAction, Color bgColor) { GUIStyle bgStyle = new GUIStyle(GUI.skin.box); bgStyle.normal.background = GetOrCreateBgTexture(bgColor.GetHashCode().ToString(), bgColor); EditorGUILayout.BeginVertical(bgStyle); drawAction(); EditorGUILayout.EndVertical(); } // ===================== 背景图片/GIF ===================== private void SaveBgImage() { if (isGif && !string.IsNullOrEmpty(gifSourcePath)) { EditorPrefs.SetString(BgImagePrefKey, "GIF:" + gifSourcePath); } else if (bgImageGlobal != null) { EditorPrefs.SetString(BgImagePrefKey, AssetDatabase.GetAssetPath(bgImageGlobal)); } else { EditorPrefs.DeleteKey(BgImagePrefKey); } } private void LoadBgImage() { string raw = EditorPrefs.GetString(BgImagePrefKey, ""); if (string.IsNullOrEmpty(raw)) return; if (raw.StartsWith("GIF:")) { string gifPath = raw.Substring(4); if (System.IO.File.Exists(gifPath)) LoadGifFromFile(gifPath); else EditorPrefs.DeleteKey(BgImagePrefKey); } else { bgImageGlobal = AssetDatabase.LoadAssetAtPath(raw); if (bgImageGlobal == null) EditorPrefs.DeleteKey(BgImagePrefKey); } } private void ClearGif() { if (gifFrames != null) { for (int i = 0; i < gifFrames.Length; i++) { if (gifFrames[i] != null) DestroyImmediate(gifFrames[i]); } gifFrames = null; gifDelays = null; } isGif = false; gifSourcePath = ""; gifTotalDuration = 0; } private void LoadGifFromFile(string filePath) { ClearGif(); if (!System.IO.File.Exists(filePath)) return; try { byte[] data = System.IO.File.ReadAllBytes(filePath); if (data.Length < 13 || data[0] != 'G' || data[1] != 'I' || data[2] != 'F') { Debug.LogWarning("不是GIF格式文件"); return; } int pos = 6; int screenWidth = data[pos] | (data[pos + 1] << 8); int screenHeight = data[pos + 2] | (data[pos + 3] << 8); pos += 4; byte packed = data[pos++]; pos++; // bg color index pos++; // pixel aspect ratio Color32[] gct = null; if ((packed & 0x80) != 0) { int gctCount = 1 << ((packed & 0x07) + 1); gct = ReadColorTable(data, ref pos, gctCount); } var frames = new List(); var delays = new List(); Color32[] canvas = new Color32[screenWidth * screenHeight]; Color32 bgColor = (gct != null) ? gct[0] : new Color32(0, 0, 0, 255); for (int i = 0; i < canvas.Length; i++) canvas[i] = bgColor; int gceDisposal = 0, gceTransIdx = -1, gceDelay = 100; Color32[] savedCanvas = null; while (pos < data.Length) { byte blockType = data[pos++]; if (blockType == 0x3B) break; // trailer if (blockType == 0x21) // extension { byte label = data[pos++]; if (label == 0xF9) // GCE { pos++; // block size byte gcPacked = data[pos++]; gceDisposal = (gcPacked >> 2) & 0x07; gceTransIdx = ((gcPacked & 0x01) != 0) ? data[pos++] : -1; if (gceTransIdx == -1) pos++; gceDelay = (data[pos] | (data[pos + 1] << 8)) * 10; if (gceDelay == 0) gceDelay = 100; pos += 2; pos++; // terminator } else { // skip other extensions while (pos < data.Length) { int sz = data[pos++]; if (sz == 0) break; pos += sz; } } continue; } if (blockType == 0x2C) // image descriptor { int left = data[pos] | (data[pos + 1] << 8); int top = data[pos + 2] | (data[pos + 3] << 8); int width = data[pos + 4] | (data[pos + 5] << 8); int height = data[pos + 6] | (data[pos + 7] << 8); pos += 8; byte imgPacked = data[pos++]; Color32[] lct = null; if ((imgPacked & 0x80) != 0) { int lctCount = 1 << ((imgPacked & 0x07) + 1); lct = ReadColorTable(data, ref pos, lctCount); } Color32[] colorTable = lct ?? gct ?? new Color32[256]; int minCodeSize = data[pos++]; int lzwPos = pos; int bytesRemaining = 0; int bytePos = 0; byte[] subBlock = new byte[256]; int clearCode = 1 << minCodeSize; int eoiCode = clearCode + 1; int codeSize = minCodeSize + 1; int codeMask = (1 << codeSize) - 1; int oldCode = -1; var codeTable = new Dictionary>(); InitializeLzwTable(codeTable, clearCode); int x = 0, y = 0; bool interlaced = (imgPacked & 0x40) != 0; int pass = 0; int[] interlaceOffsets = { 0, 4, 2, 1 }; int[] interlaceSteps = { 8, 8, 4, 2 }; int pixelIdx = 0; var pixelBuffer = new List(); while (pixelIdx < width * height) { int code = ReadNextLzwCode(data, ref lzwPos, ref bytesRemaining, ref bytePos, ref subBlock, minCodeSize, codeSize, ref codeMask, ref codeTable, ref oldCode, clearCode, eoiCode, pixelBuffer); if (code == -1) break; foreach (byte b in pixelBuffer) { if (pixelIdx >= width * height) break; int px = x; int py = interlaced ? GetInterlacedY(y, ref pass, height) : y; if (px < width && py < height) { int idx = (top + py) * screenWidth + (left + px); if (idx >= 0 && idx < canvas.Length) { int cidx = b; if (gceTransIdx >= 0 && cidx == gceTransIdx) { } else if (cidx < colorTable.Length) canvas[idx] = colorTable[cidx]; } } x++; if (x >= width) { x = 0; y++; } pixelIdx++; } } // 保存disposal=3需要的画布 if (gceDisposal == 3) { savedCanvas = new Color32[canvas.Length]; for (int ci = 0; ci < canvas.Length; ci++) savedCanvas[ci] = canvas[ci]; } // 生成帧纹理 var frameTex = new Texture2D(screenWidth, screenHeight, TextureFormat.RGBA32, false); frameTex.SetPixels32(canvas); frameTex.Apply(false, false); frames.Add(frameTex); delays.Add(gceDelay); // disposal处理 if (gceDisposal == 2) // restore to bg { Color32 bgCol = (gct != null && data[11] < gct.Length) ? gct[data[11]] : new Color32(0, 0, 0, 255); for (int fy = top; fy < top + height && fy < screenHeight; fy++) for (int fx = left; fx < left + width && fx < screenWidth; fx++) canvas[fy * screenWidth + fx] = bgCol; } else if (gceDisposal == 3 && savedCanvas != null) { for (int ci = 0; ci < canvas.Length; ci++) canvas[ci] = savedCanvas[ci]; } // 跳到下一个块 while (pos < data.Length) { int sz = data[pos++]; if (sz == 0) break; pos += sz; } } } if (frames.Count > 0) { gifFrames = frames.ToArray(); gifDelays = delays.ToArray(); gifTotalDuration = 0; foreach (int d in gifDelays) gifTotalDuration += d; isGif = true; gifSourcePath = filePath; gifStartTime = EditorApplication.timeSinceStartup; SaveBgImage(); Debug.Log($"[GIF背景] 加载完成:{gifFrames.Length} 帧,总时长 {gifTotalDuration}ms"); } } catch (System.Exception e) { Debug.LogError("GIF解析失败: " + e.Message); ClearGif(); } } private Color32[] ReadColorTable(byte[] data, ref int pos, int count) { Color32[] table = new Color32[count]; for (int i = 0; i < count; i++) { table[i] = new Color32(data[pos], data[pos + 1], data[pos + 2], 255); pos += 3; } return table; } private static void InitializeLzwTable(Dictionary> table, int clearCode) { table.Clear(); for (int i = 0; i < clearCode; i++) table[i] = new List { (byte)i }; table[clearCode] = new List(); // clear table[clearCode + 1] = new List(); // eoi } private static int ReadNextLzwCode(byte[] data, ref int lzwPos, ref int bytesRemaining, ref int bytePos, ref byte[] subBlock, int minCodeSize, int codeSize, ref int codeMask, ref Dictionary> codeTable, ref int oldCode, int clearCode, int eoiCode, List output) { output.Clear(); int code = 0; int bitsNeeded = codeSize; int bitsRead = 0; while (bitsRead < bitsNeeded) { if (bytesRemaining == 0) { if (lzwPos >= data.Length) return -1; int sz = data[lzwPos++]; if (sz == 0) return -1; bytesRemaining = sz; for (int i = 0; i < sz; i++) subBlock[i] = data[lzwPos++]; bytePos = 0; } int bitsAvail = 8 * bytesRemaining - bytePos; int bitsToRead = System.Math.Min(bitsAvail, bitsNeeded - bitsRead); int mask = (1 << bitsToRead) - 1; int val = (subBlock[bytePos >> 3] >> (bytePos & 7)) & mask; bytePos += bitsToRead; code |= val << bitsRead; bitsRead += bitsToRead; } if (code == clearCode) { codeSize = minCodeSize + 1; codeMask = (1 << codeSize) - 1; oldCode = -1; InitializeLzwTable(codeTable, clearCode); return -1; } if (code == eoiCode) return -1; if (codeTable.TryGetValue(code, out var entry)) { output.AddRange(entry); if (oldCode >= 0 && codeTable.ContainsKey(oldCode)) { var newEntry = new List(codeTable[oldCode]); newEntry.Add(entry[0]); codeTable[codeTable.Count] = newEntry; if (codeTable.Count > codeMask + 1 && codeSize < 12) { codeSize++; codeMask = (1 << codeSize) - 1; } } oldCode = code; } return code; } private static int GetInterlacedY(int y, ref int pass, int height) { int[] offsets = { 0, 4, 2, 1 }; int[] steps = { 8, 8, 4, 2 }; int py = offsets[pass] + y * steps[pass]; while (py >= height) { pass++; if (pass > 3) { pass = 0; return y; } py = offsets[pass] + y * steps[pass]; } return py; } }