XPS from FlowDocument Rendering bug in Images - wpf

This will be little longer post
Goal
Render XPS document from FlowDocument while maintaining original Images (no transformation)
Existing Scenarios
Image is added correctly (keeps format), but only first one. Cache is then broken, only 1 image is embedded in XPS and used for all images
All images are added and correct, but converted to PNG.
Difference occurs based on commenting / uncommenting of single line in method GetImage (see comments)
Minimal showcase / problem recreation code
Imports System.Windows.Documents
Imports System.Windows.Documents.Serialization
Imports System.Windows.Xps.Packaging
Imports System.Windows.Xps
Imports System.IO
Imports System.IO.Packaging
Imports System.Windows.Markup
Imports System.Windows.Media.Imaging
Imports System.Windows.Media
Imports System.Windows.Controls
Module Module1
Sub Main()
Render()
End Sub
Sub Render()
Using image1 = IO.File.OpenRead("image1.jpg"),
image2 = IO.File.OpenRead("image2.png"),
file = IO.File.Create("asdf.xps"),
pack = Package.Open(file, FileMode.Create),
d As New XpsDocument(pack)
Dim writer As XpsDocumentWriter = XpsDocument.CreateXpsDocumentWriter(d)
Dim xpsVisWriter As SerializerWriterCollator = writer.CreateVisualsCollator()
Dim x As New FlowDocument
x.PageWidth = 100
x.ColumnWidth = x.PageWidth
Dim s As New Section
DirectCast(x, IAddChild).AddChild(s)
s.BreakPageBefore = True
Dim p As New Paragraph()
DirectCast(s, IAddChild).AddChild(p)
Dim i As New Image
DirectCast(p, IAddChild).AddChild(i)
i.Source = GetImage(image1)
Dim i2 As New Image
DirectCast(p, IAddChild).AddChild(i2)
i2.Source = GetImage(image2)
Dim paginator = DirectCast(x, IDocumentPaginatorSource).DocumentPaginator
Dim pageIndex As Integer = 0
While Not paginator.IsPageCountValid OrElse paginator.PageCount > pageIndex
Dim page As DocumentPage = paginator.GetPage(pageIndex)
xpsVisWriter.Write(page.Visual)
pageIndex += 1
End While
xpsVisWriter.EndBatchWrite()
End Using
End Sub
Function GetImage(stream As Stream) As ImageSource
Dim result As ImageSource = BitmapFrame.Create(
stream,
BitmapCreateOptions.PreservePixelFormat Or BitmapCreateOptions.IgnoreImageCache,
BitmapCacheOption.None)
'If this Line Runs, scenario 2 occures, otherwise scenario 1
'result = BitmapFrame.Create(result)
Return result
End Function
End Module
I no longer have any idea what to do with this. All possible cache enabling / disabling was tried.

I have not done any visual basic, but I ran into similar problem with c#. Try returning a BitmapImage instead of ImageSource in your GetImage method. Using PngBitmapDecoder, MemoryStream, FlowDocument, XPSDocument to Preview Images

Related

Getting MetaData from an image file

We have images stored on a DB and they are being used to replace an image within a Word document - that bit works perfectly, except where the replacement image is portrait and it's replacing a landscape one, so I'm trying to get the metadata to determine how the image is orientated using this function
Public Function GetImageTags(ImageFile() As Byte) As String()
Try
Dim vReturnArray() As String = Nothing
Using MS As New System.IO.MemoryStream(ImageFile)
Dim vDecoder As BitmapDecoder = BitmapDecoder.Create(MS, BitmapCreateOptions.None, BitmapCacheOption.Default)
Dim vFrame As BitmapFrame = vDecoder.Frames(0)
Dim vMetadata As BitmapMetadata = TryCast(vFrame.Metadata, BitmapMetadata)
If vMetadata IsNot Nothing And vMetadata.Keywords IsNot Nothing Then
vReturnArray = vMetadata.Keywords.ToArray()
End If
End Using
Return vReturnArray
Catch ex As Exception
EmailError(ex)
Return Nothing
End Try
End Function
...but it throws the toys out with...
This codec does not support the specified property.
at System.Windows.Media.Imaging.BitmapMetadata.GetQuery(String query)
at System.Windows.Media.Imaging.BitmapMetadata.get_Keywords()
...at BitMapMetadata.Keywords. Any idea how I can overcome this and get the keywords?
Thank you
================ UPDATE ================
It appears that the error, and I also tried...
vReturnArray = TryCast(vMetadata.GetQuery("System.Keywords"), String())
... is only returned for some images, but all that I tried returned Nothing for the String()
There is a really good EXIF class on Code Project that is easy to implement, either with a string link to the file
Dim vEXIF As New ImageEXIF(ImagePath)
Dim vOrientation As Integer = vEXIF.Orientation
or as BitMap
Dim vOrientation As Integer = 0
Using vBitmap As System.Drawing.Image = System.Drawing.Image.FromStream(New IO.MemoryStream(ImageFile))
Dim vEXIF As New ImageEXIF(vBitmap)
vOrientation = vEXIF.Orientation
End Using
It would not be difficult to add another Sub to the class for Byte(), but the above conversion is quite straightforward and the class should work with all image types.
You could use MetadataExtractor to access the image metadata.
Check for the presence of ExifDirectoryBase.TagOrientation on any of the contained Exif directories.
Something like this (sorry it's C# as I don't know VB.NET):
var orientation = ImageMetadataReader.ReadMetadata(imagePath)
.OfType<ExifSubIfdDirectory>()
.Select(d => d.GetObject(ExifDirectoryBase.TagOrientation))
.First(o => o != null);

