Ne0
Ne0

Reputation: 2786

Windows Store App - RenderTargetBitmap creating a bitmap of incorrect size

I'm attempting to put a watermark on a picture taken by my app. The simplest way I could think of is to use FrameworkElement's to build the layers and then use RenderTargetBitmap to create the water marked image.

Here is a sample of my XAML.

    <ScrollViewer x:Name="Zoom" Grid.Column="1" HorizontalScrollMode="Enabled" VerticalScrollMode="Enabled" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden" ZoomMode="Enabled">
        <Border x:Name="BgBorder">
            <Grid x:Name="ImageGird" SizeChanged="ImageGird_SizeChanged">
                <Grid x:Name="CaptureGird">
                    <Image x:Name="CapturedImage" Stretch="None" Source="ms-appx:///Assets/Photo.jpg" />
                    <StackPanel x:Name="Watermark" VerticalAlignment="Top" HorizontalAlignment="Left" Background="#6FFFFFFF" Margin="10">
                        <TextBlock Text="Name" Foreground="Black" Margin="10,2.5,10,2.5" />
                        <TextBlock Text="12345" Foreground="Black" Margin="10,2.5,10,2.5"/>
                        <TextBlock Text="54321" Foreground="Black" Margin="10,2.5,10,2.5" />
                    </StackPanel>
                </Grid>
            </Grid>
        </Border>
    </ScrollViewer>

Due to resolution of the images they need it is wrapped in a ScrollViewer so it can be zoomed out, however when I attempt to create a bitmap of this image using the below code, the rendered bitmap is smaller then the FrameworkElement

private async void Button_Click(object sender, RoutedEventArgs e)
{
    try
    {
        var displayI = DisplayInformation.GetForCurrentView();
        var renderTargetBitmap = new RenderTargetBitmap();
        await renderTargetBitmap.RenderAsync(ImageGird, (int)ImageGird.ActualWidth, (int)ImageGird.ActualHeight);
        IBuffer pixels = await renderTargetBitmap.GetPixelsAsync();
        CapturedImage2.Source = renderTargetBitmap;

        Debug.WriteLine("Button_Click: ImageGrid: " + ImageGird.ActualWidth + "x" + ImageGird.ActualHeight + " RenderTargetBitmap: " + renderTargetBitmap.PixelWidth + "x" + renderTargetBitmap.PixelHeight);
    }
    catch (Exception )
    {
    }
}

The debug output is

Button_Click: ImageGrid: 5344x3008 RenderTargetBitmap: 4096x2306

Can anyone tell me why the rendered bitmap is much smaller then the actual element I'm creating it from?

Also is there a better way to watermark an image?

Upvotes: 3

Views: 220

Answers (3)

Ne0
Ne0

Reputation: 2786

Thanks to @A.J.Bauer pointing me to Win2D i've manage to solve the issue quite elegantly.

    /// <summary>
    /// Create a watermarked image from an image stream
    /// </summary>
    /// <param name="sender">Jpeg image stream.</param>
    private async Task<ImageSource> CreateWaterMarkedImage(IRandomAccessStream stream)
    {
        // Ensure our stream is at the beginning.
        stream.Seek(0);
        // Create our Win2D in memory renderer.
        CanvasDevice device = CanvasDevice.GetSharedDevice();
        CanvasRenderTarget offscreen = new CanvasRenderTarget(device, (float)ImageGird.ActualWidth, (float)ImageGird.ActualHeight, 96);
        // Create our Win2D bitmap
        CanvasBitmap bmp = await CanvasBitmap.LoadAsync(offscreen, stream, 96);
        // Create a text formatter for our watermark
        var format = new CanvasTextFormat()
        {
            FontSize = 40,
            HorizontalAlignment = CanvasHorizontalAlignment.Left,
            VerticalAlignment = CanvasVerticalAlignment.Top,
            WordWrapping = CanvasWordWrapping.Wrap,
            FontFamily = "Arial",
            FontWeight = FontWeights.SemiBold
        };
        // Get a Win2D drawing session instance
        using (CanvasDrawingSession ds = offscreen.CreateDrawingSession())
        {
            // Layer our resulting Watermarked image.
            ds.DrawImage(bmp);
            // Create the Win2D text layout so we can get the bounds.
            var tl = new CanvasTextLayout(ds, "Name\r\n12345\r\n54321", format, 1000, 1000);
            // Create a background for the text
            ds.FillRectangle(10, 10, (float)tl.DrawBounds.Width + 20f, (float)tl.DrawBounds.Height + 20f, new Color() { A = 0x6F, R = 0xFF, G = 0xFF, B = 0xFF });
            // Add the text layout.
            ds.DrawTextLayout(tl, 10, 10, Colors.Black);
            // Clear up the memory.
            tl.Dispose();
        }

        // Create our bitmap so we can return an ImageSource
        BitmapImage im = new BitmapImage();
        using (InMemoryRandomAccessStream oStream = new InMemoryRandomAccessStream())
        {
            // Save the Win2D canvas renderer a stream.
            await offscreen.SaveAsync(oStream, CanvasBitmapFileFormat.Jpeg, 1.0f);
            stream.Seek(0);
            // Stream our Win2D pixels into the Bitmap
            await im.SetSourceAsync(oStream);
            Debug.WriteLine("CreateWaterMarkedImage: ImageGrid: " + ImageGird.ActualWidth + "x" + ImageGird.ActualHeight + " Bitmap: " + im.PixelWidth + "x" + im.PixelHeight);
        }
        // Tidy Up.
        format.Dispose();
        bmp.Dispose();
        offscreen.Dispose();
        device.Dispose();
        return im;
    }

The resulting debug gives me.

CreateWaterMarkedImage: ImageGrid: 5344x3008 Bitmap: 5344x3008

And if set the source of an Image UIElement the watermarked image is displayed.

CapturedImage2.Source = await CreateWaterMarkedImage(photoStream);

This example is very much tailored to my needs, however anyone should easily be able to adapt it if you use the Win2D XAML controls.

Upvotes: 2

A.J.Bauer
A.J.Bauer

Reputation: 3001

Havel a Look at Win2D and use off-screen drawing.

Upvotes: 1

Jacob Lange
Jacob Lange

Reputation: 1379

ActualWidth and ActualHeight are calculated values. So especially for a grid, it makes sense that these values could change overtime. My guess is that when you set the image source that the actual width and height are being recalculated.

I think you should be able to make the stack panel a child of the image and position it relative to the parent image, this would make it work for variable image sizes. You could also try putting some constraints on the grid and using those to determine your bitmap size instead of using actual width/height.

Upvotes: 1

Related Questions