diff --git a/CHANGELOG.md b/CHANGELOG.md index 7078cf9..9882474 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,12 @@ # Changelog + +## [6.0.9] - 2023-12-14 +### Fixed +- Provide custom packing override for users to override to address DANB-526. (Case DANB-526) + ## [6.0.8] - 2023-10-25 ### Fixed -- Fixed an issue where PSDImporter atlas size does not follow PVRTC compression format for the iOS platform +- Fixed an issue where PSDImporter atlas size does not follow PVRTC compression format for the iOS platform. ## [6.0.7] - 2022-12-01 ### Fixed diff --git a/Editor/PSDImporter.cs b/Editor/PSDImporter.cs index 7720bce..e54d30f 100644 --- a/Editor/PSDImporter.cs +++ b/Editor/PSDImporter.cs @@ -5,6 +5,7 @@ using UnityEngine; using Unity.Collections; using System.Linq; +using System.Reflection; using UnityEditor.AssetImporters; using UnityEditor.U2D.Animation; using UnityEditor.U2D.Common; @@ -91,7 +92,7 @@ internal enum ELayerMappingOption wrapModeV = TextureWrapMode.Repeat, wrapModeW = TextureWrapMode.Repeat, }; - + [SerializeField] // SpriteData for both single and multiple mode List m_SpriteImportData = new List(); // we use index 0 for single sprite and the rest for multiple sprites @@ -133,10 +134,15 @@ internal enum ELayerMappingOption [SerializeField] bool m_KeepDupilcateSpriteName = true; - - [SerializeField] + + [SerializeField] private string m_SkeletonAssetReferenceID = null; + [SerializeField] + ScriptableObject m_Pipeline; + [SerializeField] + string m_PipelineVersion; + [SerializeField] SpriteCategoryList m_SpriteCategoryList = new SpriteCategoryList() {categories = new List()}; GameObjectCreationFactory m_GameObjectFactory = new GameObjectCreationFactory(); @@ -150,10 +156,10 @@ internal PSDImportData importData var returnValue = m_ImportData; if (returnValue == null) returnValue = AssetDatabase.LoadAllAssetsAtPath(assetPath).FirstOrDefault(x => x is PSDImportData) as PSDImportData; - + if (returnValue == null) returnValue = ScriptableObject.CreateInstance(); - + m_ImportData = returnValue; return returnValue; } @@ -162,7 +168,7 @@ internal PSDImportData importData internal int textureActualWidth { get => importData.textureActualWidth; - private set =>importData.textureActualWidth = value; + private set =>importData.textureActualWidth = value; } internal int textureActualHeight @@ -185,7 +191,7 @@ internal int textureActualHeight List m_SharedRigPSDLayers = new List(); [SerializeField] PSDLayerImportSetting[] m_PSDLayerImportSetting; - + [SerializeField] bool m_GenerateGOHierarchy = false; @@ -197,15 +203,39 @@ internal int textureActualHeight [SerializeField] string m_SpriteLibAssetName = null; - + [SerializeField] string m_SkeletonAssetName = null; [SerializeField] SecondarySpriteTexture[] m_SecondarySpriteTextures; - + PSDExtractLayerData[] m_ExtractData; + void PackImage(NativeArray[] buffers, int[] width, int[] height, int padding, out NativeArray outPackedBuffer, out int outPackedBufferWidth, out int outPackedBufferHeight, out RectInt[] outPackedRect, out Vector2Int[] outUVTransform, bool requireSquarePOT = false) + { + try + { + ScriptableObject pipeline = m_Pipeline; + if(pipeline == null) + pipeline = AssetDatabase.LoadAssetAtPath("Packages/com.unity.2d.psdimporter/Editor/Pipeline.asset"); + + var args = new object[] { buffers, width, height, padding, (uint)0, null, 0, 0, null, null, requireSquarePOT }; + pipeline.GetType().InvokeMember("PackImage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Static, null, + pipeline, args); + outPackedBuffer = (NativeArray)args[5]; + outPackedBufferWidth = (int)args[6]; + outPackedBufferHeight = (int)args[7]; + outPackedRect = (RectInt[])args[8]; + outUVTransform = (Vector2Int[])args[9]; + } + catch (Exception e) + { + Debug.LogError("Unable to pack image. ex:"+e.ToString(), this); + ImagePacker.Pack(buffers, width, height, padding, out outPackedBuffer, out outPackedBufferWidth, out outPackedBufferHeight, out outPackedRect, out outUVTransform, requireSquarePOT); + } + } + /// /// Implementation of ScriptedImporter.OnImportAsset /// @@ -217,11 +247,11 @@ public override void OnImportAsset(AssetImportContext ctx) { var fileStream = new FileStream(ctx.assetPath, FileMode.Open, FileAccess.Read); Document doc = null; - + if(m_ImportData == null) m_ImportData = ScriptableObject.CreateInstance(); m_ImportData.hideFlags = HideFlags.HideInHierarchy; - + try { UnityEngine.Profiling.Profiler.BeginSample("OnImportAsset"); @@ -229,9 +259,9 @@ public override void OnImportAsset(AssetImportContext ctx) UnityEngine.Profiling.Profiler.BeginSample("PsdLoad"); doc = PaintDotNet.Data.PhotoshopFileType.PsdLoad.Load(fileStream); UnityEngine.Profiling.Profiler.EndSample(); - + m_ImportData.CreatePSDLayerData(doc.Layers); - + ValidatePSDLayerId(doc); SetDocumentImportData(doc); @@ -247,7 +277,7 @@ public override void OnImportAsset(AssetImportContext ctx) { var spriteImportData = GetSpriteImportData(); FlattenImageTask.Execute(m_ExtractData, ref outputImageBuffer, m_ImportHiddenLayers, canvasSize); - + if (spriteImportData.Count <= 0 || spriteImportData[0] == null) { spriteImportData.Add(new SpriteMetaData()); @@ -268,11 +298,11 @@ public override void OnImportAsset(AssetImportContext ctx) else if (spriteImportData.Count > 1) spriteRects = spriteImportData.GetRange(1, spriteDataCount).ToArray(); } - + var output = ImportTexture(ctx, outputImageBuffer, doc.width, doc.height, spriteRects); importData.importedTextureWidth = output.texture.width; importData.importedTextureHeight = output.texture.height; - + RegisterAssets(ctx, output); } finally @@ -284,7 +314,7 @@ public override void OnImportAsset(AssetImportContext ctx) { ImportFromLayers(ctx); } - + if (!string.IsNullOrEmpty(m_SkeletonAssetReferenceID)) { var primaryAssetPath = AssetDatabase.GUIDToAssetPath(m_SkeletonAssetReferenceID); @@ -316,7 +346,7 @@ static void ValidatePSDLayerId(IEnumerable oldPsdLayer, IEnumerable oldPsdLayer, IEnumerable x.name == childBitmapLayer.Name); if (oldLayers.Count() == 0) - oldLayers = oldPsdLayer.Where(x => x.layerID == childBitmapLayer.Name.GetHashCode()); + oldLayers = oldPsdLayer.Where(x => x.layerID == childBitmapLayer.Name.GetHashCode()); // pick one that is not already on the list foreach (var ol in oldLayers) { @@ -340,7 +370,7 @@ static void ValidatePSDLayerId(IEnumerable oldPsdLayer, IEnumerable oldPsdLayer, IEnumerable imageData, int textureWidth, int textureHeight, SpriteMetaData[] sprites) { if (!imageData.IsCreated || imageData.Length == 0) return new TextureGenerationOutput(); - + UnityEngine.Profiling.Profiler.BeginSample("ImportTexture"); var platformSettings = GetPlatformTextureSettings(ctx.selectedBuildTarget); @@ -408,10 +438,10 @@ TextureGenerationOutput ImportTexture(AssetImportContext ctx, NativeArray layers, PSDExtractLayerData[ importSetting = new PSDLayerImportSetting() { flatten = c.flatten, - }; + }; } importSetting.spriteId = c.spriteID; } - + if (importSetting == null) { importSetting = new PSDLayerImportSetting() { flatten = false - }; + }; } - + extractData[i] = new PSDExtractLayerData() { bitmapLayer = layer, importSetting = importSetting, }; - + PSDExtractLayerData[] childrenextractData = null; if (layer.ChildLayer != null) { @@ -492,7 +522,7 @@ void SetDocumentImportData(IEnumerable layers, PSDExtractLayerData[ extractData[i].children = childrenextractData; } } - + void SetDocumentImportData(Document doc) { var oldPsdLayers = GetPSDLayers(); @@ -500,7 +530,7 @@ void SetDocumentImportData(Document doc) m_ExtractData = new PSDExtractLayerData[doc.Layers.Count]; SetDocumentImportData(doc.Layers, m_ExtractData, mappingStrategy, oldPsdLayers); } - + void ImportFromLayers(AssetImportContext ctx) { var output = default(NativeArray); @@ -516,7 +546,7 @@ void ImportFromLayers(AssetImportContext ctx) try { ExtractLayerTask.Execute(in m_ExtractData, out psdLayers, m_ImportHiddenLayers, canvasSize); - + var mappingStrategy = GetLayerMappingStrategy(); var layerUnique = mappingStrategy.LayersUnique(psdLayers.ConvertAll(x => (IPSDLayerMappingStrategyComparable)x)); if (!string.IsNullOrEmpty(layerUnique)) @@ -565,8 +595,8 @@ void ImportFromLayers(AssetImportContext ctx) } const int padding = 4; - ImagePacker.Pack(layerBuffers.ToArray(), layerWidth.ToArray(), layerHeight.ToArray(), padding, out output, out int width, out int height, out RectInt[] spriteData, out Vector2Int[] uvTransform, requireSquarePOT); - + PackImage(layerBuffers.ToArray(), layerWidth.ToArray(), layerHeight.ToArray(), padding, out output, out int width, out int height, out RectInt[] spriteData, out Vector2Int[] uvTransform); + var packOffsets = new Vector2[spriteData.Length]; for (var i = 0; i < packOffsets.Length; ++i) packOffsets[i] = new Vector2((uvTransform[i].x - spriteData[i].position.x) / -1f, (uvTransform[i].y - spriteData[i].position.y) / -1f); @@ -595,14 +625,14 @@ void ImportFromLayers(AssetImportContext ctx) r.position = r.position - psdLayer.mosaicPosition + spriteData[i].position; spriteSheet.rect = r; } - + psdLayer.spriteName = GetUniqueSpriteName(psdLayer.name, spriteNameHash); spriteSheet.name = psdLayer.spriteName; spriteSheet.spritePosition = psdLayer.layerPosition + packOffsets[i]; - + if(shouldResliceFromLayer) spriteSheet.rect = new Rect(spriteData[i].x, spriteData[i].y, spriteData[i].width, spriteData[i].height); - + spriteSheet.uvTransform = uvTransform[i]; psdLayer.spriteID = spriteSheet.spriteID; @@ -687,7 +717,7 @@ void ImportFromLayers(AssetImportContext ctx) importData.importedTextureWidth = textureActualWidth = width; var generatedTexture = ImportTexture(ctx, output, width, height, spriteImportData.ToArray()); - + if (generatedTexture.texture) { importData.importedTextureHeight = generatedTexture.texture.height; @@ -733,10 +763,10 @@ void RegisterAssets(AssetImportContext ctx, TextureGenerationOutput output) { Debug.LogWarning("No Sprites or Texture are generated. Possibly because all layers in file are hidden", this); return; - } - + } + SetPhysicsOutline(output.texture, output.sprites); - + UniqueNameGenerator assetNameHash = new UniqueNameGenerator(); if (!string.IsNullOrEmpty(output.importInspectorWarnings)) { @@ -761,7 +791,7 @@ void RegisterAssets(AssetImportContext ctx, TextureGenerationOutput output) var registerPrefabNameId = string.IsNullOrEmpty(m_PrefabAssetName) ? "Prefab" : m_PrefabAssetName; var spriteLibAssetNameId = string.IsNullOrEmpty(m_SpriteLibAssetName) ? "SpriteLibAsset" : m_SpriteLibAssetName; var skeletonAssetNameId = string.IsNullOrEmpty(m_SkeletonAssetName) ? "SkeletonAsset" : m_SkeletonAssetName; - + output.texture.name = assetName; ctx.AddObjectToAsset(registerTextureNameId, output.texture, output.thumbNail); UnityEngine.Object mainAsset = output.texture; @@ -837,7 +867,7 @@ bool VisibleInHierarchy(List psdGroup, int index) parentVisible = VisibleInHierarchy(psdGroup, psdLayer.parentIndex); return parentVisible && psdLayer.isVisible; } - + void BuildGroupGameObject(List psdGroup, int index, Transform root) { var psdData = psdGroup[index]; @@ -866,7 +896,7 @@ void BuildGroupGameObject(List psdGroup, int index, Transform root) psdData.gameObject.transform.SetParent(root); psdData.gameObject.transform.SetSiblingIndex(root.childCount-1); } - + } } @@ -951,7 +981,7 @@ void CreateBoneGO(int index, SpriteBone[] bones, BoneGO[] bonesGO, Transform def { go = go, index = index - }; + }; } BoneGO[] CreateBonesGO(Transform root) @@ -1091,7 +1121,7 @@ GameObject OnProducePrefab(string assetname, Sprite[] sprites, SpriteLibraryAsse var spriteRenderer = l.gameObject.AddComponent(); spriteRenderer.sprite = sprite; spriteRenderer.sortingOrder = psdLayers.Count - i; - + var pivot = spriteMetaData.pivot; pivot.x *= spriteMetaData.rect.width; pivot.y *= spriteMetaData.rect.height; @@ -1100,7 +1130,7 @@ GameObject OnProducePrefab(string assetname, Sprite[] sprites, SpriteLibraryAsse spritePosition.x += pivot.x; spritePosition.y += pivot.y; spritePosition *= (definitionScale / sprite.pixelsPerUnit); - + l.gameObject.transform.position = new Vector3(spritePosition.x, spritePosition.y, 0f); if (characterSkeleton != null) @@ -1221,7 +1251,7 @@ bool CleanUpGameobjectsWithOutRig(GameObject root) return false; } - + // ISpriteEditorDataProvider interface internal SpriteImportMode spriteImportMode @@ -1410,7 +1440,7 @@ internal void InitSpriteEditorDataProvider() {} void ISpriteEditorDataProvider.InitSpriteEditorDataProvider() { InitSpriteEditorDataProvider(); - } + } internal SpriteRect[] GetSpriteRects() { @@ -1482,7 +1512,7 @@ internal SpriteRect GetSpriteDataFromAllMode(GUID guid) spriteMetaData = m_SpriteImportData.FirstOrDefault(x => x.spriteID == guid); return spriteMetaData; } - + internal SpriteRect GetSpriteData(GUID guid) { var spriteImportData = GetSpriteImportData(); @@ -1600,7 +1630,7 @@ internal void ReadTextureSettings(TextureImporterSettings dest) { m_TextureImporterSettings.CopyTo(dest); } - + internal SpriteBone[] mainSkeletonBones { get @@ -1614,7 +1644,7 @@ internal IPSDLayerMappingStrategy GetLayerMappingStrategy() { return m_MappingCompare[(int)m_LayerMappingOption]; } - + internal bool generatePhysicsOutline { get => m_GeneratePhysicsShape; @@ -1674,5 +1704,20 @@ void SetPhysicsOutline(Texture2D texture, Sprite[] sprites) } } } + + void SetDirty() + { + EditorUtility.SetDirty(this); + } + + internal bool isReadable + { + get => m_TextureImporterSettings.readable; + set + { + m_TextureImporterSettings.readable = value; + SetDirty(); + } + } } -} +} \ No newline at end of file diff --git a/Editor/PSDImporterEditor.cs b/Editor/PSDImporterEditor.cs index 89c64e3..f6e5c13 100644 --- a/Editor/PSDImporterEditor.cs +++ b/Editor/PSDImporterEditor.cs @@ -69,6 +69,7 @@ struct InspectorGUI SerializedProperty m_GeneratePhysicsShape; SerializedProperty m_LayerMappingOption; SerializedProperty m_PlatformSettingsArrProp; + SerializedProperty m_Pipeline; private SkeletonAsset m_SkeletonAsset; readonly int[] m_FilterModeOptions = (int[])(Enum.GetValues(typeof(FilterMode))); @@ -108,6 +109,7 @@ public override void OnEnable() m_SkeletonAssetReferenceID = serializedObject.FindProperty("m_SkeletonAssetReferenceID"); m_GeneratePhysicsShape = serializedObject.FindProperty("m_GeneratePhysicsShape"); m_LayerMappingOption = serializedObject.FindProperty("m_LayerMappingOption"); + m_Pipeline = serializedObject.FindProperty("m_Pipeline"); var textureImporterSettingsSP = serializedObject.FindProperty("m_TextureImporterSettings"); m_TextureType = textureImporterSettingsSP.FindPropertyRelative("m_TextureType"); @@ -154,7 +156,8 @@ public override void OnEnable() AlphaHandlingGUI, POTScaleGUI, ReadableGUI, - MipMapGUI + MipMapGUI, + CustomPipelineGUI, }; m_AdvanceInspectorGUI.Add(TextureImporterType.Sprite, advanceGUIAction); @@ -162,7 +165,8 @@ public override void OnEnable() { POTScaleGUI, ReadableGUI, - MipMapGUI + MipMapGUI, + CustomPipelineGUI, }; m_AdvanceInspectorGUI.Add(TextureImporterType.Default, advanceGUIAction); @@ -187,6 +191,14 @@ public override void OnEnable() InitPreview(); } + void CustomPipelineGUI() + { + if (Unsupported.IsDeveloperMode()) + { + EditorGUILayout.PropertyField(m_Pipeline); + } + } + /// /// Override for AssetImporter.extraDataType /// diff --git a/Editor/Pipeline.asset b/Editor/Pipeline.asset new file mode 100644 index 0000000..9e63ea4 --- /dev/null +++ b/Editor/Pipeline.asset @@ -0,0 +1,14 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 90847c0a31834ab383dcd062f4ec41bd, type: 3} + m_Name: Pipeline + m_EditorClassIdentifier: diff --git a/Editor/Pipeline.asset.meta b/Editor/Pipeline.asset.meta new file mode 100644 index 0000000..25251f3 --- /dev/null +++ b/Editor/Pipeline.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7a602206d712e496b8b4f2c9fb6ce07c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Pipeline.cs b/Editor/Pipeline.cs new file mode 100644 index 0000000..3d5a079 --- /dev/null +++ b/Editor/Pipeline.cs @@ -0,0 +1,21 @@ +using Unity.Collections; +using UnityEditor.U2D.Common; +using UnityEngine; + +namespace UnityEditor.U2D.PSD +{ + //[CreateAssetMenu(fileName = "Pipeline.asset", menuName = "2D/PSDImporter Pipeline")] + class Pipeline : ScriptableObject + { + void PackImage(NativeArray[] buffers, + int[] width, int[] height, int padding, + uint spriteSizeExpand, + out NativeArray outPackedBuffer, + out int outPackedBufferWidth, out int outPackedBufferHeight, + out RectInt[] outPackedRect, out Vector2Int[] outUVTransform, + bool requireSquarePOT) + { + ImagePacker.Pack(buffers, width, height, padding, out outPackedBuffer, out outPackedBufferWidth, out outPackedBufferHeight, out outPackedRect, out outUVTransform); + } + } +} \ No newline at end of file diff --git a/Editor/Pipeline.cs.meta b/Editor/Pipeline.cs.meta new file mode 100644 index 0000000..507450b --- /dev/null +++ b/Editor/Pipeline.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 90847c0a31834ab383dcd062f4ec41bd +timeCreated: 1699106067 \ No newline at end of file diff --git a/Samples~/PSDImporterSamples/.sample.json b/Samples~/PSDImporterSamples/.sample.json new file mode 100644 index 0000000..bec248a --- /dev/null +++ b/Samples~/PSDImporterSamples/.sample.json @@ -0,0 +1,5 @@ +{ + "displayName": "2D PSDImporter Samples", + "interactiveImport": "true", + "description": "Various examples demonstrating the usage of 2D PSDImporter." +} diff --git a/Samples~/PSDImporterSamples/LICENSE.md b/Samples~/PSDImporterSamples/LICENSE.md new file mode 100644 index 0000000..46b111d --- /dev/null +++ b/Samples~/PSDImporterSamples/LICENSE.md @@ -0,0 +1,5 @@ +com.unity.2d.psdimporter Samples © 2023 Unity Technologies + +Licensed under the Unity Companion License for Unity-dependent projects (see https://unity3d.com/legal/licenses/unity_companion_license). + +Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions. \ No newline at end of file diff --git a/Samples~/PSDImporterSamples/LICENSE.md.meta b/Samples~/PSDImporterSamples/LICENSE.md.meta new file mode 100644 index 0000000..a398591 --- /dev/null +++ b/Samples~/PSDImporterSamples/LICENSE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b913af7f120cd4e4cb5485455afc348d +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/PSDImporterSamples/PSDImporterCustomPacker.meta b/Samples~/PSDImporterSamples/PSDImporterCustomPacker.meta new file mode 100644 index 0000000..0aee33a --- /dev/null +++ b/Samples~/PSDImporterSamples/PSDImporterCustomPacker.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f56e08d3303714e19a31605c961892d2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor.meta b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor.meta new file mode 100644 index 0000000..1c6740c --- /dev/null +++ b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 265de3b9572f8476bbe964e7d124579c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker.meta b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker.meta new file mode 100644 index 0000000..8894e3e --- /dev/null +++ b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 967bc1bc5f8444bfc807ffef1fdf5c7c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker/ImagePackNode.cs b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker/ImagePackNode.cs new file mode 100644 index 0000000..c12bc99 --- /dev/null +++ b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker/ImagePackNode.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace PSDImporterCustomPacker +{ + internal interface IImagePackNodeVisitor + { + void Visit(ImagePackNode node); + } + + class CollectEmptyNodePositionVisitor : IImagePackNodeVisitor + { + public List emptyAreas = new List(); + public void Visit(ImagePackNode node) + { + if (node.imageId == -1) + { + emptyAreas.Add(node.rect); + } + } + } + + class CollectPackNodePositionVisitor : IImagePackNodeVisitor + { + public CollectPackNodePositionVisitor() + { + positions = new Vector2Int[0]; + } + + public void Visit(ImagePackNode node) + { + if (node.imageId != -1) + { + if (positions.Length < node.imageId + 1) + { + var p = positions; + Array.Resize(ref p, node.imageId + 1); + positions = p; + } + + positions[node.imageId].x = node.rect.x; + positions[node.imageId].y = node.rect.y; + } + } + + public Vector2Int[] positions { get; private set; } + } + + internal class ImagePackNode + { + public ImagePackNode left; + public ImagePackNode right; + public RectInt rect; + public Vector2Int imageWidth; + public int imageId = -1; + + public void AcceptVisitor(IImagePackNodeVisitor visitor) + { + visitor.Visit(this); + if (left != null) + left.AcceptVisitor(visitor); + if (right != null) + right.AcceptVisitor(visitor); + } + + public void AdjustSize(int oriWidth, int oriHeight, int deltaW, int deltaH, out int adjustx, out int adjusty) + { + adjustx = adjusty = 0; + int adjustXleft = 0, adjustYleft = 0, adjustXRight = 0, adjustYRight = 0; + if (imageId == -1 || left == null) + { + if (rect.x + rect.width == oriWidth) + { + rect.width += deltaW; + adjustx = deltaW; + } + if (rect.y + rect.height == oriHeight) + { + rect.height += deltaH; + adjusty = deltaH; + } + } + else + { + left.AdjustSize(oriWidth, oriHeight, deltaW, deltaH, out adjustXleft, out adjustYleft); + right.AdjustSize(oriWidth, oriHeight, deltaW, deltaH, out adjustXRight, out adjustYRight); + + adjustx = Mathf.Max(adjustXleft, adjustXRight); + rect.width += adjustx; + adjusty = Mathf.Max(adjustYleft, adjustYRight); + rect.height += adjusty; + } + } + + public bool TryInsert(ImagePacker.ImagePackRect insert, int padding, out Vector2Int remainingSpace) + { + remainingSpace = Vector2Int.zero; + int insertWidth = insert.rect.width + padding * 2; + int insertHeight = insert.rect.height + padding * 2; + if (insertWidth > rect.width || insertHeight > rect.height) + return false; + + if (imageId == -1) + { + remainingSpace.x = rect.width - insertWidth; + remainingSpace.y = rect.height - insertHeight; + } + else + { + Vector2Int spaceLeft, spaceRight; + bool insertLeft, insertRight; + ImagePackNode tryLeft, tryRight; + tryLeft = left; + tryRight = right; + if (left == null && !SplitRects(this, insert, padding, out tryLeft, out tryRight)) + { + return false; + } + + insertLeft = tryLeft.TryInsert(insert, padding, out spaceLeft); + insertRight = tryRight.TryInsert(insert, padding, out spaceRight); + if (insertLeft && insertRight) + { + remainingSpace = spaceLeft.sqrMagnitude < spaceRight.sqrMagnitude ? spaceLeft : spaceRight; + } + else if (insertLeft) + remainingSpace = spaceLeft; + else if (insertRight) + remainingSpace = spaceRight; + else + return false; + } + + return true; + } + + static bool SplitRects(ImagePackNode node, ImagePacker.ImagePackRect insert, int padding, out ImagePackNode left, out ImagePackNode right) + { + // Find the best way to split the rect based on a new rect + left = right = null; + var tryRects = new[] + { + new ImagePackNode(), new ImagePackNode(), + new ImagePackNode(), new ImagePackNode() + }; + + tryRects[0].rect = new RectInt(node.rect.x + node.imageWidth.x, node.rect.y, node.rect.width - node.imageWidth.x, node.rect.height); + tryRects[1].rect = new RectInt(node.rect.x, node.rect.y + node.imageWidth.y, node.imageWidth.x, node.rect.height - node.imageWidth.y); + tryRects[2].rect = new RectInt(node.rect.x, node.rect.y + node.imageWidth.y, node.rect.width, node.rect.height - node.imageWidth.y); + tryRects[3].rect = new RectInt(node.rect.x + node.imageWidth.x, node.rect.y, node.rect.width - node.imageWidth.x, node.imageWidth.y); + float smallestSpace = float.MinValue; + for (int i = 0; i < tryRects.GetLength(0); ++i) + { + //for (int j = 0; j < tryRects.GetLength(1); ++j) + { + Vector2Int newSpaceLeft; + if (tryRects[i].TryInsert(insert, padding, out newSpaceLeft)) + { + if (smallestSpace < newSpaceLeft.sqrMagnitude) + { + smallestSpace = newSpaceLeft.sqrMagnitude; + int index = i / 2 * 2; + left = tryRects[index]; + right = tryRects[index + 1]; + } + } + } + } + return left != null; + } + + public bool Insert(ImagePacker.ImagePackRect insert, int padding) + { + int insertWidth = insert.rect.width + padding * 2; + int insertHeight = insert.rect.height + padding * 2; + if (insertWidth > rect.width || insertHeight > rect.height) + return false; + + if (imageId == -1) + { + imageId = insert.index; + imageWidth = new Vector2Int(insertWidth, insertHeight); + } + else + { + if (left == null && !SplitRects(this, insert, padding, out left, out right)) + { + return false; + } + // We assign to the node that has a better fit for the image + Vector2Int spaceLeft, spaceRight; + bool insertLeft, insertRight; + insertLeft = left.TryInsert(insert, padding, out spaceLeft); + insertRight = right.TryInsert(insert, padding, out spaceRight); + if (insertLeft && insertRight) + { + if (spaceLeft.sqrMagnitude < spaceRight.sqrMagnitude) + left.Insert(insert, padding); + else + right.Insert(insert, padding); + } + else if (insertLeft) + left.Insert(insert, padding); + else if (insertRight) + right.Insert(insert, padding); + else + return false; + } + return true; + } + } +} \ No newline at end of file diff --git a/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker/ImagePacker.cs b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker/ImagePacker.cs new file mode 100644 index 0000000..8064769 --- /dev/null +++ b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker/ImagePacker.cs @@ -0,0 +1,159 @@ +using System; +using UnityEngine; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace PSDImporterCustomPacker +{ + internal static class ImagePacker + { + /// + /// Given an array of rects, the method returns an array of rects arranged within outPackedWidth and outPackedHeight + /// + /// Rects to pack + /// Padding between each rect + /// Rects arranged within outPackedWidth and outPackedHeight + /// Width of the packed rects + /// Height of the packed rects + public static void Pack(RectInt[] rects, int padding, out RectInt[] outPackedRects, out int outPackedWidth, out int outPackedHeight) + { + var packNode = InternalPack(rects, padding); + outPackedWidth = packNode.rect.width; + outPackedHeight = packNode.rect.height; + var visitor = new CollectPackNodePositionVisitor(); + packNode.AcceptVisitor(visitor); + + outPackedRects = new RectInt[rects.Length]; + for (int i = 0; i < rects.Length; ++i) + outPackedRects[i] = new RectInt(visitor.positions[i].x + padding, visitor.positions[i].y + padding, rects[i].width, rects[i].height); + } + + /// + /// Packs image buffer into a single buffer. Image buffers are assumed to be 4 bytes per pixel in RGBA format + /// + /// Image buffers to pack + /// Image buffers width + /// Image buffers height + /// Padding between each packed image + /// Packed image buffer + /// Packed image buffer's width + /// Packed iamge buffer's height + /// Location of each image buffers in the packed buffer + /// Translation data from image original buffer to packed buffer + public static void Pack(NativeArray[] buffers, int[] width, int[] height, int padding, out NativeArray outPackedBuffer, out int outPackedBufferWidth, out int outPackedBufferHeight, out RectInt[] outPackedRect, out Vector2Int[] outUVTransform) + { + UnityEngine.Profiling.Profiler.BeginSample("Pack"); + // Determine the area that contains data in the buffer + outPackedBuffer = default(NativeArray); + try + { + var tightRects = FindTightRectJob.Execute(buffers, width, height); + Pack(tightRects, padding, out outPackedRect, out outPackedBufferWidth, out outPackedBufferHeight); + outUVTransform = new Vector2Int[tightRects.Length]; + for (int i = 0; i < outUVTransform.Length; ++i) + { + outUVTransform[i] = new Vector2Int(outPackedRect[i].x - tightRects[i].x, outPackedRect[i].y - tightRects[i].y); + } + outPackedBuffer = new NativeArray(outPackedBufferWidth * outPackedBufferHeight, Allocator.Persistent); + + Blit(outPackedBuffer, outPackedRect, outPackedBufferWidth, buffers, tightRects, width, padding); + } + catch (Exception ex) + { + if (outPackedBuffer.IsCreated) + outPackedBuffer.Dispose(); + throw ex; + } + finally + { + UnityEngine.Profiling.Profiler.EndSample(); + } + } + + static ImagePackNode InternalPack(RectInt[] rects, int padding) + { + if (rects == null || rects.Length == 0) + return new ImagePackNode() { rect = new RectInt(0, 0, 0, 0)}; + var sortedRects = new ImagePackRect[rects.Length]; + for (int i = 0; i < rects.Length; ++i) + { + sortedRects[i] = new ImagePackRect(); + sortedRects[i].rect = rects[i]; + sortedRects[i].index = i; + } + Array.Sort(sortedRects); + var root = new ImagePackNode(); + root.rect = new RectInt(0, 0, (int)NextPowerOfTwo((ulong)rects[0].width), (int)NextPowerOfTwo((ulong)rects[0].height)); + + for (int i = 0; i < rects.Length; ++i) + { + if (!root.Insert(sortedRects[i], padding)) // we can't fit + { + int newWidth = root.rect.width , newHeight = root.rect.height; + if (root.rect.width < root.rect.height) + newWidth = (int)NextPowerOfTwo((ulong)root.rect.width + 1); + else + newHeight = (int)NextPowerOfTwo((ulong)root.rect.height + 1); + // Reset all packing and try again + root = new ImagePackNode(); + root.rect = new RectInt(0, 0, newWidth, newHeight); + i = -1; + } + } + return root; + } + + public static unsafe void Blit(NativeArray buffer, RectInt[] blitToArea, int bufferbytesPerRow, NativeArray[] originalBuffer, RectInt[] blitFromArea, int[] bytesPerRow, int padding) + { + UnityEngine.Profiling.Profiler.BeginSample("Blit"); + + var c = (Color32*)buffer.GetUnsafePtr(); + for (int bufferIndex = 0; bufferIndex < blitToArea.Length && bufferIndex < originalBuffer.Length && bufferIndex < blitFromArea.Length; ++bufferIndex) + { + var b = (Color32*)originalBuffer[bufferIndex].GetUnsafeReadOnlyPtr(); + var rectFrom = blitFromArea[bufferIndex]; + var rectTo = blitToArea[bufferIndex]; + for (int i = 0; i < rectFrom.height; ++i) + { + for (int j = 0; j < rectFrom.width; ++j) + { + Color32 cc = b[(rectFrom.y + i) * bytesPerRow[bufferIndex] + rectFrom.x + j]; + c[((rectTo.y + i) * bufferbytesPerRow) + rectTo.x + j] = cc; + } + } + } + UnityEngine.Profiling.Profiler.EndSample(); + } + + internal static ulong NextPowerOfTwo(ulong v) + { + v -= 1; + v |= v >> 16; + v |= v >> 8; + v |= v >> 4; + v |= v >> 2; + v |= v >> 1; + return v + 1; + } + + internal class ImagePackRect : IComparable + { + public RectInt rect; + public int index; + + public int CompareTo(ImagePackRect obj) + { + var lhsArea = rect.width * rect.height; + var rhsArea = obj.rect.width * obj.rect.height; + if (lhsArea > rhsArea) + return -1; + if (lhsArea < rhsArea) + return 1; + if (index < obj.index) + return -1; + + return 1; + } + } + } +} \ No newline at end of file diff --git a/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker/ImagePacker.cs.meta b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker/ImagePacker.cs.meta new file mode 100644 index 0000000..d331c06 --- /dev/null +++ b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker/ImagePacker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8e7d441c9e1074052be514daa51da00b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker/Jobs.meta b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker/Jobs.meta new file mode 100644 index 0000000..f02203c --- /dev/null +++ b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/ImagePacker/Jobs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2c2c27855a1af4eb6ab84d3d68746f6b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/PSDImporterCustomPacker.asmdef b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/PSDImporterCustomPacker.asmdef new file mode 100644 index 0000000..f5a925e --- /dev/null +++ b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/PSDImporterCustomPacker.asmdef @@ -0,0 +1,16 @@ +{ + "name": "PSDImporterCustomPacker", + "rootNamespace": "", + "references": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/Pipeline.asset b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/Pipeline.asset new file mode 100644 index 0000000..f0a8731 --- /dev/null +++ b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/Pipeline.asset @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a6685f0db9a6426babc498e5330c2260, type: 3} + m_Name: Pipeline + m_EditorClassIdentifier: + m_ObjectsToApply: [] + m_ObjectsAppliedPreviously: [] diff --git a/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/Pipeline.asset.meta b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/Pipeline.asset.meta new file mode 100644 index 0000000..12089e3 --- /dev/null +++ b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/Editor/Pipeline.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e7ab7c5b6814049a1b493d82b6e14031 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/PSDImporterSamples/PSDImporterCustomPacker/README.md b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/README.md new file mode 100644 index 0000000..e3a5680 --- /dev/null +++ b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/README.md @@ -0,0 +1,9 @@ +# PSDImporter Custom Image Packer + +This example shows how to override the default image packing algorithm in the PSDImporter. + +The example utilizes the `m_Pipeline` SerializedProperty that is defined in the PSDImporter. + +The `m_Pipeline` is a ScriptableObject reference and in the PSDImporter it will determine what method is available in the SciptableObject and execute those methods accordingly. + +Refer to the `CustomPackScriptableObject.cs` for more details. \ No newline at end of file diff --git a/Samples~/PSDImporterSamples/PSDImporterCustomPacker/README.md.meta b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/README.md.meta new file mode 100644 index 0000000..5f3ab01 --- /dev/null +++ b/Samples~/PSDImporterSamples/PSDImporterCustomPacker/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f6afec105e3164ef28f858d824eb786a +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/PSDImporterSamples/README.md b/Samples~/PSDImporterSamples/README.md new file mode 100644 index 0000000..805c62c --- /dev/null +++ b/Samples~/PSDImporterSamples/README.md @@ -0,0 +1,2 @@ +# 2D PSDImporter Samples +2D PSDImporter samples showing various use cases. diff --git a/Samples~/PSDImporterSamples/README.md.meta b/Samples~/PSDImporterSamples/README.md.meta new file mode 100644 index 0000000..50ef435 --- /dev/null +++ b/Samples~/PSDImporterSamples/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c0b4d008961224d04ac8c041baf4a9b2 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json index f8ea74f..2811a3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.unity.2d.psdimporter", - "version": "6.0.8", + "version": "6.0.9", "unity": "2021.2", "unityRelease": "7f1", "displayName": "2D PSD Importer", @@ -12,23 +12,30 @@ ], "category": "2D", "dependencies": { - "com.unity.2d.animation": "7.0.13", + "com.unity.2d.animation": "7.1.0", "com.unity.2d.common": "6.0.7", "com.unity.2d.sprite": "1.0.0" }, "relatedPackages": { - "com.unity.2d.psdimporter.tests": "6.0.8" + "com.unity.2d.psdimporter.tests": "6.0.9" }, + "samples": [ + { + "displayName": "Samples", + "description": "Samples for using PSDImporter.", + "path": "Samples~/PSDImporterSamples" + } + ], "_upm": { - "changelog": "### Fixed\n- Fixed an issue where PSDImporter atlas size does not follow PVRTC compression format for the iOS platform" + "changelog": "### Fixed\n- Provide custom packing override for users to override to address DANB-526. (Case DANB-526)" }, "upmCi": { - "footprint": "26fb4e88d2fa9a182c2864b9a45d88ce5d1477a5" + "footprint": "ab90409b253685c853d2f83c0d2850e669098933" }, "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.2d.psdimporter@6.0/manual/index.html", "repository": { "url": "https://github.cds.internal.unity3d.com/unity/2d.git", "type": "git", - "revision": "681660f0ddc6412f1c6ffdc2f6433c5cc5adbe06" + "revision": "296f439c68fb9420382e9bd5ef0e2f626809b7b8" } }