Create Ximage from (wpf) visual in memory

I'm trying to convert a visual to a Ximage(to print on pdf).
Currently I'm using this:
Dim img As XImage = XImage.FromFile(path.LocalPath)
But it requires me to save it on the harddrive
I would like to use this but I don't know how to create a image from a visual(in memory) and load it in.
Dim img As XImage = XImage.FromGdiPlusImage(myVisualAsImage)
If you use the WPF build of PDFsharp, you can also use:
public static XImage FromBitmapSource(BitmapSource image)
Several classes (including BitmapImage and WriteableBitmap) are derived from BitmapSource and can also be passed here.
See also:
http://ryancdavidson.com/blog/2009/09/getting-and-using-the-pixels-of-your-visual-in-wpf/
vb.net - overcome missing method FromGdiPlusImage in PDFSharp 1.5
Function getTiffImage(sourceImage As Image, pageNumber As Integer) As XImage
Dim ms As MemoryStream
Dim returnImage As XImage
Try
ms = New MemoryStream()
Dim objGuid As Guid = sourceImage.FrameDimensionsList(0)
Dim objDimension As Imaging.FrameDimension = New Imaging.FrameDimension(objGuid)
sourceImage.SelectActiveFrame(objDimension, pageNumber)
sourceImage.Save(ms, Imaging.ImageFormat.Tiff)
returnImage = XImage.FromStream(ms)
Catch ex As Exception
returnImage = Nothing
End Try
Return returnImage

Get image from resource dll as MemoryStream

I use WPF and my program has images in a DLL resource file. I have this well working way to read in images from disk:
Private Function GetImageFromFile(ByVal fileName As String) As BitmapImage
Dim buffer As Byte() = IO.File.ReadAllBytes(fileName)
Dim memoryStream As New IO.MemoryStream(buffer)
Dim bitmap As New BitmapImage()
bitmap.BeginInit()
bitmap.StreamSource = memoryStream
bitmap.EndInit()
bitmap.Freeze()
Return bitmap
End Function
Now, how can I get images in this MemoryStream-way from a DLL resource?
The basic problem: If I use simply the "bitmap.UriSource = whatever uri" way and load many images in sequence like an animation it builds up the memory. I tried with the above memorystream way and it worked perfectly fine, but then I store my images in a dll and I don't know how to do this trick. If anybody knows how to read many images from a managed dll without building up the memory pls, let me know.
I found the answer to my question. I put it here for others who may need it. There are more ways to load images from a resource dll file. The easiest way to initialize the BitmapImage and set bi.UriSource=uriPath (where the path looks like I show below) but when you load images in a sequence, as an animation for example, it seems to take a lot of memory. Then you can use a StreamResourceInfo as shown below and just put like bi.StreamSource = sri.Stream. That works, too, but memorywise it has same results. So in practice I found the following way the fastest and most memory efficient way to load hundreds of images in a sequence:
Public Function GetImageFromDLL(ByVal uriPath As String) As BitmapImage
Dim sri As Windows.Resources.StreamResourceInfo = Application.GetResourceStream(New Uri(uriPath, UriKind.Absolute))
Dim binReader As New System.IO.BinaryReader(sri.Stream)
Dim buffer As Byte() = binReader.ReadBytes(sri.Stream.Length)
Dim memoryStream As New IO.MemoryStream(buffer)
Dim bi As New BitmapImage()
bi.BeginInit()
bi.CacheOption = BitmapCacheOption.Default
bi.CreateOptions = BitmapCreateOptions.None
bi.StreamSource = memoryStream
bi.EndInit()
bi.Freeze()
Return bi
End Function
Where the uriPath is something like: "pack://application:,,,/YourDLL;Component/YourImageFile.jpg"

