I'm trying to create an app that allows the user to draw on an image. I started with the PhotoPaint SDK sample. I understand that the SurfaceInkCanvas is set to transparent and is located on top of the image. When the user is finished drawing I would like to draw the strokes onto the image itself. This is where I'm stuck. Can anyone please point me in the right direction?
Thanks!
Download WriteableBitmap extenstion library from: http://writeablebitmapex.codeplex.com/
Usage:
using System;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace TestApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void SaveImage(object sender, RoutedEventArgs e)
{
var bi = FromUrl("http://www.dynamicdrive.com/dynamicindex4/lightbox2/horses.jpg", Environment.CurrentDirectory);
if (bi.Format != PixelFormats.Pbgra32)
bi = BitmapFactory.ConvertToPbgra32Format(bi);
WriteableBitmap wrb = new WriteableBitmap(bi);
foreach (var stroke in ink.Strokes)
{
foreach (var point in stroke.StylusPoints)
{
wrb.DrawLine((int)point.X, (int)point.Y, (int)(point.X + 1), (int)(point.Y + 1), Colors.Red);
}
}
var encoder = new JpegBitmapEncoder();
BitmapFrame frame = BitmapFrame.Create(wrb);
encoder.Frames.Add(frame);
using (var stream = File.Create("result.jpg"))
{
encoder.Save(stream);
}
}
public static BitmapSource FromUrl(string url, string workingPath)
{
string[] strArr = url.Split(new char[] { '/' });
string file = workingPath + "\\" + strArr[strArr.Length - 1];
if (!File.Exists(file))
{
using (WebClient client = new WebClient())
{
client.DownloadFile(url, file);
}
}
return BitmapImageFromFile(file);
}
public static BitmapSource BitmapImageFromFile(string file)
{
BitmapImage bi = new BitmapImage();
try
{
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
bi.UriSource = new Uri(file, UriKind.RelativeOrAbsolute);
bi.EndInit();
}
catch { return ToBitmapSource(System.Drawing.Bitmap.FromFile(file)); }
return bi;
}
public static BitmapSource ToBitmapSource(System.Drawing.Image bitmap)
{
using (MemoryStream stream = new MemoryStream())
{
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
stream.Position = 0;
BitmapImage result = new BitmapImage();
result.BeginInit();
result.CacheOption = BitmapCacheOption.OnLoad;
result.StreamSource = stream;
result.EndInit();
result.Freeze();
return result;
}
}
}
}
XAML:
<Window x:Class="TestApp.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>
<Image Source="http://www.dynamicdrive.com/dynamicindex4/lightbox2/horses.jpg"
Name="img"/>
<InkCanvas Background="Transparent"
Name="ink"/>
<Button Content="Save"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="5"
Click="SaveImage"/>
</Grid>
</Window>
maybe you'll find a better way to draw on the WriteableBitmap using this extension, drawing method i have used it's just an experiment.
Related
I am just beginning to experiment with Ghostscript.Net. My goal is to adapt the GhostscriptViewer to my WPF MVVM Modular environment, but I am not able to display a page in a XAML Image control. I suspect that my problem lies either in my inadequate knowledge of Ghostscript.Net or in not understanding how to bind the image parameter to the image control.
In this app, I am using Prism with the UnityContainer. The PdfPageImage property in PdfViewModel is bound to the image control in the view XAML. The conversion, which takes place in the _viewer_displaypage method gives PdfPageImage an object of type System.Windows.Interop.InteropBitmap. I was thinking that the RaisePropertyChangedEvent method would cause the control to be updated with the ImageSource object.
<Image x:Name="PdfDocumentImage" Grid.Column="1" Grid.Row="0"
Source="{Binding PdfPageImage}"
Height="100"
Width="100">
</Image>
public class PdfViewModel : ViewModelBase, IPdfViewModel
{
private readonly IBitmapToImageSourceConverter _imageSourceConverter;
private readonly GhostscriptViewer _ghostscriptViewer;
private readonly GhostscriptVersionInfo _gsVersion =
GhostscriptVersionInfo.GetLastInstalledVersion(GhostscriptLicense.GPL |
GhostscriptLicense.AFPL, GhostscriptLicense.GPL);
private ImageSource _pdfPageImage;
public ImageSource PdfPageImage
{
get => _pdfPageImage;
set
{
if (value == _pdfPageImage) return;
_pdfPageImage = value;
RaisePropertyChangedEvent(nameof(PdfPageImage));
}
}
public PdfViewModel(IPdfView view,
IEventAggregator eventAggregator,
IBitmapToImageSourceConverter imageSourceConverter,
GhostscriptViewer ghostscriptViewer)
: base(view)
{
_ghostscriptViewer = ghostscriptViewer;
_imageSourceConverter = imageSourceConverter;
_ghostscriptViewer.DisplayPage += _viewer_DisplayPage;
eventAggregator.GetEvent<PdfDocumentOpenedEvent>().Subscribe(OpenPdfDocument, ThreadOption.UIThread);
}
private void _viewer_DisplayPage(object sender, GhostscriptViewerViewEventArgs e)
{
PdfPageImage = _imageSourceConverter.ImageSourceForBitmap(e.Image);
}
private void OpenPdfDocument(string path)
{
_ghostscriptViewer.Open(path, _gsVersion, false);
}
}
public class BitmapToImageSourceConverter : IBitmapToImageSourceConverter
{
[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeleteObject(IntPtr hObject);
public ImageSource ImageSourceForBitmap(Bitmap bmp)
{
var handle = bmp.GetHbitmap();
try
{
return Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
}
finally { DeleteObject(handle); }
}
}
Okay. So I figured it out. My solution isn't perfect; while paging through a pdf document, the page orientation isn't correct, but other than that, it works.
The key was to use a memorystream in creating a BitmapImage:
public static BitmapImage ConvertBitmapToImage(System.Drawing.Image image)
{
using (MemoryStream memoryStream = new MemoryStream())
{
image.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
memoryStream.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
memoryStream.Seek(0, SeekOrigin.Begin);
bitmapImage.StreamSource = memoryStream;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
return bitmapImage;
}
}
Many other modifications were made to make the Ghostscrip.NET viewer work in my WPF MVVM app. If anybody is interested in more details, I will post more.
I am facing a peculiar issue wherein the Image does not updated when the source gets modified from second time onward.
my xaml and code behind.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="140"/>
</Grid.RowDefinitions>
<Grid Grid.Row="2" Margin="0 55 0 0" x:Name="ImageGrid" >
</Grid>
</Grid>
FileSystemWatcher myWatcher;
Dispatcher myDisp;
private int count = 0;
public MainWindow()
{
InitializeComponent();
myWatcher = new FileSystemWatcher();
myWatcher.Path = #"C:\Test";
myWatcher.Filter = "hospital.png";
myWatcher.NotifyFilter = NotifyFilters.LastWrite;
myWatcher.Changed += myWatcher_Changed;
myWatcher.EnableRaisingEvents = true;
myDisp = Dispatcher.CurrentDispatcher;
}
void myWatcher_Changed(object sender, FileSystemEventArgs e)
{
myWatcher.EnableRaisingEvents = false;
string aImgPath = #"C:\Test\hospital.png";
if (File.Exists(aImgPath))
{
BitmapImage src = new BitmapImage();
src.BeginInit();
src.UriSource = new Uri(aImgPath, UriKind.Relative);
src.CacheOption = BitmapCacheOption.OnLoad;
src.EndInit();
src.Freeze();
myDisp.Invoke(DispatcherPriority.Normal, new Action<BitmapImage>(UpdateImage), src);
}
myWatcher.EnableRaisingEvents = true;
}
void UpdateImage(BitmapImage theImage)
{
ImageGrid.Children.Clear();
Image aImg = new Image();
aImg.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
aImg.VerticalAlignment = System.Windows.VerticalAlignment.Top;
aImg.Height = 70;
aImg.Width = 250;
aImg.Stretch = Stretch.Uniform;
aImg.Source = theImage;
ImageGrid.Children.Add(aImg);
}
This code gets executed when ever the file hospital.png gets updated; but the UI still shows the old image from second time onward. Any thing i am doing incorrectly?
The problem is that the image file Uri is cached by WPF. Because it never changes, no new image is loaded.
You may however load the image from a FileStream instead:
if (File.Exists(aImgPath))
{
var src = new BitmapImage();
using (var fs = new FileStream(
aImgPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
src.BeginInit();
src.StreamSource = fs;
src.CacheOption = BitmapCacheOption.OnLoad;
src.EndInit();
}
src.Freeze();
myDisp.Invoke(DispatcherPriority.Normal, new Action<BitmapImage>(UpdateImage), src);
}
I would like to load Gravatar-Images and set them from code behind to a WPF Image-Control.
So the code looks like
imgGravatar.Source = GetGravatarImage(email);
Where GetGravatarImage looks like:
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri( GravatarImage.GetURL( "http://www.gravatar.com/avatar.php?gravatar_id=" + email) , UriKind.Absolute );
bi.EndInit();
return bi;
Unfortunately this locks the GUI when the network connection is slow. Is there a way to assign the image-source and let it load the image in the background without blocking the UI?
Thanks!
I suggest you to use a Binding on your imgGravatar from XAML. Set IsAsync=true on it and WPF will automatically utilize a thread from the thread pool to pull your image. You could encapsulate the resolving logic into an IValueConverter and simply bind the email as Source
in XAML:
<Window.Resouces>
<local:ImgConverter x:Key="imgConverter" />
</Window.Resource>
...
<Image x:Name="imgGravatar"
Source="{Binding Path=Email,
Converter={StaticResource imgConverter},
IsAsync=true}" />
in Code:
public class ImgConverter : IValueConverter
{
public override object Convert(object value, ...)
{
if (value != null)
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri(
GravatarImage.GetURL(
"http://www.gravatar.com/avatar.php?gravatar_id=" +
value.ToString()) , UriKind.Absolute
);
bi.EndInit();
return bi;
}
else
{
return null;
}
}
}
I can't see why your code would block the UI, as BitmapImage supports downloading image data in the background. That's why it has an IsDownloading property and a DownloadCompleted event.
Anyway, the following code shows a straightforward way to download and create the image entirely in a separate thread (from the ThreadPool). It uses a WebClient instance to download the whole image buffer, before creating a BitmapImage from that buffer. After the BitmapImage is created it calls Freeze to make it accessible from the UI thread. Finally it assigns the Image control's Source property in the UI thread by means of a Dispatcher.BeginInvoke call.
ThreadPool.QueueUserWorkItem(
o =>
{
var url = GravatarImage.GetURL(
"http://www.gravatar.com/avatar.php?gravatar_id=" + email);
var webClient = new WebClient();
var buffer = webClient.DownloadData(url);
var bitmapImage = new BitmapImage();
using (var stream = new MemoryStream(buffer))
{
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = stream;
bitmapImage.EndInit();
bitmapImage.Freeze();
}
Dispatcher.BeginInvoke((Action)(() => image.Source = bitmapImage));
});
EDIT: today you would just use async methods:
var url = GravatarImage.GetURL(
"http://www.gravatar.com/avatar.php?gravatar_id=" + email);
var httpClient = new HttpClient();
var responseStream = await httpClient.GetStreamAsync(url);
var bitmapImage = new BitmapImage();
using (var memoryStream = new MemoryStream())
{
await responseStream.CopyToAsync(memoryStream);
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = memoryStream;
bitmapImage.EndInit();
bitmapImage.Freeze();
}
image.Source = bitmapImage;
I want to be able to load a WPF Resource Dictionary of Paths and output them one by one to files (jpg, bmp, it doesn't matter). This will be in a class library that will be accessed by an MVC application to render to a http stream, so I'm doing this purely within code (no XAML pages).
I've been able to load the dictionary and iterate through the paths, but when I save the images to disk, they are blank. I know I'm missing something trivial, like applying the path to a piece of geometry, or adding it into some containing rectangle or something, but my WPF experience is somewhat limited.
I'm using the following code:
I have a WPF Resource Dictionary containing several paths, such as the following:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Path x:Key="Path1" Data="M 100,200 C 100,25 400,350 400,175 H 280" Fill="White" Margin="10,10,10,10" Stretch="Fill"/>
<Path x:Key="Path2" Data="M 10,50 L 200,70" Fill="White" Margin="10,10,10,10" Stretch="Fill"/>
</ResourceDictionary>
And the class to read and output the files:
public class XamlRenderer
{
public void RenderToDisk()
{
ResourceDictionary resource = null;
Thread t = new Thread(delegate()
{
var s = new FileStream(#"C:\Temp\myfile.xaml", FileMode.Open);
resource = (ResourceDictionary)XamlReader.Load(s);
s.Close();
foreach (var item in resource)
{
var resourceItem = (DictionaryEntry)item;
var path = (System.Windows.Shapes.Path)resourceItem.Value;
var panel = new StackPanel();
var greenBrush = new SolidColorBrush {Color = Colors.Green};
path.Stroke = Brushes.Blue;
path.StrokeThickness = 2;
path.Fill = greenBrush;
panel.Children.Add(path);
panel.UpdateLayout();
string filepath = #"C:\Temp\Images\" + resourceItem.Key + ".jpg";
SaveImage(panel, 64, 64, filepath);
}
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
public void SaveImage(Visual visual, int width, int height, string filePath)
{
var bitmap =
new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(visual);
var image = new PngBitmapEncoder();
image.Frames.Add(BitmapFrame.Create(bitmap));
using (Stream fs = File.Create(filePath))
{
image.Save(fs);
}
}
}
After much googling, and trial and error, I seem to have arrived at a solution. This post pointed me in the right direction.
There were a few issues:
I was now setting the size of the path and the container
The stack panel container has some nuances that were causing issues, so I replaced it with a canvas
Most importantly, Measure() and Arrange() need to be called on the container element. The UpdateLayout() call is not needed.
Once these issues were fixed, the images rendered to disk (although there's an aspect ratio problem I have yet to fix).
Here is the updated code:
public void RenderToDisk()
{
ResourceDictionary resource = null;
Thread t = new Thread(delegate()
{
var s = new FileStream(#"C:\Temp\myfile.xaml", FileMode.Open);
resource = (ResourceDictionary)XamlReader.Load(s);
s.Close();
foreach (var item in resource)
{
var resourceItem = (DictionaryEntry)item;
var path = (System.Windows.Shapes.Path)resourceItem.Value;
path.Margin = new Thickness(10);
path.HorizontalAlignment = HorizontalAlignment.Center;
path.VerticalAlignment = VerticalAlignment.Center;
path.Width = 48;
path.Height = 48;
path.Stroke = Brushes.White;
path.Fill = Brushes.Black;
var canvas = new Canvas();
canvas.Width = 64;
canvas.Height = 64;
canvas.Margin = new Thickness(0);
canvas.Background = Brushes.Transparent;
canvas.Children.Add(path);
canvas.Measure(new Size(canvas.Width, canvas.Height));
canvas.Arrange(new Rect(new Size(canvas.Width, canvas.Height)));
string filepath = #"C:\Temp\Images\" + resourceItem.Key + ".png";
SaveImage(canvas, (int)canvas.Width, (int)canvas.Height, filepath);
}
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
In an app I need to serialize an image through a binarywriter, and to get it in an other app to display it.
Here is a part of my "serialization" code :
FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write);
BinaryWriter bin = new BinaryWriter(fs);
bin.Write((short)this.Animations.Count);
for (int i = 0; i < this.Animations.Count; i++)
{
MemoryStream stream = new MemoryStream();
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(Animations[i].Image));
encoder.Save(stream);
stream.Seek(0, SeekOrigin.Begin);
bin.Write((int)stream.GetBuffer().Length);
bin.Write(stream.GetBuffer());
stream.Close();
}
bin.Close();
And here is the part of my deserialization that load the image :
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
BinaryReader bin = new BinaryReader(fs);
int animCount = bin.ReadInt16();
int imageBytesLenght;
byte[] imageBytes;
BitmapSource img;
for (int i = 0; i < animCount; i++)
{
imageBytesLenght = bin.ReadInt32();
imageBytes = bin.ReadBytes(imageBytesLenght);
img = new BitmapImage();
MemoryStream stream = new MemoryStream(imageBytes);
BitmapDecoder dec = new PngBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
img = dec.Frames[0];
stream.Close();
}
bin.Close();
When I use this method, I load the image (it seems to be stored in the "img" object) but it can't be displayed.
Has somedy an idea?
Thanks
KiTe
UPD :
I already do this : updating my binding, or even trying to affect it directly through the window code behing. None of these approaches work :s
However, when I add this :
private void CreateFile(byte[] bytes)
{
FileStream fs = new FileStream(Environment.CurrentDirectory + "/" + "testeuh.png", FileMode.Create, FileAccess.Write);
fs.Write(bytes, 0, bytes.Length);
fs.Close();
}
at the end of you function, it perfectly create the file, which can be read without any problems ... So I don't know where the problem can be.
UPD 2 :
A weird things happens.
Here is the binding I use :
<Image x:Name="imgSelectedAnim" Width="150" Height="150" Source="{Binding ElementName=lstAnims, Path=SelectedItem.Image}" Stretch="Uniform" />
(the list is itself binded on an observableCollection).
When I create manually the animation through the app, it works (the image is displayed).
But when I load it, it is not displayed (I look at my code, there are not any "new" which could break up my binding, since there are others properties binded the same way and they does not fail).
Moreover, I've put a breakpoint on the getter/setter of the properties Image of my animation.
When it is created, no problems, it has the good informations.
But when it is retreived through the getter, it return a good image the first time, and then and image with pixelWidth, pixelHeight, width, height of only 1 but without going through the setter anymore !
Any idea?
UPD3
tried what you said like this :
Added a property TEST in my viewModel :
private BitmapSource test;
public BitmapSource TEST { get { return test; } set { test = value; RaisePropertyChanged("TEST"); } }
then did it :
img = getBmpSrcFromBytes(bin.ReadBytes(imageBytesLenght));
TEST = img;
(in the code showed and modified before)
and my binding :
<Image x:Name="imgSelectedAnim" Width="150" Height="150" Source="{Binding Path=TEST}" Stretch="Uniform" />
(datacontext is set to my ViewModel)
And it still doesn't work and does the weird image modification (pixW, pixH, W and H set to 1)
FINAL UPD:
It seems I finally solved the problem. Here is simply what I did :
byte[] bytes = bin.ReadBytes(imageBytesLenght);
MemoryStream mem = new MemoryStream(bytes);
img = new BitmapImage();
img.BeginInit();
img.StreamSource = mem;
img.EndInit();
the strange thing is that it didn't work the first time, maybe it is due to an architectural modification within my spriteanimation class, but I don't think it is.
Well, thank you a lot to Eugene Cheverda for his help
At the first, I think you should store your images in a list of BitmapSource and provide reverse encoding of image for using stream as BitmapImage.StreamSource:
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
BinaryReader bin = new BinaryReader(fs);
int animCount = bin.ReadInt16();
int imageBytesLenght;
byte[] imageBytes;
List<BitmapSource> bitmaps = new List<BitmapSource>();
for (int i = 0; i < animCount; i++)
{
imageBytesLenght = bin.ReadInt32();
imageBytes = bin.ReadBytes(imageBytesLenght);
bitmaps.Add(getBmpSrcFromBytes(imageBytes));
}
bin.Close();
UPD
In code above use func written below:
private BitmapSource getBmpSrcFromBytes(byte[] bytes)
{
using (var srcStream = new MemoryStream(bytes))
{
var dec = new PngBitmapDecoder(srcStream, BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.Default);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(dec.Frames[0]);
BitmapImage bitmapImage = null;
bool isCreated;
try
{
using (var ms = new MemoryStream())
{
encoder.Save(ms);
bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = ms;
bitmapImage.EndInit();
isCreated = true;
}
}
catch
{
isCreated = false;
}
return isCreated ? bitmapImage : null;
}
}
Hope this helps.
UPD #2
Your binding is incorrect. You may be should bind selected item to for example CurrentImageSource. And then this CurrentImageSource bind to Image control.
Code:
public class MyViewModel : INotifyPropertyChanged
{
public ObservableCollection<BitmapSource> ImagesCollection { get; set; }
private BitmapSource _currentImage;
public BitmapSource CurrentImage
{
get { return _currentImage; }
set
{
_currentImage = value;
raiseOnPropertyChanged("CurrentImage");
}
}
private void raiseOnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then:
<ListBox ItemsSource="{Binding ImagesCollection}" SelectedItem="{Binding CurrentImage}"/>
<Image Source="{Binding CurrentImage}"/>
ADDED
Here is the sample project in which implemented technique as I described above.
It creates animations collection and represent them in listbox, selected animation is displayed in Image control using its Image property.
What I want to say is that if you cannot obtain image as it should be, you need to search problems in serialization/deserialization.