Using WPF on a DLL to create Images Dynamically (instead of GDI+) - c#

I need to generate an image dynamically, and after I read the tutorial here I realize i can use all the controls and layouts from WPF to generate my rendering, and then save it as a JPG.
The idea is to use this instead of GDI+, which is quite primitive.
The question is, how to I create a regular dll file that would generate programatically a WPF canvas so then i can add controls to it and then ouput that to an image file. Keep in mind that it would be consumed by an ASP.NET application.
Any ideas somebody?

That example has a good start but I've found it's got a lot of unneeded junk along with it. The main thing is that you don't need to have a separate WPF project.
Here's what to do:
Reference PresentationCore, PresentationFramework and WindowsBase in your web project.
Create a Canvas and other WPF objects programmatically in an STA thread.
Call a few special methods on them to make sure they update outside of the context of a WPF app.
Render them to an image with RenderTargetBitmap.
Shut down the thread's dispatcher.
Set the mime type and output the image with ASP.NET.
To make this more efficient you could re-use the same thread rather than creating a new one for each image. In that case you would only need to clean up the dispatcher when you shut down the thread.
Here's the full working code I have:
using System;
using System.Web;
using System.Threading;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Windows.Documents;
public partial class _Default : System.Web.UI.Page
{
private byte[] imageBuffer;
public void Page_Load(object sender, EventArgs e)
{
this.RenderImage();
Response.Clear();
Response.ContentType = #"image/png";
Response.BufferOutput = true;
Response.BinaryWrite(this.imageBuffer);
Response.Flush();
}
public void RenderImage()
{
Thread worker = new Thread(new ThreadStart(this.RenderImageWorker));
worker.SetApartmentState(ApartmentState.STA);
worker.Name = "RenderImageWorker";
worker.Start();
worker.Join();
}
public void RenderImageWorker()
{
Canvas imageCanvas = new Canvas { Width = 600, Height = 200, Background = Brushes.Azure };
TextBlock tb = new TextBlock();
tb.Width = (double)400;
//tb.Height = (double)200;
tb.TextAlignment = TextAlignment.Center;
tb.Inlines.Add(new Run("This is "));
tb.Inlines.Add(new Bold(new Run("bold")));
tb.Inlines.Add(new Run(" text."));
tb.FontSize = 30;
tb.Foreground = Brushes.Blue;
imageCanvas.Children.Add(tb);
// Update layout
imageCanvas.Measure(new Size(imageCanvas.Width, imageCanvas.Height));
imageCanvas.Arrange(new Rect(new Size(imageCanvas.Width, imageCanvas.Height)));
RenderTargetBitmap bitmapRenderer = new RenderTargetBitmap((int)imageCanvas.ActualWidth, (int)imageCanvas.ActualHeight, 96, 96, PixelFormats.Pbgra32);
bitmapRenderer.Render(imageCanvas);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(bitmapRenderer));
using (MemoryStream memoryStream = new MemoryStream())
{
png.Save(memoryStream);
this.imageBuffer = memoryStream.ToArray();
}
if (bitmapRenderer.Dispatcher.Thread.IsAlive)
{
bitmapRenderer.Dispatcher.InvokeShutdown();
}
}
}

Related

Bringing large image into view at runtime

