0
votes

I have a set of methods in my utility class.

They should be converting between WPF and Windows image types but when I use them my image gets downscaled by a lot(from reviewing the code it should only resize it by 1 pixel or so on both axes due to double to int conversions but I'm obviously missing something)

I apologize for the gigantic post, I just can't seem to find the problem.

ImageSource (WPF) to Bitmap (Windows) methods :

public Bitmap ImageSourceToBitmap(Image source)
{
    var targetBitmap = new RenderTargetBitmap(
        (int) source.Source.Width,
        (int) source.Source.Height,
        96d, 96d,
        PixelFormats.Pbgra32);

    targetBitmap.Render(source);

    var memoryStream = new MemoryStream();
    var bitmapEncoder = new BmpBitmapEncoder();
    bitmapEncoder.Frames.Add(BitmapFrame.Create(targetBitmap));
    bitmapEncoder.Save(memoryStream);

    memoryStream.Position = 0;
    var bitmapImage = new BitmapImage();
    bitmapImage.BeginInit();
    bitmapImage.StreamSource = memoryStream;
    bitmapImage.EndInit();

    var resultBitmap = BitmapSourceToBitmap(bitmapImage);

    return resultBitmap;
}

public Bitmap BitmapSourceToBitmap(BitmapSource source)
{
    var width = source.PixelWidth;
    var height = source.PixelHeight;
    var stride = width * ((source.Format.BitsPerPixel + 7) / 8);
    var ptr = IntPtr.Zero;
    try
    {
        ptr = Marshal.AllocHGlobal(height * stride);
        source.CopyPixels(new Int32Rect(0, 0, width, height), ptr, height * stride, stride);
        using (var bitmap = new Bitmap(width, height, stride, 
        PixelFormat.Format32bppPArgb, ptr))
        {
            return new Bitmap(bitmap);
        }
    }
    finally
    {
        if (ptr != IntPtr.Zero)
            Marshal.FreeHGlobal(ptr);
    }
}

Bitmap(Windows) to BitmapSource(WPF) Method :

public BitmapSource BitmapToBitmapSource(Bitmap source)
{
    var bitmapData = source.LockBits( 
        new Rectangle( 0, 0,source.Width, source.Height ),
        ImageLockMode.ReadOnly,
        source.PixelFormat );

    //Names might be a bit confusing but I didn't have time to refactor
    var bitmapSource = BitmapSource.Create( 
        bitmapData.Width,
        bitmapData.Height,
        source.HorizontalResolution,
        source.VerticalResolution,
        PixelFormats.Pbgra32,
        null,
        bitmapData.Scan0,
        bitmapData.Stride * bitmapData.Height,
        bitmapData.Stride );

    source.UnlockBits(bitmapData);
    return bitmapSource;
}

XAML of my Image Control :

<Border Name="CurrentImageGridBorder" Grid.Column="0" Grid.Row="2" Margin="10" BorderThickness="1" Width="Auto" Height="Auto">
    <Border.BorderBrush>
        <SolidColorBrush Color="{DynamicResource BorderColor}"/>
    </Border.BorderBrush>
    <Grid x:Name="CurrentImageGrid" Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Image x:Name="CurrentImage" 
               RenderOptions.BitmapScalingMode="HighQuality"
               UseLayoutRounding="True"
               SnapsToDevicePixels="True"/>
    </Grid>
</Border>

WPF Control that triggers my GetImageSource method

<Button x:Name="OpenButton"
        Content="Open" 
        Style="{DynamicResource {x:Type Button}}"
        HorizontalAlignment="Left" Margin="20" VerticalAlignment="Top" Width="75" Height="20" 
        Click="GetImageSource"/>

GetImageSource Method :

private void GetImageSource(object sender, RoutedEventArgs e)
{ 
    var openFileDialog = new OpenFileDialog
    {
        Title = "Select an Image",
        Filter = "Image Files (*.jpg;*.jpeg;*.png;*.bmp)|*.jpg;*.jpeg;*.png;*.bmp|" +
                 "JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
                 "Portable Network Graphic|*.png",
        ValidateNames = true,
        Multiselect = false
    };

    if (openFileDialog.ShowDialog() != true) return;
    CurrentImage.Source = new BitmapImage(new Uri(openFileDialog.FileName));
    CurrentImage.Stretch = Stretch.None;
    if (!(CurrentImage.Source.Width > CurrentImageGridBorder.ActualWidth) &&
        !(CurrentImage.Source.Height > CurrentImageGridBorder.ActualHeight)) return;
    CurrentImage.StretchDirection = StretchDirection.Both;
    CurrentImage.Stretch = Stretch.Uniform;

}
1

1 Answers

0
votes

EDIT: Since you are only dealing with bitmaps (that are loaded from files or other streams, or created from raw pixel arrays) you don't need the RenderTargetBitmap at all. The Source property of your Image elements always contains an ImageSource that is already a BitmapSource.

Hence you can safely always cast ImageSource to BitmapSource:

public System.Drawing.Bitmap ImageToBitmap(Image image)
{
    return BitmapSourceToBitmap((BitmapSource) image.Source));
}

Using a RenderTargetBitmap would only by necessary when the Image's Source is not already a BitmapSource (e.g. when it is a DrawingImage). Then it is still neither necessary to render the Image element, nor to encode and decode the RenderTargetBitmap to/from a MemoryStream.

Just use a DrawingVisual and directly use the RenderTargetBitmap as BitmapSource:

public BitmapSource ImageSourceToBitmapSource(ImageSource imageSource)
{
    var bitmapSource = imageSource as BitmapSource;

    if (bitmapSource == null)
    {
        // This part is only necessary if an ImageSource is not a BitmapSource,
        // which may be the case when it is a DrawingImage or a D3DImage.
        // ImageSource instances loaded from files or streams are always BitmapSources.
        //
        var rect = new Rect(0, 0, imageSource.Width, imageSource.Height);

        var renderTargetBitmap = new RenderTargetBitmap(
            (int)Math.Ceiling(rect.Width),
            (int)Math.Ceiling(rect.Height),
            96d, 96d, PixelFormats.Pbgra32);

        var drawingVisual = new DrawingVisual();

        using (var drawingContext = drawingVisual.RenderOpen())
        {
            drawingContext.DrawImage(imageSource, rect);
        }

        renderTargetBitmap.Render(drawingVisual);
        bitmapSource = renderTargetBitmap;
    }

    return bitmapSource;
}

public System.Drawing.Bitmap ImageSourceToBitmap(ImageSource imageSource)
{
    return BitmapSourceToBitmap(ImageSourceToBitmapSource(imageSource));
}

public System.Drawing.Bitmap ImageToBitmap(Image image)
{
    return ImageSourceToBitmap(image.Source));
}