From f9e8f04d5f2cecff5a88d53b70f9cc58a127a5bb Mon Sep 17 00:00:00 2001 From: Ulysses Wu Date: Wed, 31 May 2023 02:39:37 +0800 Subject: [PATCH] psd to pimg, Fix #109 --- FreeMote.Plugins/FreeMote.Plugins.csproj | 7 +- FreeMote.Plugins/Shells/PsdShell.cs | 289 ++++++++++++++++-- FreeMote.PsBuild/FreeMote.PsBuild.csproj | 2 +- FreeMote.Psb/FreeMote.Psb.csproj | 2 +- FreeMote.Tests/FreeMote.Tests.csproj | 4 +- FreeMote.Tools.EmtConvert/Program.cs | 16 + FreeMote.Tools.EmtMake/Program.cs | 1 + FreeMote.Tools.PsBuild/Program.cs | 4 + FreeMote.Tools.PsbDecompile/Program.cs | 4 + .../FreeMote.Tools.Viewer.csproj | 2 +- FreeMote.sln.DotSettings | 1 + FreeMote/FreeMote.csproj | 2 +- 12 files changed, 303 insertions(+), 31 deletions(-) diff --git a/FreeMote.Plugins/FreeMote.Plugins.csproj b/FreeMote.Plugins/FreeMote.Plugins.csproj index 013c511..5744eb2 100644 --- a/FreeMote.Plugins/FreeMote.Plugins.csproj +++ b/FreeMote.Plugins/FreeMote.Plugins.csproj @@ -103,13 +103,10 @@ 2.1.0-CI00002 - 1.2.16 + 1.3.5 - 2.5.0-CI00007 - - - 5.0.0 + 2.5.1.13 2.2.1-CI00002 diff --git a/FreeMote.Plugins/Shells/PsdShell.cs b/FreeMote.Plugins/Shells/PsdShell.cs index 0c4b288..f78eb64 100644 --- a/FreeMote.Plugins/Shells/PsdShell.cs +++ b/FreeMote.Plugins/Shells/PsdShell.cs @@ -21,6 +21,10 @@ namespace FreeMote.Plugins.Shells class PsdShell : IPsbShell { public byte[] Signature { get; } = {(byte) '8', (byte) 'B', (byte) 'P', (byte) 'S'}; + public const int MaxLayerId = 65535; + public const string LayerIdSuffix = "#lyid#"; + private const string PsdTypeEmt = "emt"; + private const string PsdTypePimg = "pimg"; public int Width { get; set; } = -1; public int Height { get; set; } = -1; @@ -49,12 +53,237 @@ public bool IsInShell(Stream stream, Dictionary context = null) public MemoryStream ToPsb(Stream stream, Dictionary context = null) { - Logger.Log("PSD to PSB conversion is not supported."); + try + { + PsdFile psd = new PsdFile(stream, new LoadContext {Encoding = Encoding.UTF8}); + var xmp = psd.ImageResources.FirstOrDefault(info => info is XmpResource); + if (xmp is XmpResource xmpRes) + { + var type = xmpRes.Name.ToLowerInvariant(); + //if (type != PsdTypePimg) + //{ + // return null; + //} + } + var layers = new PsbList(); + PSB psb = new PSB(3) + { + Objects = new PsbDictionary + { + ["width"] = psd.Width.ToPsbNumber(), + ["height"] = psd.Height.ToPsbNumber(), + ["layers"] = layers + } + }; //TODO: set version + + psd.Layers.Reverse(); + Stack groupLayers = new Stack(); + List<(GroupLayer Group, Layer Layer)> groupLayerList = new(); + List imageMetadatas = new List(); + Dictionary layerIdMap = new Dictionary(psd.Layers.Count); + Dictionary layerSectionTypes = new(psd.Layers.Count); + HashSet idRegistry = new HashSet(psd.Layers.Count); + int newId = 1; + foreach (var layer in psd.Layers) + { + var sectionInfo = layer.AdditionalInfo.FirstOrDefault(info => info is LayerSectionInfo); + if (sectionInfo is LayerSectionInfo section) + { + layerSectionTypes[layer] = section.SectionType; + if (section.SectionType == LayerSectionType.SectionDivider) + { + continue; + } + } + else + { + layerSectionTypes[layer] = LayerSectionType.Layer; + } + + if (layer.Name.Contains(LayerIdSuffix)) + { + try + { + var lines = layer.Name.Split(new[] { LayerIdSuffix }, StringSplitOptions.None); + var idStr = lines[1]; + var id = int.Parse(idStr); + layerIdMap[layer] = id; + layer.Name = lines[0]; + } + catch + { + layerIdMap[layer] = 0; + } + } + else + { + var idLayer = layer.AdditionalInfo.FirstOrDefault(info => info is LayerId); + if (idLayer is LayerId layerId) + { + var id = (int) layerId.Id; + if (!idRegistry.Contains(id)) + { + layerIdMap[layer] = id; + idRegistry.Add(id); + } + else if (id == -1) + { + layerIdMap[layer] = id; + } + else + { + layerIdMap[layer] = 0; + } + } + else + { + layerIdMap[layer] = 0; + } + } + + } + + foreach (var l in layerIdMap.Keys) + { + var id = layerIdMap[l]; + if (id == 0) + { + while (idRegistry.Contains(newId)) + { + newId++; + } + layerIdMap[l] = newId; + idRegistry.Add(newId); + } + } + + foreach (var layer in psd.Layers) + { + var sectionType = layerSectionTypes[layer]; + var id = sectionType == LayerSectionType.SectionDivider ? 0 : layerIdMap[layer]; + + if (sectionType is LayerSectionType.OpenFolder or LayerSectionType.ClosedFolder) + { + var group = new GroupLayer + { + LayerId = id, + Name = layer.Name, + Open = sectionType == LayerSectionType.OpenFolder, + Parent = groupLayers.Count > 0 ? groupLayers.Peek() : null + }; + groupLayers.Push(group); + groupLayerList.Add((group, layer)); + continue; + } + + if (sectionType == LayerSectionType.SectionDivider) + { + if (groupLayers.Count > 0) + { + groupLayers.Pop(); + } + continue; + } + + var bitmap = layer.GetBitmap(); + bool useTlg = true; + var imageMetadata = new ImageMetadata() { LayerType = id, Resource = new PsbResource(), Compress = PsbCompressType.Tlg}; + imageMetadata.SetData(bitmap); + if (imageMetadata.Data == null) + { + useTlg = false; + Logger.LogWarn($"Cannot convert bitmap to TLG, maybe FreeMote.Plugins.x64 is missing, or you're not working on Windows."); + using var pngMs = new MemoryStream(); + bitmap.Save(pngMs, ImageFormat.Png); + imageMetadata.Data = pngMs.ToArray(); + } + + int sameImageId = -1; + if (imageMetadata.Data != null) + { + var same = imageMetadatas.FirstOrDefault(r => r.Data.SequenceEqual(imageMetadata.Data)); + if (same != null) + { + sameImageId = same.LayerType; + } + } + + if (sameImageId < 0) + { + imageMetadatas.Add(imageMetadata); + } + + var rect = layer.Rect; + var left = rect.X; + var top = rect.Y; + var width = rect.Width; + var height = rect.Height; + int? currentGroupId = groupLayers.Count > 0? groupLayers.Peek().LayerId : null; + var obj = new PsbDictionary + { + ["layer_id"] = id.ToPsbNumber(), + ["layer_type"] = PsbNumber.Zero, + ["name"] = layer.Name.ToPsbString(), + ["left"] = left.ToPsbNumber(), + ["top"] = top.ToPsbNumber(), + ["width"] = width.ToPsbNumber(), + ["height"] = height.ToPsbNumber(), + ["type"] = 13.ToPsbNumber(), + ["visible"] = layer.Visible ? 1.ToPsbNumber() : PsbNumber.Zero, + ["opacity"] = new PsbNumber(layer.Opacity), + }; + if (currentGroupId != null) + { + obj["group_layer_id"] = currentGroupId.Value.ToPsbNumber(); + } + + if (sameImageId < 0) + { + if (id >= 0) + { + //Set resource here + psb.Objects[$"{id}.{(useTlg ? "tlg" : "png")}"] = imageMetadata.Resource; + } + } + else + { + obj["same_image"] = sameImageId.ToPsbNumber(); + } + + layers.Add(obj); + } + + foreach (var g in groupLayerList) + { + var obj = new PsbDictionary + { + ["layer_id"] = g.Group.LayerId.ToPsbNumber(), + ["layer_type"] = 2.ToPsbNumber(), + ["type"] = 13.ToPsbNumber(), + ["width"] = g.Layer.Width.ToPsbNumber(), + ["height"] = g.Layer.Height.ToPsbNumber(), + ["left"] = g.Layer.Rect.X.ToPsbNumber(), + ["top"] = g.Layer.Rect.Y.ToPsbNumber(), + ["name"] = g.Layer.Name.ToPsbString(), + ["visible"] = g.Layer.Visible ? 1.ToPsbNumber() : PsbNumber.Zero, + ["opacity"] = new PsbNumber(g.Layer.Opacity), + }; + if (g.Group.Parent != null) + { + obj["group_layer_id"] = g.Group.Parent.LayerId.ToPsbNumber(); + } + + layers.Add(obj); + } + + psb.Merge(); + return psb.ToStream(); + } + catch (Exception e) + { + Logger.LogError(e); + } return null; - //throw new NotSupportedException("PSD to PSB conversion is not supported."); - //var ms = new MemoryStream(); - //stream.CopyTo(ms); - //return ms; } public MemoryStream ToShell(Stream stream, Dictionary context = null) @@ -70,7 +299,7 @@ public MemoryStream ToShell(Stream stream, Dictionary context = if (psb.Type == PsbType.Pimg) { - return ConvertPImgToPsd(psb); + return ConvertPimgToPsd(psb); } EmtPainter painter = new EmtPainter(psb); @@ -114,7 +343,7 @@ public MemoryStream ToShell(Stream stream, Dictionary context = return ms; } - private MemoryStream ConvertPImgToPsd(PSB psb) + private MemoryStream ConvertPimgToPsd(PSB psb) { var width = psb.Objects["width"].GetInt(); var height = psb.Objects["height"].GetInt(); @@ -135,7 +364,7 @@ private MemoryStream ConvertPImgToPsd(PSB psb) ImageCompression = ImageCompression.Rle }; - psd.ImageResources.Add(new XmpResource("") {XmpMetaString = Resources.Xmp}); + psd.ImageResources.Add(new XmpResource(PsdTypePimg) {XmpMetaString = Resources.Xmp}); psd.BaseLayer.SetBitmap(new Bitmap(width, height, PixelFormat.Format32bppArgb), ImageReplaceOption.KeepCenter, psd.ImageCompression); @@ -241,7 +470,6 @@ GroupLayer CreateGroupLayer(PsbDictionary lObj, ILayer child = null) } psd.Layers.Reverse(); - var ms = new MemoryStream(); psd.Save(ms, Encoding.UTF8); return ms; @@ -287,7 +515,7 @@ private PsdFile ConvertToPsd(EmtPainter painter, int width, int height) ImageCompression = ImageCompression.Rle }; - psd.ImageResources.Add(new XmpResource("") {XmpMetaString = Resources.Xmp}); + psd.ImageResources.Add(new XmpResource(PsdTypeEmt) {XmpMetaString = Resources.Xmp}); psd.BaseLayer.SetBitmap(new Bitmap(width, height, PixelFormat.Format32bppArgb), ImageReplaceOption.KeepCenter, psd.ImageCompression); @@ -372,6 +600,7 @@ interface ILayer [DebuggerDisplay("{Name,nq} ({LayerId})")] class GroupLayer : ILayer { + public bool Open { get; set; } = false; public GroupLayer Parent { get; set; } public int LayerId { get; set; } public PsbDictionary Object { get; set; } @@ -380,7 +609,12 @@ class GroupLayer : ILayer public void CreateLayers(PsdFile psd) { - var beginLayer = psd.MakeSectionLayers(Name, out var endLayer, false); + var beginLayer = psd.MakeSectionLayers(Name, out var endLayer, Open); + if (LayerId is >= 0 and <= PsdShell.MaxLayerId) + { + var idLayer = new LayerId((uint) LayerId); + beginLayer.AdditionalInfo.Add(idLayer); + } psd.Layers.Add(beginLayer); foreach (var child in Children) { @@ -411,19 +645,34 @@ public void CreateLayers(PsdFile psd) var emptyLayer = psd.MakeImageLayer(new Bitmap(layerWidth, layerHeight), Name, layerLeft, layerTop); emptyLayer.Visible = Object["visible"].GetInt() != 0; emptyLayer.Opacity = (byte) Object["opacity"].GetInt(); + if (LayerId is >= 0 and <= PsdShell.MaxLayerId) + { + var idLayer = new LayerId((uint) LayerId); + emptyLayer.AdditionalInfo.Add(idLayer); + } + else if (LayerId == -1) + { + emptyLayer.Name += $"{PsdShell.LayerIdSuffix}{LayerId}"; + } + 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; + var layerTop = Object["top"].GetInt(); + var layerLeft = Object["left"].GetInt(); + var imageLayer = psd.MakeImageLayer(md.ToImage(), Name, layerLeft, layerTop); + imageLayer.Visible = Object["visible"].GetInt() != 0; + imageLayer.Opacity = (byte) Object["opacity"].GetInt(); + if (LayerId is >= 0 and <= PsdShell.MaxLayerId) + { + var idLayer = new LayerId((uint) LayerId); + imageLayer.AdditionalInfo.Add(idLayer); + } + else if (LayerId == -1) + { + imageLayer.Name += $"{PsdShell.LayerIdSuffix}{LayerId}"; + } psd.Layers.Add(imageLayer); } } diff --git a/FreeMote.PsBuild/FreeMote.PsBuild.csproj b/FreeMote.PsBuild/FreeMote.PsBuild.csproj index 79a4b7f..957adc9 100644 --- a/FreeMote.PsBuild/FreeMote.PsBuild.csproj +++ b/FreeMote.PsBuild/FreeMote.PsBuild.csproj @@ -73,7 +73,7 @@ - 13.0.2 + 13.0.3 4.5.0 diff --git a/FreeMote.Psb/FreeMote.Psb.csproj b/FreeMote.Psb/FreeMote.Psb.csproj index 8bb773e..042f3da 100644 --- a/FreeMote.Psb/FreeMote.Psb.csproj +++ b/FreeMote.Psb/FreeMote.Psb.csproj @@ -100,7 +100,7 @@ 4.5.5 - 5.0.0 + 6.0.0 4.5.0 diff --git a/FreeMote.Tests/FreeMote.Tests.csproj b/FreeMote.Tests/FreeMote.Tests.csproj index c675146..ea779ad 100644 --- a/FreeMote.Tests/FreeMote.Tests.csproj +++ b/FreeMote.Tests/FreeMote.Tests.csproj @@ -107,10 +107,10 @@ 2.2.10 - 13.0.2 + 13.0.3 - 5.0.0 + 6.0.0 2.2.1-CI00002 diff --git a/FreeMote.Tools.EmtConvert/Program.cs b/FreeMote.Tools.EmtConvert/Program.cs index 5ab7fc2..647356b 100644 --- a/FreeMote.Tools.EmtConvert/Program.cs +++ b/FreeMote.Tools.EmtConvert/Program.cs @@ -227,6 +227,10 @@ EmtConvert pack -s LZ4 sample.psb { ShellConvert(s, type); } + else + { + Console.WriteLine($"Input path not found: {s}"); + } } }); }); @@ -261,6 +265,10 @@ EmtConvert print -w 4096 -h 4096 sample.psb { Draw(s, width, height); } + else + { + Console.WriteLine($"Input path not found: {s}"); + } } }); }); @@ -347,6 +355,10 @@ EmtConvert fix -m MetadataBase sample.psb Convert(key, s); } } + else + { + Console.WriteLine($"Input path not found: {s}"); + } } }); @@ -433,6 +445,10 @@ EmtConvert mpack -s 1234567890absample.psb -l 131 sample.psb { ShellConvert(s, "MDF"); } + else + { + Console.WriteLine($"Input path not found: {s}"); + } } return; diff --git a/FreeMote.Tools.EmtMake/Program.cs b/FreeMote.Tools.EmtMake/Program.cs index 2344470..7d40e33 100644 --- a/FreeMote.Tools.EmtMake/Program.cs +++ b/FreeMote.Tools.EmtMake/Program.cs @@ -20,6 +20,7 @@ static void Main(string[] args) Console.ReadLine(); if (args.Length < 1 || !File.Exists(args[0])) { + PrintHelp(); return; } diff --git a/FreeMote.Tools.PsBuild/Program.cs b/FreeMote.Tools.PsBuild/Program.cs index 693a2ce..6875b20 100644 --- a/FreeMote.Tools.PsBuild/Program.cs +++ b/FreeMote.Tools.PsBuild/Program.cs @@ -152,6 +152,10 @@ PsBuild port -p win sample.psb { Port(s, portSpec, enableResolution); } + else + { + Console.WriteLine($"Input path not found: {s}"); + } } }); }); diff --git a/FreeMote.Tools.PsbDecompile/Program.cs b/FreeMote.Tools.PsbDecompile/Program.cs index 84b90dd..85d9701 100644 --- a/FreeMote.Tools.PsbDecompile/Program.cs +++ b/FreeMote.Tools.PsbDecompile/Program.cs @@ -182,6 +182,10 @@ PsbDecompile unlink sample.psb Console.WriteLine(e); } } + else + { + Console.WriteLine($"Input path not found: {psbPath}"); + } } }); }); diff --git a/FreeMote.Tools.Viewer/FreeMote.Tools.Viewer.csproj b/FreeMote.Tools.Viewer/FreeMote.Tools.Viewer.csproj index b59b9f2..0cf80b5 100644 --- a/FreeMote.Tools.Viewer/FreeMote.Tools.Viewer.csproj +++ b/FreeMote.Tools.Viewer/FreeMote.Tools.Viewer.csproj @@ -132,7 +132,7 @@ 3.0.0 - 5.0.0 + 6.0.0 4.5.0 diff --git a/FreeMote.sln.DotSettings b/FreeMote.sln.DotSettings index f883fea..a0138e1 100644 --- a/FreeMote.sln.DotSettings +++ b/FreeMote.sln.DotSettings @@ -18,6 +18,7 @@ True True True + True True True True diff --git a/FreeMote/FreeMote.csproj b/FreeMote/FreeMote.csproj index b3b7bb9..db108c1 100644 --- a/FreeMote/FreeMote.csproj +++ b/FreeMote/FreeMote.csproj @@ -79,7 +79,7 @@ - 2.2.1 + 2.3.2 4.5.5