WPF image vector format export (XPS?) - wpf

Our tool allows export to PNG, which works very nicely.
Now, I would like to add export to some vector format. I tried XPS, but the results are not satisfying at all.
Take a look at a comparison http://www.jakubmaly.cz/xps-vs-png.png.
The picture on the left comes from an XPS export, the picture on the right from PNG export, the XPS picture is visibly blurred when opened in XPS Viewer and zoomed 100%.
Are there any settings that I am missing or why is it so?
Thanks,
Jakub.
A sample xps output can be found here: http://www.jakubmaly.cz/files/a.xps.
This is the code that does the XPS export:
if (!boundingRectangle.HasValue)
{
boundingRectangle = new Rect(0, 0, frameworkElement.ActualWidth, frameworkElement.ActualHeight);
}
// Save current canvas transorm
Transform transform = frameworkElement.LayoutTransform;
// Temporarily reset the layout transform before saving
frameworkElement.LayoutTransform = null;
// Get the size of the canvas
Size size = new Size(boundingRectangle.Value.Width, boundingRectangle.Value.Height);
// Measure and arrange elements
frameworkElement.Measure(size);
frameworkElement.Arrange(new Rect(size));
// Open new package
System.IO.Packaging.Package package = System.IO.Packaging.Package.Open(filename, FileMode.Create);
// Create new xps document based on the package opened
XpsDocument doc = new XpsDocument(package);
// Create an instance of XpsDocumentWriter for the document
XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(doc);
// Write the canvas (as Visual) to the document
writer.Write(frameworkElement);
// Close document
doc.Close();
// Close package
package.Close();
// Restore previously saved layout
frameworkElement.LayoutTransform = transform;

