Пример #1
0
        static void Main(string[] args)
        {
            Stopwatch stopwatch = new Stopwatch();

            PackingRectangle[] rectangles = GetRectangles();
            Console.WriteLine("Packing " + rectangles.Length + " rectangles...");

            stopwatch.Restart();
            RectanglePacker.Pack(rectangles, out PackingRectangle bounds);
            stopwatch.Stop();

            Console.WriteLine("Took ~" + stopwatch.Elapsed.TotalMilliseconds.ToString() + "ms");

            if (RectanglePacker.AnyIntersects(rectangles))
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("Some rectangles intersect!");
            }
            else
            {
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("No rectangles intersect.");
            }

            Console.ResetColor();

            string filename = GetImageName();

            Console.WriteLine("Saving as " + filename);
            SaveAsImage(rectangles, bounds, filename);
        }
Пример #2
0
        void placeRooms()
        {
            var packer = new RectanglePacker();

            for (int roomIndex = 0; roomIndex < rooms.Count; roomIndex++)
            {
                var roomData     = rooms[roomIndex];
                var roomTiledMap = roomData.roomPrefab.GetComponent <Tiled2Unity.TiledMap>();
                roomData.size = myMath.getTiledMapSize(roomTiledMap);
            }
            rooms.Sort((a, b) => b.Area.CompareTo(a.Area));

            for (int roomIndex = 0; roomIndex < rooms.Count; roomIndex++)
            {
                var   roomData = rooms[roomIndex];
                float newx;
                float newy;

                if (!packer.Pack(roomData.size.x, roomData.size.y, out newx, out newy))
                {
                    throw new Exception("Uh oh, we couldn't pack the rectangle :(");
                }

                //This rectangle is now packed into position!
                roomData.position = new Vector3(newx, newy);

                roomData.roomGameObject = Instantiate(roomData.roomPrefab, roomData.position, Quaternion.identity);
            }
        }
        public Texture2D SetupTextureAtlas(GraphicsDevice graphicsDevice)
        {
            var triangles = Models.SelectMany(m => m.ModelTriangles).ToList();
            var dict      = new Dictionary <KeyValuePair <string, Rectangle>, TrianglesWithCroppedTexture>();

            foreach (var triangle in triangles)
            {
                if (!dict.ContainsKey(triangle.TextureKey))
                {
                    if (!TextureHandler.HasCroppedTexture(triangle.TextureKey))
                    {
                        TextureHandler.AddCroppedTexture(triangle.TextureKey, TextureHandler.CropTexture(triangle.OriginalTexture, triangle.OriginalTextureRectangle));
                    }

                    dict.Add(triangle.TextureKey, new TrianglesWithCroppedTexture()
                    {
                        CroppedTexture       = TextureHandler.GetCroppedTexture(triangle.TextureKey).Key,
                        HasTransparentPixels = TextureHandler.GetCroppedTexture(triangle.TextureKey).Value
                    });
                }

                dict[triangle.TextureKey].ModelTriangles.Add(triangle);
            }

            var packer = new RectanglePacker(4096, 4096);

            foreach (var pair in dict)
            {
                var rect = pair.Value.CroppedTexture.Bounds;
                if (!packer.Pack(rect.Width, rect.Height, out rect.X, out rect.Y))
                {
                    throw new Exception("Uh oh, we couldn't pack the rectangles");
                }
                pair.Value.AtlasRectangle = rect;
            }

            foreach (var pair in dict)
            {
                foreach (var triangle in pair.Value.ModelTriangles)
                {
                    triangle.AtlasTextureRectangle = pair.Value.AtlasRectangle;
                    triangle.HasTransparentPixels  = pair.Value.HasTransparentPixels;
                }
            }

            var atlas = new Texture2D(graphicsDevice, 4096, 4096);

            foreach (var pair in dict)
            {
                var data = new Color[pair.Value.CroppedTexture.Width * pair.Value.CroppedTexture.Height];
                pair.Value.CroppedTexture.GetData(0, pair.Value.CroppedTexture.Bounds, data, 0, data.Length);
                atlas.SetData(0, pair.Value.AtlasRectangle, data, 0, data.Length);
                pair.Value.CroppedTexture = null;
            }

            return(atlas);
        }
