I have a converter in some legacy code, that is doing something that seems wrong, but I don't know bitmaps very well.
It looks like it's based on https://stackoverflow.com/a/3427114/57883
with some added capabilities.
using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Windows.Media.Imaging;
namespace CompanyName.Converters
{
[ValueConversion(typeof(System.Drawing.Image), typeof(System.Windows.Media.ImageSource))]
/// <summary>
/// One-way converter from System.Drawing.Image to System.Windows.Media.ImageSource
/// </summary>
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// empty images are empty...
if (value == null)
{
return null;
}
if (value.GetType() == typeof(System.Drawing.Image))
{
var image = (System.Drawing.Image)value;
// Winforms Image we want to get the WPF Image from...
var bitmap = new System.Windows.Media.Imaging.BitmapImage();
bitmap.BeginInit();
MemoryStream memoryStream = new MemoryStream();
// Save to a memory stream...
image.Save(memoryStream, ImageFormat.Bmp);
// Rewind the stream...
memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
bitmap.StreamSource = memoryStream;
bitmap.EndInit();
bitmap.Freeze();
return bitmap;
}
else if (value.GetType() == typeof(System.Drawing.Bitmap))
{
var image = value as System.Drawing.Bitmap;
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(image);
using (MemoryStream memory = new MemoryStream())
{
bitmap.Save(memory, ImageFormat.Png);
memory.Position = 0;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
bitmapImage.Freeze();
return bitmapImage;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
}
Why in the world would you System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(image); ? isn't this just creating a copy of the bitmap in memory without any benefit? Is this just horrible code or is there a legitimate reason to copy the bitmap before converting it?
Copying the bitmap via the constructor without explicitly specifying the PixelFormat will result in the image being converted to 32bpp argb, which will in turn prevent problems arising if you try to bind to an image with a pixel format that the target doesn't support. In the case of WPF the Image control does render indexed images correctly but there might be other formats or other control types that will fail. The original author was probably just trying to cover all bases.
Related
I'am trying convert DrawingImage into MemoryStream. My code looks like this:
public MemoryStream ImageStream(DrawingImage drawingImage)
{
MemoryStream stream = new MemoryStream();
ImageSource imageSource = drawingImage;
if (imageSource != null)
{
BitmapSource bitmap = imageSource as BitmapSource;
if (bitmap != null)
{
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
encoder.Save(stream);
}
}
return stream;
}
But problem is after casting ImageSource into BitmapSource bitmap is always null. Any sugestion how to fix that?
The reason your bitmap variable is always null is because DrawingImage does not extend BitmapImage or vice-versa, so the cast is guaranteed to fail. A DrawingImage does not contain any pixel data of any kind. It references a Drawing that is used whenever the image needs to be rasterized.
How did you find yourself in a situation where you want to rasterize a DrawingImage and serialize it into a stream? I get the feeling you are going about something in an unusual way if you have need of a function like this.
Nevertheless, you could implement this function by drawing the DrawingImage to a DrawingVisual, rendering it to a RenderTargetBitmap, and then passing the render target to the encoder to serialize the raster data to a stream.
public MemoryStream ImageStream(DrawingImage drawingImage)
{
DrawingVisual visual = new DrawingVisual();
using (DrawingContext dc = visual.RenderOpen())
{
dc.DrawDrawing(drawingImage.Drawing);
dc.Close();
}
RenderTargetBitmap target = new RenderTargetBitmap((int)visual.Drawing.Bounds.Right, (int)visual.Drawing.Bounds.Bottom, 96.0, 96.0, PixelFormats.Pbgra32);
target.Render(visual);
MemoryStream stream = new MemoryStream();
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(target));
encoder.Save(stream);
return stream;
}
If you want something a little more generic, I would split this into two methods and change some of the types.
public BitmapSource Rasterize(Drawing drawing)
{
DrawingVisual visual = new DrawingVisual();
using (DrawingContext dc = visual.RenderOpen())
{
dc.DrawDrawing(drawing);
dc.Close();
}
RenderTargetBitmap target = new RenderTargetBitmap((int)drawing.Bounds.Right, (int)drawing.Bounds.Bottom, 96.0, 96.0, PixelFormats.Pbgra32);
target.Render(visual);
return target;
}
public void SavePng(BitmapSource source, Stream target)
{
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(source));
encoder.Save(target);
}
Then you could use it with any kind of stream. For example, to save the drawing to a file:
using (FileStream file = File.Create("somepath.png"))
{
SavePng(Rasterize(drawingImage.Drawing), file);
}
I have an application with RichTextBox and DocumentViewer (placed in a TabControl), and I want to make something like "hot preview". I've binded DocumentViewer.Document property to RichTextBox.Document
Binding:
<DocumentViewer Document="{Binding Document, Converter={StaticResource FlowDocumentToPaginatorConverter}, ElementName=mainRTB, Mode=OneWay}" />
And this is Converter code:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FlowDocument d = value as FlowDocument;
DocumentPaginator pagin = ((IDocumentPaginatorSource)d).DocumentPaginator;
FixedDocumentSequence result = null;
Size s = new Size(793.700787402, 1122.519685039);
pagin.PageSize = s;
using (MemoryStream ms = new MemoryStream())
{
TextRange tr = new TextRange(d.ContentStart, d.ContentEnd);
tr.Save(ms, DataFormats.XamlPackage);
Package p = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
Uri uri = new Uri(#"memorystream://doc.xps");
PackageStore.AddPackage(uri, p);
XpsDocument xpsDoc = new XpsDocument(p);
xpsDoc.Uri = uri;
XpsDocument.CreateXpsDocumentWriter(xpsDoc).Write(pagin);
result = xpsDoc.GetFixedDocumentSequence();
}
return result;
}
When I start this application everything is ok until I switch to tab with DocumentViewer. Application crushes and I get such Exception:
Cannot perform a read operation in write-only mode.
What I am doing wrong? Is it possible to make this binding?
The error message is indeed confusing and reason not immediately obvious. Basically you are closing the MemoryStream that holds XpsDocument too early and when the DocumentViewer attempts to read the document it cannot as it is write-only mode (because the stream was closed).
The solution is to not immediately close the MemoryStream until after you have finished viewing the document. To achieve this I wrote an XpsDocumentConverter that returns XpsReference.
Also, as you never been able to convert and display a single XpsDocument you won't have yet encountered the next issue of having multiple packages in the PackageStore with the same Uri. I have taken care of this in my implementation below.
public static XpsDocumentReference CreateXpsDocument(FlowDocument document)
{
// Do not close the memory stream as it still being used, it will be closed
// later when the XpsDocumentReference is Disposed.
MemoryStream ms = new MemoryStream();
// We store the package in the PackageStore
Uri uri = new Uri(String.Format("pack://temp_{0}.xps/", Guid.NewGuid().ToString("N")));
Package pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
PackageStore.AddPackage(uri, pkg);
XpsDocument xpsDocument = new XpsDocument(pkg, CompressionOption.Normal, uri.AbsoluteUri);
// Need to force render the FlowDocument before pagination.
// HACK: This is done by *briefly* showing the document.
DocumentHelper.ForceRenderFlowDocument(document);
XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(xpsDocument), false);
DocumentPaginator paginator = new FixedDocumentPaginator(document, A4PageDefinition.Default);
rsm.SaveAsXaml(paginator);
return new XpsDocumentReference(ms, xpsDocument);
}
public class XpsDocumentReference : IDisposable
{
private MemoryStream MemoryStream;
public XpsDocument XpsDocument { get; private set; }
public FixedDocument FixedDocument { get; private set; }
public XpsDocumentReference(MemoryStream ms, XpsDocument xpsDocument)
{
MemoryStream = ms;
XpsDocument = xpsDocument;
DocumentReference reference = xpsDocument.GetFixedDocumentSequence().References.FirstOrDefault();
if (reference != null)
FixedDocument = reference.GetDocument(false);
}
public void Dispose()
{
Package pkg = PackageStore.GetPackage(XpsDocument.Uri);
if (pkg != null)
{
pkg.Close();
PackageStore.RemovePackage(XpsDocument.Uri);
}
if (MemoryStream != null)
{
MemoryStream.Dispose();
MemoryStream = null;
}
}
}
XpsReference implements IDisposable so remember to call Dispose() on it.
Also, once you resolve the above error the next problem you are likely to encounter will be content not rendering as you would expect. This is caused by the fact you need to clone FlowDocument and it has not undergone a full measure and arrange layout pass. Read
Printing BlockUIContainer to XpsDocument/FixedDocument on how to solve this.
Having to display bitmap images - not vector at several user's dpi settings in a XBAP WPF application, I'd like to setup a dpiFactor global variable at startup, that will be calculated as a percentage of the original bitmSizeap:
i.e. for 120 dpi I want both size of the image to be: newSize = originalSize * (100 - (120 - 96)) / 100
which means multiply by 75% if the dpi is 125% of original.
The dpiFactor have to be defined at startup, and then all measurement to be scaled down (or up) when page is launched.
How can I express that in XAML perhaps with a bound property?
Maybe you can use a converter that looks like this:
[ValueConversion(typeof(string), typeof(BitmapImage))]
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string imageSource = value as string;
if (imageSource == null)
return DependencyProperty.UnsetValue;
try
{
BitmapImage originalImage = new BitmapImage(new Uri(imageSource));
int originalWidth = originalImage.PixelWidth;
int originalHeight = originalImage.PixelHeight;
double originalDpiX = originalImage.DpiX;
double originalDpiY = originalImage.DpiY;
BitmapImage scaledImage = new BitmapImage();
scaledImage.BeginInit();
scaledImage.DecodePixelWidth = originalWidth; // Place your calculation here,
scaledImage.DecodePixelHeight = originalHeight; // and here.
scaledImage.UriSource = new Uri(imageSource);
scaledImage.EndInit();
scaledImage.Freeze();
return scaledImage;
}
catch
{
}
return new BitmapImage();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And in xaml this will looks like this:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:test="clr-namespace:Test">
<Window.Resources>
<test:ImageConverter x:Key="imageConverter" />
</Window.Resources>
<Image Source="{Binding SomePath, Converter={StaticResource imageConverter}}" />
</Window>
To get the system's dpi i think you can use this code.
In a ValueConverter, I was trying to convert a System.Data.Linq.Binary (SQL CE image) to a BitmapImage. This method works (image is show correctly on the form):
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture) {
Binary binary = value as Binary;
if (binary != null) {
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = new MemoryStream(binary.ToArray());
bitmap.EndInit();
return bitmap;
}
return null;
}
This method does NOT work (but no exception is thrown, strangely):
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture) {
Binary binary = value as Binary;
if (binary != null) {
using (var stream = new MemoryStream(binary.ToArray())) {
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = stream;
bitmap.EndInit();
return bitmap;
}
}
return null;
}
Good programming practice states that you should dispose of any streams you create... so I'm confused why the second method doesn't work, but the first does. Any insights?
Try this instead:
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture) {
Binary binary = value as Binary;
if (binary != null) {
using (var stream = new MemoryStream(binary.ToArray())) {
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
bitmap.Freeze();
return bitmap;
}
}
return null;
}
In your non-working version, your using block means the stream is closed before the image is actually decoded.
My guess would that when you are disposing the MemoryStream, you are nullifying the StreamSource of the bitmap. So, when the bitmap tries to render, there is no valid data available.
I'm trying to save a wpf control to file, but I'm applying a PixelShader effect to it, and when I try to save, the saved image is entirely white, black or red... deppends on the parameters of the effect.
I'm using the code here: WPF - Programmatic Binding on a BitmapEffect
how can I properly save it?
thanks!
UPDATE:
the code I'm using is:
BitmapSource bitmap = preview.Source as BitmapImage;
Rectangle r = new Rectangle();
r.Fill = new ImageBrush(bitmap);
r.Effect = effect;
Size sz = new Size(bitmap.PixelWidth, bitmap.PixelHeight);
r.Measure(sz);
r.Arrange(new Rect(sz));
var rtb = new RenderTargetBitmap(bitmap.PixelWidth, bitmap.PixelHeight, bitmap.DpiX, bitmap.DpiY, PixelFormats.Pbgra32);
rtb.Render(r);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(rtb));
Stream stm = File.Create("new.png");
png.Save(stm);
stm.Close();
Try this piece of code:
/// <summary>
/// Creates a screenshot of the given visual at the desired dots/inch (DPI).
/// </summary>
/// <param name="target">Visual component of which to capture as a screenshot.</param>
/// <param name="dpiX">Resolution, in dots per inch, in the X axis. Typical value is 96.0</param>
/// <param name="dpiY">Resolution, in dots per inch, in the Y axis. Typical value is 96.0</param>
/// <returns>A BitmapSource of the given Visual at the requested DPI, or null if there was an error.</returns>
public static BitmapSource CaptureScreen(Visual target, double dpiX, double dpiY)
{
if (target == null)
{
return null;
}
RenderTargetBitmap rtb = null;
try
{
// Get the actual size
Rect bounds = VisualTreeHelper.GetDescendantBounds(target);
rtb = new RenderTargetBitmap((int)(bounds.Width * dpiX / 96.0),
(int)(bounds.Height * dpiY / 96.0),
dpiX,
dpiY,
PixelFormats.Pbgra32);
DrawingVisual dv = new DrawingVisual();
using (DrawingContext ctx = dv.RenderOpen())
{
VisualBrush vb = new VisualBrush(target);
ctx.DrawRectangle(vb, null, new Rect(new Point(), bounds.Size));
}
rtb.Render(dv);
}
catch (Exception ex)
{
Console.Error.WriteLine("Error capturing image: " + ex.Message);
return null;
}
return rtb;
}