Here is the problem.
I have a view, that should display an image and some controls.
User add new images, changes some options and click "finish".
Images are large and very large (400-1500 MB Tiff)
User should see the preview of image, but it is ok if it loading for 10-15 sec or even more, he have a job for this time.
Image is binding through MVVM pattern like simple string (file will be always in local folder)
<Image Name="ImagePreview" Source="{Binding SFilePathForPreview,
FallbackValue={StaticResource DefaultImage},
TargetNullValue={StaticResource DefaultImage}}"
HorizontalAlignment="Center" Width="200" Height="200"
VerticalAlignment="Center" />
Problem is that all is hangs when user try to add a file for loading time.
I understand that this case should be solved through multithreading - but have no idea how to implement this.
I tryed to update image from view in different thread like this:
Thread newThread = new Thread(LazyLoad);
newThread.Name = "LazyLoad";
newThread.Start(SFilePathForPreview);
public void LazyLoad(object SFilePath)
{
try
{
string path = (string)SFilePath;
BitmapImage t_source = new BitmapImage();
t_source.BeginInit();
t_source.UriSource = new Uri(path);
t_source.DecodePixelWidth = 200;
t_source.EndInit();
t_source.Freeze();
this.Dispatcher.Invoke(new Action(delegate
{
ImagePreview.Source = t_source;
}));
}
catch
{
//...
}
}
But anyway at point
ImagePreview.Source = t_source;
everything hangs up until image fully loaded.
Is there a way to load a preview in the background and show it without those terrible hangs?
The probably most simple way of asynchronously loading an image is via an asynchronous Binding. You would not have to deal with Threads or Tasks at all.
<Image Source="{Binding Image, IsAsync=True}"/>
A possible view model could look like shown below, where you must make sure that the Image property getter can be called from a background thread.
public class ViewModel : ViewModelBase
{
private string imagePath;
private BitmapImage image;
public string ImagePath
{
get { return imagePath; }
set
{
imagePath = value;
image = null;
OnPropertyChanged(nameof(ImagePath));
OnPropertyChanged(nameof(Image));
}
}
public BitmapImage Image
{
get
{
lock (this)
{
if (image == null &&
!string.IsNullOrEmpty(imagePath) &&
File.Exists(imagePath))
{
using (var stream = File.OpenRead(imagePath))
{
image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.DecodePixelWidth = 200;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
}
}
}
return image;
}
}
}
As you already mentioned, you are blocking the UI thread with the image load. You can use a WriteableBitmap class instance as the source for your Image. This will let you load the image on a background thread or async task. Here is a quick guide (not mine) on the issue.
https://www.i-programmer.info/programming/wpf-workings/527-writeablebitmap.html
Another option would be using priortybinding with the highest priorty to the full image and a lower priority to the faster-loading preview image. MS has documented priority binding here:
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-prioritybinding

How to replace bookmark image along with formating in word using C#?

I am automating a document using interop.word.
I have a template which contents are populated through WinForms controls. Rest all is peace of cake but when it comes to an image, the code is not replacing the image rather it inserts the image and doesn't keep any format so all my template effort goes to zero.
I want to insert image at bookmark where it should replace the existing bookmark image and keep its formatting. Can anyone give me the best way of doing it?
Here is the code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Office.Interop.Word;
using System.IO;
namespace GISv1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
string address = Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath);
//Creating Object for word application
Microsoft.Office.Interop.Word.Application app = new Microsoft.Office.Interop.Word.Application();
//Creating Object for documnt
Microsoft.Office.Interop.Word.Document doc = new Document();
//Opening the document
doc = app.Documents.Open(address + "//ReportTemplate11.dotx");
//Activating for Editing
doc.Activate();
//Bookmarks Finding and Replacing
Bookmarks books = doc.Bookmarks;
books["Ref"].Range.Text = tb_Reference.Text;
books["Id"].Range.Text = tb_RptId.Text;
books["RptNo"].Range.Text = tb_RptNo.Text;
books["ClientName"].Range.Text = tb_Client.Text;
books["ProjectName1"].Range.Text = tb_Project.Text;
books["ProjectName2"].Range.Text = tb_Project.Text;
books["ProjectName3"].Range.Text = tb_Project.Text;
books["ProjectArea"].Range.Text = cb_Area.Text;
books["FieldTestName"].Range.Text = tb_FieldName.Text;
books["FieldTestDesignation"].Range.Text = tb_FieldDesignation.Text;
books["FieldTestDate"].Range.Text = tb_FieldDate.Text;
books["LabTestName"].Range.Text = tb_LabName.Text;
books["LabTestDesignation"].Range.Text = tb_LabDesignation.Text;
books["LabTestDate"].Range.Text = tb_LabDate.Text;
books["PreparedName"].Range.Text = tb_PreparedName.Text;
books["PreparedDesignation"].Range.Text = tb_PreparedDesignation.Text;
books["PreparedDate"].Range.Text = tb_PreparedDate.Text;
books["VettedName"].Range.Text = tb_VettedName.Text;
books["VettedDesignation"].Range.Text = tb_VettedDesignation.Text;
books["VettedDate"].Range.Text = tb_VettedDate.Text;
books["ApprovedName"].Range.Text = tb_ApprovedName.Text;
books["ApprovedDesignation"].Range.Text = tb_ApprovedDesignation.Text;
books["ApprovedDate"].Range.Text = tb_ApprovedDate.Text;
books["TitlePagePic"].Range.InlineShapes.AddPicture(address + "//PAF SIte Water Tanks.jpg");
MessageBox.Show("Data is saved");
app.Visible = true;
}
}
}
If I understand your question correctly, Word's object model doesn't support what you want. There's no way to stick another graphic into a bookmark and have it replace the original InlineShape and take on the properties of that original. More recent versions of Word have functionality in the UI, for the user, that do that. But it hasn't been provided in the object model.
You'd need to store every relevant property and re-apply it after inserting the graphic.
A possible work-around for the sizing is to store the graphic in a one-cell table with the exact width needed for the image (the height of an inserted graphic will adjust to the width proportionally). Bookmark the entire cell, not just the graphic. The following code snippet will insert the new graphic then delete the old:
Word.Range rngImage = books["TitlePagePic"].Range;
rngImage.InlineShapes.AddPicture(address + "//PAF SIte Water Tanks.jpg");
rngImage.InlineShapes[2].Delete();
But if you have any other kind of formatting that will need to be re-applied.