Пример #4
0
        private bool TryPacking(int width, int height, Packable[] packables)
        {
            var packer = new RectanglePacker(width, height, 2048, 2048);

            foreach (var face in packables)
            {
                int x, y;
                if (!packer.Pack(face.Width, face.Height, out x, out y))
                {
                    return(false);
                }
                _packing[face.Index] = new IntRect(x + 1, y + 1, face.Width - 2, face.Height - 2);
            }

            SetBoundingSize(packer.Width, packer.Height);
            return(true);
        }
Пример #5
0
    public static void sort_rects(List <PackerRect> rects, int dx = 0, int dy = 0, float mx = 1.0f, float my = 1.0f)
    {
        rects.Sort((a, b) => b.Area.CompareTo(a.Area));
        var packer = new RectanglePacker();

        for (int i = 0; i < rects.Count; ++i)
        {
            var rect = rects[i];
            int x, y;
            if (!packer.Pack((int)rect.w, (int)rect.h, out x, out y))
            {
                throw new Exception("Uh oh, we couldn't pack the rectangle :(");
            }
            rect.x = x;
            rect.y = y;

            var       p   = currentPackage.QueryComponent(rect.ID);
            int []    m   = { 1024, 0, 0, 1024, (int)((x + dx) * 16 * mx), (int)((y + dy) * 16 * my) };
            Matrix4x4 mat = Matrix.ToMatrix4x4(m);
            Matrix.SetTransformFromMatrix(p.transform, ref mat);
        }
    }
Пример #6
0
        public Glyph?GetGlyph(uint Unicode)
        {
            if (GlyphTexture.ContainsKey(Unicode))
            {
                return(GlyphTexture[Unicode]);
            }

            FontCharacter FChar = new FontCharacter();

            if (Msdfgen.LoadGlyphNormal(Fnt, Unicode, ref FChar))
            {
                if (FChar.Width == 0 || FChar.Height == 0)
                {
                    if (!GlyphTexture.ContainsKey(Unicode))
                    {
                        GlyphTexture.Add(Unicode, new Glyph(FChar, null));
                    }

                    return(GetGlyph(Unicode));
                }

                Glyph G = new Glyph(FChar, FChar.GetBitmap());
                if (!Packer.Pack(G.Bitmap.Width, G.Bitmap.Height, out G.X, out G.Y))
                {
                    throw new NotImplementedException("Cannot pack glyph");
                }

                G.Bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
                TextureAtlas.SubRect2D(G.Bitmap, G.X, G.Y);
                G.Bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);

                GlyphTexture.Add(Unicode, G);
                return(GetGlyph(Unicode));
            }

            return(null);
        }
Пример #7
0
        public unsafe FontAtlas(FontFamily fontFamily, int fontSize, IEnumerable <int> characters)
        {
            Stopwatch watch = new Stopwatch();

            watch.Start();

            Size = fontSize;
            Font  font   = new Font(fontFamily, fontSize);
            float factor = (float)fontSize / font.EmSize;
            List <PackingRectangle> glyphRectangles = new List <PackingRectangle>();

            foreach (int character in characters)
            {
                GlyphInstance glyph = font.GetGlyph(character).Instance;
                glyphRectangles.Add(new PackingRectangle(0, 0, (uint)((glyph.LeftSideBearing + glyph.AdvanceWidth) * factor), (uint)(glyph.Height * factor), character));
            }

            PackingRectangle[] rectArray = glyphRectangles.ToArray();
            RectanglePacker.Pack(rectArray, out PackingRectangle bounds);

            Image <A8> image = new Image <A8>((int)bounds.Width, (int)bounds.Height);

            Characters = new Dictionary <char, CharacterData>();
            image.Mutate(x =>
            {
                foreach (PackingRectangle glyphRectangle in rectArray)
                {
                    GlyphInstance glyph = font.GetGlyph(glyphRectangle.Id).Instance;
                    Characters.Add((char)glyphRectangle.Id, new CharacterData()
                    {
                        uvX1     = glyphRectangle.X / (float)image.Width,
                        uvX2     = (glyphRectangle.X + glyphRectangle.Width) / (float)image.Width,
                        uvY1     = glyphRectangle.Y / (float)image.Height,
                        uvY2     = (glyphRectangle.Y + glyphRectangle.Height) / (float)image.Height,
                        xAdvance = glyph.AdvanceWidth * factor,
                        height   = glyph.Height * factor
                    });
                    x.DrawText(((char)glyphRectangle.Id).ToString(), font, Color.White,
                               new PointF(glyphRectangle.X, glyphRectangle.Y));
                }
            });

            image.TryGetSinglePixelSpan(out Span <A8> pixels);

            TextureId = GL.GenTexture();
            GL.ActiveTexture(TextureUnit.Texture0);
            GL.BindTexture(TextureTarget.Texture2D, TextureId);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
            GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
            fixed(A8 *pixelPointer = pixels)
            {
                GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Alpha, image.Width, image.Height, 0, PixelFormat.Alpha, PixelType.UnsignedByte, new IntPtr(pixelPointer));
            }

            GL.GenerateTextureMipmap(TextureId);

            watch.Stop();
            Console.WriteLine($"Font atlas with size {image.Width}x{image.Height} was created in {watch.ElapsedMilliseconds}ms!");
        }
