private void ApplyControlValues(AnimationClipOptions value, bool isNew) { var controls = GetInputGroup(isNew); if (value != null) { controls[0].ValueObject = value.StartTime; controls[1].ValueObject = value.StopTime; controls[2].ValueObject = value.Left; controls[3].ValueObject = value.Top; controls[4].ValueObject = value.Right; controls[5].ValueObject = value.Bottom; controls[6].ValueObject = value.OutputWidth; controls[7].ValueObject = value.OutputHeight; } else { foreach (var txt in controls) { txt.ValueObject = null; } } }
public bool SaveAsGif(AnimationItem aniItem, string fileName, ImageHandlerConfig config, bool options) { var rec = new AnimationRecoder(this.GraphicsDevice); rec.Items.Add(aniItem); int length = rec.GetMaxLength(); int delay = Math.Max(10, config.MinDelay); var timeline = rec.GetGifTimeLine(delay, 655350); // calc available canvas area rec.ResetAll(); Microsoft.Xna.Framework.Rectangle bounds = aniItem.Measure(); if (length > 0) { IEnumerable <int> delays = timeline?.Take(timeline.Length - 1) ?? Enumerable.Range(0, (int)Math.Ceiling(1.0 * length / delay) - 1); foreach (var frameDelay in delays) { rec.Update(TimeSpan.FromMilliseconds(frameDelay)); var rect = aniItem.Measure(); bounds = Microsoft.Xna.Framework.Rectangle.Union(bounds, rect); } } bounds.Offset(aniItem.Position); // customize clip/scale options AnimationClipOptions clipOptions = new AnimationClipOptions() { StartTime = 0, StopTime = length, Left = bounds.Left, Top = bounds.Top, Right = bounds.Right, Bottom = bounds.Bottom, OutputWidth = bounds.Width, OutputHeight = bounds.Height, }; if (options) { var frmOptions = new FrmGifClipOptions() { ClipOptions = clipOptions, ClipOptionsNew = clipOptions, }; if (frmOptions.ShowDialog() == DialogResult.OK) { var clipOptionsNew = frmOptions.ClipOptionsNew; clipOptions.StartTime = clipOptionsNew.StartTime ?? clipOptions.StartTime; clipOptions.StopTime = clipOptionsNew.StopTime ?? clipOptions.StopTime; clipOptions.Left = clipOptionsNew.Left ?? clipOptions.Left; clipOptions.Top = clipOptionsNew.Top ?? clipOptions.Top; clipOptions.Right = clipOptionsNew.Right ?? clipOptions.Right; clipOptions.Bottom = clipOptionsNew.Bottom ?? clipOptions.Bottom; clipOptions.OutputWidth = clipOptionsNew.OutputWidth ?? (clipOptions.Right - clipOptions.Left); clipOptions.OutputHeight = clipOptionsNew.OutputHeight ?? (clipOptions.Bottom - clipOptions.Top); } else { return(false); } } // validate params bounds = new Rectangle( clipOptions.Left.Value, clipOptions.Top.Value, clipOptions.Right.Value - clipOptions.Left.Value, clipOptions.Bottom.Value - clipOptions.Top.Value ); var targetSize = new Point(clipOptions.OutputWidth.Value, clipOptions.OutputHeight.Value); var startTime = clipOptions.StartTime.Value; var stopTime = clipOptions.StopTime.Value; if (bounds.Width <= 0 || bounds.Height <= 0 || targetSize.X <= 0 || targetSize.Y <= 0 || startTime < 0 || stopTime > length || stopTime - startTime < 0) { return(false); } length = stopTime - startTime; // create output dir string framesDirName = Path.Combine(Path.GetDirectoryName(fileName), Path.GetFileNameWithoutExtension(fileName) + ".frames"); if (config.SavePngFramesEnabled && !Directory.Exists(framesDirName)) { Directory.CreateDirectory(framesDirName); } // pre-render rec.ResetAll(); switch (config.BackgroundType.Value) { default: case ImageBackgroundType.Transparent: rec.BackgroundColor = Color.TransparentBlack; break; case ImageBackgroundType.Color: rec.BackgroundColor = System.Drawing.Color.FromArgb(255, config.BackgroundColor.Value).ToXnaColor(); break; case ImageBackgroundType.Mosaic: rec.BackgroundImage = MonogameUtils.CreateMosaic(GraphicsDevice, config.MosaicInfo.Color0.ToXnaColor(), config.MosaicInfo.Color1.ToXnaColor(), Math.Max(1, config.MosaicInfo.BlockSize)); break; } // select encoder GifEncoder enc = AnimateEncoderFactory.CreateEncoder(fileName, targetSize.X, targetSize.Y, config); var encParams = AnimateEncoderFactory.GetEncoderParams(config.GifEncoder.Value); // pipeline functions IEnumerable <Tuple <byte[], int> > MergeFrames(IEnumerable <Tuple <byte[], int> > frames) { byte[] prevFrame = null; int prevDelay = 0; foreach (var frame in frames) { byte[] currentFrame = frame.Item1; int currentDelay = frame.Item2; if (prevFrame == null) { prevFrame = currentFrame; prevDelay = currentDelay; } else if (memcmp(prevFrame, currentFrame, (IntPtr)prevFrame.Length) == 0) { prevDelay += currentDelay; } else { yield return(Tuple.Create(prevFrame, prevDelay)); prevFrame = currentFrame; prevDelay = currentDelay; } } if (prevFrame != null) { yield return(Tuple.Create(prevFrame, prevDelay)); } } IEnumerable <int> RenderDelay() { int t = 0; while (t < length) { int frameDelay = Math.Min(length - t, delay); t += frameDelay; yield return(frameDelay); } } IEnumerable <int> ClipTimeline(int[] _timeline) { int t = 0; for (int i = 0; i < timeline.Length; i++) { var frameDelay = timeline[i]; if (t < startTime) { if (t + frameDelay > startTime) { frameDelay = t + frameDelay - startTime; t = startTime; } else { t += frameDelay; continue; } } if (t + frameDelay < stopTime) { yield return(frameDelay); t += frameDelay; } else { frameDelay = stopTime - t; yield return(frameDelay); break; } } } int prevTime = 0; async Task <int> ApplyFrame(byte[] frameData, int frameDelay) { byte[] gifData = null; if (!encParams.SupportAlphaChannel && config.BackgroundType.Value == ImageBackgroundType.Transparent) { using (var rt2 = rec.GetGifTexture(config.BackgroundColor.Value.ToXnaColor(), config.MinMixedAlpha)) { if (gifData == null) { gifData = new byte[frameData.Length]; } rt2.GetData(gifData); } } else { gifData = frameData; } var tasks = new List <Task>(); // save each frame as png if (config.SavePngFramesEnabled) { tasks.Add(Task.Run(() => { string pngFileName = Path.Combine(framesDirName, $"{prevTime}_{prevTime + frameDelay}.png"); unsafe { fixed(byte *pFrameBuffer = frameData) { using (var bmp = new System.Drawing.Bitmap(targetSize.X, targetSize.Y, targetSize.X * 4, System.Drawing.Imaging.PixelFormat.Format32bppArgb, new IntPtr(pFrameBuffer))) { bmp.Save(pngFileName, System.Drawing.Imaging.ImageFormat.Png); } } } })); } // append frame data to gif stream tasks.Add(Task.Run(() => { // TODO: only for gif here? frameDelay = Math.Max(10, (int)(Math.Round(frameDelay / 10.0) * 10)); unsafe { fixed(byte *pGifBuffer = gifData) { enc.AppendFrame(new IntPtr(pGifBuffer), frameDelay); } } })); await Task.WhenAll(tasks); prevTime += frameDelay; return(prevTime); } async Task RenderJob(IProgressDialogContext context, CancellationToken cancellationToken) { bool isCompareAndMergeFrames = timeline == null; // build pipeline IEnumerable <int> delayEnumerator = timeline == null?RenderDelay() : ClipTimeline(timeline); var step1 = delayEnumerator.TakeWhile(_ => !cancellationToken.IsCancellationRequested); var frameRenderEnumerator = step1.Select(frameDelay => { rec.Draw(); rec.Update(TimeSpan.FromMilliseconds(frameDelay)); return(frameDelay); }); var step2 = frameRenderEnumerator.TakeWhile(_ => !cancellationToken.IsCancellationRequested); var getFrameData = step2.Select(frameDelay => { using (var t2d = rec.GetPngTexture()) { byte[] frameData = new byte[t2d.Width * t2d.Height * 4]; t2d.GetData(frameData); return(Tuple.Create(frameData, frameDelay)); } }); var step3 = getFrameData.TakeWhile(_ => !cancellationToken.IsCancellationRequested); if (isCompareAndMergeFrames) { var mergedFrameData = MergeFrames(step3); step3 = mergedFrameData.TakeWhile(_ => !cancellationToken.IsCancellationRequested); } var step4 = step3.Select(item => ApplyFrame(item.Item1, item.Item2)); // run pipeline bool isPlaying = this.IsPlaying; try { this.IsPlaying = false; rec.Begin(bounds, targetSize); if (startTime > 0) { rec.Update(TimeSpan.FromMilliseconds(startTime)); } context.ProgressMin = 0; context.ProgressMax = length; foreach (var task in step4) { int currentTime = await task; context.Progress = currentTime; } } catch (Exception ex) { context.Message = $"Error: {ex.Message}"; throw; } finally { rec.End(); enc.Dispose(); this.IsPlaying = isPlaying; } } var dialogResult = ProgressDialog.Show(this.FindForm(), "Exporting...", "Save animation file...", true, false, RenderJob); return(dialogResult == DialogResult.OK); }