/// <summary> /// Exports a given Bundle asynchronously, calling a progress handler along the way /// </summary> /// <param name="bundle">The bundle to export</param> /// <param name="cancellationToken">A cancelation token that is passed to the exporters and can be used to cancel the export process mid-way</param> /// <param name="progressHandler">Optional event handler for reporting the export progress</param> /// <returns>A task representing the concurrent export progress</returns> public async Task ExportBundleConcurrent(Bundle bundle, CancellationToken cancellationToken = new CancellationToken(), BundleExportProgressEventHandler progressHandler = null) { // Start with initial values for the progress export of every sheet float[] stageProgresses = new float[bundle.AnimationSheets.Count]; var exports = new List <BundleSheetJson>(); var progressAction = new Action <AnimationSheet>(sheet => { if (progressHandler == null) { return; } // Calculate total progress int total = (int)Math.Floor(stageProgresses.Sum() / stageProgresses.Length * 100); progressHandler(new SheetGenerationBundleExportProgressEventArgs(sheet, BundleExportStage.TextureAtlasGeneration, total, total / 2, "Generating sheets")); }); var generationList = new List <Task>(); for (int i = 0; i < bundle.AnimationSheets.Count; i++) { var sheet = bundle.AnimationSheets[i]; var j = i; generationList.Add(new Task(() => { var exp = ExportBundleSheet(sheet, cancellationToken, args => { stageProgresses[j] = (float)args.StageProgress / 100; progressAction(sheet); }); try { var sheetJson = new BundleSheetJson(exp.Result, sheet.Name, Path.GetFullPath(bundle.ExportPath) + "\\" + sheet.Name); exports.Add(sheetJson); } catch (TaskCanceledException) { // unused } })); } var concurrent = new Task(() => { StartAndWaitAllThrottled(generationList, 7, cancellationToken); }); concurrent.Start(); await concurrent; if (cancellationToken.IsCancellationRequested) { return; } // // 2. Save the sheets to disk // for (int i = 0; i < exports.Count; i++) { var exp = exports[i]; if (progressHandler != null) { int progress = (int)((float)i / exports.Count * 100); progressHandler.Invoke(new BundleExportProgressEventArgs(BundleExportStage.SavingToDisk, progress, 50 + progress / 2)); } exp.BundleSheet.SaveToDisk(exp.ExportPath); } // // 3. Compose the main bundle .json // if (exports.Any(export => export.BundleSheet.ExportSettings.ExportJson)) { var jsonSheetList = new List <Dictionary <string, object> >(); foreach (var export in exports) { if (!export.BundleSheet.ExportSettings.ExportJson) { continue; } var sheet = new Dictionary <string, object>(); // Path of final JSON file var filePath = Utilities.GetRelativePath(Path.ChangeExtension(export.ExportPath, "json"), bundle.ExportPath); sheet["name"] = export.SheetName; sheet["sprite_file"] = filePath; // Export animation list var names = export.BundleSheet.Animations.Select(anim => anim.Name); sheet["animations"] = names; jsonSheetList.Add(sheet); } var json = new Dictionary <string, object> { ["sheets"] = jsonSheetList }; string finalPath = Path.ChangeExtension(Path.GetFullPath(bundle.ExportPath) + "\\" + bundle.Name, "json"); string output = JsonConvert.SerializeObject(json); File.WriteAllText(finalPath, output, Encoding.UTF8); } progressHandler?.Invoke(new BundleExportProgressEventArgs(BundleExportStage.Ended, 100, 100)); }
/// <summary> /// Generates a BundleSheetExport object that contains information about the export of a sheet, using a custom event handler /// for export progress callback /// </summary> /// <param name="exportSettings">The export settings for the sheet</param> /// <param name="cancellationToken">A cancelation token that can be used to cancel the process mid-way</param> /// <param name="callback">The callback delegate to be used during the generation process</param> /// <param name="anims">The list of animations to export</param> /// <returns>A BundleSheetExport object that contains information about the export of the sheet</returns> public Task <BundleSheetExport> GenerateBundleSheet(AnimationExportSettings exportSettings, CancellationToken cancellationToken, BundleExportProgressEventHandler callback, params Animation[] anims) { return(GetExporter().ExportBundleSheet(exportSettings, anims, cancellationToken, callback)); }
/// <summary> /// Generates a TextureAtlas from the given AnimationSheet object /// </summary> /// <param name="sheet">The AnimationSheet to generate the TextureAtlas of</param> /// <param name="cancellationToken">A cancelation token that is passed to the exporters and can be used to cancel the export process mid-way</param> /// <param name="progressHandler">Optional event handler for reporting the export progress</param> /// <returns>A TextureAtlas generated from the given AnimationSheet</returns> public async Task <TextureAtlas> GenerateAtlasFromAnimationSheet(AnimationSheet sheet, CancellationToken cancellationToken = new CancellationToken(), BundleExportProgressEventHandler progressHandler = null) { return(await GenerateAtlasFromAnimations(sheet.ExportSettings, sheet.Animations, sheet.Name, cancellationToken, args => { progressHandler?.Invoke(args); _sheetProgress[sheet.ID] = (float)args.StageProgress / 100; })); }
/// <summary> /// Exports the given animations into an image sheet and returns the created sheet /// </summary> /// <param name="exportSettings">The export settings for the sheet</param> /// <param name="anims">The list of animations to export</param> /// <param name="name">The name for the generated texture atlas. Used for progress reports</param> /// <param name="cancellationToken">A cancelation token that is passed to the exporters and can be used to cancel the export process mid-way</param> /// <param name="progressHandler">Optional event handler for reporting the export progress</param> /// <returns>An image sheet representing the animations passed</returns> public async Task <TextureAtlas> GenerateAtlasFromAnimations(AnimationExportSettings exportSettings, Animation[] anims, string name = "", CancellationToken cancellationToken = new CancellationToken(), BundleExportProgressEventHandler progressHandler = null) { var atlas = new TextureAtlas(exportSettings, name); // // 1. Add the frames to the texture atlas // foreach (var anim in anims) { for (int i = 0; i < anim.FrameCount; i++) { atlas.InsertFrame(anim.GetFrameAtIndex(i)); } } // // 2. Pack the frames into the atlas // ITexturePacker packer = new DefaultTexturePacker(); await packer.Pack(atlas, cancellationToken, progressHandler); return(atlas); }
/// <summary> /// Exports the given animations into a BundleSheetExport and returns the created sheet /// </summary> /// <param name="settings">The export settings for the sheet</param> /// <param name="anims">The list of animations to export</param> /// <param name="cancellationToken">A cancelation token that is passed to the exporters and can be used to cancel the export process mid-way</param> /// <param name="progressHandler">Optional event handler for reporting the export progress</param> /// <returns>A BundleSheetExport representing the animations passed ready to be saved to disk</returns> public async Task <BundleSheetExport> ExportBundleSheet(AnimationExportSettings settings, Animation[] anims, CancellationToken cancellationToken = new CancellationToken(), BundleExportProgressEventHandler progressHandler = null) { using (var atlas = await GenerateAtlasFromAnimations(settings, anims, "", cancellationToken, progressHandler)) { return(BundleSheetExport.FromAtlas(atlas)); } }
/// <summary> /// Exports the given animations into a BundleSheetExport and returns the created sheet /// </summary> /// <param name="sheet">The sheet to export</param> /// <param name="cancellationToken">A cancelation token that is passed to the exporters and can be used to cancel the export process mid-way</param> /// <param name="progressHandler">Optional event handler for reporting the export progress</param> /// <returns>A BundleSheetExport representing the animation sheet passed ready to be saved to disk</returns> public async Task <BundleSheetExport> ExportBundleSheet(AnimationSheet sheet, CancellationToken cancellationToken = new CancellationToken(), BundleExportProgressEventHandler progressHandler = null) { // // 1. Generate texture atlas // using (var atlas = await GenerateAtlasFromAnimationSheet(sheet, cancellationToken, progressHandler)) { // // 2. Generate an export sheet from the texture atlas // return(BundleSheetExport.FromAtlas(atlas)); } }
/// <summary> /// Packs a given atlas with a specified progress event handler /// </summary> /// <param name="atlas">The texture atlas to pack</param> /// <param name="cancellationToken">A cancellation token that can be used to abort this task</param> /// <param name="handler">The event handler for the packing process</param> public async Task Pack(TextureAtlas atlas, CancellationToken cancellationToken, BundleExportProgressEventHandler handler = null) { if (atlas.FrameCount == 0) { atlas.AtlasRectangle = new Rectangle(0, 0, 1, 1); return; } // Cache some fields as locals List <IFrame> frameList = atlas.FrameList; _progressHandler = handler; _frameComparision = new FrameComparision(atlas.ExportSettings.ForceMinimumDimensions); // 1. (Optional) Sort the frames from largest to smallest before packing if (atlas.ExportSettings.AllowUnorderedFrames) { // Use a stable sort frameList.AddRange(frameList.OrderBy(frame => frame, _frameComparision).ToList()); frameList.RemoveRange(0, frameList.Count / 2); } // 2. (Optional) Find identical frames and pack them to use the same sheet area if (atlas.ExportSettings.ReuseIdenticalFramesArea) { MarkIdenticalFramesFromList(frameList); } // 3. Find the maximum possible horizontal sheet size CalculateMaximumSizes(frameList); // 4. Iterate through possible widths and match the smallest area to use as a maxWidth uint atlasWidth; uint atlasHeight; var frameBoundsMap = PrepareAtlas(atlas, atlas.ExportSettings.UseUniformGrid ? _maxFrameWidth : -1, atlas.ExportSettings.UseUniformGrid ? _maxFrameHeight : -1); Rectangle[] frameBounds = frameBoundsMap.SheetBounds; var minAreaTask = new Task <int>(() => IterateAtlasSize(atlas, frameBounds, _maxWidthCapped, out atlasWidth, out atlasHeight, cancellationToken), cancellationToken); minAreaTask.Start(); int minAreaWidth = await minAreaTask; atlasWidth = 0; atlasHeight = 0; // 5. Pack the texture atlas Rectangle[] finalFrameRegions = InternalPack(atlas.ExportSettings, frameBounds, ref atlasWidth, ref atlasHeight, minAreaWidth); // Replace bounds now frameBoundsMap.ReplaceSheetBounds(finalFrameRegions); // Unwrap on atlas now //atlas.BoundsList for (int i = 0; i < atlas.FrameList.Count; i++) { var frame = atlas.FrameList[i]; var sheetBounds = frameBoundsMap.GetSheetBoundsForFrame(frame); if (sheetBounds != null) { atlas.BoundsList[i] = sheetBounds.Value; } var localBounds = frameBoundsMap.GetLocalBoundsForFrame(frame); if (localBounds != null) { atlas.OriginsList[i] = localBounds.Value; } } // Round up to the closest power of two if (atlas.ExportSettings.ForcePowerOfTwoDimensions) { atlasWidth = Utilities.SnapToNextPowerOfTwo(atlasWidth); atlasHeight = Utilities.SnapToNextPowerOfTwo(atlasHeight); } if (atlasWidth == 0) { atlasWidth = 1; } if (atlasHeight == 0) { atlasHeight = 1; } atlas.AtlasRectangle = new Rectangle(0, 0, (int)atlasWidth, (int)atlasHeight); // Assign the information on the texture atlas atlas.Information.ReusedFrameOriginsCount = _frameComparision.CachedSimilarCount; // Register reusal in atlas atlas.ReuseCount.Clear(); var simMatrix = _frameComparision.SimilarMatrixIndexDictionary; foreach (IFrame frame in atlas.FrameList) { if (atlas.ExportSettings.ReuseIdenticalFramesArea) { int repCount = 0; int index; if (simMatrix.TryGetValue(frame.ID, out index)) { repCount = _frameComparision.SimilarFramesMatrix[index].Count - 1; } atlas.ReuseCount.Add(repCount); } else { atlas.ReuseCount.Add(0); } } }
/// <summary> /// Packs a given atlas with a specified progress event handler /// </summary> /// <param name="atlas">The texture atlas to pack</param> /// <param name="handler">The event handler for the packing process</param> public void Pack(TextureAtlas atlas, BundleExportProgressEventHandler handler = null) { Pack(atlas, new CancellationToken(), handler).Wait(); }
/// <summary> /// Generates a preview for the AnimationSheet currently loaded into this form /// </summary> public void GeneratePreview() { RepopulateExportSettings(); UpdateCountLabels(); if (_sheetToEdit.Animations.Length <= 0) { lbl_alertLabel.Text = AnimationMessages.TextNoAnimationInSheetToGeneratePreview; pnl_alertPanel.Visible = true; return; } // Time the bundle export pb_exportProgress.Visible = true; BundleExportProgressEventHandler handler = args => { Invoke(new Action(() => { pb_exportProgress.Value = args.StageProgress; })); }; var form = FindForm(); if (form != null) { form.Cursor = Cursors.WaitCursor; } btn_generatePreview.Enabled = false; var sw = Stopwatch.StartNew(); _sheetCancellation = new CancellationTokenSource(); // Export the bundle var t = _controller.GenerateBundleSheet(_exportSettings, _sheetCancellation.Token, handler, _sheetToEdit.Animations); t.ContinueWith(task => { Invoke(new Action(() => { btn_generatePreview.Enabled = true; // Dispose of current preview RemovePreview(); if (_sheetCancellation.IsCancellationRequested) { _sheetCancellation = null; Close(); return; } _sheetCancellation = null; _bundleSheetExport = task.Result; Image img = _bundleSheetExport.Sheet; sw.Stop(); if (form != null) { form.Cursor = Cursors.Default; } zpb_sheetPreview.SetImage(img); pb_exportProgress.Visible = false; // Update labels lbl_sheetPreview.Text = AnimationMessages.TextSheetPreviewGenerated + sw.ElapsedMilliseconds + @"ms)"; lbl_dimensions.Text = img.Width + @"x" + img.Height; lbl_pixelCount.Text = (img.Width * img.Height).ToString("N0"); lbl_framesOnSheet.Text = (_bundleSheetExport.FrameCount - _bundleSheetExport.ReusedFrameCount) + ""; lbl_reusedFrames.Text = (_bundleSheetExport.ReusedFrameCount) + ""; lbl_memoryUsage.Text = Utilities.FormatByteSize(ImageUtilities.MemoryUsageOfImage(img)); if (pnl_alertPanel.Visible && lbl_alertLabel.Text == AnimationMessages.TextNoAnimationInSheetToGeneratePreview) { pnl_alertPanel.Visible = false; } if (cb_showFrameBounds.Checked) { ShowFrameBounds(); } })); }); }