Duplicated Images Printed in an XPS File

First of all, I would like to note that I raised this as an error with Microsoft, but they do not want to fix it at this point in time. What I'm looking for is a workaround or the best way to achieve what I'm trying to do, as our client considers this a pretty important issue.

Code

MainWindow.xaml

<Grid x:Name="mainGrid"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <ListBox ItemsSource="{Binding Images}"> <ListBox.ItemTemplate> <DataTemplate> <Image Source="{Binding}"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Button Content="Print to file" Grid.Row="1" Click="PrintToFile_Click"/> <Button Content="Print to device" Grid.Row="2" Click="PrintToDevice_Click"/> </Grid> 

MainWindow.xaml.cs

 public partial class MainWindow : Window { public IList<byte[]> Images { get; set; } public MainWindow() { InitializeComponent(); Assembly currentAssembly = Assembly.GetExecutingAssembly(); this.Images = new List<byte[]> { ReadToEnd(currentAssembly.GetManifestResourceStream("PrintingInvestigation.Images.Chrysanthemum.jpg")), ReadToEnd(currentAssembly.GetManifestResourceStream("PrintingInvestigation.Images.Desert.jpg")), ReadToEnd(currentAssembly.GetManifestResourceStream("PrintingInvestigation.Images.Hydrangeas.jpg")), }; this.DataContext = this; } public static byte[] ReadToEnd(System.IO.Stream stream) { long originalPosition = 0; if (stream.CanSeek) { originalPosition = stream.Position; stream.Position = 0; } try { byte[] readBuffer = new byte[4096]; int totalBytesRead = 0; int bytesRead; while ((bytesRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0) { totalBytesRead += bytesRead; if (totalBytesRead == readBuffer.Length) { int nextByte = stream.ReadByte(); if (nextByte != -1) { byte[] temp = new byte[readBuffer.Length * 2]; Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length); Buffer.SetByte(temp, totalBytesRead, (byte)nextByte); readBuffer = temp; totalBytesRead++; } } } byte[] buffer = readBuffer; if (readBuffer.Length != totalBytesRead) { buffer = new byte[totalBytesRead]; Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead); } return buffer; } finally { if (stream.CanSeek) { stream.Position = originalPosition; } } } private void PrintToDevice_Click(object sender, RoutedEventArgs e) { PrintDialog dialog = new PrintDialog(); if (dialog.ShowDialog() == true) { Thickness pageMargins; if (dialog.PrintTicket.PageBorderless.HasValue == true) { if (dialog.PrintTicket.PageBorderless.Value == PageBorderless.Borderless) { pageMargins = new Thickness(0, 0, 0, 0); } else { pageMargins = new Thickness(20, 20, 20, 20); } } else { pageMargins = new Thickness(20, 20, 20, 20); } int dpiX = 300; int dpiY = 300; if (dialog.PrintTicket.PageResolution != null && dialog.PrintTicket.PageResolution.X.HasValue && dialog.PrintTicket.PageResolution.Y.HasValue) { dpiX = dialog.PrintTicket.PageResolution.X.Value; dpiY = dialog.PrintTicket.PageResolution.Y.Value; } else { dialog.PrintTicket.PageResolution = new PageResolution(dpiX, dpiY); } VisualDocumentPaginator paginator = new VisualDocumentPaginator(this.mainGrid, this.mainGrid.ActualWidth); paginator.PageSize = new Size(dialog.PrintableAreaWidth, dialog.PrintableAreaHeight); dialog.PrintDocument(paginator, "My first print"); GC.Collect(); } } private void PrintToFile_Click(object sender, RoutedEventArgs e) { string filePath = this.PrintToFile(null, this.mainGrid, "My first print", this.mainGrid.ActualHeight, this.mainGrid.ActualWidth); Process.Start(filePath); } public string PrintToFile(Visual titleVisual, Visual contentVisual, string title, double bottomMost, double rightMost) { string printedFilePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), string.Format(CultureInfo.InvariantCulture, "{0}.xps", title)); XpsDocument printedDocument = new XpsDocument(printedFilePath, FileAccess.Write, System.IO.Packaging.CompressionOption.SuperFast); VisualDocumentPaginator paginator = new VisualDocumentPaginator(contentVisual as FrameworkElement, rightMost); paginator.PageSize = new Size(793.7, 1122.5); XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(printedDocument); writer.Write(paginator, new PrintTicket { Collation = Collation.Collated, CopyCount = 1, DeviceFontSubstitution = DeviceFontSubstitution.On, Duplexing = Duplexing.OneSided, InputBin = InputBin.AutoSelect, OutputColor = OutputColor.Color, OutputQuality = OutputQuality.High, PageMediaSize = new PageMediaSize(PageMediaSizeName.ISOA4), PageOrientation = PageOrientation.Portrait, PageResolution = new PageResolution(PageQualitativeResolution.High), PagesPerSheet = 1, TrueTypeFontMode = TrueTypeFontMode.Automatic }); printedDocument.Close(); return printedFilePath; } } 

