The texture atlas
Example #1
0
        private Image CreateAtlasImage(Atlas _Atlas)
        {
            Image    img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            Graphics g   = Graphics.FromImage(img);

            if (DebugMode)
            {
                g.FillRectangle(Brushes.Green, new Rectangle(0, 0, _Atlas.Width, _Atlas.Height));
            }

            foreach (Node n in _Atlas.Nodes)
            {
                if (n.Texture != null)
                {
                    Image sourceImg = Image.FromFile(n.Texture.Source);
                    g.DrawImage(sourceImg, n.Bounds);

                    if (DebugMode)
                    {
                        string     label      = Path.GetFileNameWithoutExtension(n.Texture.Source);
                        SizeF      labelBox   = g.MeasureString(label, SystemFonts.MenuFont, new SizeF(n.Bounds.Size));
                        RectangleF rectBounds = new Rectangle(n.Bounds.Location, new Size((int)labelBox.Width, (int)labelBox.Height));
                        g.FillRectangle(Brushes.Black, rectBounds);
                        g.DrawString(label, SystemFonts.MenuFont, Brushes.White, rectBounds);
                    }
                }
                else
                {
                    g.FillRectangle(Brushes.DarkMagenta, n.Bounds);

                    if (DebugMode)
                    {
                        string     label      = n.Bounds.Width.ToString() + "x" + n.Bounds.Height.ToString();
                        SizeF      labelBox   = g.MeasureString(label, SystemFonts.MenuFont, new SizeF(n.Bounds.Size));
                        RectangleF rectBounds = new Rectangle(n.Bounds.Location, new Size((int)labelBox.Width, (int)labelBox.Height));
                        g.FillRectangle(Brushes.Black, rectBounds);
                        g.DrawString(label, SystemFonts.MenuFont, Brushes.White, rectBounds);
                    }
                }
            }

            return(img);
        }
Example #2
0
        public void Process(string _SourceDir, string _Pattern, int _AtlasSize, int _Padding, bool _DebugMode)
        {
            Padding   = _Padding;
            AtlasSize = _AtlasSize;
            DebugMode = _DebugMode;

            //1: scan for all the textures we need to pack
            ScanForTextures(_SourceDir, _Pattern);

            List <TextureInfo> textures = new List <TextureInfo>();

            textures = SourceTextures.ToList();

            //2: generate as many atlasses as needed (with the latest one as small as possible)
            Atlasses = new List <Atlas>();
            while (textures.Count > 0)
            {
                Atlas atlas = new Atlas();
                atlas.Width  = _AtlasSize;
                atlas.Height = _AtlasSize;

                List <TextureInfo> leftovers = LayoutAtlas(textures, atlas);

                if (leftovers.Count == 0)
                {
                    // we reached the last atlas. Check if this last atlas could have been twice smaller
                    while (leftovers.Count == 0)
                    {
                        atlas.Width  /= 2;
                        atlas.Height /= 2;
                        leftovers     = LayoutAtlas(textures, atlas);
                    }
                    // we need to go 1 step larger as we found the first size that is to small
                    atlas.Width  *= 2;
                    atlas.Height *= 2;
                    leftovers     = LayoutAtlas(textures, atlas);
                }

                Atlasses.Add(atlas);

                textures = leftovers;
            }
        }
Example #3
0
        private List<TextureInfo> LayoutAtlas(List<TextureInfo> _Textures, Atlas _Atlas)
        {
            List<Node> freeList = new List<Node>();
            List<TextureInfo> textures = new List<TextureInfo>();

            _Atlas.Nodes = new List<Node>();

            textures = _Textures.ToList();

            Node root = new Node();
            root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height);
            root.SplitType = SplitType.Horizontal;

            freeList.Add(root);

            while (freeList.Count > 0 && textures.Count > 0)
            {
                Node node = freeList[0];
                freeList.RemoveAt(0);

                TextureInfo bestFit = FindBestFitForNode(node, textures);
                if (bestFit != null)
                {
                    if (node.SplitType == SplitType.Horizontal)
                    {
                        HorizontalSplit(node, bestFit.Width, bestFit.Height, freeList);
                    }
                    else
                    {
                        VerticalSplit(node, bestFit.Width, bestFit.Height, freeList);
                    }

                    node.Texture = bestFit;
                    node.Bounds.Width = bestFit.Width;
                    node.Bounds.Height = bestFit.Height;

                    textures.Remove(bestFit);
                }

                _Atlas.Nodes.Add(node);
            }

            return textures;
        }
