I have a problem interoperating between system.drawing and WPF. To simplify this question I've build an example to illustrate the problem. I draw using System.Drawing and then I copy the result to the System.Windows.Controls.Image using CreateBitmapSourceFromHBitmap. And somehow the alpha information is lost as you can see in the screenshot.
Black text is drawn on the transparent Bitmap and then copied to the image component which is transparent itself. The parent grid itself has a black background. I would guess that the result is a completely black image, but it is not, the text seems to be drawn using white as background when doing the anti-aliasing.
Link to Screenshot
Here's the code for MainWindow.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid Name="grid" Background="Black">
<Image Name="image" Stretch="Fill" Loaded="image_Loaded"></Image>
</Grid>
</Window>
And the code for MainWindow.xaml.cs:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void image_Loaded(object sender, RoutedEventArgs e)
{
//use system drawing to draw text
Bitmap bmp = new Bitmap ( (Int32) grid.ActualWidth, (Int32) grid.ActualHeight );
Graphics graphics = Graphics.FromImage ( bmp );
var brush = new SolidBrush(Color.FromArgb(255, 0, 0, 0));
var font = new Font(System.Drawing.FontFamily.GenericSansSerif, 30.0f, System.Drawing.FontStyle.Regular, GraphicsUnit.Pixel);
graphics.Clear(Color.Transparent);
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.CompositingMode = CompositingMode.SourceOver;
graphics.CompositingQuality = CompositingQuality.HighQuality;
//draw text
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
graphics.TextContrast = 5;
graphics.DrawString("My Text", font, brush, 10, 10);
//try saving
bmp.Save("E:\\temp.png", System.Drawing.Imaging.ImageFormat.Png);
//back to wpf image control
image.Source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap ( bmp.GetHbitmap ( ), IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromWidthAndHeight ( (Int32) grid.ActualWidth, (Int32) grid.ActualHeight ) );
}
}
}
Does anyone have experienced this? How can I have a smooth anti-aliasing effect which is not pre-multiplied with white?
Not the most glamorous solution, but the only way I can get it to work properly is by saving the bitmap with the Save() function and then decoding it and loading it as a BitmapSource:
bmp.Save("E:\\temp.png", System.Drawing.Imaging.ImageFormat.Png);
//A neater way to free up the file!
bmp.Dispose();
bmp = null;
//Load up the image.
BitmapFrame frm = new BitmapFrame(new Uri("E:\\temp.png"));
image.Source = frm as BitmapSource;
Now that's not the neatest solution, but it's all I can come across.
Related
I'm looking for a way to draw a bitmap to a WPF DrawingContext as greyscale. I'd like to be able to draw it at the given x,y location and scale it to a given width and height. 256 levels of grey (8-bit greyscale) is good enough. I have a colour bitmap file on disk which will be either bmp, png or jpg format.
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
void DrawBitmapGreyscale(DrawingContext dc, string filename, int x, int y, int width, int height)
{
// Load the bitmap into a bitmap image object
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = new Uri(filename);
bitmap.EndInit();
// Convert the bitmap to greyscale, and draw it.
FormatConvertedBitmap bitmapGreyscale = new FormatConvertedBitmap(bitmap, PixelFormats.Gray8, BitmapPalettes.Gray256, 0.0);
dc.DrawImage(bitmapGreyscale, new Rect(x, y, width, height));
}
You can do this using an effect - possibly not as performant as pure code, but offers flexibility.
<Image Source="../Images/ChartSample.png" Stretch="Uniform" Margin="5">
<Image.Effect>
<ee:ColorToneEffect DarkColor="Black" LightColor="White" ToneAmount="0" Desaturation="1" />
</Image.Effect>
Where you reference namespace as
xmlns:ee=”http://schemas.microsoft.com/expression/2010/effects”
In a page I display a picture that I receive from CameraCapureTask in the View
<Grid x:Name="EditPageGrid" Margin="{Binding}">
<Grid Name="ViewportContainer" Margin="12,0,12,24">
<Image x:Name="Viewport" LayoutUpdated="Viewport_LayoutUpdated"
Source="{Binding}"/>
</Grid>
</Grid>
And I wish to be able to place a border around this image. How would it be possible to do this? I was thinking perhaps on a click event of some sort a border could be toggled on or off, but actually applying a border is where I am at a loss.
You can contain the image in a Border, like this:
<Grid x:Name="EditPageGrid" Margin="{Binding}">
<Grid Name="ViewportContainer" Margin="12,0,12,24">
<Border HorizontalAlignment="Center" BorderThickness="4" BorderBrush="Red">
<Image Source="C:\Users\Public\Pictures\Sample Pictures\Jellyfish.jpg"/>
</Border>
</Grid>
</Grid>
I figured out such an event: (probably there is better method, but this also works)
private void Viewport_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
int imageHeight = (Viewport.Source as BitmapImage).PixelHeight;
int imageWidth = (Viewport.Source as BitmapImage).PixelWidth;
Canvas myCanvas = new Canvas();
Rectangle myBorder = new Rectangle();
myBorder.Width = imageWidth;
myBorder.Height = imageHeight;
myBorder.Stroke = new SolidColorBrush(Colors.Red);
myBorder.StrokeThickness = 10;
Image toBorder = new Image();
toBorder.Source = Viewport.Source as BitmapImage;
myCanvas.Children.Add(toBorder);
myCanvas.Children.Add(myBorder);
WriteableBitmap newImage = new WriteableBitmap(myCanvas, null);
//Viewport.Source = newImage; - you can use this but watch out that Viewport.Source now is not BitmapImage
//Below is one method how to make it BitmapImage
//You can of course save newImage to file or whatever you want
//You can also unsubscribe this event to prevent it from second tap which will cause Exception at first line (BitmaImage != WriteableBitmap)
MemoryStream memoryStream = new MemoryStream();
newImage.SaveJpeg(memoryStream, imageWidth, imageHeight, 0, 100);
BitmapImage newBitmap = new BitmapImage();
newBitmap.SetSource(memoryStream);
Viewport.Source = newBitmap;
}
Playing with this memory stream isn't good, but I've not known what you are planning to do with your new Bitmap.
As I've said - it's only example and I'm sure better methods exist (which I don't know). Hope this helps.
My requirements:
a persistent UserControl that handles logic for a custom image, such as a map or drawing
a set of containers to implement caching on the image during zoom or pan movements
VisualBrush copies of the UserControl that I can add to the containers for use with Effects
I currently implement image caching with a RenderTargetBitmap, but that seems to have trouble with the VisualBrush-covered Rectangle objects I'm using.
My question: What can I add/change in this code to get the VisualBrush objects to render correctly after RenderTargetBitmap uses them? What strange thing is RenderTargetBitmap doing that makes the VisualBrush invisible?
This is a problem that I have been unable to reproduce without a decent amount of code.
In my xaml file I have:
<Window x:Class="ElementRender.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525">
<Grid>
<Grid Name="_contentContainer">
<Rectangle Fill="White"/>
<Grid Name="_content">
<Grid Name="_back"/>
<Grid Name="_body"/>
</Grid>
</Grid>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
<Button Content="New" Name="New"/>
<Button Content="Move" Name="Move"/>
<Button Content="Update" Name="Update"/>
</StackPanel>
</Grid>
</Window>
and the .xaml.cs:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
public partial class MainWindow : Window
{
private const int imageWidth = 150;
private const int imageHeight = 150;
private readonly UserControl Control;
public MainWindow()
{
InitializeComponent();
// User Control setup
Control = new UserControl() {
Width = imageWidth, Height = imageHeight,
Content = BuildImage()
};
_body.Children.Add(SoftCopy(Control));
// event setup
Move.Click += (sender, e) => _content.RenderTransform = new TranslateTransform(50, 50);
New.Click += (sender, e) => {
HardCopy();
_content.RenderTransform = null;
Control.Content = BuildImage();
};
}
private FrameworkElement BuildImage()
{
return new Rectangle{Fill=Brushes.Blue};
}
private void HardCopy()
{
int width = (int) _contentContainer.ActualWidth;
int height = (int) _contentContainer.ActualHeight;
// render the current image
var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual dv = new DrawingVisual();
using (var context = dv.RenderOpen())
{
var brush = new VisualBrush(_contentContainer) { Opacity = .5 };
context.DrawRectangle(brush, null, new Rect(0, 0, width, height));
}
rtb.Render(dv);
var lastRender = new Image
{
Source = rtb,
Stretch = Stretch.None,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Width = width,
Height = height
};
_back.Children.Clear();
_back.Children.Add(lastRender);
}
private FrameworkElement SoftCopy(FrameworkElement element)
{
return new Rectangle{Fill= new VisualBrush(element), Width=element.Width, Height=element.Height};
}
}
A few helping notes about the code:
the xaml's _contentContainer works with HardCopy() to copy the current images into the image cache, _back.
SoftCopy returns a FrameworkElement that looks exactly like the one past in, but without any transforms, effects, or visual parents. This is very important.
BuildImage simulates building a new image to be pasted over the cache after the initial image has been transformed somehow.
If you build and run the application removing the SoftCopy() from the _body.Children.Add(SoftCopy(Control));, you see the effect that I want to get: the new element is pasted above the old element, and the old element seems to retain its transform.
Alternatively, if you cut out the line var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); from HardCopy, the caching function is broken, but the SoftCopy is displayed correctly.
However, if you run the application as-is, you notice that the new BlueRectangle (as rendered through a VisualBrush) doesn't display at all, until you hit the "New" button again, pushing the image to the cache, and still not showing you the new created image.
I'm going to be pompous enough to call this a bug in WPF. I eventually found out how to fix the strange behavior I was getting:
var visual = visualBrush.Visual;
visualBrush.Visual = null;
visualBrush.Visual = visual;
This should essentially be a null operation: by the end, the visual brush has the same visual as when it started. However, adding this code segment after rendering the VisualBrush into the RenderTargetBitmap fixed the issue I was having.
I didn't quite understand the post but there are few important things:
If you apply RenderTransform/Margins to element and take picture of it(RenderTargetBItmap), you're gonna have bad time. It will be offseted and you will get only sub-picture.
The idea is to take picture without any rendertransforms, and then later copy RenderTransform over from the old one. If needed.
In a windows forms application, I have as input a Drawing.Bitmap and a DrawingImage. I need to overlay them and put there output in a Controls.Image. How can I do this?
It doesn't matter if you use Image object or Bitmap object, The Drawing.Image is abstract class and Drawing.Bitmap inherited from it. to
draw image over image, get the graphics object from the base image and then use Graphics.DrawImage which accept parameter of type Image.
So you have two images here, one should be printed "overlay" over the other image:
System.Drawing.Image primaryImage = Image.FromFile(#"Your file path");//or resource..
using (Graphics graphics = Graphics.FromImage(primaryImage))//get the underlying graphics object from the image.
{
using (Bitmap overlayImage = new Bitmap(primaryImage.Width, primaryImage.Hieght,
System.Drawing.Imaging.PixelFormat.Format32bppArgb)//or your overlay image from file or resource...
{
graphics.DrawImage(overlayImage, new Point(0, 0));//this will draw the overlay image over the base image at (0, 0) coordination.
}
}
Control.Image = primaryImage;
Not that if the overlay image doesn't have some transparent, and its size is equals or larger than the base image, it will overlap the other image completely, so you the overlay image must have some opacity.
I realize it has been awhile, but the answers here weren't quite working for me. A little tweaking, though made them work fine. For what it is worth, here is my final version.
SCENARIO:
background image is RGB 24
overlay image is ARGB 32 with alpha channel already set properly.
images created from a memory stream
PROBLEM:
Creating the overlay image from the memory stream assumed I meant: Format32bppRgb
But what is needed is Format32bppArgb since the transparency is already in place..
SOLUTION:
pictureBox1.Image = MergeImages( backgroundImage, overlayImage);
using System.Drawing;
using System.Drawing.Imaging;
// ...
private Image MergeImages(Image backgroundImage,
Image overlayImage)
{
Image theResult = backgroundImage;
if (null != overlayImage)
{
Image theOverlay = overlayImage;
if (PixelFormat.Format32bppArgb != overlayImage.PixelFormat)
{
theOverlay = new Bitmap(overlayImage.Width,
overlayImage.Height,
PixelFormat.Format32bppArgb);
using (Graphics graphics = Graphics.FromImage(theOverlay))
{
graphics.DrawImage(overlayImage,
new Rectangle(0, 0, theOverlay.Width, theOverlay.Height),
new Rectangle(0, 0, overlayImage.Width, overlayImage.Height),
GraphicsUnit.Pixel);
}
((Bitmap)theOverlay).MakeTransparent();
}
using (Graphics graphics = Graphics.FromImage(theResult))
{
graphics.DrawImage(theOverlay,
new Rectangle(0, 0, theResult.Width, theResult.Height),
new Rectangle(0, 0, theOverlay.Width, theOverlay.Height),
GraphicsUnit.Pixel);
}
}
return theResult;
}
I have an image viewer created with WPF 3D graphics. Image quality is really WORSE there, so I've started researching this issue, created simple application which shows the image using 2D graphics on the top part of the window, and the same image on the bottom part using 3D graphics. I noticed that image looks much worse on 3D surface than on 2D. The colors on the 3D surface are less saturated and do not have clear boundaries. Note, that I applied linear bitmap scaling mode to the root Grid. Other weird thing is that when I'm changing bitmap scaling mode to 'Fant' or 'NearestNeighbor' it affects 2D graphics, but image on the 3D surface REMAINS THE SAME! I'm using image for this sample with Height = 466px, Width = 490px. I'm zooming out it in the code (both 2D and 3D implementation) a little bit to see the scaling quality degradation. The code is:
<Window x:Class="Scaling3DSample.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="340">
<Grid x:Name="backgroundGrid">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
</Grid>
</Window>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;
namespace Scaling3DSample
{
public partial class Window2 : Window
{
private static double _distanceFromCamera = 0.62618;
public Window2()
{
InitializeComponent();
RenderOptions.SetBitmapScalingMode(backgroundGrid, BitmapScalingMode.Linear);
Create2DGraphics();
// THE SAME IMAGE ON 3D SURFACE LOOKS MUCH WORSE
Create3DGraphics();
}
private void Create2DGraphics()
{
Rectangle exampleRectangle = new Rectangle();
Grid.SetRow(exampleRectangle, 0);
exampleRectangle.Width = 335;
exampleRectangle.Height = 317;
exampleRectangle.Fill = GetBrush();
backgroundGrid.Children.Add(exampleRectangle);
}
private void Create3DGraphics()
{
Viewport3D mainViewPort3D = new Viewport3D();
Grid.SetRow(mainViewPort3D, 1);
mainViewPort3D.Camera = new PerspectiveCamera { LookDirection = new Vector3D(-1, 0, 0), UpDirection = new Vector3D(0, 0, 1), FieldOfView = 77.0942 };
mainViewPort3D.Children.Add(new ModelVisual3D { Content = new AmbientLight() });
MeshGeometry3D geometry3D = new MeshGeometry3D();
Point3D topLeft = new Point3D(-_distanceFromCamera, 0.5, -0.5);
Point3D bottomRight = new Point3D(-_distanceFromCamera, -0.5, 0.5);
geometry3D.Positions.Add(bottomRight);
geometry3D.Positions.Add(new Point3D(-_distanceFromCamera, topLeft.Y, bottomRight.Z));
geometry3D.Positions.Add(new Point3D(-_distanceFromCamera, bottomRight.Y, topLeft.Z));
geometry3D.Positions.Add(topLeft);
geometry3D.TriangleIndices.Add(1);
geometry3D.TriangleIndices.Add(0);
geometry3D.TriangleIndices.Add(2);
geometry3D.TriangleIndices.Add(2);
geometry3D.TriangleIndices.Add(3);
geometry3D.TriangleIndices.Add(1);
geometry3D.TextureCoordinates.Add(new Point(0, 0));
geometry3D.TextureCoordinates.Add(new Point(1, 0));
geometry3D.TextureCoordinates.Add(new Point(0, 1));
geometry3D.TextureCoordinates.Add(new Point(1, 1));
Material material = new DiffuseMaterial(GetBrush());
ModelVisual3D modelForGeometry = new ModelVisual3D { Content = new GeometryModel3D(geometry3D, material) };
mainViewPort3D.Children.Add(modelForGeometry);
backgroundGrid.Children.Add(mainViewPort3D);
}
private ImageBrush GetBrush()
{
// put any other image URI here, image Height = 466px, Width = 490px
ImageBrush brush = new ImageBrush(new BitmapImage(new Uri("lion.jpg", UriKind.Relative)));
brush.Stretch = Stretch.Fill;
return brush;
}
}
}
Thanks in advance for all your help!
There are some other variables to consider, then.
Your graphics card settings could be forcing the interpolation mode down despite WPF's request for something nicer looking. WPF's 3D is hardware accelerated on Tier 2 hardware, so check your drivers' control software. It might not be possible for WPF to request anything better!
Try enabling anti-aliasing in your application and graphics card settings, too.
Just guessing: you did not define any lights nor any normals. Sometimes that will cause a darker image than you would expect.