WPF printing of multiple pages with preview

The more I read about the subject the less I understand so apologies in advance if the below seems completely off the wall.
I have a usercontrol that contains a flowdocument - a view with a corresponding viewmodel. The aim is to send this to a preview window where the user can view the document and also print it.
I lifted some code from an example at http://www.eggheadcafe.com/tutorials/aspnet/9cbb4841-8677-49e9-a3a8-46031e699b2e/wpf-printing-and-print-pr.aspx
When the below is called
Public Shared Sub PrintPreview(owner As Window, data As FormData)
Using xpsStream As New MemoryStream()
Using package__1 As Package = Package.Open(xpsStream, FileMode.Create, FileAccess.ReadWrite)
Dim packageUriString As String = "memorystream://data.xps"
Dim packageUri As New Uri(packageUriString)
PackageStore.AddPackage(packageUri, package__1)
Dim xpsDocument__2 As New XpsDocument(package__1, CompressionOption.Maximum, packageUriString)
Dim writer As XpsDocumentWriter = XpsDocument.CreateXpsDocumentWriter(xpsDocument__2)
Dim visual As New Form(data)
Dim printTicket As New PrintTicket()
printTicket.PageMediaSize = A4PaperSize
writer.Write(visual, printTicket)
Dim document As FixedDocumentSequence = xpsDocument__2.GetFixedDocumentSequence()
xpsDocument__2.Close()
Dim printPreviewWnd As New PrintPreviewWindow(document)
printPreviewWnd.Owner = owner
printPreviewWnd.ShowDialog()
PackageStore.RemovePackage(packageUri)
End Using
End Using
This brings up the print preview window and shows the user control that holds the flowdocument. However, it only shows the first of what should be multiple pages. I was under the assumption the whole point of writing the xps and then reading it back again in this window was to work around the problem of printing a visual but I'm obviously misunderstanding the whole process. Any help in getting through my thick head what I need to do to be able to view all of the pages in the document would be much appreciated.
Cheers
I thought the above with using xpsdocument and getfixeddocumentsequence would convert the flowdocument to a fixeddocument but seeing it doesn't, am I perhaps writing it wrongly??
You can print a visual to an XPS. However, as I understand it, it is your job to manage pages. If your visual is too big to fit on a page, you need to find a way to split it onto multiple pages.
The source code I posted here prints a list of items over many pages:
https://bitbucket.org/paulstovell/samples/src/a323f0c65ea4/XPS%20Report%20Generator/
If you can find a way to split your visuals (perhaps create 3 forms, with 15 items per form) into pages, you can then use this:
using (var doc = new XpsDocument("P:\\Test2.xps", FileAccess.Write))
{
var writer = XpsDocument.CreateXpsDocumentWriter(doc);
var collator = writer.CreateVisualsCollator();
collator.BeginBatchWrite();
collator.Write(form1);
collator.Write(form2);
collator.Write(form3);
collator.EndBatchWrite();
}
var doc2 = new XpsDocument("P:\\Test2.xps", FileAccess.Read);
var seq = doc2.GetFixedDocumentSequence();
var window = new Window();
window.Content = new DocumentViewer {Document = seq};
window.ShowDialog();
Also, note that if you're newing up a visual and printing it, you'll need to size it first, otherwise you may get an empty screen. Here's an example of generating a page of data (of course you'd change the sizes to fit an A4 sheet).
private StackPanel CreatePage()
{
var panel = new StackPanel();
panel.Width = 1000;
panel.Height = 1000;
for (var i = 0; i < 10; i++)
{
panel.Children.Add(new TextBlock() {Text = "Item " + i});
}
panel.Measure(new Size(1000, 1000));
panel.Arrange(new Rect(0, 0, 1000, 1000));
return panel;
}

XAML Parser cannot find resource within dynamically loaded XAP when creating form instance