Example #4
0
        private Image CreateAtlasImage(Atlas _Atlas)
        {
            Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            Graphics g = Graphics.FromImage(img);

            if (DebugMode)
            {
                g.FillRectangle(Brushes.Green, new Rectangle(0, 0, _Atlas.Width, _Atlas.Height));
            }

            foreach (Node n in _Atlas.Nodes)
            {
                if (n.Texture != null)
                {
                    Image sourceImg = Image.FromFile(n.Texture.Source);
                    g.DrawImage(sourceImg, n.Bounds);

                    if (DebugMode)
                    {
                        string label = Path.GetFileNameWithoutExtension(n.Texture.Source);
                        SizeF labelBox = g.MeasureString(label, SystemFonts.MenuFont, new SizeF(n.Bounds.Size));
                        RectangleF rectBounds = new Rectangle(n.Bounds.Location, new Size((int)labelBox.Width, (int)labelBox.Height));
                        g.FillRectangle(Brushes.Black, rectBounds);
                        g.DrawString(label, SystemFonts.MenuFont, Brushes.White, rectBounds);
                    }
                }
                else
                {
                    g.FillRectangle(Brushes.DarkMagenta, n.Bounds);

                    if (DebugMode)
                    {
                        string label = n.Bounds.Width.ToString() + "x" + n.Bounds.Height.ToString();
                        SizeF labelBox = g.MeasureString(label, SystemFonts.MenuFont, new SizeF(n.Bounds.Size));
                        RectangleF rectBounds = new Rectangle(n.Bounds.Location, new Size((int)labelBox.Width, (int)labelBox.Height));
                        g.FillRectangle(Brushes.Black, rectBounds);
                        g.DrawString(label, SystemFonts.MenuFont, Brushes.White, rectBounds);
                    }
                }
            }

            return img;
        }
Example #5
0
        public void Process(string _SourceDir, string _Pattern, int _AtlasSize, int _Padding, bool _DebugMode)
        {
            Padding = _Padding;
            AtlasSize = _AtlasSize;
            DebugMode = _DebugMode;

            //1: scan for all the textures we need to pack
            ScanForTextures(_SourceDir, _Pattern);

            List<TextureInfo> textures = new List<TextureInfo>();
            textures = SourceTextures.ToList();

            //2: generate as many atlasses as needed (with the latest one as small as possible)
            Atlasses = new List<Atlas>();
            while (textures.Count > 0)
            {
                Atlas atlas = new Atlas();
                atlas.Width = _AtlasSize;
                atlas.Height = _AtlasSize;

                List<TextureInfo> leftovers = LayoutAtlas(textures, atlas);

                if (leftovers.Count == 0)
                {
                    // we reached the last atlas. Check if this last atlas could have been twice smaller
                    while (leftovers.Count == 0)
                    {
                        atlas.Width /= 2;
                        atlas.Height /= 2;
                        leftovers = LayoutAtlas(textures, atlas);
                    }
                    // we need to go 1 step larger as we found the first size that is to small
                    atlas.Width *= 2;
                    atlas.Height *= 2;
                    leftovers = LayoutAtlas(textures, atlas);
                }

                Atlasses.Add(atlas);

                textures = leftovers;
            }
        }
Example #6
0
        public void Process(string _SourceDir, string _Pattern, int _AtlasSize, int _Padding, bool _DebugMode)
        {
            Padding   = _Padding;
            AtlasSize = _AtlasSize;
            DebugMode = _DebugMode;

            //1: scan for all the textures we need to pack
            ScanForTextures(_SourceDir, _Pattern);

            List <TextureInfo> textures = new List <TextureInfo>();

            textures = SourceTextures.ToList();

            bool areIcons = textures.All(tex => tex.Width == tex.Height) &&
                            (int)textures.Average(tex => tex.Width * tex.Height) == textures[0].Width * textures[1].Height;

            if (AtlasSize == 1024 && areIcons)
            {
                int atlasSize = (int)Math.Ceiling(Math.Sqrt(textures.Count)) * textures[0].Width;

                if (atlasSize <= 8192)
                {
                    Console.WriteLine($"Icon mode detected, resizing atlas to {AtlasSize} pixels.");
                    AtlasSize = atlasSize;
                }
                else
                {
                    Console.WriteLine($"Atlas exceeded max size of 8192 pixels ({atlasSize})");
                }
            }

            //2: generate as many atlasses as needed (with the latest one as small as possible)
            Atlasses = new List <Atlas>();
            while (textures.Count > 0)
            {
                Atlas atlas = new Atlas();
                atlas.Width  = _AtlasSize;
                atlas.Height = _AtlasSize;

                List <TextureInfo> leftovers = LayoutAtlas(textures, atlas);

                if (leftovers.Count == 0)
                {
                    // we reached the last atlas. Check if this last atlas could have been twice smaller
                    while (leftovers.Count == 0)
                    {
                        atlas.Width  /= 2;
                        atlas.Height /= 2;
                        leftovers     = LayoutAtlas(textures, atlas);
                    }
                    // we need to go 1 step larger as we found the first size that is to small
                    atlas.Width  *= 2;
                    atlas.Height *= 2;
                    leftovers     = LayoutAtlas(textures, atlas);
                }

                Atlasses.Add(atlas);

                textures = leftovers;
            }
        }
