/// <summary> /// Create the best atlas layout possible given the elementsToPack to pack, the algorithm and the atlas maximum size. /// Note: when all the elementsToPack cannot fit into the texture, it tries to pack as much as possible of them. /// </summary> /// <returns>False if</returns> private AtlasTextureLayout CreateBestAtlasLayout(List <AtlasTextureElement> elementsToPack, TexturePackingMethod algorithm, out List <AtlasTextureElement> remainingElements) { remainingElements = elementsToPack; var textureAtlas = new AtlasTextureLayout(); var bestElementPackedCount = int.MaxValue; // Generate sub size array var subSizeArray = CreateSubSizeArray(atlasMaxSize.X, atlasMaxSize.Y, 512, 512); foreach (var subArray in subSizeArray) { var currentRemaingElements = new List <AtlasTextureElement>(elementsToPack); // Reset packer state maxRectPacker.Initialize(subArray.Width, subArray.Height, AllowRotation); // Pack maxRectPacker.PackRectangles(currentRemaingElements, algorithm); // Find true size from packed regions var packedSize = CalculatePackedRectanglesBound(maxRectPacker.PackedElements); // Alter the size of atlas so that it is a power of two if (!AllowNonPowerOfTwo) { packedSize.Width = MathUtil.NextPowerOfTwo(packedSize.Width); packedSize.Height = MathUtil.NextPowerOfTwo(packedSize.Height); if (packedSize.Width > subArray.Width || packedSize.Height > subArray.Height) { continue; } } if (currentRemaingElements.Count >= bestElementPackedCount) { continue; } // Found new best pack, cache it bestElementPackedCount = currentRemaingElements.Count; // Resize texture atlas textureAtlas.Width = packedSize.Width; textureAtlas.Height = packedSize.Height; textureAtlas.Textures.Clear(); // Store all packed regions into Atlas foreach (var element in maxRectPacker.PackedElements) { textureAtlas.Textures.Add(element.Clone()); } remainingElements = currentRemaingElements; } return(textureAtlas); }
/// <summary> /// Creates texture atlas image from a given texture atlas /// </summary> /// <param name="atlasTextureLayout">Input texture atlas</param> /// <param name="srgb">True if the texture atlas should be generated to a SRgb texture</param> /// <returns></returns> public static Image CreateTextureAtlas(AtlasTextureLayout atlasTextureLayout, bool srgb) { var atlasTexture = Image.New2D(atlasTextureLayout.Width, atlasTextureLayout.Height, 1, srgb ? PixelFormat.R8G8B8A8_UNorm_SRgb : PixelFormat.R8G8B8A8_UNorm); unsafe { var ptr = (Color*)atlasTexture.DataPointer; // Clean the data for (var i = 0; i < atlasTexture.PixelBuffer[0].Height * atlasTexture.PixelBuffer[0].Width; ++i) ptr[i] = Color.Zero; } // Fill in textureData from AtlasTextureLayout foreach (var element in atlasTextureLayout.Textures) { var isSourceRotated = element.SourceRegion.IsRotated; var isDestinationRotated = element.DestinationRegion.IsRotated; var sourceTexture = element.Texture; var sourceTextureWidth = sourceTexture.Description.Width; var addressModeU = element.BorderModeU; var addressModeV = element.BorderModeV; var borderColor = element.BorderColor; // calculate the source region guaranteed to be in the source texture. var sourceSize = new Int2(sourceTexture.Description.Width, sourceTexture.Description.Height); var safeSourceRegion = new Rectangle { X = Math.Max(0, element.SourceRegion.X), Y = Math.Max(0, element.SourceRegion.Y), Width = Math.Min(sourceSize.X, element.SourceRegion.Right), Height = Math.Min(sourceSize.Y, element.SourceRegion.Bottom), }; safeSourceRegion.Width -= safeSourceRegion.X; safeSourceRegion.Height -= safeSourceRegion.Y; // calculate the size of the source region and the starting offsets taking into account the rotation var sourceRegionSize = new Int2(safeSourceRegion.Width, safeSourceRegion.Height); var destRegionSize = new Int2(element.DestinationRegion.Width, element.DestinationRegion.Height); var sourceStartOffsets = new Int2(Math.Min(0, element.SourceRegion.X), Math.Min(0, element.SourceRegion.Y)); if (isDestinationRotated) { var oldSourceStartOffsetX = sourceStartOffsets.X; if (isSourceRotated) { sourceStartOffsets.X = sourceStartOffsets.Y; sourceStartOffsets.Y = sourceRegionSize.X - sourceStartOffsets.X - destRegionSize.Y + 2*element.BorderSize; } else { sourceStartOffsets.X = sourceRegionSize.Y - sourceStartOffsets.Y - destRegionSize.X + 2*element.BorderSize; sourceStartOffsets.Y = oldSourceStartOffsetX; } Utilities.Swap(ref sourceRegionSize.X, ref sourceRegionSize.Y); } { var format = sourceTexture.Description.Format; GetColorDelegate getPixel = GetColorBlack; if (format == PixelFormat.R8G8B8A8_UNorm_SRgb || format == PixelFormat.R8G8B8A8_UNorm) getPixel = GetColorRGBA; if (format == PixelFormat.A8_UNorm || format == PixelFormat.R8_UNorm) getPixel = GetColorRRR1; if (format == PixelFormat.R8G8_UNorm) getPixel = GetColorRG01; for (var y = 0; y < element.DestinationRegion.Height; ++y) { for (var x = 0; x < element.DestinationRegion.Width; ++x) { // Get index of source image, if it's the border at this point sourceIndexX and sourceIndexY will be -1 var sourceCoordinateX = GetSourceTextureCoordinate(x - element.BorderSize + sourceStartOffsets.X, sourceRegionSize.X, addressModeU); var sourceCoordinateY = GetSourceTextureCoordinate(y - element.BorderSize + sourceStartOffsets.Y, sourceRegionSize.Y, addressModeV); // Check if this image uses border mode, and is in the border area var isBorderMode = sourceCoordinateX < 0 || sourceCoordinateY < 0; if (isDestinationRotated) { // Modify index for rotating var tmp = sourceCoordinateY; if (isSourceRotated) { // Since intemediateTexture.DestinationRegion contains the border, we need to delete the border out sourceCoordinateY = sourceCoordinateX; sourceCoordinateX = safeSourceRegion.Width - 1 - tmp; } else { // Since intemediateTexture.DestinationRegion contains the border, we need to delete the border out sourceCoordinateY = safeSourceRegion.Height - 1 - sourceCoordinateX; sourceCoordinateX = tmp; } } // Add offset from the region sourceCoordinateX += safeSourceRegion.X; sourceCoordinateY += safeSourceRegion.Y; var readFromIndex = sourceCoordinateY*sourceTextureWidth + sourceCoordinateX; // read index from source image // Prepare writeToIndex var targetCoordinateX = element.DestinationRegion.X + x; var targetCoordinateY = element.DestinationRegion.Y + y; var writeToIndex = targetCoordinateY*atlasTextureLayout.Width + targetCoordinateX; // write index to atlas buffer SetPixel(atlasTexture.DataPointer, writeToIndex, isBorderMode ? borderColor : getPixel(sourceTexture.DataPointer, readFromIndex)); } } } } return atlasTexture; }
/// <summary> /// Creates texture atlas image from a given texture atlas /// </summary> /// <param name="atlasTextureLayout">Input texture atlas</param> /// <param name="srgb">True if the texture atlas should be generated to a SRgb texture</param> /// <returns></returns> public static Image CreateTextureAtlas(AtlasTextureLayout atlasTextureLayout, bool srgb) { var atlasTexture = Image.New2D(atlasTextureLayout.Width, atlasTextureLayout.Height, 1, srgb ? PixelFormat.R8G8B8A8_UNorm_SRgb : PixelFormat.R8G8B8A8_UNorm); unsafe { var ptr = (Color *)atlasTexture.DataPointer; // Clean the data for (var i = 0; i < atlasTexture.PixelBuffer[0].Height * atlasTexture.PixelBuffer[0].Width; ++i) { ptr[i] = Color.Zero; } } // Fill in textureData from AtlasTextureLayout foreach (var element in atlasTextureLayout.Textures) { var isSourceRotated = element.SourceRegion.IsRotated; var isDestinationRotated = element.DestinationRegion.IsRotated; var sourceTexture = element.Texture; var sourceTextureWidth = sourceTexture.Description.Width; var addressModeU = element.BorderModeU; var addressModeV = element.BorderModeV; var borderColor = element.BorderColor; // calculate the source region guaranteed to be in the source texture. var sourceSize = new Int2(sourceTexture.Description.Width, sourceTexture.Description.Height); var safeSourceRegion = new Rectangle { X = Math.Max(0, element.SourceRegion.X), Y = Math.Max(0, element.SourceRegion.Y), Width = Math.Min(sourceSize.X, element.SourceRegion.Right), Height = Math.Min(sourceSize.Y, element.SourceRegion.Bottom), }; safeSourceRegion.Width -= safeSourceRegion.X; safeSourceRegion.Height -= safeSourceRegion.Y; // calculate the size of the source region and the starting offsets taking into account the rotation var sourceRegionSize = new Int2(safeSourceRegion.Width, safeSourceRegion.Height); var destRegionSize = new Int2(element.DestinationRegion.Width, element.DestinationRegion.Height); var sourceStartOffsets = new Int2(Math.Min(0, element.SourceRegion.X), Math.Min(0, element.SourceRegion.Y)); if (isDestinationRotated) { var oldSourceStartOffsetX = sourceStartOffsets.X; if (isSourceRotated) { sourceStartOffsets.X = sourceStartOffsets.Y; sourceStartOffsets.Y = sourceRegionSize.X - sourceStartOffsets.X - destRegionSize.Y + 2 * element.BorderSize; } else { sourceStartOffsets.X = sourceRegionSize.Y - sourceStartOffsets.Y - destRegionSize.X + 2 * element.BorderSize; sourceStartOffsets.Y = oldSourceStartOffsetX; } Utilities.Swap(ref sourceRegionSize.X, ref sourceRegionSize.Y); } { var format = sourceTexture.Description.Format; GetColorDelegate getPixel = GetColorBlack; if (format == PixelFormat.R8G8B8A8_UNorm_SRgb || format == PixelFormat.R8G8B8A8_UNorm) { getPixel = GetColorRGBA; } if (format == PixelFormat.A8_UNorm || format == PixelFormat.R8_UNorm) { getPixel = GetColorRRR1; } if (format == PixelFormat.R8G8_UNorm) { getPixel = GetColorRG01; } for (var y = 0; y < element.DestinationRegion.Height; ++y) { for (var x = 0; x < element.DestinationRegion.Width; ++x) { // Get index of source image, if it's the border at this point sourceIndexX and sourceIndexY will be -1 var sourceCoordinateX = GetSourceTextureCoordinate(x - element.BorderSize + sourceStartOffsets.X, sourceRegionSize.X, addressModeU); var sourceCoordinateY = GetSourceTextureCoordinate(y - element.BorderSize + sourceStartOffsets.Y, sourceRegionSize.Y, addressModeV); // Check if this image uses border mode, and is in the border area var isBorderMode = sourceCoordinateX < 0 || sourceCoordinateY < 0; if (isDestinationRotated) { // Modify index for rotating var tmp = sourceCoordinateY; if (isSourceRotated) { // Since intemediateTexture.DestinationRegion contains the border, we need to delete the border out sourceCoordinateY = sourceCoordinateX; sourceCoordinateX = safeSourceRegion.Width - 1 - tmp; } else { // Since intemediateTexture.DestinationRegion contains the border, we need to delete the border out sourceCoordinateY = safeSourceRegion.Height - 1 - sourceCoordinateX; sourceCoordinateX = tmp; } } // Add offset from the region sourceCoordinateX += safeSourceRegion.X; sourceCoordinateY += safeSourceRegion.Y; var readFromIndex = sourceCoordinateY * sourceTextureWidth + sourceCoordinateX; // read index from source image // Prepare writeToIndex var targetCoordinateX = element.DestinationRegion.X + x; var targetCoordinateY = element.DestinationRegion.Y + y; var writeToIndex = targetCoordinateY * atlasTextureLayout.Width + targetCoordinateX; // write index to atlas buffer SetPixel(atlasTexture.DataPointer, writeToIndex, isBorderMode ? borderColor : getPixel(sourceTexture.DataPointer, readFromIndex)); } } } } return(atlasTexture); }
/// <summary> /// Create the best atlas layout possible given the elementsToPack to pack, the algorithm and the atlas maximum size. /// Note: when all the elementsToPack cannot fit into the texture, it tries to pack as much as possible of them. /// </summary> /// <returns>False if</returns> private AtlasTextureLayout CreateBestAtlasLayout(List<AtlasTextureElement> elementsToPack, TexturePackingMethod algorithm, out List<AtlasTextureElement> remainingElements) { remainingElements = elementsToPack; var textureAtlas = new AtlasTextureLayout(); var bestElementPackedCount = int.MaxValue; // Generate sub size array var subSizeArray = CreateSubSizeArray(atlasMaxSize.X, atlasMaxSize.Y, 512, 512); foreach (var subArray in subSizeArray) { var currentRemaingElements = new List<AtlasTextureElement>(elementsToPack); // Reset packer state maxRectPacker.Initialize(subArray.Width, subArray.Height, AllowRotation); // Pack maxRectPacker.PackRectangles(currentRemaingElements, algorithm); // Find true size from packed regions var packedSize = CalculatePackedRectanglesBound(maxRectPacker.PackedElements); // Alter the size of atlas so that it is a power of two if (!AllowNonPowerOfTwo) { packedSize.Width = MathUtil.NextPowerOfTwo(packedSize.Width); packedSize.Height = MathUtil.NextPowerOfTwo(packedSize.Height); if (packedSize.Width > subArray.Width || packedSize.Height > subArray.Height) continue; } if (currentRemaingElements.Count >= bestElementPackedCount) continue; // Found new best pack, cache it bestElementPackedCount = currentRemaingElements.Count; // Resize texture atlas textureAtlas.Width = packedSize.Width; textureAtlas.Height = packedSize.Height; textureAtlas.Textures.Clear(); // Store all packed regions into Atlas foreach (var element in maxRectPacker.PackedElements) textureAtlas.Textures.Add(element.Clone()); remainingElements = currentRemaingElements; } return textureAtlas; }