From b486f4c7279039899d85ca2fcdd6c7f0b31b9ca2 Mon Sep 17 00:00:00 2001
From: UlyssesWu <wdwxy12345@gmail.com>
Date: Tue, 30 May 2023 21:53:01 +0800
Subject: [PATCH] pimg to psd #109

---
 FreeMote.Plugins/Shells/PsdShell.cs     | 231 ++++++++++++++++++++++--
 FreeMote.Psb/EmtPainter.cs              |   4 +-
 FreeMote.Psb/Resources/ImageMetadata.cs |   4 +
 FreeMote.Psb/Types/PimgType.cs          |   9 +-
 4 files changed, 234 insertions(+), 14 deletions(-)

diff --git a/FreeMote.Plugins/Shells/PsdShell.cs b/FreeMote.Plugins/Shells/PsdShell.cs
index 5cbe2f5..0c4b288 100644
--- a/FreeMote.Plugins/Shells/PsdShell.cs
+++ b/FreeMote.Plugins/Shells/PsdShell.cs
@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel.Composition;
+using System.Diagnostics;
 using System.Drawing;
 using System.Drawing.Imaging;
 using System.Drawing.Text;
@@ -19,6 +20,8 @@ namespace FreeMote.Plugins.Shells
     [ExportMetadata("Comment", "PSD export.")]
     class PsdShell : IPsbShell
     {
+        public byte[] Signature { get; } = {(byte) '8', (byte) 'B', (byte) 'P', (byte) 'S'};
+
         public int Width { get; set; } = -1;
         public int Height { get; set; } = -1;
 
@@ -65,25 +68,30 @@ public MemoryStream ToShell(Stream stream, Dictionary<string, object> context =
                 throw new BadImageFormatException("Not a valid PSB file.");
             }
 
+            if (psb.Type == PsbType.Pimg)
+            {
+                return ConvertPImgToPsd(psb);
+            }
+
             EmtPainter painter = new EmtPainter(psb);
 
             if (TryGetCanvasSize && painter.Resources.Count > 0)
             {
                 if (psb.TryGetCanvasSize(out var cw, out var ch))
                 {
-                    Width = (int)(cw * 1.8f);
-                    Height = (int)(ch * 1.4f);
+                    Width = (int) (cw * 1.8f);
+                    Height = (int) (ch * 1.4f);
                 }
                 else
                 {
                     //Try get from painter, not accurate if the PSB center is not (0,0)
-                    Width = (int)(painter.Resources.Max(r => r.OriginX + r.Width / 2.0f) -
-                                  painter.Resources.Min(r => r.OriginX - r.Width / 2.0f));
-                    Height = (int)(painter.Resources.Max(r => r.OriginY + r.Height / 2.0f) -
-                                   painter.Resources.Min(r => r.OriginY - r.Height / 2.0f));
+                    Width = (int) (painter.Resources.Max(r => r.OriginX + r.Width / 2.0f) -
+                                   painter.Resources.Min(r => r.OriginX - r.Width / 2.0f));
+                    Height = (int) (painter.Resources.Max(r => r.OriginY + r.Height / 2.0f) -
+                                    painter.Resources.Min(r => r.OriginY - r.Height / 2.0f));
 
-                    Width = (int)(Width * 1.4f);
-                    Height = (int)(Height * 1.4f);
+                    Width = (int) (Width * 1.4f);
+                    Height = (int) (Height * 1.4f);
                 }
 
                 if (context != null)
@@ -106,6 +114,139 @@ public MemoryStream ToShell(Stream stream, Dictionary<string, object> context =
             return ms;
         }
 
+        private MemoryStream ConvertPImgToPsd(PSB psb)
+        {
+            var width = psb.Objects["width"].GetInt();
+            var height = psb.Objects["height"].GetInt();
+
+            PsdFile psd = new PsdFile
+            {
+                Width = width,
+                Height = height,
+                Resolution = new ResolutionInfo
+                {
+                    HeightDisplayUnit = ResolutionInfo.Unit.Centimeters,
+                    WidthDisplayUnit = ResolutionInfo.Unit.Centimeters,
+                    HResDisplayUnit = ResolutionInfo.ResUnit.PxPerInch,
+                    VResDisplayUnit = ResolutionInfo.ResUnit.PxPerInch,
+                    HDpi = new UFixed16_16(0, 350),
+                    VDpi = new UFixed16_16(0, 350)
+                },
+                ImageCompression = ImageCompression.Rle
+            };
+
+            psd.ImageResources.Add(new XmpResource("") {XmpMetaString = Resources.Xmp});
+            psd.BaseLayer.SetBitmap(new Bitmap(width, height, PixelFormat.Format32bppArgb),
+                ImageReplaceOption.KeepCenter, psd.ImageCompression);
+
+            var images = psb.CollectResources<ImageMetadata>();
+
+            //layer type: 0 = image, 2 = folder
+            List<ILayer> layers = new List<ILayer>();
+            List<GroupLayer> groupLayers = new List<GroupLayer>();
+
+            var layerObjects = (PsbList) psb.Objects["layers"];
+
+            GroupLayer CreateGroupLayer(PsbDictionary lObj, ILayer child = null)
+            {
+                var layerId = lObj["layer_id"].GetInt();
+                var existedGroup = groupLayers.FirstOrDefault(l => l.LayerId == layerId);
+                if (existedGroup != null)
+                {
+                    if (child != null)
+                    {
+                        existedGroup.Children.Add(child);
+                        child.Parent = existedGroup;
+                    }
+                    return null;
+                }
+
+                var groupLayer = new GroupLayer {Object = lObj, LayerId = layerId, Name = lObj["name"].ToString() };
+                if (child != null)
+                {
+                    groupLayer.Children.Add(child);
+                    child.Parent = groupLayer;
+                }
+
+                groupLayers.Add(groupLayer);
+                if (lObj["group_layer_id"] is PsbNumber groupLayerId)
+                {
+                    var parent1 = groupLayers.FirstOrDefault(g => g.LayerId == groupLayerId.IntValue);
+                    if (parent1 != null)
+                    {
+                        parent1.Children.Add(groupLayer);
+                        groupLayer.Parent = parent1;
+                        return null;
+                    }
+
+                    var parent = layerObjects.FirstOrDefault(l =>
+                        l is PsbDictionary lo && lo["layer_type"].GetInt() == 2 && lo["layer_id"].GetInt() == groupLayerId.GetInt());
+                    if (parent is PsbDictionary parentObj)
+                    {
+                        return CreateGroupLayer(parentObj, groupLayer);
+                    }
+                }
+
+                return groupLayer;
+            }
+
+            foreach (var layer in layerObjects)
+            {
+                if (layer is not PsbDictionary layerObj)
+                {
+                    continue;
+                }
+
+                if (layerObj["layer_type"].GetInt() == 2) // layer group
+                {
+                    var g = CreateGroupLayer(layerObj);
+                    if (g != null)
+                    {
+                        layers.Add(g);
+                    }
+                }
+                else
+                {
+                    var layerId = layerObj["layer_id"].GetInt();
+                    var imageLayer = new ImageLayer
+                        {Object = layerObj, Name = layerObj["name"].ToString(), LayerId = layerId, ImageMetadata = images.FirstOrDefault(md => md.Index == (uint) layerId)};
+                    if (imageLayer.ImageMetadata == null && layerObj.TryGetValue("same_image", out var sameImageId))
+                    {
+                        var sameImage = sameImageId.GetInt();
+                        imageLayer.ImageMetadata = images.FirstOrDefault(md => md.Index == (uint) sameImage);
+                    }
+                    if (layerObj["group_layer_id"] is PsbNumber groupLayerId)
+                    {
+                        var parent = layerObjects.FirstOrDefault(l =>
+                            l is PsbDictionary lo && lo["layer_type"].GetInt() == 2 && lo["layer_id"].GetInt() == groupLayerId.GetInt());
+                        if (parent is PsbDictionary parentObj)
+                        {
+                            var g = CreateGroupLayer(parentObj, imageLayer);
+                            if (g != null)
+                            {
+                                layers.Add(g);
+                            }
+                        }
+                    }
+                    else
+                    {
+                        layers.Add(imageLayer);
+                    }
+                }
+            }
+
+            foreach (var layer in layers)
+            {
+                layer.CreateLayers(psd);
+            }
+
+            psd.Layers.Reverse();
+
+            var ms = new MemoryStream();
+            psd.Save(ms, Encoding.UTF8);
+            return ms;
+        }
+
         private PsdFile ConvertToPsd(EmtPainter painter, int width, int height)
         {
             float offsetX = 0;
@@ -170,7 +311,7 @@ private PsdFile ConvertToPsd(EmtPainter painter, int width, int height)
                 }
                 else
                 {
-                    layer.Opacity = (byte)(resMd.Opacity /10.0f * 255);
+                    layer.Opacity = (byte) (resMd.Opacity / 10.0f * 255);
                 }
 
                 if (resMd.MotionName != currentGroup)
@@ -206,7 +347,7 @@ private Bitmap GenerateMarkText(string text, int width, int height, string fontN
         {
             Font drawFont = new Font(fontName, fontSize, FontStyle.Bold);
             var size = PsbResHelper.MeasureString(text, drawFont);
-            Bitmap bmp = new Bitmap((int)Math.Ceiling(size.Width + 1), (int)Math.Ceiling(size.Height + 1), PixelFormat.Format32bppArgb);
+            Bitmap bmp = new Bitmap((int) Math.Ceiling(size.Width + 1), (int) Math.Ceiling(size.Height + 1), PixelFormat.Format32bppArgb);
             Graphics g = Graphics.FromImage(bmp);
             g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
             StringFormat sf = StringFormat.GenericTypographic;
@@ -216,7 +357,75 @@ private Bitmap GenerateMarkText(string text, int width, int height, string fontN
             g.Dispose();
             return bmp;
         }
+    }
 
-        public byte[] Signature { get; } = {(byte) '8', (byte) 'B', (byte) 'P', (byte) 'S'};
+    interface ILayer
+    {
+        public GroupLayer Parent { get; set; }
+        public int LayerId { get; }
+        PsbDictionary Object { get; }
+        public string Name { get; set; }
+
+        void CreateLayers(PsdFile psd);
+    }
+
+    [DebuggerDisplay("{Name,nq} ({LayerId})")]
+    class GroupLayer : ILayer
+    {
+        public GroupLayer Parent { get; set; }
+        public int LayerId { get; set; }
+        public PsbDictionary Object { get; set; }
+        public List<ILayer> Children { get; private set; } = new();
+        public string Name { get; set; }
+        
+        public void CreateLayers(PsdFile psd)
+        {
+            var beginLayer = psd.MakeSectionLayers(Name, out var endLayer, false);
+            psd.Layers.Add(beginLayer);
+            foreach (var child in Children)
+            {
+                child.CreateLayers(psd);
+            }
+            psd.Layers.Add(endLayer);
+        }
+    }
+
+    [DebuggerDisplay("{Name,nq} ({LayerId})")]
+    class ImageLayer : ILayer
+    {
+        public GroupLayer Parent { get; set; }
+        public int LayerId { get; set; }
+        public PsbDictionary Object { get; set; }
+        public string Name { get; set; }
+        public ImageMetadata ImageMetadata { get; set; }
+
+        public void CreateLayers(PsdFile psd)
+        {
+            var md = ImageMetadata;
+            if (md == null)
+            {
+                var layerWidth = Object["width"].GetInt();
+                var layerHeight = Object["height"].GetInt();
+                var layerTop = Object["top"].GetInt();
+                var layerLeft = Object["left"].GetInt();
+                var emptyLayer = psd.MakeImageLayer(new Bitmap(layerWidth, layerHeight), Name, layerLeft, layerTop);
+                emptyLayer.Visible = Object["visible"].GetInt() != 0;
+                emptyLayer.Opacity = (byte) Object["opacity"].GetInt();
+                psd.Layers.Add(emptyLayer);
+            }
+            else
+            {
+                var imageLayer = psd.MakeImageLayer(md.ToImage(), Name, md.Left, md.Top);
+                //var idLayer = new RawLayerInfo("lyid");
+                //create 8 bytes array, first 4 is int value 4, second 4 is Id
+                //idLayer.Data = new byte[8];
+                //BitConverter.GetBytes(4).CopyTo(idLayer.Data, 0);
+                //BitConverter.GetBytes(LayerId).CopyTo(idLayer.Data, 4);
+                //imageLayer.AdditionalInfo.Add(idLayer);
+                imageLayer.Visible = md.Visible;
+                imageLayer.Opacity = (byte) md.Opacity;
+                psd.Layers.Add(imageLayer);
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/FreeMote.Psb/EmtPainter.cs b/FreeMote.Psb/EmtPainter.cs
index e65ecbf..e97ff48 100644
--- a/FreeMote.Psb/EmtPainter.cs
+++ b/FreeMote.Psb/EmtPainter.cs
@@ -243,14 +243,14 @@ void Travel(IPsbCollection collection, string motionName, (float x, float y, flo
 
                             if (baseLocation == null)
                             {
-                                //Console.WriteLine($"Set coord: {coordTuple.x},{coordTuple.y},{coordTuple.z}");
+                                Debug.WriteLine($"Set coord: {coordTuple.x},{coordTuple.y},{coordTuple.z} | {dic.Path}");
                                 baseLocation = coordTuple;
                             }
                             else
                             {
                                 var loc = baseLocation.Value;
                                 baseLocation = (loc.x + coordTuple.x, loc.y + coordTuple.y, loc.z + coordTuple.z);
-                                //Console.WriteLine($"Update coord: {loc.x},{loc.y},{loc.z} -> {baseLocation?.x},{baseLocation?.y},{baseLocation?.z}");
+                                Debug.WriteLine($"Update coord: {loc.x},{loc.y},{loc.z} + {coordTuple.x},{coordTuple.y},{coordTuple.z} -> {baseLocation?.x},{baseLocation?.y},{baseLocation?.z} | {dic.Path}");
                             }
                         }
 
diff --git a/FreeMote.Psb/Resources/ImageMetadata.cs b/FreeMote.Psb/Resources/ImageMetadata.cs
index 50f628f..d2d9cdc 100644
--- a/FreeMote.Psb/Resources/ImageMetadata.cs
+++ b/FreeMote.Psb/Resources/ImageMetadata.cs
@@ -102,6 +102,10 @@ public uint Index
         public PsbString TypeString { get; set; }
         public RectangleF Clip { get; set; }
         public PsbResource Resource { get; set; }
+        /// <summary>
+        /// PIMG layer_type
+        /// </summary>
+        public int LayerType { get; set; }
 
         /// <summary>
         /// Pal
diff --git a/FreeMote.Psb/Types/PimgType.cs b/FreeMote.Psb/Types/PimgType.cs
index 3e17a27..912411a 100644
--- a/FreeMote.Psb/Types/PimgType.cs
+++ b/FreeMote.Psb/Types/PimgType.cs
@@ -73,7 +73,9 @@ private static void FindPimgResources<T>(List<T> list, IPsbValue obj, bool deDup
                             continue;
                         }
 
-                        var res = (ImageMetadata)(IResourceMetadata)list.FirstOrDefault(k => k.Name.StartsWith(layerId.ToString(), true, null));
+                        var res =
+                            (ImageMetadata) (IResourceMetadata) (list.FirstOrDefault(k => k.Name.StartsWith($"{layerId}.", true, null)) ??
+                                                                 list.FirstOrDefault(k => k.Name.StartsWith($"{layerId}", true, null)));
                         if (res == null)
                         {
                             continue;
@@ -121,6 +123,11 @@ private static void FindPimgResources<T>(List<T> list, IPsbValue obj, bool deDup
                         {
                             res.Label = nn.Value;
                         }
+
+                        if (dic["layer_type"] is PsbNumber lt)
+                        {
+                            res.LayerType = lt.IntValue;
+                        }
                     }
                 }
             }