How do you blur an image in Windows 10 XAML?

I've looked all around this site and have yet to find a way to blur an image in XAML. I've tried some functions in C#, but they haven't worked. I'd like to achieve the blurred background image, like in the Audiocloud app (https://store-images.s-microsoft.com/image/apps.28769.9007199266467874.da79334d-0f1c-4851-8e0b-7ee566918b20.0658cd96-487e-4e1f-a330-10fb030aaa22?w=443&h=788&q=60). How would I go about accomplishing this?
Add Microsofts Win2D.uwp NuGet package to your project. (Right click your project in Solution Explorer > "Manage NuGet packages...")
Have this example XAML UI:
<Grid>
<Image x:Name="myBackground" Stretch="UniformToFill" />
<Image x:Name="myImage" Width="360" Height="300" Source="Assets/test.png" Stretch="UniformToFill" ImageOpened="myImage_ImageOpened"/>
</Grid>
Create an Extension Method for UIElement in a static class:
static class Extension
{
public static async Task<IRandomAccessStream>
RenderToRandomAccessStream(this UIElement element)
{
RenderTargetBitmap rtb = new RenderTargetBitmap();
await rtb.RenderAsync(element);
var pixelBuffer = await rtb.GetPixelsAsync();
var pixels = pixelBuffer.ToArray();
var displayInformation = DisplayInformation.GetForCurrentView();
var stream = new InMemoryRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied,
(uint)rtb.PixelWidth,
(uint)rtb.PixelHeight,
displayInformation.RawDpiX,
displayInformation.RawDpiY,
pixels);
await encoder.FlushAsync();
stream.Seek(0);
return stream;
}
}
Use it. For example in your public sealed partial class MainPage : Page:
private async void BlurThisUI(UIElement sourceElement, Image outputImage)
{
using (var stream = await sourceElement.RenderToRandomAccessStream())
{
var device = new CanvasDevice();
var bitmap = await CanvasBitmap.LoadAsync(device, stream);
var renderer = new CanvasRenderTarget(device,
bitmap.SizeInPixels.Width,
bitmap.SizeInPixels.Height,
bitmap.Dpi);
using (var ds = renderer.CreateDrawingSession())
{
var blur = new GaussianBlurEffect();
blur.BlurAmount = 5.0f;
blur.Source = bitmap;
ds.DrawImage(blur);
}
stream.Seek(0);
await renderer.SaveAsync(stream, CanvasBitmapFileFormat.Png);
BitmapImage image = new BitmapImage();
image.SetSource(stream);
outputImage.Source = image;
}
}
Call it. In this example when the image was opened (see the XAML above: ImageOpened="myImage_ImageOpened"):
private void myImage_ImageOpened(object sender, RoutedEventArgs e)
{
this.BlurThisUI(myImage, myBackground);
}
Credits goes to Nikola Metulev by this tutorial
Note
As you can see, you can not just use this to blur images but also any UIElement (for example a grid with all its containing UI elements like buttons, checkboxes etc.)
all the usings
using System;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Graphics.Display;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;

How to add image to Togglebutton without using XAML?

