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 void PackerTexture1() { Texture2D[] packerTexs = _texMap.Values.ToArray(); _texMap.Clear(); _mainTex = new Texture2D(_defaultWh, _defaultWh, TextureFormat.ARGB32, false); _mainTex.wrapMode = TextureWrapMode.Clamp; const int padding = 0; RectanglePacker packer = new RectanglePacker(_mainTex.width, _mainTex.height, padding); for (int i = 0; i < packerTexs.Length; i++) { packer.insertRectangle(packerTexs[i].width, packerTexs[i].height, i); } packer.packRectangles(); IntegerRectangle rect = new IntegerRectangle(); for (int i = 0; i < packer.rectangleCount; i++) { rect = packer.getRectangle(i, rect); int index = packer.getRectangleId(i); _mainTex.SetPixels32(rect.x, rect.y, rect.width, rect.height, packerTexs[index].GetPixels32()); Sprite sprite = Sprite.Create(_mainTex, new Rect(rect.x, rect.y, rect.width, rect.height), Vector2.zero, _pixelsPerUnit, 0, SpriteMeshType.FullRect); _spriteMap.Add(packerTexs[i].name, sprite); } _mainTex.Apply(); }
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); }
static void Main(string[] args) { List <Rectangle> rectangles = new List <Rectangle>(); for (int i = 0; i < 5; i++) { rectangles.Add(new Rectangle(5, 5)); } int Hight; int Width; Console.WriteLine("Write Hight"); Hight = Convert.ToInt32(Console.ReadLine()); Console.WriteLine("Write Width"); Width = Convert.ToInt32(Console.ReadLine()); RectanglePacker rectanglePacker = new RectanglePacker(Hight, Width); if (rectanglePacker.PackRectangleWithNFDH(rectangles)) { Console.WriteLine("Its fit"); } else { Console.WriteLine("Cant fit"); } Console.ReadKey(); }
public FreetypeFont(byte[] FontFile, int Size = 12, int AtlasW = 512, int AtlasH = 512) { GlyphTexture = new Dictionary <uint, Glyph>(); Fnt = Msdfgen.LoadFontMemory(FontFile); FontSize = Size; TextureAtlas = Texture.Empty(AtlasW, AtlasH); Packer = new RectanglePacker(AtlasW, AtlasH); }
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); }
public static Dictionary <string, object> PackRectangles( List <Rectangle> items, List <Rectangle> containers, [DefaultArgument("RectanglePackingStrategy.BestAreaFits")] RectanglePackingStrategy strategy) { var packer = new RectanglePacker(); var results = packer.PackMultipleContainers(items, containers, strategy); return(results.ToDictionary()); }
private static void Main(string[] args) { Application.Run(() => { var window = new Window("Atlas Thrashing", vsync: false) { Size = (1280, 720) }; window.Graphics.Performance.OverlayMode = PerformanceOverlayMode.Simple; window.MoveToCenter(); var xcount = window.Size.Width / 64; var ycount = window.Size.Height / 64; // Using the shelf packer here for fast visualization sake. // The skyline is the actual packer used by the texture atlas system. var packer = new RectanglePacker <Image>(window.Surface.Size, PackingAlgorithm.Skyline); // Create the main loop var loop = GameLoop.Create(window.Graphics, (gfx, dt) => { packer.Clear(); window.Graphics.Clear(Color.White); while (true) { var w = Calc.Random.Next(8, 128); var h = Calc.Random.Next(8, 128); var r = Calc.Random.NextFloat(); var g = Calc.Random.NextFloat(); var b = Calc.Random.NextFloat(); // Attempt to pack image, if unable break var image = Image.CreateColor(w, h, new Color(r, g, b)); if (!packer.TryAdd(image, image.Size)) { break; } var packed = packer.GetRectangle(image); window.Graphics.DrawImage(image, packed.Position); } }); // Begin main loop loop.Start(); }); }
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); }
public DynamicAtlas(string atlas, int width, int height, int padding, bool compress = true) { this.atlas = atlas; this.width = width; this.height = height; this.padding = padding; this.compress = compress; m_packer = new RectanglePacker(width, height, padding); m_texture = new Texture2D(width, height, TextureFormat.RGBA32, false); m_color = m_texture.GetPixels32(); }
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); } }
private void updateRectangles() { DateTime start = DateTime.Now; const int padding = 1; if (mPacker == null) { mPacker = new RectanglePacker((int)sliderWidth.value, (int)sliderHeight.value, padding); } else { mPacker.reset((int)sliderWidth.value, (int)sliderHeight.value, padding); } for (int i = 0; i < RECTANGLE_COUNT; i++) { mPacker.insertRectangle((int)mRectangles[i].width, (int)mRectangles[i].height, i); } mPacker.packRectangles(); Clear(); DateTime end = DateTime.Now; if (mPacker.rectangleCount > 0) { packingTimeText.text = mPacker.rectangleCount + " rectangles packed in " + (end - start).Milliseconds + "ms"; IntegerRectangle rect = new IntegerRectangle(); for (int j = 0; j < mPacker.rectangleCount; j++) { rect = mPacker.getRectangle(j, rect); int index = mPacker.getRectangleId(j); Texture2D texture = textures[index]; SetTexture(texture, IntegerRectangle2Rect(rect)); } } }
public void InitRectPacker(int typeIndex) { int binaryNum1 = this.GetBinaryNum(this.maxWidth); int binaryNum2 = this.GetBinaryNum(this.maxHeight); switch (typeIndex) { case 0: this.rectPacker = (RectanglePacker) new SimpleRectanglePacker(binaryNum1, binaryNum2); break; case 1: this.rectPacker = (RectanglePacker) new CygonRectanglePacker(binaryNum1, binaryNum2); break; case 2: this.rectPacker = (RectanglePacker) new ArevaloRectanglePacker(binaryNum1, binaryNum2); break; default: this.rectPacker = (RectanglePacker) new ArevaloRectanglePacker(binaryNum1, binaryNum2); break; } }
protected IEnumerator createPack(string savePath = "") { if (savePath != "") { if (deletePreviousCacheVersion && Directory.Exists(Application.persistentDataPath + "/AssetPacker/" + cacheName + "/")) { foreach (string dirPath in Directory.GetDirectories(Application.persistentDataPath + "/AssetPacker/" + cacheName + "/", "*", SearchOption.AllDirectories)) { Directory.Delete(dirPath, true); } } Directory.CreateDirectory(savePath); } List <Texture2D> textures = new List <Texture2D>(); List <string> images = new List <string>(); foreach (TextureToPack itemToRaster in itemsToRaster) { WWW loader = new WWW("file:///" + itemToRaster.file); yield return(loader); textures.Add(loader.texture); images.Add(itemToRaster.id); } int textureSize = allow4096Textures ? 4096 : 2048; List <Rect> rectangles = new List <Rect>(); for (int i = 0; i < textures.Count; i++) { if (textures[i].width > textureSize || textures[i].height > textureSize) { throw new Exception("A texture size is bigger than the sprite sheet size!"); } else { rectangles.Add(new Rect(0, 0, textures[i].width, textures[i].height)); } } const int padding = 1; int numSpriteSheet = 0; while (rectangles.Count > 0) { Texture2D texture = new Texture2D(textureSize, textureSize, TextureFormat.ARGB32, false); Color32[] fillColor = texture.GetPixels32(); for (int i = 0; i < fillColor.Length; ++i) { fillColor[i] = Color.clear; } RectanglePacker packer = new RectanglePacker(texture.width, texture.height, padding); for (int i = 0; i < rectangles.Count; i++) { packer.insertRectangle((int)rectangles[i].width, (int)rectangles[i].height, i); } packer.packRectangles(); if (packer.rectangleCount > 0) { texture.SetPixels32(fillColor); IntegerRectangle rect = new IntegerRectangle(); List <TextureAsset> textureAssets = new List <TextureAsset>(); List <Rect> garbageRect = new List <Rect>(); List <Texture2D> garabeTextures = new List <Texture2D>(); List <string> garbageImages = new List <string>(); for (int j = 0; j < packer.rectangleCount; j++) { rect = packer.getRectangle(j, rect); int index = packer.getRectangleId(j); texture.SetPixels32(rect.x, rect.y, rect.width, rect.height, textures[index].GetPixels32()); TextureAsset textureAsset = new TextureAsset(); textureAsset.x = rect.x; textureAsset.y = rect.y; textureAsset.width = rect.width; textureAsset.height = rect.height; textureAsset.name = images[index]; textureAssets.Add(textureAsset); garbageRect.Add(rectangles[index]); garabeTextures.Add(textures[index]); garbageImages.Add(images[index]); } foreach (Rect garbage in garbageRect) { rectangles.Remove(garbage); } foreach (Texture2D garbage in garabeTextures) { textures.Remove(garbage); } foreach (string garbage in garbageImages) { images.Remove(garbage); } texture.Apply(); if (savePath != "") { File.WriteAllBytes(savePath + "/data" + numSpriteSheet + ".png", texture.EncodeToPNG()); File.WriteAllText(savePath + "/data" + numSpriteSheet + ".json", JsonUtility.ToJson(new TextureAssets(textureAssets.ToArray()))); ++numSpriteSheet; } foreach (TextureAsset textureAsset in textureAssets) { mSprites.Add(textureAsset.name, Sprite.Create(texture, new Rect(textureAsset.x, textureAsset.y, textureAsset.width, textureAsset.height), Vector2.zero, pixelsPerUnit, 0, SpriteMeshType.FullRect)); } } } OnProcessCompleted.Invoke(); }
// 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..."); } }
private void Button_Calculate_Click(object sender, RoutedEventArgs e) { Drowing.Children.Clear(); try { boxHight = Convert.ToInt32(MainHight.Text); boxWidth = Convert.ToInt32(MainWidth.Text); rectanglePacker = new RectanglePacker(boxHight, boxWidth); bool result; //result = rectanglePacker.PackRectangleWithNFDH(rectangles); result = rectanglePacker.PackRectangleWithFFDH(rectangles); if (!result) { MassageBox.Text = "Cant fit"; } if (result) { MassageBox.Text = "successfully fited"; int h = rectanglePacker.Hight; int w = rectanglePacker.Width; RectangleGeometry rectangle = new RectangleGeometry(new Rect(0, 0, w, h)); Path myPath = new Path(); myPath.HorizontalAlignment = HorizontalAlignment.Left; myPath.VerticalAlignment = VerticalAlignment.Bottom; myPath.Stroke = Brushes.Black; myPath.StrokeThickness = 3; GeometryGroup geometryGroup = new GeometryGroup(); geometryGroup.Children.Add(rectangle); foreach (var item in rectanglePacker.PackedRectengles) { var cord = item.Key; var rect = item.Value; var rectangle2 = new RectangleGeometry(new Rect(cord.X, cord.Y, rect.Width, rect.Hight)); geometryGroup.Children.Add(rectangle2); } myPath.Data = geometryGroup; Drowing.Children.Add(myPath); } } catch (Exception ex) { MassageBox.Text = ex.Message; } }
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"); }
/// <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; } }
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!"); }
static DataNode ExportToNode(string[] files, Dictionary <string, Bitmap> images, Dictionary <string, Margin> margins, RectanglePacker <string> packer, bool giveNames) { var node = DataNode.CreateArray("images"); 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]; var child = DataNode.CreateObject(giveNames ? "image": null); child.AddField("name", Path.GetFileNameWithoutExtension(file)); child.AddField("x", x); child.AddField("y", y); child.AddField("width", img.Width); child.AddField("height", img.Height); node.AddNode(child); } return(node); }
private void updateRectangles() { DateTime start = DateTime.Now; const int padding = 1; if (mPacker == null) { mPacker = new RectanglePacker((int)sliderWidth.value, (int)sliderHeight.value, padding); } else { mPacker.Reset((int)sliderWidth.value, (int)sliderHeight.value, padding); } for (int i = 0; i < RECTANGLE_COUNT; i++) { mPacker.InsertRectangle((int)mRectangles[i].width, (int)mRectangles[i].height, i); } mPacker.PackRectangles(); DateTime end = DateTime.Now; if (mPacker.rectangleCount > 0) { packingTimeText.text = mPacker.rectangleCount + " rectangles packed in " + (end - start).Milliseconds + "ms"; mTexture.SetPixels32(mFillColor); IntegerRectangle rect = new IntegerRectangle(); Color32[] tmpColor; for (int j = 0; j < mPacker.rectangleCount; j++) { rect = mPacker.GetRectangle(j, rect); int size = rect.width * rect.height; tmpColor = new Color32[size]; for (int k = 0; k < size; ++k) { tmpColor[k] = Color.black; } mTexture.SetPixels32(rect.x, rect.y, rect.width, rect.height, tmpColor); int index = mPacker.GetRectangleId(j); Color color = convertHexToRGBA((uint)(0xFF171703 + (((18 * ((index + 4) % 13)) << 16) + ((31 * ((index * 3) % 8)) << 8) + 63 * (((index + 1) * 3) % 5)))); size -= 4; tmpColor = new Color32[size]; for (int k = 0; k < size; ++k) { tmpColor[k] = color; } mTexture.SetPixels32(rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2, tmpColor); } mTexture.Apply(); } }
protected IEnumerator createPack(string savePath = "") { if (savePath != "") { if (deletePreviousCacheVersion && Directory.Exists(Application.persistentDataPath + "/AssetPacker/" + cacheName + "/")) { foreach (string dirPath in Directory.GetDirectories(Application.persistentDataPath + "/AssetPacker/" + cacheName + "/", "*", SearchOption.AllDirectories)) { Directory.Delete(dirPath, true); } } Directory.CreateDirectory(savePath); } List <Texture2D> textures = new List <Texture2D>(); List <string> images = new List <string>(); foreach (TextureToPack itemToRaster in itemsToRaster) { Texture2D baseTexture = itemToRaster.preLoadedTexture; if (!baseTexture) { WWW loader = new WWW("file:///" + itemToRaster.file); yield return(loader); baseTexture = loader.texture; } if (itemToRaster.sliceParams != null) { TextureToPack.GridSlice sliceParams = itemToRaster.sliceParams; if (sliceParams.width > baseTexture.width || sliceParams.height > baseTexture.height) { throw new Exception(string.Format("Width and/or height of texture {0} is less than the provided slice parameters.", itemToRaster.id)); } if (!(baseTexture.width % sliceParams.width == 0 && baseTexture.height % sliceParams.height == 0)) { throw new Exception(string.Format("Width and/or height of texture {0} not a multiple of the provided slice parameters.", itemToRaster.id)); } int totalRows = baseTexture.height / sliceParams.height; int totalColumns = baseTexture.width / sliceParams.width; int textureIndex = 0; // Scan each slice left to right, top to bottom for (int rowIndex = 0; rowIndex < totalRows; ++rowIndex) { for (int columnIndex = 0; columnIndex < totalColumns; ++columnIndex) { int x = columnIndex * sliceParams.width; int y = (totalRows - rowIndex - 1) * sliceParams.height; // Starting from the top, not the bottom. Specific to GH3 sprite sheets, not nesacarily universal. Color[] pixels = baseTexture.GetPixels(x, y, sliceParams.width, sliceParams.height); Texture2D texture = new Texture2D(sliceParams.width, sliceParams.height); texture.SetPixels(pixels); texture.Apply(); textures.Add(texture); images.Add(string.Format("{0}_{1}", itemToRaster.id, textureIndex.ToString("D8"))); ++textureIndex; } } } else { textures.Add(baseTexture); images.Add(itemToRaster.id); } } int textureSize = allow4096Textures ? 4096 : 2048; List <Rect> rectangles = new List <Rect>(); for (int i = 0; i < textures.Count; i++) { if (textures[i].width > textureSize || textures[i].height > textureSize) { throw new Exception("A texture size is bigger than the sprite sheet size!"); } else { rectangles.Add(new Rect(0, 0, textures[i].width, textures[i].height)); } } const int padding = 1; int numSpriteSheet = 0; while (rectangles.Count > 0) { Texture2D texture = new Texture2D(textureSize, textureSize, TextureFormat.ARGB32, false); Color32[] fillColor = texture.GetPixels32(); for (int i = 0; i < fillColor.Length; ++i) { fillColor[i] = Color.clear; } RectanglePacker packer = new RectanglePacker(texture.width, texture.height, padding); for (int i = 0; i < rectangles.Count; i++) { packer.insertRectangle((int)rectangles[i].width, (int)rectangles[i].height, i); } packer.packRectangles(); if (packer.rectangleCount > 0) { texture.SetPixels32(fillColor); IntegerRectangle rect = new IntegerRectangle(); List <TextureAsset> textureAssets = new List <TextureAsset>(); List <Rect> garbageRect = new List <Rect>(); List <Texture2D> garabeTextures = new List <Texture2D>(); List <string> garbageImages = new List <string>(); for (int j = 0; j < packer.rectangleCount; j++) { rect = packer.getRectangle(j, rect); int index = packer.getRectangleId(j); texture.SetPixels32(rect.x, rect.y, rect.width, rect.height, textures[index].GetPixels32()); TextureAsset textureAsset = new TextureAsset(); textureAsset.x = rect.x; textureAsset.y = rect.y; textureAsset.width = rect.width; textureAsset.height = rect.height; textureAsset.name = images[index]; textureAssets.Add(textureAsset); garbageRect.Add(rectangles[index]); garabeTextures.Add(textures[index]); garbageImages.Add(images[index]); } foreach (Rect garbage in garbageRect) { rectangles.Remove(garbage); } foreach (Texture2D garbage in garabeTextures) { textures.Remove(garbage); } foreach (string garbage in garbageImages) { images.Remove(garbage); } texture.Apply(); if (savePath != "") { File.WriteAllBytes(savePath + "/data" + numSpriteSheet + ".png", texture.EncodeToPNG()); File.WriteAllText(savePath + "/data" + numSpriteSheet + ".json", JsonUtility.ToJson(new TextureAssets(textureAssets.ToArray()), true)); ++numSpriteSheet; } foreach (TextureAsset textureAsset in textureAssets) { Sprite sprite = Sprite.Create(texture, new Rect(textureAsset.x, textureAsset.y, textureAsset.width, textureAsset.height), spritePivot, pixelsPerUnit, 0, SpriteMeshType.FullRect); sprite.name = textureAsset.name; mSprites.Add(textureAsset.name, sprite); } } } OnProcessCompleted.Invoke(); }