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; + } } } }