I'm trying to add a image to a togglebutton in WPF - C#. The thing is that the assignment I'm working on can't be made with the use of XAML at all. I've tried to set the Content property to an image, but all I get is a normal togglebutton, which isn't helping my cause at all.
myToggleButton = new ToggleButton();
myImage = new Image();
BitmapImage bmi = new BitmapImage();
bmi.BeginInit();
bmi.UriSource = new Uri("myImageResource.bmp", UriKind.Relative);
bmi.EndInit();
myImage.Source = bmi;
myToggleButton.Content = myImage;
Hope I supplied enough info, if not please ask for more.
Updated #Phil Wright:
When I ad an image like this:
myImage = new Image();
BitmapImage bmi = new BitmapImage();
bmi.BeginInit();
bmi.UriSource = new Uri("myImageResource.bmp", UriKind.Relative);
bmi.EndInit();
myImage.Source = bmi;
it works...
Update #Matt West:
myGrid.Children.add(MyToggleButton); // This gives me an empty ToggleButton
myGrid.Children.add(MyImage); // This gives me an image with content
You are creating a new toggle button but you aren't adding it to anything. The image is getting added to the toggle button but the actual toggle button isn't added as a Child to anything. You either need to add the toggle button in code behind with something like this:
this.AddChild(myToggleButton);
Or if you already have the toggle button defined in XAML with a name of myToggleButton then remove this line from your code above
myToggleButton = new ToggleButton();
As requested here is the code that works for me in its entirety:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid Name="_Root">
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Controls.Primitives;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var tb = new ToggleButton();
var image = new Image();
BitmapImage bmi = new BitmapImage();
bmi.BeginInit();
bmi.UriSource = new Uri("/Images/6.png", UriKind.Relative);
bmi.EndInit();
image.Source = bmi;
tb.Content = image;
_Root.Children.Add(tb);
}
}
}
Where the image is a resource; as noted before those last two lines make no sense, if you can get the image to display on its own it should also display inside the button.
Are you sure that the provided bitmap resource is able to be located. If not then the image will be empty and so occupy no space and hence the toggle button looks empty.
image of a toggle button can be set like this:
ToggleButton tgb = new ToggleButton();
BitmapImage bmi = new BitmapImage();
bmi.BeginInit();
bmi.UriSource = new Uri("myImageResource.bmp", UriKind.Relative);
bmi.EndInit();
tgb.Content = new Image { Source = bmi };

WP7 silverlight custom control using popup

I'm creating a custom datepicker, I have a textbox, once clicked it opens a calendar within a popup.
What I want to do is change the size of the popup so it shows my whole calendar, but I can't manage to change it..., I've tried using Height, Width, MinHeight, MinWidth... but it doesn't work, the popup keep showing with a fixed size.
The thing is that my popup's parent property isn't evaluated since it has expression issues (according to debugger), so I'm sure my popup's parent isn't the main screen( say layout grid).
How can I for example make my popup open within a specific context ?
This part of my code isn't XAML, it's C# code only and it looks like:
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Controls.Primitives;
namespace CalendarBranch.components
{
public class wpDatePicker:TextBox
{
private CalendarPopup calendar;
private Popup popup;
public wpDatePicker()
{
this.calendar = new CalendarPopup();
this.popup = new Popup();
this.popup.Child = this.calendar;
this.popup.Margin = new Thickness(0);
this.MouseLeftButtonUp += new MouseButtonEventHandler(wpDatePicker_MouseLeftButtonUp);
this.calendar.onDateSelect += new EventHandler(onDateSelected);
this.IsReadOnly = true;
}
protected void wpDatePicker_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
this.popup.Height = this.calendar.Height;
this.popup.Width = this.calendar.Width;
this.popup.HorizontalAlignment = HorizontalAlignment.Center;
this.popup.VerticalAlignment = VerticalAlignment.Center;
this.popup.HorizontalOffset = 0;
this.popup.VerticalOffset = 0;
this.popup.MinHeight = this.calendar.Height;
this.popup.MinWidth = this.calendar.Width;
this.popup.IsOpen = true;
}
private void onDateSelected(Object sender, EventArgs ea) {
this.Text = this.calendar.SelectedValue.ToShortDateString();
this.popup.IsOpen = false;
}
}
}
PS: the class Calendar is simply a UserControl that contains a grid with multiple columns, HyperLinkButtons and TextBlocks, so nothing special.
Thank you in advance guys ;)
Cheers
Miloud B.
Popup control resizes itself to fit the content inside of it. For example, if you set the child of Popup to be StackPanel with width/height set to 100, the popup will be 100x100.
So it's really important to set the sizes not of your popup, but of your inner panel. Try wrapping your content into the stackpanel and assign necessary width/height there.

Categories

Resources