I've followed Tim Heuer's video for dynamically loading other XAP's (into a 'master' Silverlight application), as well as some other links to tweak the loading of resources and am stuck on the particular issue of loading style resources from within the dynamically loaded XAP (i.e. the contents of Assets\Styles.xaml). When I run the master/hosting applcation, it successfully streams the dynamic XAP and I can read the deployment info etc. and load the assembly parts. However, when I actuall try to create an instance of a form from the Dynamic XAP, it fails with
Cannot find a Resource with the Name/Key LayoutRootGridStyle
which is in it's Assets\Styles.xaml file (it works if I run it directly so I know it's OK). For some reason these don't show up as application resources - not sure if I've totally got the wrong end of the stick, or am just missing something? Code snippet below (apologies it's a bit messy - just trying to get it working first) ...
'' # Here's the code that reads the dynamic XAP from the web server ...
'' #...
wCli = New WebClient
AddHandler wCli.OpenReadCompleted, AddressOf OpenXAPCompleted
wCli.OpenReadAsync(New Uri("MyTest.xap", UriKind.Relative))
'' #...
'' #Here's the sub that's called when openread is completed
'' #...
Private Sub OpenXAPCompleted(ByVal sender As Object, ByVal e As System.Net.OpenReadCompletedEventArgs)
Dim sManifest As String = New StreamReader(Application.GetResourceStream(New StreamResourceInfo(e.Result, Nothing), New Uri("AppManifest.xaml", UriKind.Relative)).Stream).ReadToEnd
Dim deploymentRoot As XElement = XDocument.Parse(sManifest).Root
Dim deploymentParts As List(Of XElement) = _
(From assemblyParts In deploymentRoot.Elements().Elements() Select assemblyParts).ToList()
Dim oAssembly As Assembly = Nothing
For Each xElement As XElement In deploymentParts
Dim asmPart As AssemblyPart = New AssemblyPart()
Dim source As String = xElement.Attribute("Source").Value
Dim sInfo As StreamResourceInfo = Application.GetResourceStream(New StreamResourceInfo(e.Result, "application/binary"), New Uri(source, UriKind.Relative))
If source = "MyTest.dll" Then
oAssembly = asmPart.Load(sInfo.Stream)
Else
asmPart.Load(sInfo.Stream)
End If
Next
Dim t As Type() = oAssembly.GetTypes()
Dim AppClass = (From parts In t Where parts.FullName.EndsWith(".App") Select parts).SingleOrDefault()
Dim mykeys As Array
If Not AppClass Is Nothing Then
Dim a As Application = DirectCast(oAssembly.CreateInstance(AppClass.FullName), Application)
For Each strKey As String In a.Resources.Keys
If Not Application.Current.Resources.Contains(strKey) Then
Application.Current.Resources.Add(strKey, a.Resources(strKey))
End If
Next
End If
Dim objectType As Type = oAssembly.GetType("MyTest.MainPage")
Dim ouiel = Activator.CreateInstance(objectType)
Dim myData As UIElement = DirectCast(ouiel, UIElement)
Me.splMain.Children.Add(myData)
Me.splMain.UpdateLayout()
End Sub
'' #...
'' # And here's the line that fails with "Cannot find a Resource with the Name/Key LayoutRootGridStyle"
'' # ...
System.Windows.Application.LoadComponent(Me, New System.Uri("/MyTest;component/MainPage.xaml", System.UriKind.Relative))
'' #...
Just to re-cap, there are 3 scenarios to consider ...
1) The dynamically loaded XAP's style resources are left in a merged resource dictionary (in a seperate xaml file), referenced from the app.xaml of the dynamically loaded silverlight app (XAP) - When running the master application the resources from the dynamic XAP do not appear to be present under the current application (after loading the XAP assembly parts). Error occurs.
2) The dynamically loaded XAP's style resources are moved from the merged resource dictionary (from a seperate xaml file), into the app.xaml of the dynamic application, in place of the merged resource dictionary reference. - When running the master application the resources from the dynamic XAP DO appear to be present under the current application (after loading the XAP assembly parts). However, the error still occurs.
3) The dynamically loaded XAP's style resources are copied into the app.xaml of the calling/master application (not desirable). - Error no longer occurs.
Answer provided by bykinag on silverlight forums ...
I added the following line after loading the assembly.
App.Current.Resources.MergedDictionaries.Add(New ResourceDictionary() With {.Source = New Uri("/MyTest;component/Assets/Styles.xaml", UriKind.RelativeOrAbsolute)})
I now have an issue where the dynamic application can't seem to see other pages within it (Page not found) but I'll probably raise that separately if I can't resolve it.

Resources