Crop/copy stretched image in WPF

250 Views Asked by At

I want to build a small image and text editor. I have two Images in a UserControl.

On the left side is the editor and looks momentarily (thanks to some try and error) a little bit overstated, but it works nonetheless. (I will clean up the code later)

<Canvas x:Name="EditCanvas" Grid.Column="0"
        Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
        MaxWidth="426"
        MouseLeftButtonDown="EditCanvas_MouseLeftButtonDown">
    <Canvas.Background>
        <VisualBrush TileMode="Tile" Viewport="0, 0, 1, 1" Stretch="{Binding SelectedStretch}">
            <VisualBrush.Visual>
                <Image x:Name="EditorImage"
                       Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
                       Source="{Binding FileImagePath}" Margin="1"/>
            </VisualBrush.Visual>
        </VisualBrush>
    </Canvas.Background>
</Canvas>

Here I bind the Image to a UriSource. The stretching will change the Image itself, not the underlying source. Now I want to crop this image 'as it is'! So, when it is set to Stretch.UniformToFill or Stretch.Fill (for example) I want the cropped image to look exactly as it is shown in the left area. That means, the cropped image can be cropped on the bottom, the right, not at all or something like this. The formula to calculate this by myself would be a little bit greater. Now I wonder if there isn't a better way. I have to draw text later on to this cropped image, so I cannot simply convert it to a DrawingContext, DrawingVisual, and so on and draw text in it, because that would also stretch the text later on the right side. And if possible it would be nice to get a fast method, that can be redrawn a few times per second without killing the GPU.

For showing the preview image I use this XAML at the moment:

<Canvas Grid.Column="1"
        Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
        MaxWidth="426">
    <Canvas.Background>
        <VisualBrush TileMode="Tile"
                        Viewport="0, 0, 1, 1"
                        Stretch="{Binding SelectedStretch}">
            <VisualBrush.Visual>
                <Image Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
                        Source="{Binding PreviewImage}"
                        Margin="1" x:Name="TestImage"/>
            </VisualBrush.Visual>
        </VisualBrush>
    </Canvas.Background>
</Canvas>
<Border Grid.Column="1"
        BorderThickness="1" BorderBrush="Black"
        Height="{Binding AreaHeight}" Width="{Binding AreaWidth}" />

And this code behind to calculate the image and fill it with the desired text (even if it is faulted at this moment):

BitmapSource imageSource = EditorImage.Source as BitmapSource;
DrawingVisual drawingVisual = new DrawingVisual();

using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
    drawingContext.DrawImage(imageSource, new Rect(0, 0, imageSource.PixelWidth, imageSource.PixelHeight));

    List<TextPosition> textPositions = GetTextPositions();

    if (textPositions.Any())
    {
        textPositions.ForEach(x =>
            drawingContext.DrawText(new FormattedText(x.Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
                new Typeface(x.FontFamily, x.Style, x.Weight, FontStretch), x.FontSize, new SolidColorBrush(x.ForegroundColor),
                VisualTreeHelper.GetDpi(drawingVisual).PixelsPerDip), x.Position)
        );
    }
}

PreviewImage = new RenderTargetBitmap(imageSource.PixelWidth, imageSource.PixelHeight, 96, 96, PixelFormats.Pbgra32);
PreviewImage.Render(drawingVisual);
TestImage.InvalidateVisual();
1

There are 1 best solutions below

1
Marcel Grüger On

Ok, it doesn't seem to be possible. So I took a detour and do it now like this (if ever someone else does have a similar problem):

// Get image from the editor
BitmapSource bitmapSource = EditorImage.Source as BitmapSource;
// Get bitmap and transform it to the stretched version
Size scaleFactor = ComputeScaleFactor(new Size(AreaWidth, AreaHeight), new Size(bitmapSource.PixelWidth, bitmapSource.PixelHeight), SelectedStretch, StretchDirection.Both);
Size newCompleteSize = new Size(bitmapSource.PixelWidth * scaleFactor.Width, bitmapSource.PixelHeight * scaleFactor.Height);
Size renderSize = new Size
{
    Width = newCompleteSize.Width > AreaWidth ? AreaWidth : newCompleteSize.Width,
    Height = newCompleteSize.Height > AreaHeight ? AreaHeight : newCompleteSize.Height
};
Point imagePosition = GetImagePosition(renderSize);
        
BitmapImage bitmapImage = new BitmapImage();
using (MemoryStream memoryStream = new MemoryStream())
{
    JpegBitmapEncoder encoder = new JpegBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
    encoder.Save(memoryStream);
    memoryStream.Position = 0;

    bitmapImage.BeginInit();
    bitmapImage.StreamSource = memoryStream;
    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
    bitmapImage.DecodePixelWidth = (int)Math.Round(newCompleteSize.Width, 0, MidpointRounding.AwayFromZero);
    bitmapImage.DecodePixelHeight = (int)Math.Round(newCompleteSize.Height, 0, MidpointRounding.AwayFromZero);
    bitmapImage.EndInit();
}

DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
    // Draw the copy of the image
    drawingContext.DrawImage(bitmapImage, new Rect(imagePosition, new Size(bitmapImage.PixelWidth, bitmapImage.PixelHeight)));

    // Check if text has to be drawn
    List<TextPosition> textPositions = GetTextPositions();

    if (textPositions.Any())
    {
        textPositions.ForEach(x =>
        {
            FormattedText text = new FormattedText(x.ReplacedText, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
                new Typeface(x.FontFamily, x.Style, x.Weight, FontStretch), x.FontSize, new SolidColorBrush(x.ForegroundColor),
                VisualTreeHelper.GetDpi(drawingVisual).PixelsPerDip)
            {
                TextAlignment = x.Alignment
            };
            TextOptions.SetTextRenderingMode(this, TextRenderingMode.Aliased);
            TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display);
            drawingContext.DrawText(text, new Point(x.Position.X, x.Position.Y));
        });
    }
}

PreviewImage = new RenderTargetBitmap(
    (int)Math.Round(AreaWidth, 0, MidpointRounding.AwayFromZero),
    (int)Math.Round(AreaHeight, 0, MidpointRounding.AwayFromZero),
    96, 96, PixelFormats.Pbgra32);
PreviewImage.Render(drawingVisual);

And in the XAML I use two images. The first one stretched, the other one not:

<Image x:Name="EditorImage"
        HorizontalAlignment="{Binding AlignX}" VerticalAlignment="{Binding AlignY}"
        MaxHeight="426" MaxWidth="426" Margin="1"
        Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
        Source="{Binding FileImagePath}" Stretch="{Binding SelectedStretch}" />

<Image Grid.Column="1"
        VerticalAlignment="Center" HorizontalAlignment="Center"
        Source="{Binding PreviewImage}"
        Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
        Stretch="None"/>