Пример #8
0
        /// <summary>
        /// Creates a <see cref="TrippyFontFile"/> holding information for multiple fonts.
        /// </summary>
        /// <param name="glyphSources">The <see cref="IGlyphSource"/>-s for getting the information of each font.</param>
        /// <param name="backgroundColor">The background color of the generated image. Null for transparent.</param>
        public static TrippyFontFile CreateFontFile(ReadOnlySpan <IGlyphSource> glyphSources, Color?backgroundColor = null)
        {
            // We create all the TextureFontData-s and query their basic information from the glyph sources.
            TextureFontData[] fontDatas = new TextureFontData[glyphSources.Length];
            for (int i = 0; i < fontDatas.Length; i++)
            {
                fontDatas[i] = new TextureFontData()
                {
                    Size      = glyphSources[i].Size,
                    FirstChar = glyphSources[i].FirstChar,
                    LastChar  = glyphSources[i].LastChar,
                    Ascender  = glyphSources[i].Ascender,
                    Descender = glyphSources[i].Descender,
                    LineGap   = glyphSources[i].LineGap,
                    Name      = glyphSources[i].Name
                };
            }

            // We count the total amount of characters in all glyph sources combined.
            int charCount = 0;

            for (int i = 0; i < glyphSources.Length; i++)
            {
                charCount += fontDatas[i].CharCount;
            }

            // We need to find a way to pack all the characters into a single texture.
            // For this we use the included Rectpack library in TrippyGL.Fonts.Rectpack.
            PackingRectangle[] packingRects = new PackingRectangle[charCount];

            // The way we identify the PackingRectangles is with their rect.Id property.
            // Since we have multiple fonts, we can't set these to the characters they represent
            // because there can be collisions. So the way we assign IDs will be basically like this:
            // The IDs for the characters of the first font start at 0 and go up to font.CharCount
            // inclusive. These are, of course, in order of character.
            // The second font then gets the range that starts right after where the first font's
            // range ends and gets enough range for all it's characters, etc etc.

            // So we need to know where the range of IDs for each font starts and end. We'll store it here:
            Span <int> idsStart = glyphSources.Length <= 96 ? stackalloc int[glyphSources.Length] : new int[glyphSources.Length];

            int packingRectCount = 0;

            for (int i = 0; i < fontDatas.Length; i++)
            {
                // We calculate the starting ID for the current glyph source.
                int idStart = i == 0 ? 0 : idsStart[i - 1] + fontDatas[i - 1].CharCount;
                idsStart[i] = idStart;

                // We go through all the characters in the current glyph source.
                for (int c = fontDatas[i].FirstChar; c <= fontDatas[i].LastChar; c++)
                {
                    // We get the size. If it is positive, we add a PackingRectangle to represent it.
                    System.Drawing.Point size = glyphSources[i].GetGlyphSize(c);
                    if (size.X > 0 && size.Y > 0)
                    {
                        // We add 2 to the width and height of the rectangle so chars have an empty border.
                        int id = idStart + c - fontDatas[i].FirstChar;
                        packingRects[packingRectCount++] = new PackingRectangle(0, 0, (uint)size.X + 2, (uint)size.Y + 2, id);
                    }
                }
            }

            // We trim extra elements off the packingRects array.
            if (packingRects.Length != packingRectCount)
            {
                Array.Resize(ref packingRects, packingRectCount);
            }

            // We use RectanglePacker to find a bin for all the rectangles.
            RectanglePacker.Pack(packingRects, out PackingRectangle bounds);

            Image <Rgba32> image = new Image <Rgba32>((int)bounds.Width, (int)bounds.Height);

            try
            {
                // First we clear the image to the specified background color, or transparent.
                Color bgColor = backgroundColor ?? Color.Transparent;
                image.Mutate(x => x.BackgroundColor(bgColor));

                // We create the source rectangles arrays for all the fontDatas.
                for (int i = 0; i < fontDatas.Length; i++)
                {
                    fontDatas[i].SourceRectangles = new System.Drawing.Rectangle[fontDatas[i].CharCount];
                }

                // We go through all the packing rectangles.
                for (int i = 0; i < packingRects.Length; i++)
                {
                    PackingRectangle rect = packingRects[i];

                    // We find which glyph source this rectangle belongs to.
                    int glyphSourceIndex = 0;
                    while (glyphSourceIndex + 1 < idsStart.Length && idsStart[glyphSourceIndex + 1] < rect.Id)
                    {
                        glyphSourceIndex++;
                    }

                    // We find which character this rectangle represents.
                    int charIndex = rect.Id - idsStart[glyphSourceIndex];
                    int charCode  = charIndex + fontDatas[glyphSourceIndex].FirstChar;

                    // We draw the glyph onto the image at this rectangle's location.
                    glyphSources[glyphSourceIndex].DrawGlyphToImage(charCode, new System.Drawing.Point((int)rect.X + 1, (int)rect.Y + 1), image);

                    // We set the glyph's source to match this rectangle.
                    fontDatas[glyphSourceIndex].SourceRectangles[charIndex] = new System.Drawing.Rectangle((int)rect.X + 1, (int)rect.Y + 1, (int)rect.Width - 2, (int)rect.Height - 2);
                }

                // We go through all the fontDatas and set the remaining information.
                for (int i = 0; i < fontDatas.Length; i++)
                {
                    fontDatas[i].RenderOffsets = glyphSources[i].GetRenderOffsets();

                    glyphSources[i].GetAdvances(out fontDatas[i].Advances);

                    if (!glyphSources[i].TryGetKerning(out fontDatas[i].KerningOffsets))
                    {
                        fontDatas[i].KerningOffsets = null;
                    }
                }

                // Done!
                return(new TrippyFontFile(fontDatas, image));
            }
            catch
            {
                // If anything failed, we dispose the image and re-throw the exception.
                image.Dispose();
                throw;
            }
        }
