338 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			338 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using System.Collections.Generic; | ||
|  | using Unity.Burst; | ||
|  | using Unity.Collections; | ||
|  | using Unity.Collections.LowLevel.Unsafe; | ||
|  | using Unity.Jobs; | ||
|  | using Unity.Mathematics; | ||
|  | using UnityEngine; | ||
|  | 
 | ||
|  | namespace UnityEditor.U2D.PSD | ||
|  | { | ||
|  |     internal static class ExtractLayerTask | ||
|  |     { | ||
|  |         struct LayerGroupData | ||
|  |         { | ||
|  |             public int startIndex { get; set; } | ||
|  |             public int endIndex { get; set; } | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// The layer's bounding box in document space. | ||
|  |             /// </summary> | ||
|  |             public int4 documentRect { get; set; } | ||
|  |         } | ||
|  | 
 | ||
|  |         [BurstCompile] | ||
|  |         struct ConvertBufferJob : IJobParallelFor | ||
|  |         { | ||
|  |             [ReadOnly, DeallocateOnJobCompletion] | ||
|  |             public NativeArray<int> inputTextureBufferSizes; | ||
|  |             [ReadOnly, DeallocateOnJobCompletion] | ||
|  |             public NativeArray<IntPtr> inputTextures; | ||
|  |             [ReadOnly, DeallocateOnJobCompletion] | ||
|  |             public NativeArray<int4> inputLayerRects; | ||
|  |             [ReadOnly, DeallocateOnJobCompletion] | ||
|  |             public NativeArray<LayerGroupData> layerGroupDataData; | ||
|  | 
 | ||
|  |             [ReadOnly, DeallocateOnJobCompletion] | ||
|  |             public NativeArray<int4> outputLayerRect; | ||
|  |             [DeallocateOnJobCompletion] | ||
|  |             public NativeArray<IntPtr> outputTextures; | ||
|  |             public unsafe void Execute(int groupIndex) | ||
|  |             { | ||
|  |                 Color32* outputColor = (Color32*)outputTextures[groupIndex]; | ||
|  |                 int groupStartIndex = layerGroupDataData[groupIndex].startIndex; | ||
|  |                 int groupEndIndex = layerGroupDataData[groupIndex].endIndex; | ||
|  | 
 | ||
|  |                 int outStartX = outputLayerRect[groupIndex].x; | ||
|  |                 int outStartY = outputLayerRect[groupIndex].y; | ||
|  |                 int outWidth = outputLayerRect[groupIndex].z; | ||
|  |                 int outHeight = outputLayerRect[groupIndex].w; | ||
|  | 
 | ||
|  |                 for (int layerIndex = groupEndIndex; layerIndex >= groupStartIndex; --layerIndex) | ||
|  |                 { | ||
|  |                     if (inputTextures[layerIndex] == IntPtr.Zero) | ||
|  |                         continue; | ||
|  | 
 | ||
|  |                     Color32* inputColor = (Color32*)inputTextures[layerIndex]; | ||
|  |                     int inX = inputLayerRects[layerIndex].x; | ||
|  |                     int inY = inputLayerRects[layerIndex].y; | ||
|  |                     int inWidth = inputLayerRects[layerIndex].z; | ||
|  |                     int inHeight = inputLayerRects[layerIndex].w; | ||
|  | 
 | ||
|  |                     for (int y = 0; y < inHeight; ++y) | ||
|  |                     { | ||
|  |                         int outPosY = (y + inY) - outStartY; | ||
|  |                         // If pixel is outside of output texture's Y, move to the next pixel. | ||
|  |                         if (outPosY < 0 || outPosY >= outHeight) | ||
|  |                             continue; | ||
|  | 
 | ||
|  |                         // Flip Y position on the input texture, because | ||
|  |                         // PSDs textures are stored "upside-down" | ||
|  |                         int inRow = ((inHeight - 1) - y) * inWidth; | ||
|  |                         int outRow = outPosY * outWidth; | ||
|  | 
 | ||
|  |                         for (int x = 0; x < inWidth; ++x) | ||
|  |                         { | ||
|  |                             int outPosX = (x + inX) - outStartX; | ||
|  |                             // If pixel is outside of output texture's X, move to the next pixel. | ||
|  |                             if (outPosX < 0 || outPosX >= outWidth) | ||
|  |                                 continue; | ||
|  | 
 | ||
|  |                             int inBufferIndex = inRow + x; | ||
|  |                             int outBufferIndex = outRow + outPosX; | ||
|  |                             if (outBufferIndex < 0 || outBufferIndex > (outWidth * outHeight)) | ||
|  |                                 continue; | ||
|  | 
 | ||
|  |                             Color inColor = inputColor[inBufferIndex]; | ||
|  |                             Color prevOutColor = outputColor[outBufferIndex]; | ||
|  |                             Color outColor = new Color(); | ||
|  | 
 | ||
|  |                             float destAlpha = prevOutColor.a * (1 - inColor.a); | ||
|  |                             outColor.a = inColor.a + prevOutColor.a * (1 - inColor.a); | ||
|  | 
 | ||
|  |                             float premultiplyAlpha = outColor.a > 0.0f ? 1 / outColor.a : 1f; | ||
|  |                             outColor.r = (inColor.r * inColor.a + prevOutColor.r * destAlpha) * premultiplyAlpha; | ||
|  |                             outColor.g = (inColor.g * inColor.a + prevOutColor.g * destAlpha) * premultiplyAlpha; | ||
|  |                             outColor.b = (inColor.b * inColor.a + prevOutColor.b * destAlpha) * premultiplyAlpha; | ||
|  | 
 | ||
|  |                             outputColor[outBufferIndex] = outColor; | ||
|  |                         } | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public static unsafe void Execute(in PSDExtractLayerData[] psdExtractLayerData, out List<PSDLayer> outputLayers, bool importHiddenLayer, Vector2Int canvasSize) | ||
|  |         { | ||
|  |             outputLayers = new List<PSDLayer>(); | ||
|  |             UnityEngine.Profiling.Profiler.BeginSample("ExtractLayer_PrepareJob"); | ||
|  | 
 | ||
|  |             List<PSDLayer> inputLayers = new List<PSDLayer>(); | ||
|  |             ExtractLayerData(in psdExtractLayerData, ref inputLayers, importHiddenLayer, false, true, canvasSize); | ||
|  | 
 | ||
|  |             List<LayerGroupData> layerGroupData = new List<LayerGroupData>(); | ||
|  |             GenerateOutputLayers(in inputLayers, ref outputLayers, ref layerGroupData, false, canvasSize); | ||
|  | 
 | ||
|  |             if (layerGroupData.Count == 0) | ||
|  |             { | ||
|  |                 foreach (PSDLayer layer in outputLayers) | ||
|  |                     layer.texture = default; | ||
|  |                 return; | ||
|  |             } | ||
|  | 
 | ||
|  |             ConvertBufferJob job = new ConvertBufferJob(); | ||
|  |             job.inputTextureBufferSizes = new NativeArray<int>(inputLayers.Count, Allocator.TempJob); | ||
|  |             job.inputTextures = new NativeArray<IntPtr>(inputLayers.Count, Allocator.TempJob); | ||
|  |             job.inputLayerRects = new NativeArray<int4>(inputLayers.Count, Allocator.TempJob); | ||
|  |             job.outputLayerRect = new NativeArray<int4>(layerGroupData.Count, Allocator.TempJob); | ||
|  |             job.outputTextures = new NativeArray<IntPtr>(layerGroupData.Count, Allocator.TempJob); | ||
|  | 
 | ||
|  |             for (int i = 0, groupIndex = 0; i < inputLayers.Count; ++i) | ||
|  |             { | ||
|  |                 PSDLayer inputLayer = inputLayers[i]; | ||
|  |                 PSDLayer outputLayer = outputLayers[i]; | ||
|  | 
 | ||
|  |                 job.inputTextures[i] = inputLayer.texture.IsCreated ? new IntPtr(inputLayer.texture.GetUnsafePtr()) : IntPtr.Zero; | ||
|  | 
 | ||
|  |                 bool isGroupOwner = groupIndex < layerGroupData.Count && layerGroupData[groupIndex].startIndex == i; | ||
|  |                 if (isGroupOwner) | ||
|  |                 { | ||
|  |                     outputLayer.texture = new NativeArray<Color32>(outputLayer.width * outputLayer.height, Allocator.Persistent); | ||
|  | 
 | ||
|  |                     job.outputLayerRect[groupIndex] = new int4((int)outputLayer.layerPosition.x, (int)outputLayer.layerPosition.y, outputLayer.width, outputLayer.height); | ||
|  |                     job.outputTextures[groupIndex] = outputLayer.texture.IsCreated ? new IntPtr(outputLayer.texture.GetUnsafePtr()) : IntPtr.Zero; | ||
|  |                     job.inputTextureBufferSizes[i] = inputLayer.texture.IsCreated ? inputLayer.texture.Length : -1; | ||
|  |                     job.inputLayerRects[i] = layerGroupData[groupIndex].documentRect; | ||
|  |                     ++groupIndex; | ||
|  |                 } | ||
|  |                 else | ||
|  |                 { | ||
|  |                     job.inputTextureBufferSizes[i] = inputLayer.texture.IsCreated ? inputLayer.texture.Length : -1; | ||
|  |                     job.inputLayerRects[i] = new int4((int)inputLayer.layerPosition.x, (int)inputLayer.layerPosition.y, inputLayer.width, inputLayer.height); | ||
|  |                     outputLayer.texture = default; | ||
|  |                 } | ||
|  |             } | ||
|  |             job.layerGroupDataData = new NativeArray<LayerGroupData>(layerGroupData.ToArray(), Allocator.TempJob); | ||
|  | 
 | ||
|  |             int jobsPerThread = layerGroupData.Count / (SystemInfo.processorCount == 0 ? 8 : SystemInfo.processorCount); | ||
|  |             jobsPerThread = Mathf.Max(jobsPerThread, 1); | ||
|  |             JobHandle handle = job.Schedule(layerGroupData.Count, jobsPerThread); | ||
|  |             UnityEngine.Profiling.Profiler.EndSample(); | ||
|  |             handle.Complete(); | ||
|  |         } | ||
|  | 
 | ||
|  |         static void ExtractLayerData(in PSDExtractLayerData[] inputLayers, ref List<PSDLayer> extractedLayers, bool importHiddenLayer, bool flatten, bool parentGroupVisible, Vector2Int canvasSize) | ||
|  |         { | ||
|  |             int parentGroupIndex = extractedLayers.Count - 1; | ||
|  | 
 | ||
|  |             foreach (PSDExtractLayerData inputLayer in inputLayers) | ||
|  |             { | ||
|  |                 PDNWrapper.BitmapLayer bitmapLayer = inputLayer.bitmapLayer; | ||
|  |                 PSDLayerImportSetting importSettings = inputLayer.importSetting; | ||
|  |                 bool layerVisible = bitmapLayer.Visible && parentGroupVisible; | ||
|  | 
 | ||
|  |                 RectInt layerRect = new RectInt(bitmapLayer.documentRect.X, bitmapLayer.documentRect.Y, bitmapLayer.Surface.width, bitmapLayer.Surface.height); | ||
|  | 
 | ||
|  |                 if (!bitmapLayer.IsGroup) | ||
|  |                     layerRect.y = (canvasSize.y - layerRect.y - layerRect.height); | ||
|  | 
 | ||
|  |                 NativeArray<Color32> surface = default; | ||
|  |                 if ((importHiddenLayer || bitmapLayer.Visible) && | ||
|  |                     importSettings.importLayer && | ||
|  |                     bitmapLayer.Surface.color.IsCreated && | ||
|  |                     bitmapLayer.Surface.color.Length > 0) | ||
|  |                     surface = bitmapLayer.Surface.color; | ||
|  | 
 | ||
|  |                 PSDLayer extractedLayer = new PSDLayer(surface, parentGroupIndex, bitmapLayer.IsGroup, bitmapLayer.Name, layerRect.width, layerRect.height, bitmapLayer.LayerID, bitmapLayer.Visible) | ||
|  |                 { | ||
|  |                     spriteID = inputLayer.importSetting.spriteId, | ||
|  |                     flatten = bitmapLayer.IsGroup && inputLayer.importSetting.flatten, | ||
|  |                     layerPosition = bitmapLayer.IsGroup ? Vector2.zero : layerRect.position | ||
|  |                 }; | ||
|  | 
 | ||
|  |                 extractedLayer.isImported = (importHiddenLayer || layerVisible) && !flatten && importSettings.importLayer; | ||
|  |                 if (extractedLayer.isGroup) | ||
|  |                     extractedLayer.isImported = extractedLayer.isImported && extractedLayer.flatten; | ||
|  | 
 | ||
|  |                 extractedLayers.Add(extractedLayer); | ||
|  | 
 | ||
|  |                 if (inputLayer.children.Length > 0) | ||
|  |                     ExtractLayerData(in inputLayer.children, ref extractedLayers, importHiddenLayer, flatten || extractedLayer.flatten, layerVisible, canvasSize); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         static void GenerateOutputLayers(in List<PSDLayer> inputLayers, ref List<PSDLayer> outputLayers, ref List<LayerGroupData> layerGroupData, bool flatten, Vector2Int canvasSize) | ||
|  |         { | ||
|  |             RectInt canvasRect = new RectInt(Vector2Int.zero, canvasSize); | ||
|  | 
 | ||
|  |             for (int i = 0; i < inputLayers.Count; ++i) | ||
|  |             { | ||
|  |                 PSDLayer inputLayer = inputLayers[i]; | ||
|  | 
 | ||
|  |                 PSDLayer outputLayer = new PSDLayer(inputLayer); | ||
|  |                 RectInt outputRect = new RectInt((int)outputLayer.layerPosition.x, (int)outputLayer.layerPosition.y, outputLayer.width, outputLayer.height); | ||
|  | 
 | ||
|  |                 if (inputLayer.isGroup) | ||
|  |                 { | ||
|  |                     List<int> childIndices = FindAllChildrenOfParent(i, in inputLayers); | ||
|  |                     childIndices.Sort(); | ||
|  | 
 | ||
|  |                     int startIndex = i; | ||
|  |                     int endIndex = i + childIndices.Count; | ||
|  | 
 | ||
|  |                     if (flatten == false && inputLayer.flatten && startIndex < endIndex) | ||
|  |                     { | ||
|  |                         RectInt groupBoundingBox = CalculateLayerRectInChildren(in inputLayers, in childIndices); | ||
|  |                         layerGroupData.Add(new LayerGroupData() | ||
|  |                         { | ||
|  |                             startIndex = startIndex, | ||
|  |                             endIndex = endIndex, | ||
|  |                             documentRect = new int4(groupBoundingBox.x, groupBoundingBox.y, groupBoundingBox.width, groupBoundingBox.height) | ||
|  |                         }); | ||
|  |                         outputLayer.texture = default; | ||
|  |                         outputRect = groupBoundingBox; | ||
|  |                     } | ||
|  |                 } | ||
|  |                 else if (!inputLayer.isGroup && inputLayer.isImported) | ||
|  |                 { | ||
|  |                     int4 inputRect = new int4((int)inputLayer.layerPosition.x, (int)inputLayer.layerPosition.y, inputLayer.width, inputLayer.height); | ||
|  |                     layerGroupData.Add(new LayerGroupData() | ||
|  |                     { | ||
|  |                         startIndex = i, | ||
|  |                         endIndex = i, | ||
|  |                         documentRect = inputRect | ||
|  |                     }); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 CropRect(ref outputRect, canvasRect); | ||
|  |                 outputLayer.layerPosition = outputRect.position; | ||
|  |                 outputLayer.width = outputRect.width; | ||
|  |                 outputLayer.height = outputRect.height; | ||
|  | 
 | ||
|  |                 outputLayers.Add(outputLayer); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         static List<int> FindAllChildrenOfParent(int parentIndex, in List<PSDLayer> layers) | ||
|  |         { | ||
|  |             List<int> childIndices = new List<int>(); | ||
|  |             for (int i = parentIndex + 1; i < layers.Count; ++i) | ||
|  |             { | ||
|  |                 if (layers[i].parentIndex == parentIndex) | ||
|  |                 { | ||
|  |                     childIndices.Add(i); | ||
|  |                     if (layers[i].isGroup) | ||
|  |                         childIndices.AddRange(FindAllChildrenOfParent(i, in layers)); | ||
|  |                 } | ||
|  |             } | ||
|  |             return childIndices; | ||
|  |         } | ||
|  | 
 | ||
|  |         static RectInt CalculateLayerRectInChildren(in List<PSDLayer> inputLayers, in List<int> childIndices) | ||
|  |         { | ||
|  |             RectInt groupBoundingBox = default(RectInt); | ||
|  |             for (int m = 0; m < childIndices.Count; ++m) | ||
|  |             { | ||
|  |                 PSDLayer childLayer = inputLayers[childIndices[m]]; | ||
|  |                 if (childLayer.isGroup) | ||
|  |                     continue; | ||
|  | 
 | ||
|  |                 RectInt layerRect = new RectInt((int)childLayer.layerPosition.x, (int)childLayer.layerPosition.y, | ||
|  |                     childLayer.width, childLayer.height); | ||
|  |                 if (IsRectIntDefault(groupBoundingBox)) | ||
|  |                     groupBoundingBox = layerRect; | ||
|  |                 else | ||
|  |                     FitRectInsideRect(ref groupBoundingBox, in layerRect); | ||
|  |             } | ||
|  | 
 | ||
|  |             return groupBoundingBox; | ||
|  |         } | ||
|  | 
 | ||
|  |         static bool IsRectIntDefault(RectInt rectInt) | ||
|  |         { | ||
|  |             return rectInt.x == 0 && | ||
|  |                    rectInt.y == 0 && | ||
|  |                    rectInt.width == 0 && | ||
|  |                    rectInt.height == 0; | ||
|  |         } | ||
|  | 
 | ||
|  |         static void CropRect(ref RectInt baseRect, in RectInt cropArea) | ||
|  |         { | ||
|  |             if (baseRect.x < cropArea.x) | ||
|  |             { | ||
|  |                 baseRect.width = Mathf.Max(baseRect.width - (cropArea.x - baseRect.x), 0); | ||
|  |                 baseRect.x = cropArea.x; | ||
|  |             } | ||
|  |             if (baseRect.xMax > cropArea.xMax) | ||
|  |             { | ||
|  |                 baseRect.x = Mathf.Min(baseRect.x, cropArea.xMax); | ||
|  |                 baseRect.width = Mathf.Max(cropArea.xMax - baseRect.x, 0); | ||
|  |             } | ||
|  | 
 | ||
|  |             if (baseRect.y < cropArea.y) | ||
|  |             { | ||
|  |                 baseRect.height = Mathf.Max(baseRect.height - (cropArea.y - baseRect.y), 0); | ||
|  |                 baseRect.y = cropArea.y; | ||
|  |             } | ||
|  |             if (baseRect.yMax > cropArea.yMax) | ||
|  |             { | ||
|  |                 baseRect.y = Mathf.Min(baseRect.y, cropArea.yMax); | ||
|  |                 baseRect.height = Mathf.Max(cropArea.yMax - baseRect.y, 0); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         static void FitRectInsideRect(ref RectInt baseRect, in RectInt rectToFitIn) | ||
|  |         { | ||
|  |             if (baseRect.xMin > rectToFitIn.xMin) | ||
|  |                 baseRect.xMin = rectToFitIn.xMin; | ||
|  |             if (baseRect.yMin > rectToFitIn.yMin) | ||
|  |                 baseRect.yMin = rectToFitIn.yMin; | ||
|  |             if (baseRect.xMax < rectToFitIn.xMax) | ||
|  |                 baseRect.xMax = rectToFitIn.xMax; | ||
|  |             if (baseRect.yMax < rectToFitIn.yMax) | ||
|  |                 baseRect.yMax = rectToFitIn.yMax; | ||
|  |         } | ||
|  |     } | ||
|  | } |