Example #7
0
        static unsafe void Main(string[] args)
        {
            int ValidateAtlasSize(int atlassize)
            {
                if (atlassize > 4096)
                {
                    atlassize = 4096;
                }
                else if (atlassize > 2048)
                {
                    atlassize = 2048;
                }
                else if (atlassize > 1024)
                {
                    atlassize = 1024;
                }
                else if (atlassize > 512)
                {
                    atlassize = 512;
                }
                else if (atlassize > 256)
                {
                    atlassize = 256;
                }
                else if (atlassize > 128)
                {
                    atlassize = 128;
                }
                else
                {
                    atlassize = 64;
                }

                return(atlassize);
            }

            int ValidatePaddingAmount(int Padding)
            {
                return(Math.Min(Math.Max(Padding, 0), 16));
            }

            int  AtlasSize            = 4096;
            int  PaddingBetweenImages = 1;
            bool OutputXML            = false;
            bool OutputBinary         = false;
            bool OutputJson           = false;
            bool EnablePremultiply    = false;
            bool EnableTrimming       = false;
            bool VerboseOutput        = false;
            bool ForcePack            = false;
            bool CheckUnique          = false;
            bool CheckRotate          = false;

            if (args.Length < 4)
            {
                Console.WriteLine(@"
TexturePacker - command line texture packer
 ====================================
 
 usage:
    TexturePacker [OUTPUT] [INPUT1,INPUT2,INPUT3...] [OPTIONS...]
 
 example:
    TexturePacker bin/atlases/atlas assets/characters,assets/tiles -p -t -v -u -r
 
 options:
    -d  --default           use default settings (-x -p -t -u)
    -x  --xml               saves the atlas data as a .xml file
    -b  --binary            saves the atlas data as a .bin file
    -j  --json              saves the atlas data as a .json file
    -p  --premultiply       premultiplies the pixels of the bitmaps by their alpha channel
    -t  --trim              trims excess transparency off the bitmaps
    -v  --verbose           print to the debug console as the packer works
    -f  --force             ignore the hash, forcing the packer to repack
    -u  --unique            remove duplicate bitmaps from the atlas
    -r  --rotate            enabled rotating bitmaps 90 degrees clockwise when packing
    -s# --size#             max atlas size (# can be 4096, 2048, 1024, 512, 256, 128, or 64)
    -p# --pad#              padding between images (# can be from 0 to 16)
 
      binary format:
                    [int16] num_textures (below block is repeated this many times)
                    [byte] img_rotated          
                    [byte] img_trimmed          
                    [byte] img_premultiplied         
                        [string] name
                        [int16] atlas_width
                        [int16] atlas_height
                        [int16] num_images (below block is repeated this many times)
                            [string] img_name
                            [int16] img_x
                            [int16] img_y
                            [int16] img_width
                            [int16] img_height
                            [int16] img_frame_x         (if --trim enabled)
                            [int16] img_frame_y         (if --trim enabled)
                            [int16] img_frame_width     (if --trim enabled)
                            [int16] img_frame_height    (if --trim enabled)
");
            }
            else
            {
                var OutputFileInfo = new FileInfo(args[0]);

                var InputDirectories = args[1].Split(',').Select(x => new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), x))).ToList();

                for (int i = 2; i < args.Length; i++)
                {
                    string arg = args[i];
                    if (arg == "-d" || arg == "--default")
                    {
                        OutputXML = EnablePremultiply = EnableTrimming = CheckUnique = true;
                    }
                    else if (arg == "-x" || arg == "--xml")
                    {
                        OutputXML = true;
                    }
                    else if (arg == "-b" || arg == "--binary")
                    {
                        OutputBinary = true;
                    }
                    else if (arg == "-j" || arg == "--json")
                    {
                        OutputJson = true;
                    }
                    else if (arg == "-p" || arg == "--premultiply")
                    {
                        EnablePremultiply = true;
                    }
                    else if (arg == "-t" || arg == "--trim")
                    {
                        EnableTrimming = true;
                    }
                    else if (arg == "-v" || arg == "--verbose")
                    {
                        VerboseOutput = true;
                    }
                    else if (arg == "-f" || arg == "--force")
                    {
                        ForcePack = true;
                    }
                    else if (arg == "-u" || arg == "--unique")
                    {
                        CheckUnique = true;
                    }
                    else if (arg == "-r" || arg == "--rotate")
                    {
                        CheckRotate = true;
                    }
                    else if (arg.Contains("--size"))
                    {
                        AtlasSize = ValidateAtlasSize(int.Parse(arg.Substring("--size".Length)));
                    }
                    else if (arg.Contains("-s"))
                    {
                        AtlasSize = ValidateAtlasSize(int.Parse(arg.Substring("-s".Length)));
                    }
                    else if (arg.Contains("--pad"))
                    {
                        PaddingBetweenImages = ValidatePaddingAmount(int.Parse(arg.Substring("--pad".Length)));
                    }
                    else if (arg.Contains("-p"))
                    {
                        PaddingBetweenImages = ValidatePaddingAmount(int.Parse(arg.Substring("-p".Length)));
                    }
                    else
                    {
                        Console.WriteLine($"unexpected argument: {arg}");
                    }
                }

                int NewHash = 0;

                for (int i = 0; i < args.Length; i++)
                {
                    NewHash += args[i].GetHashCode();
                }

                for (int i = 0; i < InputDirectories.Count; i++)
                {
                    var Directory = InputDirectories[i];
                    foreach (var texfile in Directory.GetFiles("*.png"))
                    {
                        NewHash += texfile.GetHashCode();
                    }
                }
                bool AllowPack = ForcePack;
                if (OutputFileInfo.Directory.Exists)
                {
                    if (File.Exists($"{OutputFileInfo.FullName}.hash") && !ForcePack)
                    {
                        int oldhash = int.Parse(File.ReadAllText($"{OutputFileInfo.FullName}.hash"));
                        if (oldhash != NewHash)
                        {
                            File.WriteAllText($"{OutputFileInfo.FullName}.hash", NewHash.ToString());
                            AllowPack = true;
                        }
                    }
                    else
                    {
                        File.WriteAllText($"{OutputFileInfo.FullName}.hash", NewHash.ToString());
                        AllowPack = true;
                    }
                }
                else
                {
                    OutputFileInfo.Directory.Create();
                    File.WriteAllText($"{OutputFileInfo.FullName}.hash", NewHash.ToString());
                }

                if (AllowPack)
                {
                    if (VerboseOutput)
                    {
                        Console.WriteLine("Reading all pngs ");
                    }

                    List <PackerBitmap> Bitmaps = new List <PackerBitmap>();
                    List <Packer>       Packers = new List <Packer>();

                    foreach (var Directory in InputDirectories)
                    {
                        foreach (var texfile in Directory.GetFiles("*.png"))
                        {
                            var texture = new TexHandle();
                            TextureLoadUtil.LoadTexture(texfile.FullName, ref texture);
                            Bitmaps.Add(new PackerBitmap(texture, texfile.Name, EnablePremultiply, EnableTrimming));
                        }
                    }

                    Bitmaps.Sort();

                    while (Bitmaps.Count > 0)
                    {
                        if (VerboseOutput)
                        {
                            Console.WriteLine("packing " + Bitmaps.Count + " images...");
                        }
                        var packer = new Packer(AtlasSize, AtlasSize, PaddingBetweenImages);
                        packer.Pack(Bitmaps, VerboseOutput, CheckUnique, CheckRotate);
                        Packers.Add(packer);
                        if (VerboseOutput)
                        {
                            Console.WriteLine("finished packing: " + Packers.Count + " (" + packer.Width + " x " + packer.Height + ')');
                        }
                        if (packer.Bitmaps.Count <= 0)
                        {
                            Console.WriteLine("packing failed, could not fit any bitmap ");
                            return;
                        }
                    }

                    var OutputAtlasData = new List <Atlas>();

                    for (int i = 0; i < Packers.Count; i++)
                    {
                        var OutAtlas = new Atlas();
                        OutAtlas.Name            = OutputFileInfo.Name + i + ".png";
                        OutAtlas.Width           = Packers[i].Width;
                        OutAtlas.Height          = Packers[i].Height;
                        OutAtlas.IsRotated       = CheckRotate;
                        OutAtlas.IsTrimmed       = EnableTrimming;
                        OutAtlas.IsPremultiplied = EnablePremultiply;
                        OutAtlas.Images          = new List <AtlasImage>();
                        for (int t = 0; t < Packers[i].Bitmaps.Count; t++)
                        {
                            var Image = new AtlasImage();
                            Image.Name   = Packers[i].Bitmaps[t].Name.Remove(Packers[i].Bitmaps[t].Name.Length - ".png".Length);
                            Image.X      = Packers[i].Points[t].x;
                            Image.Y      = Packers[i].Points[t].y;
                            Image.Width  = Packers[i].Bitmaps[t].Width;
                            Image.Height = Packers[i].Bitmaps[t].Height;
                            Image.FrameX = Packers[i].Bitmaps[t].FrameX;
                            Image.FrameY = Packers[i].Bitmaps[t].FrameY;
                            Image.FrameW = Packers[i].Bitmaps[t].FrameW;
                            Image.FrameH = Packers[i].Bitmaps[t].FrameH;
                            OutAtlas.Images.Add(Image);
                        }
                        OutputAtlasData.Add(OutAtlas);
                    }


                    for (int i = 0; i < Packers.Count; ++i)
                    {
                        if (VerboseOutput)
                        {
                            Console.WriteLine("writing png: " + OutputFileInfo.Name + i + ".png");
                        }
                        Packers[i].SavePng(OutputFileInfo.FullName + i + ".png");
                    }


                    if (OutputBinary)
                    {
                        if (VerboseOutput)
                        {
                            Console.WriteLine("Saving binary: " + OutputFileInfo.Name + ".bin");
                        }

                        var FStream = File.OpenWrite(OutputFileInfo.FullName + ".bin");
                        using (var stringwriter = new BinaryWriter(FStream, Encoding.ASCII))
                        {
                            stringwriter.Write((ushort)OutputAtlasData.Count);
                            stringwriter.Write((byte)(CheckRotate ? 1 : 0));
                            stringwriter.Write((byte)(EnableTrimming ? 1 : 0));
                            stringwriter.Write((byte)(EnablePremultiply ? 1 : 0));
                            for (int i = 0; i < OutputAtlasData.Count; ++i)
                            {
                                stringwriter.Write(OutputAtlasData[i].Name);
                                stringwriter.Write((ushort)OutputAtlasData[i].Width);
                                stringwriter.Write((ushort)OutputAtlasData[i].Height);
                                stringwriter.Write((ushort)OutputAtlasData[i].Images.Count);
                                for (int t = 0; t < OutputAtlasData[i].Images.Count; t++)
                                {
                                    stringwriter.Write(OutputAtlasData[i].Images[t].Name);
                                    stringwriter.Write((ushort)OutputAtlasData[i].Images[t].X);
                                    stringwriter.Write((ushort)OutputAtlasData[i].Images[t].Y);
                                    stringwriter.Write((ushort)OutputAtlasData[i].Images[t].Width);
                                    stringwriter.Write((ushort)OutputAtlasData[i].Images[t].Height);
                                    stringwriter.Write((ushort)OutputAtlasData[i].Images[t].FrameX);
                                    stringwriter.Write((ushort)OutputAtlasData[i].Images[t].FrameY);
                                    stringwriter.Write((ushort)OutputAtlasData[i].Images[t].FrameW);
                                    stringwriter.Write((ushort)OutputAtlasData[i].Images[t].FrameH);
                                }
                            }
                        }
                        FStream.Close();
                    }


                    if (OutputXML)
                    {
                        if (VerboseOutput)
                        {
                            Console.WriteLine("Saving xml: " + OutputFileInfo.Name + ".xml");
                        }

                        using (var stringwriter = new StringWriter())
                        {
                            var serializer = new XmlSerializer(OutputAtlasData.GetType());
                            serializer.Serialize(stringwriter, OutputAtlasData);
                            File.WriteAllText(OutputFileInfo.FullName + ".xml", stringwriter.ToString());
                        }
                    }


                    if (OutputJson)
                    {
                        if (VerboseOutput)
                        {
                            Console.WriteLine("Saving json: " + OutputFileInfo.Name + ".json");
                        }
                        File.WriteAllText(OutputFileInfo.FullName + ".json", JsonConvert.SerializeObject(OutputAtlasData, Newtonsoft.Json.Formatting.Indented));
                    }
                }
                else
                {
                    Console.WriteLine("Atlas is unchanged");
                }
            }
        }