Пример #9
0
        public static void Main(string[] args)
        {
            //Create a list of random rectangles, sorted by size
            var rand = new Random();
            var rects = new List<Rect>();
            for (int i = 0; i < 50; ++i)
                rects.Add(new Rect(0, 0, rand.Next(2, 10), rand.Next(2, 10)));
            for (int i = 0; i < 50; ++i)
                rects.Add(new Rect(0, 0, rand.Next(7, 10), rand.Next(1, 4)));
            for (int i = 0; i < 50; ++i)
                rects.Add(new Rect(0, 0, rand.Next(1, 4), rand.Next(7, 10)));
            for (int i = 0; i < 50; ++i)
                rects.Add(new Rect(0, 0, rand.Next(1, 4), rand.Next(1, 4)));

            rects.Sort((a, b) => b.Area.CompareTo(a.Area));

            var watch = Stopwatch.StartNew();

            //Pack the rectangles
            var packer = new RectanglePacker();
            for (int i = 0; i < rects.Count; ++i)
            {
                var rect = rects[i];

                //Pad the rectangles by 1px so there is a gap between them
                if (!packer.Pack(rect.W + 1, rect.H + 1, out rect.X, out rect.Y))
                {
                    Console.WriteLine("Packing failed");
                    return;
                }

                rects[i] = rect;
            }

            int packTime = (int)watch.ElapsedMilliseconds;
            int totalArea = packer.Width * packer.Height;
            int usedArea = 0;

            //Create a 2D grid of "pixels" and fill them where rectangles are
            var grid = new bool[packer.Width, packer.Height];
            foreach (var rect in rects)
            {
                usedArea += (rect.W + 1) * (rect.H + 1);

                for (int y = 0; y < rect.H; ++y)
                    for (int x = 0; x < rect.W; ++x)
                        grid[rect.X + x, rect.Y + y] = true;
            }

            //Print out the results to a text file
            using (var writer = new StreamWriter("results.txt"))
            {
                int percent = (int)(((float)usedArea / totalArea) * 100f);
                writer.WriteLine("Packed: " + rects.Count + " rectangles");
                writer.WriteLine("Size: " + packer.Width + " x " + packer.Height);
                writer.WriteLine("Usage: " + usedArea + " / " + totalArea + " (" + percent + "%)");
                writer.WriteLine("Pack Time: " + packTime + " ms");
                writer.WriteLine();

                for (int y = 0; y < packer.Height; ++y)
                {
                    for (int x = 0; x < packer.Width; ++x)
                        writer.Write(grid[x, y] ? '#' : ' ');
                    writer.WriteLine();
                }
            }

            Console.WriteLine("Finished successfully. See results.txt");
        }
