I am rendring a PDF Page to a CanvasBitmap and draw this Image on a VirtualCanvasControl. But whenever I zoom in the picture gets pixelated.
I tried to render just the part that is visible but that does not seem to work. What am I doing wrong?
<ScrollViewer Grid.RowSpan="2"
Grid.Row="0"
Name="PdfScrollViewer"
ZoomMode="Enabled"
MaxZoomFactor="4"
MinZoomFactor="0.8"
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BringIntoViewOnFocusChange="False">
<canvas:CanvasVirtualControl x:Name="PageRenderCanvas" RegionsInvalidated="PageRenderCanvas_OnRegionsInvalidated"
/>
</ScrollViewer>
The RegionsInvalidated EventHandler:
private async void PageRenderCanvas_OnRegionsInvalidated(CanvasVirtualControl sender,
CanvasRegionsInvalidatedEventArgs args)
{
PdfPageRenderOptions options = new PdfPageRenderOptions();
options.DestinationWidth = (uint) (PdfScrollViewer.ActualWidth*PdfScrollViewer.ZoomFactor);
options.DestinationHeight = (uint) (PdfScrollViewer.ActualHeight*PdfScrollViewer.ZoomFactor);
options.SourceRect = args.VisibleRegion;
var output = new MemoryStream().AsRandomAccessStream();
await page.RenderToStreamAsync(output, options); // page is the PDFPage
var image = await CanvasBitmap.LoadAsync(PageRenderCanvas, output);
using (var ds = sender.CreateDrawingSession(args.InvalidatedRegions[0]))
{
ds.DrawImage(image, args.VisibleRegion);
ds.Dispose();
}
}
The image is pixelated because you are performing the zoom on the rendered bitmap.
Since there is not much documentation on VirtualCanvasControl and RegionsInvalidated event I recommend to perform the rendering every time the zoom changes and just display the rendered image in the drawing event.
However this approach will generate memory problems at large zoom levels. Considering 96dpi for 100%, a letter page will be 816*1056 pixels and will take about 3MB. At 500% zoom the image will be 4080*5280 pixels and will take about 75MB.
Related
I have tried to set the width and also min height and min width but still the dialogue wont change to full screen. tried window.bounds too but teh dialog wont expand beyond a fixed width.
public sealed partial class ContentDialog1 : ContentDialog
{
public ContentDialog1()
{
this.InitializeComponent();
this.MinWidth = Window.Current.Bounds.Width;
}
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
}
private void ContentDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
}
}
}
<ContentDialog
x:Class="PowerUp.UWP.View.ContentDialog1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:PowerUp.UWP.View"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="TITLE"
PrimaryButtonText="Button1"
SecondaryButtonText="Button2"
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
SecondaryButtonClick="ContentDialog_SecondaryButtonClick"
MinWidth ="2000">
<Grid x:Name="cd1" >
</Grid>
This is what I want
This is how content dialog is shown in my application
It is actually very simple, did a bit research and found the simplest answer, you can keep doing what you were already doing in the first place and just set the FullSizeDesired property of your ContentDialog to true.
Popup
Or you can try it with popup.
var c = Window.Current.Bounds;
var okButton=new Button{Content="Ok"};
okButton.Click += okButtonClicked; // now where you have this okButtonClicked event you can execute any code you want including, closing the popup.
var g = new Grid
{
Width = c.Width,
Height = c.Height,
Background = new SolidColorBrush(Color.FromArgb(0x20, 0, 0, 0)),
Children =
{
new StackPanel
{
Width = 400,
Height = 200,
Background = new SolidColorBrush(Colors.White),
Children=
{
new TextBlock{Text="Title"},
new TextBlocl{Text="description"},
okButton
}
}
}
};
var p = new Popup
{
HorizontalOffset = 0,
VerticalOffset = 0,
Width = c.Width,
Height = c.Height,
Child = g
};
p.IsOpen = true; // open when ready
Notice that Child of popup is g which is a grid, you can put your content within this grid or you can use a StackPanel instead of this grid and then put your contents within that StackPanel, whatever you want to do here is your decision, putting elements in popup is exactly like putting elements in a ContentDialog.
Achieving the same with a simple Grid alongside your frame
<Grid>
<Grid Horizontallignment="Stretch" VerticalAlignment="Stretch" Visibility="Collapsed" x:Name="ContentGrid" canvas.ZIndex="5"><--this grid will act as content dialog, just toggle its visibility to the backend-->
<--put all your content here along with your ok and cancel buttons-->
</Grid>
<Frame/> <--this is the code where you have your frame-->
</Grid>
in above code the frame and your actually content grid will be parallel to each other, and whenever content grid is visible only it will be shown in the app because it has ZIndex greater than 0 and your frame will hide behind it, and whenever its visibility is collapsed it will not be shown and you will be able to see your frame normally.
What I'm doing now: I am working on an UWP-App that contains a very simple PDF-Viewer for scrolling and zooming PDFs. For handling the PDFs, I'm using Windows.Data.Pdf, which renders PDF-pages to images. At the moment, I'm rendering an image for each page of an PDF-file, which I then insert into a (vertically orientated) StackPanel. The StackPanel - again - is embedded in a ScrollViewer, which easily allows to implement scrolling and zooming. This method basically works; however, the quality of the PDF-images isn't yet as good as when the same PDF-file is displayed with Adobe Acrobat Reader or Microsoft Edge: I only get good renderings of PDFs if I render them in the original size; otherwise they are blurred.
My perception of the problem: Although the Windows.Data.Pdf-Namespace provides methods for zooming and scaling, it seems to me that the PDF-page are nevertheless always rendered to images with a specific/constant resolution, which thereafter is zoomed to the required zoom-factor. Therefore, I spare using zooming via Windows.Data.Pdf and instead just zoom the rendered Image.
Is there a way to render high quality/high resolution images with Windows.Data.Pdf?
My current XAML for the UI:
<Page
...>
<Grid ...>
<ScrollViewer
x:Name="ScrollViewerOutput"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="black"
HorizontalScrollMode="Enabled"
HorizontalScrollBarVisibility="Visible"
ZoomMode="Enabled">
<StackPanel
x:Name="StackPanelViewer"
Orientation="Vertical"/>
</ScrollViewer>
</Grid>
</Page>
My current VB-Code:
Imports Windows.Data.Pdf
...
Public Class ...
Dim pdfDocument As PdfDocument
Dim BAnz As Integer = 0
Dim Bild(999) As Image
Dim stream(999) As InMemoryRandomAccessStream
Dim src(999) As BitmapImage
...
Async Function ... (ByVal SFI As StorageFile) As Task
pdfDocument = Await PdfDocument.LoadFromFileAsync(SFI)
BAnz = 0
For i As Integer = 0 To pdfDocument.PageCount - 1
stream(BAnz) = New InMemoryRandomAccessStream()
src(BAnz) = New BitmapImage()
Bild(BAnz) = New Image()
Using page As PdfPage = pdfDocument.GetPage(i)
Dim rect As Rect = page.Dimensions.TrimBox
Dim options As New PdfPageRenderOptions
options.SourceRect = New Rect(rect.X, rect.Y, rect.Width, rect.Height)
Await page.RenderToStreamAsync(stream(BAnz), options)
Bild(BAnz).MaxWidth = rect.Width
Bild(BAnz).MaxHeight = rect.Height
End Using
Bild(BAnz).Source = src(BAnz)
Await src(BAnz).SetSourceAsync(stream(BAnz))
Bild(BAnz).HorizontalAlignment = HorizontalAlignment.Stretch
Bild(BAnz).VerticalAlignment = VerticalAlignment.Stretch
Bild(BAnz).Margin = New Thickness(10)
StackPanelViewer.Children.Add(Bild(BAnz))
BAnz = BAnz + 1
Next
End Function
End Class ...
The Windows.Data.Pdf Namespace is for converting a page in a PDF document to an image file. PDF APIs support high fidelity rendering, but only for C++ applications using Direct2D. You can check out the PDF Showcase sample on how to do it. Though it is a C# Project call C++ library, it is similar to your VB project.
I'm struggling with new UWP InkCanvas for my apps.If you are familiar with the new InkCanvas in UWP, I would truly appreciate your help.
I have a UWP apps need to switching between InkCanvas and Canvas, which I wish to use InkCanvas for Drawing, Canvas for creating Contents such as RichEditbox, Image, etc...
My XAML is below
<Grid Margin="100,0,100,0" Background="White" x:Name="MainGrid" PointerMoved="MyCanvas_PointerMoved" PointerReleased="MyCanvas_PointerReleased" PointerPressed="MyCanvas_PointerPressed">
<Canvas x:Name="MyCanvas" />
<InkCanvas x:Name="inkCanvas" Canvas.ZIndex="0" />
<!-- End "Step 2: Use InkCanvas to support basic inking" -->
</Grid>
I tried to make my Canvas ZIndex greater than InkCanvas by using
private void AppBarButton_Click(object sender, RoutedEventArgs e)
{
if(Canvas.GetZIndex(MyCanvas) > 1)
{
Canvas.SetZIndex(MyCanvas, 0);
}
else
{
Canvas.SetZIndex(MyCanvas, 5);
}
}
However I can't make my Canvas come to the front, the InkCanvas keep Capturing my stroke if I Draw on it instead.
Does anyone know how to switch the Canvas and InkCanvas in my Grid without changing the Visibility of my InkCanvas to Collapsed?
By default the Canvas background is empty (null), so it will not capture pointer input and will pass it through to the InkCanvas.
If you want to prevent this, you could set its Background to Transparent.
However, when you start putting some controls on the Canvas itself, they should capture the pen input and not let it through to the InkCanvas.
I have a Grid which is as high as the application and has the width of 50. I have got a button in it on the left top with the width of 50 also. I want to move this button along the vertical left axis by dragging it with the mouse. But it should be stil able to be clicked normally. I tried to do this with the drag-and-drop sample by microsoft but the procedure I want to implement is not quite drag-and-drop. How can I implement this by using XAML and c++-cx as code behind in an universal windows app ?
My XAML-Code for the Grid/Button:
<Grid x:Name="Grid1" Width="50" >
<Button x:Name="btnMove" Content="Move me!" Background="PaleGreen" Click="btnMove_Click" VerticalAlignment="Top" HorizontalAlignment="Left" Width="50" Height="50"></Button>
</Grid>
For your requirement, you could move the button on the vertical axis by using ManipulationDelta class. And you could achieve it with the following code.
For more please refer to Handle pointer input. Here is official code sample.
MainPage::MainPage()
{
InitializeComponent();
InitManipulationTransforms();
btnMove->ManipulationDelta += ref new ManipulationDeltaEventHandler(this, &MainPage::btnMove_ManipulationDelta);
btnMove->ManipulationMode = ManipulationModes::TranslateX;
}
void App14::MainPage::InitManipulationTransforms()
{
transforms = ref new TransformGroup();
previousTransform = ref new MatrixTransform();
previousTransform->Matrix = Matrix::Identity;
deltaTransform = ref new CompositeTransform();
transforms->Children->Append(previousTransform);
transforms->Children->Append(deltaTransform);
// Set the render transform on the rect
btnMove->RenderTransform = transforms;
}
void App14::MainPage::btnMove_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
}
void MainPage::btnMove_ManipulationDelta(Platform::Object^ sender, ManipulationDeltaRoutedEventArgs^ e)
{
previousTransform->Matrix = transforms->Value;
// Get center point for rotation
Point center = previousTransform->TransformPoint(Point(e->Position.X, e->Position.Y));
deltaTransform->CenterX = center.X;
deltaTransform->CenterY = center.Y;
// Look at the Delta property of the ManipulationDeltaRoutedEventArgs to retrieve
// the rotation, scale, X, and Y changes
deltaTransform->Rotation = e->Delta.Rotation;
deltaTransform->TranslateX = e->Delta.Translation.X;
deltaTransform->TranslateY = e->Delta.Translation.Y;
}
You could change the scrolling direction of the button by modifying the ManipulationMode of button.
btnMove->ManipulationMode = ManipulationModes::TranslateY;
I have a very simple repro case of RenderTargetBitmap.RenderAsync overload messing up the WebView scaling. All I have on the MainPage is a WebView and a button:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<WebView Source="http://bing.com"></WebView>
<Button Content="Render me"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Click="ButtonBase_OnClick" />
</Grid>
In code behind there's only a simple event handler
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
RenderTargetBitmap rtb = new RenderTargetBitmap();
await rtb.RenderAsync(this, 1280, 720);
}
This is what the page looks like before RenderAsync call:
and this is what happens after the call:
Any idea why and how to prevent this? Note that it only happens if I call
await rtb.RenderAsync(this, 1280, 720);
but NOT if I call the overload without the scaling
await rtb.RenderAsync(this);
EDIT: Due to the first answer I received, I wanted to clarify why the aspect ratio is not the problem here, but only serves the purpose of proving that there actually is a problem. Think of the following scenario - very high DPI screen where only a lower resolution screenshot is needed - even if you scale it down with the RIGHT ratio, it still messes up the WebView. Also, for my scenario, resizing the screenshot manually afterwards is not an option - the RenderAsync overload with scaled dimensions is much much faster and I would really prefer to use that method.
Very strange behavior...
I found one very dirty (!) fix to this. I basically hide and show the webview (wv) again.
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
RenderTargetBitmap rtb = new RenderTargetBitmap();
await rtb.RenderAsync(wv, 1280, 720);
wv.Visibility = Visibility.Collapsed;
await Task.Delay(100);
wv.Visibility = Visibility.Visible;
}
I'm not proud of this solution and the webview flashes, but at least it's not 'blown up' any more...
This is a bit of a hack too, but I found that if you set the contents of another control through a WebViewBrush and then render that control, then the source WebView doesn't get any scaling. I have modified the XAML you provided so it looks like this:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border x:Name="Target" Width="1280" Height="720" />
<WebView x:Name="webView" Source="http://bing.com" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></WebView>
<Button Content="Render me" Grid.Row="1"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Click="ButtonBase_OnClick" />
</Grid>
In your case, you should opt to set the Border control behind your WebView (however, don't change its Visibility or put it outside of the window bounds, as RenderAsync will fail). Then, on the code behind, set the Background of the target control to an instance of a WebViewBrush that feeds on the WebView:
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
WebViewBrush brush = new WebViewBrush();
brush.SetSource(webView);
Target.Background = brush;
Target.InvalidateMeasure();
Target.InvalidateArrange();
RenderTargetBitmap rtb = new RenderTargetBitmap();
await rtb.RenderAsync(Target, 1280, 720);
var pixels = await rtb.GetPixelsAsync();
}
You will get your final image without any issues caused to the source WebView (however, note that the final image will look distorted since the aspect ratios don't match). However this comes with a few caveats, the most important one being that the WebView size must match the one of the RenderTargetBitmap or you will get empty areas.
Instead of using fixed values, use VisibleBounds to get the current window size.
Here's the code:
private async void pressMe_Click(object sender, RoutedEventArgs e)
{
var windowBounds = ApplicationView.GetForCurrentView().VisibleBounds;
RenderTargetBitmap rtb = new RenderTargetBitmap();
await rtb.RenderAsync(this, (int)windowBounds.Width, (int)windowBounds.Height);
}