diff --git a/src/DynamoCoreWpf/DynamoCoreWpf.csproj b/src/DynamoCoreWpf/DynamoCoreWpf.csproj
index de264f43fac..9e261f3e4ba 100644
--- a/src/DynamoCoreWpf/DynamoCoreWpf.csproj
+++ b/src/DynamoCoreWpf/DynamoCoreWpf.csproj
@@ -891,6 +891,7 @@
PackageManagerWizard.xaml
+
diff --git a/src/DynamoCoreWpf/Utilities/Watch3DImageExporter.cs b/src/DynamoCoreWpf/Utilities/Watch3DImageExporter.cs
new file mode 100644
index 00000000000..d3a0f1c6909
--- /dev/null
+++ b/src/DynamoCoreWpf/Utilities/Watch3DImageExporter.cs
@@ -0,0 +1,139 @@
+using System;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using HelixToolkit.Wpf.SharpDX;
+
+namespace Dynamo.Wpf.Utilities
+{
+ ///
+ /// Captures Background 3D Preview viewport content to PNG files.
+ ///
+ internal static class Watch3DImageExporter
+ {
+ ///
+ /// Maximum pixel dimension for exported images. Prevents excessive memory use on
+ /// high-DPI displays and large viewports when exporting complex geometry.
+ ///
+ internal const int DefaultMaxExportDimension = 4096;
+
+ ///
+ /// Renders the viewport to a bitmap at the requested pixel dimensions.
+ ///
+ /// The Helix viewport to capture.
+ /// Target pixel width.
+ /// Target pixel height.
+ /// The rendered bitmap.
+ internal static BitmapSource RenderViewportBitmapAtSize(
+ Viewport3DX view,
+ int targetPixelWidth,
+ int targetPixelHeight)
+ {
+ if (view?.RenderHost == null) throw new ArgumentNullException(nameof(view));
+ if (targetPixelWidth <= 0) throw new ArgumentOutOfRangeException(nameof(targetPixelWidth));
+ if (targetPixelHeight <= 0) throw new ArgumentOutOfRangeException(nameof(targetPixelHeight));
+
+ view.InvalidateRender();
+
+ var dpiScale = view.RenderHost.DpiScale;
+ var originalWidth = view.RenderHost.ActualWidth;
+ var originalHeight = view.RenderHost.ActualHeight;
+ var captureWidth = Math.Max(1, (int)(targetPixelWidth / dpiScale));
+ var captureHeight = Math.Max(1, (int)(targetPixelHeight / dpiScale));
+
+ view.RenderHost.Resize(captureWidth, captureHeight);
+ try
+ {
+ return view.RenderBitmap();
+ }
+ finally
+ {
+ if (originalWidth > 0 && originalHeight > 0)
+ {
+ view.RenderHost.Resize((int)originalWidth, (int)originalHeight);
+ }
+ }
+ }
+
+ ///
+ /// Renders the viewport and saves the result as a PNG file.
+ ///
+ /// The Helix viewport to capture.
+ /// Destination file path.
+ ///
+ /// Maximum pixel width or height. Use 0 to capture at the current viewport resolution.
+ ///
+ /// True when the image was saved successfully.
+ internal static bool TrySaveViewportToPng(Viewport3DX view, string path, int maxExportDimension = DefaultMaxExportDimension)
+ {
+ if (view == null) throw new ArgumentNullException(nameof(view));
+ if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path));
+
+ var bitmap = RenderViewportBitmap(view, maxExportDimension);
+ SaveBitmapSourceAsPng(bitmap, path);
+ return true;
+ }
+
+ ///
+ /// Renders the viewport to a bitmap, optionally scaling down when the pixel size exceeds
+ /// .
+ ///
+ /// The Helix viewport to capture.
+ ///
+ /// Maximum pixel width or height. Use 0 to capture at the current viewport resolution.
+ ///
+ /// The rendered bitmap.
+ internal static BitmapSource RenderViewportBitmap(Viewport3DX view, int maxExportDimension = DefaultMaxExportDimension)
+ {
+ if (view?.RenderHost == null) throw new ArgumentNullException(nameof(view));
+
+ view.InvalidateRender();
+
+ var bitmap = view.RenderBitmap();
+ return ScaleBitmapSourceToMaxDimension(bitmap, maxExportDimension);
+ }
+
+ private static BitmapSource ScaleBitmapSourceToMaxDimension(BitmapSource bitmapSource, int maxExportDimension)
+ {
+ if (bitmapSource == null) throw new ArgumentNullException(nameof(bitmapSource));
+ if (maxExportDimension <= 0)
+ {
+ return bitmapSource;
+ }
+
+ var maxPixelDimension = Math.Max(bitmapSource.PixelWidth, bitmapSource.PixelHeight);
+ if (maxPixelDimension <= maxExportDimension)
+ {
+ return bitmapSource;
+ }
+
+ var scale = maxExportDimension / (double)maxPixelDimension;
+ var transformedBitmap = new TransformedBitmap(bitmapSource, new ScaleTransform(scale, scale));
+ transformedBitmap.Freeze();
+ return transformedBitmap;
+ }
+
+ ///
+ /// Saves a bitmap source as a PNG file, replacing any existing file at the path.
+ ///
+ /// The bitmap to save.
+ /// Destination file path.
+ internal static void SaveBitmapSourceAsPng(BitmapSource bitmapSource, string path)
+ {
+ if (bitmapSource == null) throw new ArgumentNullException(nameof(bitmapSource));
+ if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path));
+
+ if (File.Exists(path))
+ {
+ File.Delete(path);
+ }
+
+ using (var stream = File.Create(path))
+ {
+ var encoder = new PngBitmapEncoder();
+ encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
+ encoder.Save(stream);
+ }
+ }
+ }
+}
diff --git a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
index 3ebbb582a54..820455336b5 100644
--- a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
+++ b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
@@ -3736,6 +3736,13 @@ public void ShowSaveImageDialogAndSave(object parameter)
public void ValidateWorkSpaceBeforeToExportAsImage(object parameter)
{
+ if (parameter?.ToString() == Resources.ScreenShotFrom3DParameter ||
+ (parameter?.ToString() == Resources.ScreenShotFrom3DShortcutParameter && BackgroundPreviewViewModel.CanNavigateBackground))
+ {
+ ShowSaveImageDialogAndSave(parameter);
+ return;
+ }
+
OnRequestExportWorkSpaceAsImage(parameter);
}
diff --git a/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs b/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs
index f3f46eb3223..62b7df2a1d8 100644
--- a/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs
+++ b/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs
@@ -1887,44 +1887,31 @@ private void DynamoViewModelRequestSaveImage(object sender, ImageSaveEventArgs e
private void DynamoViewModelRequestSave3DImage(object sender, ImageSaveEventArgs e)
{
- var dpiX = 0.0;
- var dpiY = 0.0;
-
- // dpi aware, otherwise incorrect images are created
- try
- {
- var scale = VisualTreeHelper.GetDpi(this);
- dpiX = scale.PixelsPerInchX;
- dpiY = scale.PixelsPerInchY;
- }
- catch (Exception ex)
+ if (BackgroundPreview == null)
{
- Log(ex.ToString());
-
- dpiX = 96;
- dpiY = 96;
+ e.Success = false;
+ return;
}
- if (BackgroundPreview == null)
+ if (!dynamoViewModel.BackgroundPreviewViewModel.Active)
{
e.Success = false;
return;
}
- var bitmapSource = BackgroundPreview.View.RenderBitmap();
- // this image only really needs 24bits per pixel but to match previous implementation we'll use 32bit images.
- var rtBitmap = new RenderTargetBitmap(bitmapSource.PixelWidth, bitmapSource.PixelHeight, dpiX, dpiY, PixelFormats.Pbgra32);
- rtBitmap.Render(BackgroundPreview.View);
- var encoder = new PngBitmapEncoder();
- encoder.Frames.Add(BitmapFrame.Create(rtBitmap));
- if (File.Exists(e.Path))
+ try
{
- File.Delete(e.Path);
+ Watch3DImageExporter.TrySaveViewportToPng(BackgroundPreview.View, e.Path);
}
-
- using (var stream = File.Create(e.Path))
+ catch (OutOfMemoryException ex)
+ {
+ e.Success = false;
+ Log(ex.ToString());
+ }
+ catch (Exception ex)
{
- encoder.Save(stream);
+ e.Success = false;
+ Log(ex.ToString());
}
}
diff --git a/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs b/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs
index e7479ec8dc1..5faf1f2cd44 100644
--- a/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs
+++ b/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs
@@ -68,6 +68,8 @@ public enum CursorState
private List hitResultsList = new List();
static internal event Action RequestShowNodeAutoCompleteBar;
+ internal const int MaxWorkspaceImagePixelDimension = 16384;
+ internal const long MaxWorkspaceImagePixelCount = 67108864;
private double currentRenderScale = -1;
public WorkspaceViewModel ViewModel
@@ -343,94 +345,107 @@ internal Rect GetVisibleBounds()
return new Rect(topLeft, bottomRight);
}
- private RenderTargetBitmap GetRender()
+ private bool TryGetRenderBounds(out Rect bounds, out double minX, out double minY)
{
- RenderTargetBitmap rtb;
- try
- {
- var initialized = false;
- var bounds = new Rect();
+ var initialized = false;
+ bounds = new Rect();
+ minX = 0.0;
+ minY = 0.0;
- double minX = 0.0, minY = 0.0;
- var dragCanvas = WpfUtilities.ChildOfType(this);
- var childrenCount = VisualTreeHelper.GetChildrenCount(dragCanvas);
- for (int index = 0; index < childrenCount; ++index)
- {
- ContentPresenter contentPresenter = VisualTreeHelper.GetChild(dragCanvas, index) as ContentPresenter;
- if (contentPresenter.Children().Count() < 1) continue;
+ var dragCanvas = WpfUtilities.ChildOfType(this);
+ if (dragCanvas == null) return false;
- var firstChild = VisualTreeHelper.GetChild(contentPresenter, 0);
+ var childrenCount = VisualTreeHelper.GetChildrenCount(dragCanvas);
+ for (int index = 0; index < childrenCount; ++index)
+ {
+ ContentPresenter contentPresenter = VisualTreeHelper.GetChild(dragCanvas, index) as ContentPresenter;
+ if (contentPresenter.Children().Count() < 1) continue;
- switch (firstChild.GetType().Name)
- {
- case "NodeView":
- case "NoteView":
- case "AnnotationView":
- break;
-
- // Until we completely removed InfoBubbleView (or fixed its broken
- // size calculation), we will not be including it in our size
- // calculation here. This means that the info bubble, if any, will
- // still go beyond the boundaries of the final PNG file. I would
- // prefer not to add this hack here as it introduces multiple issues
- // (including NaN for Grid inside the view and the fix would be too
- // ugly to type in). Suffice to say that InfoBubbleView is not
- // included in the size calculation for screen capture (work-around
- // should be obvious).
- //
- // case "InfoBubbleView":
- // child = WpfUtilities.ChildOfType(child);
- // break;
-
- // We do not take anything other than those above
- // into consideration when the canvas size is measured.
- default:
- continue;
- }
+ var firstChild = VisualTreeHelper.GetChild(contentPresenter, 0);
- // Determine the smallest corner of all given visual elements on the
- // graph. This smallest top-left corner value will be useful in making
- // the offset later on.
+ switch (firstChild.GetType().Name)
+ {
+ case "NodeView":
+ case "NoteView":
+ case "AnnotationView":
+ break;
+
+ // Until we completely removed InfoBubbleView (or fixed its broken
+ // size calculation), we will not be including it in our size
+ // calculation here. This means that the info bubble, if any, will
+ // still go beyond the boundaries of the final PNG file. I would
+ // prefer not to add this hack here as it introduces multiple issues
+ // (including NaN for Grid inside the view and the fix would be too
+ // ugly to type in). Suffice to say that InfoBubbleView is not
+ // included in the size calculation for screen capture (work-around
+ // should be obvious).
//
- var childBounds = VisualTreeHelper.GetDescendantBounds(contentPresenter as Visual);
- minX = childBounds.X < minX ? childBounds.X : minX;
- minY = childBounds.Y < minY ? childBounds.Y : minY;
- childBounds.X = (double)(contentPresenter as Visual).GetValue(Canvas.LeftProperty);
- childBounds.Y = (double)(contentPresenter as Visual).GetValue(Canvas.TopProperty);
+ // case "InfoBubbleView":
+ // child = WpfUtilities.ChildOfType(child);
+ // break;
+
+ // We do not take anything other than those above
+ // into consideration when the canvas size is measured.
+ default:
+ continue;
+ }
- if (initialized)
- {
- bounds.Union(childBounds);
- }
- else
- {
- initialized = true;
- bounds = childBounds;
- }
+ // Determine the smallest corner of all given visual elements on the
+ // graph. This smallest top-left corner value will be useful in making
+ // the offset later on.
+ //
+ var childBounds = VisualTreeHelper.GetDescendantBounds(contentPresenter as Visual);
+ minX = childBounds.X < minX ? childBounds.X : minX;
+ minY = childBounds.Y < minY ? childBounds.Y : minY;
+ childBounds.X = (double)(contentPresenter as Visual).GetValue(Canvas.LeftProperty);
+ childBounds.Y = (double)(contentPresenter as Visual).GetValue(Canvas.TopProperty);
+
+ if (initialized)
+ {
+ bounds.Union(childBounds);
}
+ else
+ {
+ initialized = true;
+ bounds = childBounds;
+ }
+ }
- // Nothing found in the canvas, bail out.
- if (!initialized) return null;
+ if (!initialized) return false;
- // Add padding to the edge and make them multiples of two (pad 10px on each side).
- bounds.Width = 20 + ((((int)Math.Ceiling(bounds.Width)) + 1) & ~0x01);
- bounds.Height = 20 + ((((int)Math.Ceiling(bounds.Height)) + 1) & ~0x01);
+ // Add padding to the edge and make them multiples of two (pad 10px on each side).
+ bounds.Width = 20 + ((((int)Math.Ceiling(bounds.Width)) + 1) & ~0x01);
+ bounds.Height = 20 + ((((int)Math.Ceiling(bounds.Height)) + 1) & ~0x01);
+ return true;
+ }
- var currentTransformGroup = WorkspaceElements.RenderTransform as TransformGroup;
- WorkspaceElements.RenderTransform = new TranslateTransform(10.0 - bounds.X - minX, 10.0 - bounds.Y - minY);
+ private bool IsRenderBoundsValidAsImage(Rect bounds)
+ {
+ if (bounds.Width <= 0 || bounds.Height <= 0) return false;
+ if (bounds.Width > MaxWorkspaceImagePixelDimension || bounds.Height > MaxWorkspaceImagePixelDimension) return false;
+
+ return bounds.Width * bounds.Height <= MaxWorkspaceImagePixelCount;
+ }
+
+ private RenderTargetBitmap GetRender(Rect bounds, double minX, double minY)
+ {
+ Transform currentTransform = WorkspaceElements.RenderTransform;
+ WorkspaceElements.RenderTransform = new TranslateTransform(10.0 - bounds.X - minX, 10.0 - bounds.Y - minY);
+
+ try
+ {
WorkspaceElements.UpdateLayout();
- rtb = new RenderTargetBitmap(((int)bounds.Width),
+ var rtb = new RenderTargetBitmap(((int)bounds.Width),
((int)bounds.Height), 96, 96, PixelFormats.Default);
rtb.Render(WorkspaceElements);
- WorkspaceElements.RenderTransform = currentTransformGroup;
+ return rtb;
}
- catch (Exception)
+ finally
{
- throw;
+ WorkspaceElements.RenderTransform = currentTransform;
}
- return rtb;
}
///
@@ -443,8 +458,8 @@ private RenderTargetBitmap GetRender()
internal ExportImageResult IsWorkSpaceRenderValidAsImage(bool validating, string path = null)
{
ExportImageResult result = ExportImageResult.EmptyDrawing;
- RenderTargetBitmap workSpaceRender = GetRender();
- if (workSpaceRender == null) return result;
+ if (!TryGetRenderBounds(out var bounds, out var minX, out var minY)) return result;
+ if (!IsRenderBoundsValidAsImage(bounds)) return ExportImageResult.NotValidAsImage;
result = ExportImageResult.IsValidAsImage;
if (validating)
@@ -454,6 +469,7 @@ internal ExportImageResult IsWorkSpaceRenderValidAsImage(bool validating, string
try
{
+ RenderTargetBitmap workSpaceRender = GetRender(bounds, minX, minY);
using (var stm = System.IO.File.Create(path))
{
// Encode as PNG format
diff --git a/test/DynamoCoreWpfTests/CoreUITests.cs b/test/DynamoCoreWpfTests/CoreUITests.cs
index f3d076e0763..d562afb7ddb 100644
--- a/test/DynamoCoreWpfTests/CoreUITests.cs
+++ b/test/DynamoCoreWpfTests/CoreUITests.cs
@@ -60,6 +60,29 @@ public void CanSaveImage()
Assert.False(File.Exists(path));
}
+ [Test]
+ [Category("DynamoUI")]
+ public void CannotSaveImageWhenWorkspaceImageWouldBeTooLarge()
+ {
+ var firstNode = new DoubleInput { X = 0, Y = 0 };
+ var secondNode = new DoubleInput { X = WorkspaceView.MaxWorkspaceImagePixelDimension + 1000, Y = 0 };
+ ViewModel.Model.CurrentWorkspace.AddAndRegisterNode(firstNode, true);
+ ViewModel.Model.CurrentWorkspace.AddAndRegisterNode(secondNode, true);
+ DispatcherUtil.DoEvents();
+
+ var workspace = View.ChildOfType();
+ Assert.IsNotNull(workspace, "DynamoView does not have any WorkspaceView");
+
+ Assert.AreEqual(
+ WorkspaceView.ExportImageResult.NotValidAsImage,
+ workspace.IsWorkSpaceRenderValidAsImage(true));
+
+ string path = Path.Combine(TempFolder, "oversized-output.png");
+ ViewModel.SaveImageCommand.Execute(path);
+
+ Assert.False(File.Exists(path));
+ }
+
[Test]
[Category("DynamoUI")]
public void CannotSaveImageWithBadPath()
diff --git a/test/VisualizationTests/BackgroundPreviewExportTests.cs b/test/VisualizationTests/BackgroundPreviewExportTests.cs
new file mode 100644
index 00000000000..eb8e5139c2e
--- /dev/null
+++ b/test/VisualizationTests/BackgroundPreviewExportTests.cs
@@ -0,0 +1,121 @@
+using System;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using Dynamo.ViewModels;
+using Dynamo.Wpf.Utilities;
+using DynamoCoreWpfTests.Utility;
+using NUnit.Framework;
+
+namespace WpfVisualizationTests
+{
+ [TestFixture]
+ public class BackgroundPreviewExportTests : VisualizationTest
+ {
+ [Test]
+ public void WhenBackgroundPreviewIsRenderedThenExportCreatesPngFile()
+ {
+ OpenVisualizationTest(@"imageComparison\spherecolors.dyn");
+ RunCurrentModel();
+ ViewModel.BackgroundPreviewViewModel.Active = true;
+ DispatcherUtil.DoEvents();
+
+ Assert.That(View.BackgroundPreview, Is.Not.Null);
+ Assert.That(View.BackgroundPreview.View, Is.Not.Null);
+
+ var exportPath = Path.Combine(Path.GetTempPath(), $"DynamoBackgroundExportTest_{Guid.NewGuid():N}.png");
+
+ try
+ {
+ Watch3DImageExporter.TrySaveViewportToPng(
+ View.BackgroundPreview.View,
+ exportPath,
+ maxExportDimension: 1024);
+
+ Assert.That(File.Exists(exportPath), Is.True);
+ Assert.That(new FileInfo(exportPath).Length, Is.GreaterThan(0));
+ }
+ finally
+ {
+ if (File.Exists(exportPath))
+ {
+ File.Delete(exportPath);
+ }
+ }
+ }
+
+ [Test]
+ public void WhenBackgroundPreviewExportIsScaledThenExportCreatesNonBlankPngFile()
+ {
+ OpenVisualizationTest(@"imageComparison\spherecolors.dyn");
+ RunCurrentModel();
+ ViewModel.BackgroundPreviewViewModel.Active = true;
+ DispatcherUtil.DoEvents();
+
+ Assert.That(View.BackgroundPreview, Is.Not.Null);
+ Assert.That(View.BackgroundPreview.View, Is.Not.Null);
+
+ var exportPath = Path.Combine(Path.GetTempPath(), $"DynamoBackgroundExportScaledTest_{Guid.NewGuid():N}.png");
+
+ try
+ {
+ Watch3DImageExporter.TrySaveViewportToPng(
+ View.BackgroundPreview.View,
+ exportPath,
+ maxExportDimension: 128);
+
+ Assert.That(File.Exists(exportPath), Is.True);
+ Assert.That(PngHasImageVariation(exportPath), Is.True);
+ }
+ finally
+ {
+ if (File.Exists(exportPath))
+ {
+ File.Delete(exportPath);
+ }
+ }
+ }
+
+ [Test]
+ public void WhenBackgroundPreviewIsInactiveThenExportIsRejected()
+ {
+ OpenVisualizationTest(@"imageComparison\spherecolors.dyn");
+ RunCurrentModel();
+ ViewModel.BackgroundPreviewViewModel.Active = false;
+
+ var args = new ImageSaveEventArgs(Path.Combine(Path.GetTempPath(), "unused.png"));
+ ViewModel.OnRequestSave3DImage(ViewModel, args);
+
+ Assert.That(args.Success, Is.False);
+ }
+
+ private static bool PngHasImageVariation(string path)
+ {
+ using (var stream = File.OpenRead(path))
+ {
+ var decoder = new PngBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
+ var frame = decoder.Frames[0];
+ BitmapSource bitmapSource = frame.Format == PixelFormats.Bgra32
+ ? frame
+ : new FormatConvertedBitmap(frame, PixelFormats.Bgra32, null, 0);
+
+ var stride = bitmapSource.PixelWidth * 4;
+ var pixels = new byte[stride * bitmapSource.PixelHeight];
+ bitmapSource.CopyPixels(pixels, stride, 0);
+
+ for (var i = 4; i < pixels.Length; i += 4)
+ {
+ if (pixels[i] != pixels[0] ||
+ pixels[i + 1] != pixels[1] ||
+ pixels[i + 2] != pixels[2] ||
+ pixels[i + 3] != pixels[3])
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+ }
+}
diff --git a/test/VisualizationTests/HelixImageComparisonTests.cs b/test/VisualizationTests/HelixImageComparisonTests.cs
index 4b49008d8ff..2f6e2da7b1c 100644
--- a/test/VisualizationTests/HelixImageComparisonTests.cs
+++ b/test/VisualizationTests/HelixImageComparisonTests.cs
@@ -13,6 +13,7 @@
using Dynamo.Controls;
using Dynamo.Selection;
using Dynamo.Utilities;
+using Dynamo.Wpf.Utilities;
using DynamoCoreWpfTests.Utility;
using HelixToolkit.Wpf.SharpDX;
using NUnit.Framework;
@@ -43,28 +44,17 @@ protected Watch3DView BackgroundPreview
private static BitmapSource DynamoRenderBitmap(
Viewport3DX view, int width, int height)
{
- var w = view.RenderHost.ActualWidth;
- var h = view.RenderHost.ActualHeight;
- var dpiscaling = view.RenderHost.DpiScale;
- view.RenderHost.Resize((int)(width/ dpiscaling), (int)(height/dpiscaling));
- var rtb = view.RenderBitmap();
- view.RenderHost.Resize((int)w, (int)h);
- return rtb;
+ return Watch3DImageExporter.RenderViewportBitmapAtSize(view, width, height);
}
private static void UpdateTestData(string pathToUpdate, BitmapSource imageFileSource)
{
- SaveBitMapSourceAsPNG(pathToUpdate, imageFileSource);
+ Watch3DImageExporter.SaveBitmapSourceAsPng(imageFileSource, pathToUpdate);
}
private static void SaveBitMapSourceAsPNG(string filePath, BitmapSource bitmapSource)
{
- using (var fileStream = new FileStream(filePath, FileMode.Create))
- {
- BitmapEncoder encoder = new PngBitmapEncoder();
- encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
- encoder.Save(fileStream);
- }
+ Watch3DImageExporter.SaveBitmapSourceAsPng(bitmapSource, filePath);
}
private static System.Drawing.Bitmap BitmapFromSource(BitmapSource bitmapsource)