Interesting (and annoying) issue - you may want to check out the lengthy answer from Jo0815 to Printing XpsDocument causes resampled images (96dpi?) - FixedDocument prints sharp, quoting a Microsoft support response - a couple of excerpts:
Some vector features from WPF cannot be emulated in our GDI code and
we resort to converting subsets of the scene to GDI bitmaps. These
bitmaps are the cause of the blurred zooming.
[...]
These bitmaps are the cause of the blurred zooming. The problem is
that the WPF is being rasterised to a bitmap at the -wrong resolution.
The print path is designed to rasterise unsupported features into a
bitmap, but it is supposed to do it at device resolution. Instead the
rasterisation is always being done at 96dpi. That's fine for a screen
but produces blurred output for a 600dpi printer. [emphasis mine]
Please note that the latter will apply for nowadays higher DPI screens as well of course, I've encountered blurring like this various times already - do you by chance use a high DPI monitor?
Now, apparently Microsoft is not entirely in control of the apparatus regarding this:
Additionally the problem only occurs when printing XPS and isn't a
problem when printing XAML directly. I'm pretty sure there is
documentation somewhere that says XPS will print at device resolution.
[...] It is something we
plan to improve in the next version of the product but not for Win 7.
The problem is that when printing XAML it will correctly render the
image at 600dpi, but when printing XPS it will still render the image
at 96dpi. Since XAML is converted to XPS before printing it seems
highly odd that one method of printing XPS produces different results
to another method of printing XPS. [emphasis mine]
[...]
There is no UI to configure the XPS Document Writer DPI. If you later
print a generated XPS document at a different DPI from the writers
internal default you may get poor results for bitmap content. With GDI
printers you can control the final DPI and your final desitination is
usally paper - no chance to reprint the document.
Conclusion
In conclusion, I'd still try to adjust PrintTicket.PageResolution Property within Néstor Sánchez' approach (+1), if your use case does allow this (though I remotely recall reading somewhere, that this doesn't have any effect as well); section Bitmap Resolution and Pixel Format in Using the XPS Rasterization Service confirms the issue he encountered with FixedDocument:
XPS rasterizer object for a fixed page must know the resolution at
which the page will be rendered. The XPSDrv filter specifies this
resolution, in dots per inch (DPI), as an input parameter [...] For example, if a display device has a resolution
of 600 DPI, and a fixed page describes a standard letter-size page, a
bitmap image of the entire page has the following dimensions [...]
Workaround
As a potential workaround you might want to explore alexandrud's solution for the related question How to convert a XPS file to an image in high quality (rather than blurry low resolution)?, which recommends using xps2img, a XPS (XML Paper Specification) document to set of images conversion utility. In particular it Allows to specify images size or DPI, which might help depending on the print path solution applied in turn.
Good luck!

I've had a similar problem. My image was very blurry when passed to XPS intermediated thru a FixedDocument.
The solution was to write the image directly to the XPS...
/// <summary>
/// Saves the supplied visual Source, within the specified Bounds, as XPS in the specified File-Name.
/// Returns error message or null when succeeded.
/// </summary>
public static string SaveVisualAsXPS(Visual Source, Size Bounds, string FileName)
{
string ErrorMessage = null;
try
{
using (var Container = Package.Open(FileName, FileMode.Create))
{
using (var TargetDocument = new XpsDocument(Container, CompressionOption.Maximum))
{
var Writer = XpsDocument.CreateXpsDocumentWriter(TargetDocument);
var Ticket = GetPrintTicketFromPrinter();
if (Ticket == null)
return "No printer is defined.";
Ticket.PageMediaSize = new PageMediaSize(Bounds.Width, Bounds.Height);
var SourceVisual = Source;
Writer.Write(SourceVisual, Ticket);
}
}
}
catch (Exception Problem)
{
ErrorMessage = "Cannot export document to XPS.\nProblem: " + Problem.Message;
}
return ErrorMessage;
}
Giving a print-ticket with the exact width and height avoids scaling (that was I wanted in my case).
Get the function from the example in:
http://msdn.microsoft.com/en-us/library/system.printing.printticket.aspx

Related

How to convert a byte[] to a BufferedImage in Java?

I'm posting this thread because I have some difficulties to deal with pictures in Java. I would like to be able to convert a picture into a byte[] array, and then to be able to do the reverse operation, so I can change the RGB of each pixel, then make a new picture. I want to use this solution because setRGB() and getRGB() of BufferedImage may be too slow for huge pictures (correct me if I'm wrong).
I read some posts here to obtain a byte[] array (such as here) so that each pixel is represented by 3 or 4 cells of the array containing the red, the green and the blue values (with the additional alpha value, when there are 4 cells), which is quite useful and easy to use for me. Here's the code I use to obtain this array (stored in a PixelArray class I've created) :
public PixelArray(BufferedImage image)
{
width = image.getWidth();
height = image.getHeight();
DataBuffer toArray = image.getRaster().getDataBuffer();
array = ((DataBufferByte) toArray).getData();
hasAlphaChannel = image.getAlphaRaster() != null;
}
My big trouble is that I haven't found any efficient method to convert this byte[] array to a new image, if I wanted to transform the picture (for example, remove the blue/green values and only keeping the red one). I tried those solutions :
1) Making a DataBuffer object, then make a SampleModel, to finally create a WritableRaster and then BufferedImage (with additional ColorModel and Hashtable objects). It didn't work because I apparently don't have all the information I need (I have no idea what's the Hashtable for BufferedImage() constructor).
2) Using a ByteArrayInputStream. This didn't work because the byte[] array expected with ByteArrayInputStream has nothing to do with mine : it represents each byte of the file, and not each component of each pixel (with 3-4 bytes for each pixel)...
Could someone help me?
Try this:
private BufferedImage createImageFromBytes(byte[] imageData) {
ByteArrayInputStream bais = new ByteArrayInputStream(imageData);
try {
return ImageIO.read(bais);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
I have tried the approaches mentioned here but for some reason neither of them worked. Using ByteArrayInputStream and ImageIO.read(...) returns null, whereas byte[] array = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); returns a copy of the image data, not a direct reference to them (see also here).
However, the following worked for me. Let's suppose that the dimensions and the type of the image data are known. Let also byte[] srcbuf be the buffer of the data to be converted into BufferedImage. Then,
Create a blank image, for example
img=new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
Convert the data array to Raster and use setData to fill the image, i.e.
img.setData(Raster.createRaster(img.getSampleModel(), new DataBufferByte(srcbuf, srcbuf.length), new Point() ) );
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
byte[] array = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
System.arraycopy(pixelArray, 0, array, 0, array.length);
This method does tend to get out of sync when you try to use the Graphics object of the resulting image. If you need to draw on top of your image, construct a second image (which can be persistant, i.e. not constructed every time but re-used) and drawImage the first one onto it.
Several people upvoted the comment that the accepted answer is wrong.
If the accepted answer isn't working, it may be because Image.IO doesn't have support for the type of image you're trying, for example tiff images.
To make it work, you need to add an extra jar to handle the image type.
You can add jai-imageio-core-1.3.1.jar to your classpath with:
<!-- https://mvnrepository.com/artifact/com.github.jai-imageio/jai-imageio-core -->
<dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-core</artifactId>
<version>1.3.1</version>
</dependency>
To add support for:
wbmp
bmp
pcx
pnm
raw
tiff
gif (write)
You can check the list of supported formats with:
for(String format : ImageIO.getReaderFormatNames())
System.out.println(format);
Note that you only have to drop the jar (jai-imageio-core-1.3.1.jar for example) into your classpath to make it work.
Other projects that add additional support for image types include:
https://github.com/haraldk/TwelveMonkeys
https://github.com/geosolutions-it/imageio-ext
The approach by using ImageIO.read directly is not right in some cases. In my case, the raw byte[] doesn't contain any information about the width and height and format of the image. By only using ImageIO.read, It is impossible for the program to construct a valid image.
It is necessary to pass the basic information of the image to BufferedImage object:
BufferedImage outBufImg = new BufferedImage(width, height, bufferedImage.TYPE_3BYTE_BGR);
Then set the data for the BufferedImage object by using setRGB or setData. (When using setRGB, it seems we must convert byte[] to int[] first. As a result, it may cause performance issues if the source image data is big. Maybe setData is a better idea for big byte[] typed source data.)

Cloning a PathGeometry in Silverlight / WPF

I have a simple handler that adds an ellipse to an empty Silverlight canvas
private void UCLoaded(object sender, RoutedEventArgs e)
{
var geometry = MakeElipse(20, 15, new Point(100, 100));
var ellipsePath = new Path
{
Data = geometry,
Fill = new SolidColorBrush(Colors.DarkGray),
StrokeThickness = 4,
Stroke = new SolidColorBrush(Colors.Gray)
};
LayoutRoot.Children.Add(ellipsePath);
//
var duplicateEllipsePath = new Path();
//duplicateEllipsePath.Data = ellipsePath.Data;
duplicateEllipsePath.Data = geometry;
duplicateEllipsePath.Fill = ellipsePath.Fill;
duplicateEllipsePath.StrokeThickness = ellipsePath.StrokeThickness;
duplicateEllipsePath.Stroke = ellipsePath.Stroke;
LayoutRoot.Children.Add(duplicateEllipsePath);
}
The first ellipse, ellipsePath, is fine and renders as expected. But the line duplicateEllipsePath.Data = ellipsePath.Data or the alternative duplicateEllipsePath.Data = geometry each throw the System.ArgumentException "Value does not fall within the expected range". How can it be in range once, and out-of-range immediately afterwards? What is the correct way of duplicating a path in code like this?
It looks like the only way to clone a path is to do so manually. To quote this answer from Yi-Lun Luo:
The Data property is actually a Geometry. While not noticeable in Silverlight, A Geometry actually relies on an underlying system resource (because it needs to draw something). If you need to draw another Geometry, you'll need another system resource. So you must clone it before you assign it to a new Path. In WPF, we do have a Clone method on Geometry, unfortunately this is not supported in Silverlight. So you have to manually do the clone.
Another post above Yi-Lun's claims to contain reflective code to clone a geometry, and the same code seems to appear here, although the latter is more clearly formatted. However, in your case, it seems overkill to use a method such as this. The geometry you use is created by your MakeElipse [sic] method. Extracting the common code to generate the geometries into a method seems about the best way to proceed here.
The error message 'Value does not fall within the expected range' is a bit misleading. I don't see anything 'out of range', given that the exact same object was supposedly in range for your first ellipse. I can't say exactly why this error message is reported, but I can speculate. Silverlight is implemented in native code, and I believe that because the native code can't throw exceptions it instead returns numeric error codes. Perhaps there's a limited number of error codes and the one for 'Value does not fall within the expected range' was the one chosen for this error?

WPF BitmapImage and TIFF with CMYK + Alpha

I am using this code snippet to load various image files:
BitmapImage bitmap = new BitmapImage ();
bitmap.BeginInit ();
bitmap.UriSource = new System.Uri (path);
bitmap.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
bitmap.EndInit ();
This works fine for TIFF files stored as RGB, RGB+Alpha and CMYK. However, if I try to load a TIFF file using CMYK colors and an alpha channel, I get an exception (the file format is not recognized as being valid by the decoder).
I was previously using the FreeImage library and a thin C# wrapper on top of it. FreeImage 3.x has partial support for this kind of image format, i.e. I had to load the TIFF twice, once as CMYK without transparency and once as RGB+Alpha; this trick is needed since FreeImage only gives access to at most 4 simultaneous color channels.
I'd like to know if there is a supported way to load CMYK+Alpha bitmaps? Either directly in C# or by going through some interop code, but preferably without having to use a third-party DLL (other than the .NET 4 framework libraries).
An example of such a TIFF file can be found here.
EDIT : I can no longer reproduce the problem, the following code works just fine:
BitmapImage bitmap = new BitmapImage ();
bitmap.BeginInit ();
bitmap.UriSource = new System.Uri (path);
bitmap.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
bitmap.EndInit ();
byte[] pixels = new byte[bitmap.PixelHeight*bitmap.PixelWidth*5];
bitmap.CopyPixels (pixels, bitmap.PixelWidth * 5, 0);
But I am still stuck: how can I find out that the source image was encoded as CMYK plus Alpha channel? When looking at the Format property, I get only the information that the image has 40 bits per pixel. All the interesting stuff is stored in the following non-public properties:
bitmap.Format.FormatFlags == IsCMYK | NChannelAlpha;
bitmap.Format.HasAlpha == true;
Is there any official way of getting to them, without resorting to reflection?
I can only say this because I've had the issues with some files: it might be a better way to convert the tiff to png24 first and then load it up.
Even Photoshop puts up a warning if a user tries to save a CMYK Tiff file and ticks 'Transparency': "Many programs do not support transparency in TIFF. Save transparency information?"
So converting prior to opening might be the safe way to go.
Maybe http://msdn.microsoft.com/en-us/library/system.drawing.imageconverter.aspx would do it but I doubt it, you probably need some extra piping.
HTH.
It's only a guess, but GDI+ might be able to load such files.
System.Drawing.Image etc.
There is an interop class which can render GDI+ images in WPF.
I found another question on SO which linked to this library:
http://freeimage.sourceforge.net/
Good Tiff library for .NET
I hope this might help.

How do I convert a WPF size to physical pixels?

What's the best way to convert a WPF (resolution-independent) width and height to physical screen pixels?
I'm showing WPF content in a WinForms Form (via ElementHost) and trying to work out some sizing logic. I've got it working fine when the OS is running at the default 96 dpi. But it won't work when the OS is set to 120 dpi or some other resolution, because then a WPF element that reports its Width as 96 will actually be 120 pixels wide as far as WinForms is concerned.
I couldn't find any "pixels per inch" settings on System.Windows.SystemParameters. I'm sure I could use the WinForms equivalent (System.Windows.Forms.SystemInformation), but is there a better way to do this (read: a way using WPF APIs, rather than using WinForms APIs and manually doing the math)? What's the "best way" to convert WPF "pixels" to real screen pixels?
EDIT: I'm also looking to do this before the WPF control is shown on the screen. It looks like Visual.PointToScreen could be made to give me the right answer, but I can't use it, because the control isn't parented yet and I get InvalidOperationException "This Visual is not connected to a PresentationSource".
Transforming a known size to device pixels
If your visual element is already attached to a PresentationSource (for example, it is part of a window that is visible on screen), the transform is found this way:
var source = PresentationSource.FromVisual(element);
Matrix transformToDevice = source.CompositionTarget.TransformToDevice;
If not, use HwndSource to create a temporary hWnd:
Matrix transformToDevice;
using(var source = new HwndSource(new HwndSourceParameters()))
transformToDevice = source.CompositionTarget.TransformToDevice;
Note that this is less efficient than constructing using a hWnd of IntPtr.Zero but I consider it more reliable because the hWnd created by HwndSource will be attached to the same display device as an actual newly-created Window would. That way, if different display devices have different DPIs you are sure to get the right DPI value.
Once you have the transform, you can convert any size from a WPF size to a pixel size:
var pixelSize = (Size)transformToDevice.Transform((Vector)wpfSize);
Converting the pixel size to integers
If you want to convert the pixel size to integers, you can simply do:
int pixelWidth = (int)pixelSize.Width;
int pixelHeight = (int)pixelSize.Height;
but a more robust solution would be the one used by ElementHost:
int pixelWidth = (int)Math.Max(int.MinValue, Math.Min(int.MaxValue, pixelSize.Width));
int pixelHeight = (int)Math.Max(int.MinValue, Math.Min(int.MaxValue, pixelSize.Height));
Getting the desired size of a UIElement
To get the desired size of a UIElement you need to make sure it is measured. In some circumstances it will already be measured, either because:
You measured it already
You measured one of its ancestors, or
It is part of a PresentationSource (eg it is in a visible Window) and you are executing below DispatcherPriority.Render so you know measurement has already happened automatically.
If your visual element has not been measured yet, you should call Measure on the control or one of its ancestors as appropriate, passing in the available size (or new Size(double.PositivieInfinity, double.PositiveInfinity) if you want to size to content:
element.Measure(availableSize);
Once the measuring is done, all that is necessary is to use the matrix to transform the DesiredSize:
var pixelSize = (Size)transformToDevice.Transform((Vector)element.DesiredSize);
Putting it all together
Here is a simple method that shows how to get the pixel size of an element:
public Size GetElementPixelSize(UIElement element)
{
Matrix transformToDevice;
var source = PresentationSource.FromVisual(element);
if(source!=null)
transformToDevice = source.CompositionTarget.TransformToDevice;
else
using(var source = new HwndSource(new HwndSourceParameters()))
transformToDevice = source.CompositionTarget.TransformToDevice;
if(element.DesiredSize == new Size())
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
return (Size)transformToDevice.Transform((Vector)element.DesiredSize);
}
Note that in this code I call Measure only if no DesiredSize is present. This provides a convenient method to do everything but has several deficiencies:
It may be that the element's parent would have passed in a smaller availableSize
It is inefficient if the actual DesiredSize is zero (it is remeasured repeatedly)
It may mask bugs in a way that causes the application to fail due to unexpected timing (eg. the code being called at or above DispatchPriority.Render)
Because of these reasons, I would be inclined to omit the Measure call in GetElementPixelSize and just let the client do it.
Simple proportion between Screen.WorkingArea and SystemParameters.WorkArea:
private double PointsToPixels (double wpfPoints, LengthDirection direction)
{
if (direction == LengthDirection.Horizontal)
{
return wpfPoints * Screen.PrimaryScreen.WorkingArea.Width / SystemParameters.WorkArea.Width;
}
else
{
return wpfPoints * Screen.PrimaryScreen.WorkingArea.Height / SystemParameters.WorkArea.Height;
}
}
private double PixelsToPoints(int pixels, LengthDirection direction)
{
if (direction == LengthDirection.Horizontal)
{
return pixels * SystemParameters.WorkArea.Width / Screen.PrimaryScreen.WorkingArea.Width;
}
else
{
return pixels * SystemParameters.WorkArea.Height / Screen.PrimaryScreen.WorkingArea.Height;
}
}
public enum LengthDirection
{
Vertical, // |
Horizontal // ——
}
This works fine with multiple monitors as well.
I found a way to do it, but I don't like it much:
using (var graphics = Graphics.FromHwnd(IntPtr.Zero))
{
var pixelWidth = (int) (element.DesiredSize.Width * graphics.DpiX / 96.0);
var pixelHeight = (int) (element.DesiredSize.Height * graphics.DpiY / 96.0);
// ...
}
I don't like it because (a) it requires a reference to System.Drawing, rather than using WPF APIs; and (b) I have to do the math myself, which means I'm duplicating WPF's implementation details. In .NET 3.5, I have to truncate the result of the calculation to match what ElementHost does with AutoSize=true, but I don't know whether this will still be accurate in future versions of .NET.
This does seem to work, so I'm posting it in case it helps others. But if anyone has a better answer, please, post away.
Just did a quick lookup in the ObjectBrowser and found something quite interesting, you might want to check it out.
System.Windows.Form.AutoScaleMode, it has a property called DPI. Here's the docs, it might be what you are looking for :
public const
System.Windows.Forms.AutoScaleMode Dpi
= 2
Member of System.Windows.Forms.AutoScaleMode
Summary: Controls scale relative to
the display resolution. Common
resolutions are 96 and 120 DPI.
Apply that to your form, it should do the trick.
{enjoy}

WPF Printing Flow Document

Greetings,
I have a problem with printing in WPF.
I am creating a flow document and add some controls to that flow document.
Print Preview works ok and i have no problem with printing from a print preview window.
The problem exists when I print directly to the printer without a print preview. But what is more surprisingly - when I use XPS Document Writer as a printer
everyting is ok, when i use some physical printer, some controls on my flow document are not displayed.
Thanks in advance
Important thing to note : You can use XpsDocumentWriter even when printing directly to a physical printer. Don't make the mistake I did of avoiding it just because you're not creating an .xps file!
Anyway - I had this same problem, and none of the DoEvents() hacks seemed to work. I also wasn't particularly happy about having to use them in the first place. In my situation some of the databound controls printed fine, but some others (nested UserControls) didnt. It was as if only one 'level' was being databound and the rest wouldn't bind even with a 'DoEvents()' hack.
The solution was simple though. Use XpsDocumentWriter like this. it will open a dialog where you can choose whichever installed physical printer you want.
// 8.5 x 11 paper
Size sz = new Size(96 * 8.5, 96 * 11);
// create your visual (this is a WPF UserControl)
var template = new PackingSlipTemplate()
{
DataContext = new PackingSlipViewModel(order)
};
// arrange
template.Measure(sz);
template.Arrange(new Rect(sz));
template.UpdateLayout();
// print to XpsDocumentWriter
// this will open a dialog and you can print to any installed printer
// not just a 'virtual' .xps file
PrintDocumentImageableArea area = null;
XpsDocumentWriter xps = PrintQueue.CreateXpsDocumentWriter(ref area,);
xps.Write(template);
I found the OReilly book on 'Programming WPF' quite useful with its chapter on Printing - found through Google Books.
If you don't want a print dialog to appear, but want to print directly to the default printer you can do the following. (For me the application is to print packing slips in a warehouse environment - and I don't want a dialog popping up every time).
var template = new PackingSlipTemplate()
{
DataContext = new PackingSlipViewModel(orders.Single())
};
// arrange
template.Measure(sz);
template.Arrange(new Rect(sz));
template.UpdateLayout();
LocalPrintServer localPrintServer = new LocalPrintServer();
var defaultPrintQueue = localPrintServer.DefaultPrintQueue;
XpsDocumentWriter xps = PrintQueue.CreateXpsDocumentWriter(defaultPrintQueue);
xps.Write(template, defaultPrinter.DefaultPrintTicket);
XPS Document can be printed without a problem
i have noticed one thing:
tip: the controls that are not displayed are the controls I am binding some data, so the conclusion is that the binding doesn't work. Can it be the case that binding is not executing before sending the document to the printer?

Resources