Пример #10
0
        // example args: -input.path=D:\some\path\to\images\here -filter=*.jpg -prefix=team -output.extension=jpg -output.path= -output.resize=240
        static void Main(string[] args)
        {
            string folder         = null;
            string filter         = "*.*";
            string atlasPath      = Directory.GetCurrentDirectory();
            string cssPath        = null;
            string jsonPath       = null;
            string xmlPath        = null;
            string csvPath        = null;
            string atlasExtension = "jpg";
            string prefix         = null;
            int    resize         = 0;

            var  globalMargin = new Margin();
            bool normalize    = false;

            foreach (var entry in args)
            {
                var arg = entry;

                if (!arg.StartsWith("-"))
                {
                    Console.WriteLine("Invalid argument: " + arg);
                    return;
                }

                arg = arg.Substring(1);

                var temp = arg.Split(new char[] { '=' }, 2);

                var key = temp[0].ToLower();
                var val = temp.Length == 2 ? temp[1] : null;

                switch (key)
                {
                case "input.path": folder = val; break;

                case "input.filter": filter = val; break;

                case "prefix": prefix = val; break;

                case "atlas.extension": atlasExtension = val.Replace(".", ""); break;

                case "atlas.path": atlasPath = val; break;

                case "resize": resize = int.Parse(val); break;

                case "css.path": cssPath = val; break;

                case "json.path": jsonPath = val; break;

                case "xml.path": xmlPath = val; break;

                case "csv.path": csvPath = val; break;

                case "margin.X": globalMargin.X = int.Parse(val); break;

                case "margin.Y": globalMargin.Y = int.Parse(val); break;

                case "margin": globalMargin.X = int.Parse(val); globalMargin.Y = globalMargin.X; break;

                case "normalize": normalize = bool.Parse(val); break;
                }
            }

            if (normalize && resize > 0)
            {
                Console.WriteLine("Normalize and resize options are mutually exclusive.");
                return;
            }

            FixPath(ref atlasPath);
            FixPath(ref cssPath);
            FixPath(ref jsonPath);
            FixPath(ref xmlPath);
            FixPath(ref csvPath);

            if (folder == null)
            {
                Console.WriteLine("Please specify a folder. Eg: -input.path=some_path");
                return;
            }

            if (prefix == null)
            {
                Console.WriteLine("Please specify a atlas prefix. Eg: --prefix=something");
                return;
            }

            var outPicName = prefix + "." + atlasExtension;
            var outCSSName = prefix + ".css";

            var files = Directory.GetFiles(folder, filter);

            var images = new Dictionary <string, Bitmap>();

            int count = 0;

            int avgWidth  = 0;
            int avgHeight = 0;

            int maxWidth  = 0;
            int maxHeight = 0;

            foreach (var file in files)
            {
                var img = new Bitmap(Bitmap.FromFile(file));

                if (resize != 0)
                {
                    img = new Bitmap(img, new Size(resize, resize));
                }

                images[file] = img;
                count++;

                maxWidth  = Math.Max(maxWidth, img.Width);
                maxHeight = Math.Max(maxHeight, img.Height);

                avgWidth  += img.Width;
                avgHeight += img.Height;
            }

            int maxSize = Math.Max(maxHeight, maxWidth);

            avgWidth  /= count;
            avgHeight /= count;

            int side = (int)Math.Ceiling(Math.Sqrt(count));

            int atlasWidth  = avgWidth * side;
            int atlasHeight = avgHeight * side;

            int tot;

            int tries = 0;
            RectanglePacker <string> packer;

            var margins = new Dictionary <string, Margin>();

            if (normalize)
            {
                foreach (var file in files)
                {
                    var img    = images[file];
                    var margin = new Margin()
                    {
                        X = (maxSize - img.Width) / 2 + globalMargin.X,
                        Y = (maxSize - img.Height) / 2 + globalMargin.Y,
                    };
                    margins[file] = margin;
                }
            }
            else
            {
                foreach (var file in files)
                {
                    margins[file] = globalMargin;
                }
            }

            do
            {
                packer = new RectanglePacker <string>();


                foreach (var entry in images)
                {
                    var margin = margins[entry.Key];
                    packer.AddRect(entry.Value.Width + margin.X * 2, entry.Value.Height + margin.Y * 2, entry.Key);
                }

                tot = packer.Pack(0, 0, atlasWidth, atlasHeight);
                if (tot == 0)
                {
                    break;
                }

                if (tries % 2 == 0)
                {
                    atlasWidth *= 2;
                }
                else
                {
                    atlasHeight *= 2;
                }

                tries++;
                if (tries > 5)
                {
                    break;
                }
            } while (true);

            if (tot == 0)
            {
                var output = new Bitmap(atlasWidth, atlasHeight);

                using (Graphics g = Graphics.FromImage(output))
                {
                    foreach (var file in files)
                    {
                        int x, y;
                        packer.GetRect(file, out x, out y);

                        var margin = margins[file];
                        x += margin.X;
                        y += margin.Y;

                        var img = images[file];
                        g.DrawImage(img, x, y, img.Width, img.Height);

                        Console.WriteLine("Merged " + file);
                    }
                }

                output.Save(atlasPath + outPicName);

                Console.WriteLine("Generated " + atlasPath + outPicName);

                if (cssPath != null)
                {
                    var sb = new StringBuilder();

                    int index = 0;
                    foreach (var file in files)
                    {
                        var name = Path.GetFileNameWithoutExtension(file).ToLower();

                        if (index > 0)
                        {
                            sb.Append(", ");
                        }

                        if (index % side == side - 1)
                        {
                            sb.AppendLine();
                        }

                        sb.Append($".{prefix}-{name}");

                        index++;
                    }

                    sb.Append('{');
                    sb.AppendLine();
                    sb.AppendLine($"\tbackground-image: url('{outPicName}');");
                    sb.AppendLine("\tbackground-repeat: no-repeat;");
                    sb.Append('}');
                    sb.AppendLine();

                    foreach (var file in files)
                    {
                        int x, y;
                        packer.GetRect(file, out x, out y);

                        var margin = margins[file];
                        x += margin.X;
                        y += margin.Y;

                        var name = Path.GetFileNameWithoutExtension(file).ToLower();
                        var img  = images[file];

                        sb.AppendLine();
                        sb.AppendLine("." + prefix + "-" + name + " {");
                        sb.AppendLine($"\twidth: {img.Width}px;");
                        sb.AppendLine($"\theight: {img.Height}px;");
                        sb.AppendLine($"\tbackground-position: -{x}px -{y}px;");
                        sb.Append('}');
                        sb.AppendLine();
                    }

                    File.WriteAllText(cssPath + outCSSName, sb.ToString());

                    Console.WriteLine("Generated " + cssPath + outCSSName);
                }

                if (xmlPath != null || csvPath != null || jsonPath != null)
                {
                    if (jsonPath != null)
                    {
                        var node = ExportToNode(files, images, margins, packer, false);
                        var json = JSONWriter.WriteToString(node);
                        File.WriteAllText(jsonPath + prefix + ".json", json);
                    }

                    if (xmlPath != null)
                    {
                        var node = ExportToNode(files, images, margins, packer, true);
                        var xml  = XMLWriter.WriteToString(node);
                        File.WriteAllText(xmlPath + prefix + ".xml", xml);
                    }

                    if (csvPath != null)
                    {
                        var node = ExportToNode(files, images, margins, packer, false);
                        var csv  = CSVWriter.WriteToString(node);
                        File.WriteAllText(csvPath + prefix + ".csv", csv);
                    }
                }
            }
            else
            {
                Console.WriteLine("Failed packing...");
            }
        }