VisualDocumentPaginator.cs

 public class VisualDocumentPaginator : DocumentPaginator { #region Fields private double desiredWidth; private FrameworkElement element; #endregion #region Properties public int Columns { get { return 1;// (int)Math.Ceiling(Element.ActualWidth / PageSize.Width); } } public int Rows { get { return (int)Math.Ceiling(element.ActualHeight / PageSize.Height); } } #endregion #region Constructors public VisualDocumentPaginator(FrameworkElement element, double desiredWidth) { this.desiredWidth = desiredWidth; this.element = element; } #endregion #region DocumentPaginator Members public override DocumentPage GetPage(int pageNumber) { TransformGroup transforms = new TransformGroup(); double scaleRatio = this.PageSize.Width / this.desiredWidth; int row = (pageNumber / Columns); double pageHeight = -PageSize.Height * row / scaleRatio; double pageWidth = -PageSize.Width * (pageNumber % Columns); transforms.Children.Add(new TranslateTransform(pageWidth, pageHeight)); // Make sure the control is stretched to fit the page size. if (scaleRatio != double.NaN) { ScaleTransform st = new ScaleTransform(scaleRatio, scaleRatio); transforms.Children.Add(st); } element.RenderTransform = transforms; Size elementSize = new Size(this.desiredWidth, element.ActualHeight); element.Measure(elementSize); element.Arrange(new Rect(new Point(0, 0), elementSize)); var page = new DocumentPage(element, this.PageSize, new Rect(), new Rect()); element.RenderTransform = null; return page; } public override bool IsPageCountValid { get { return true; } } public override int PageCount { get { return Columns * Rows; } } public override Size PageSize { set; get; } public override IDocumentPaginatorSource Source { get { return null; } } #endregion } 

Sorry to post all the code, but it covers all areas where I see the problem. If this helps here, a Microsoft bug report in which there is an example project in which the problem can be reproduced.

Problem
The problem occurs only when writing to the XPS file, when only the first image is printed 3 times, if the "Print to Device" button is pressed, then the correct images are printed.

The reason I attach to byte [] is because I save my images in a local SQL CE database. We store them in the database, because they are only small ~ 2 KB each plus we allow users to import their own icons into the system for use, and we need a mechanism to ensure that they are not accidentally deleted.

Note
I noticed that if I do not contact the byte [], as mentioned above, I see no problem. Given the fact that the system is currently working with an approach to storing images in the database, I would prefer to stick to it if there is a workaround, however I am not completely against replacing the storage mechanism for these images.

+6
source share
2 answers

I built my own print solution for a WPF-based document management system. I started using the System.Printing namespace, but found endless .NET errors that Microsoft hasn't resolved for a long time. Without possible workarounds, I ended up using the more mature System.Drawing.Printing namespace created for Windows Forms, with which I did not find any problems.

You will probably need some time to rewrite the code on System.Drawing.Printing , but you are much less likely to hit a brick wall somewhere along the way.

+1
source

I had a similar problem when the first image was duplicated and replaced all other images. In my case, printing to a device, to an XPS document or a PDF document did not matter, there was still a problem.

Analysis

I used the .NET assembly decompiler to find out how the System.Windows.Xps.XpsDocumentWriter class accesses images to find out if there was a problem in my code or in the code in the code. I found that the framework uses dictionnairies to import resources, such as images into a document. Even if images are imported only once in an XPS document, the document is allowed to reference them many times.

In my case, I was able to find out that the problem was detected in the System.Windows.Xps.Serialization.ImageSourceTypeConverter.GetBitmapSourceFromImageTable method. When the image is built from System.Windows.Media.Imaging.BitmapFrame , the converter will search for a resource in one of its dictionaries using the key. In this case, the key corresponds to the hash code of the string returned by the BitmapFrame.Decoder.ToString() method. Unfortunately, since my images are built from byte arrays instead of URIs, the ToString decoder method returns an β€œimage”. Since this line always generates the same hash code regardless of the image, ImageSourceTypeConverter will assume that all images are already added to the XPS document and will return the Uri of the first and only image to be used. This explains why the first image is duplicated and replaces all other images.

Attempt

My first attempt to work around the problem was to override the System.Windows.Media.Imaging.BitmapDecoder.ToString() method. To do this, I tried to wrap BitmapFrame and BitmapDecoder in my own BitmapFrame and BitmapDecoder . Unfortunately, the BitmapDecoder class contains an internal abstract method that I cannot define. Since I could not create my own BitmapDecoder , I could not implement this workaround.

Bypass

As mentioned earlier, the System.Windows.Xps.Serialization.ImageSourceTypeConverter.GetBitmapSourceFromImageTable method will look up the hash code in the dictionary if BitmapSource is a BitmapFrame . If it is not a BitmapFrame , it instead generates a CRC value based on the binary image data and looks for it in another dictionary.

In my case, I decided to wrap the BitmapFrame that was generated from the byte arrays using System.Windows.Media.ImageSourceConverter in another type of BitmapSource , for example System.Windows.Media.Imaging.CachedBitmap . Since I really did not want to cache the bitmap, I created the following parameters for CachedBitmap :

 var imageSource = new CachedBitmap( bitmapFrame, BitmapCreateOptions.None, BitmapCacheOption.None ); 

With these options, CachedBitmap is basically a simple BitmapSource wrapper. In my case, this workaround solved my problem.

+1
source

Source: https://habr.com/ru/post/988136/


All Articles