I have a program which generates a System.Drawing.Bitmap and then uses it to create a BitmapSource. I then assign the CurrentImage property of a WPF Image to the BitmapSource I create.
And here is the problem: no matter what I set the DPI values of the BitmapSource to, I end up with a grainy picture on my 4K display.
The piece of XAML I'm using is:
<Image Source="{Binding Path=CurrentImage, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SnapsToDevicePixels="True"/>
and the code is as follows:
private static void SetBitmap(FreeformEditor fe, Bitmap bmp)
{
if (fe.lastBitmap != IntPtr.Zero)
DeleteObject(fe.lastBitmap);
try
{
var data = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly, bmp.PixelFormat);
var dpi = baseDPI * (1 + fe.RenderForRetina.ToInt());
fe.CurrentImage = BitmapSource.Create(
data.Width, data.Height, dpi, dpi, PixelFormats.Bgr32, null,
data.Scan0, data.Stride * data.Height, data.Stride);
bmp.UnlockBits(data);
fe.lastBitmap = bmp.GetHbitmap();
fe.imageSizeRun.Content = string.Format("{0}x{1} #{2}dpi", fe.CurrentImage.Width,
fe.CurrentImage.Height,
fe.CurrentImage.DpiX);
}
catch
{
// this happens due to framework bugs
}
}
Related
I can't English very well yet. please understand even if you can't understand me clearly.
I have huge data table in UserControl.xaml, but downscale this UserControl object showing whole in MainWindow.
I want same size datatable showing of partially UserControl in MainWindow.
Like this image display way:
<Image>
<Image.Source>
<CroppedBitmap Source="<path to source image>" SourceRect="20,20,50,50"/>
</Image.Source>
</Image>
Showing UserControl in MainWindow like a SourceRect.
If I understand you correctly, you have several options. The first way, and I think the easiest is to use ViewBox control.
1. ViewBox
The Viewbox control inherited from Decorator is used to stretch or scale a child element, but it is scaled proportionally, ie you can not set the him size such 300x100.
Example
<Viewbox Width="300"
Height="300">
<DataGrid>
...
</DataGrid>
</ViewBox>
The second way is to use a screen capture of your control, that you would like to show, and then if you want to use CroppedBitmap.
2. Capturing screen
I found a great article by Pete Brown on this subject here:
Capturing Screen Images in WPF using GDI, Win32 and a little WPF Interop Help
In this article is an example, and it looks like this:
ScreenCapture
class ScreenCapture
{
public static BitmapSource CaptureFullScreen(bool addToClipboard)
{
return CaptureRegion(
User32.GetDesktopWindow(),
(int)SystemParameters.VirtualScreenLeft,
(int)SystemParameters.VirtualScreenTop,
(int)SystemParameters.VirtualScreenWidth,
(int)SystemParameters.VirtualScreenHeight,
addToClipboard);
}
// capture a window. This doesn't do the alt-prtscrn version that loses the window shadow.
// this version captures the shadow and optionally inserts a blank (usually white) area behind
// it to keep the screen shot clean
public static BitmapSource CaptureWindow(IntPtr hWnd, bool recolorBackground, Color substituteBackgroundColor, bool addToClipboard)
{
Int32Rect rect = GetWindowActualRect(hWnd);
Window blankingWindow = null;
if (recolorBackground)
{
blankingWindow = new Window();
blankingWindow.WindowStyle = WindowStyle.None;
blankingWindow.Title = string.Empty;
blankingWindow.ShowInTaskbar = false;
blankingWindow.AllowsTransparency = true;
blankingWindow.Background = new SolidColorBrush(substituteBackgroundColor);
blankingWindow.Show();
int fudge = 20;
blankingWindow.Left = rect.X - fudge / 2;
blankingWindow.Top = rect.Y - fudge / 2;
blankingWindow.Width = rect.Width + fudge;
blankingWindow.Height = rect.Height + fudge;
}
// bring the to-be-captured window to capture to the foreground
// there's a race condition here where the blanking window
// sometimes comes to the top. Hate those. There is surely
// a non-WPF native solution to the blanking window which likely
// involves drawing directly on the desktop or the target window
User32.SetForegroundWindow(hWnd);
BitmapSource captured = CaptureRegion(
hWnd,
rect.X,
rect.Y,
rect.Width,
rect.Height,
true);
if (blankingWindow != null)
blankingWindow.Close();
return captured;
}
// capture a region of the full screen
public static BitmapSource CaptureRegion(int x, int y, int width, int height, bool addToClipboard)
{
return CaptureRegion(User32.GetDesktopWindow(), x, y, width, height, addToClipboard);
}
// capture a region of a the screen, defined by the hWnd
public static BitmapSource CaptureRegion(
IntPtr hWnd, int x, int y, int width, int height, bool addToClipboard)
{
IntPtr sourceDC = IntPtr.Zero;
IntPtr targetDC = IntPtr.Zero;
IntPtr compatibleBitmapHandle = IntPtr.Zero;
BitmapSource bitmap = null;
try
{
// gets the main desktop and all open windows
sourceDC = User32.GetDC(User32.GetDesktopWindow());
//sourceDC = User32.GetDC(hWnd);
targetDC = Gdi32.CreateCompatibleDC(sourceDC);
// create a bitmap compatible with our target DC
compatibleBitmapHandle = Gdi32.CreateCompatibleBitmap(sourceDC, width, height);
// gets the bitmap into the target device context
Gdi32.SelectObject(targetDC, compatibleBitmapHandle);
// copy from source to destination
Gdi32.BitBlt(targetDC, 0, 0, width, height, sourceDC, x, y, Gdi32.SRCCOPY);
// Here's the WPF glue to make it all work. It converts from an
// hBitmap to a BitmapSource. Love the WPF interop functions
bitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
compatibleBitmapHandle, IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
if (addToClipboard)
{
//Clipboard.SetImage(bitmap); // high memory usage for large images
IDataObject data = new DataObject();
data.SetData(DataFormats.Dib, bitmap, false);
Clipboard.SetDataObject(data, false);
}
}
catch (Exception ex)
{
throw new ScreenCaptureException(string.Format("Error capturing region {0},{1},{2},{3}", x, y, width, height), ex);
}
finally
{
Gdi32.DeleteObject(compatibleBitmapHandle);
User32.ReleaseDC(IntPtr.Zero, sourceDC);
User32.ReleaseDC(IntPtr.Zero, targetDC);
}
return bitmap;
}
// this accounts for the border and shadow. Serious fudgery here.
private static Int32Rect GetWindowActualRect(IntPtr hWnd)
{
Win32Rect windowRect = new Win32Rect();
Win32Rect clientRect = new Win32Rect();
User32.GetWindowRect(hWnd, out windowRect);
User32.GetClientRect(hWnd, out clientRect);
int sideBorder = (windowRect.Width - clientRect.Width)/2 + 1;
// sooo, yeah.
const int hackToAccountForShadow = 4;
Win32Point topLeftPoint = new Win32Point(windowRect.Left - sideBorder, windowRect.Top - sideBorder);
//User32.ClientToScreen(hWnd, ref topLeftPoint);
Int32Rect actualRect = new Int32Rect(
topLeftPoint.X,
topLeftPoint.Y,
windowRect.Width + sideBorder * 2 + hackToAccountForShadow,
windowRect.Height + sideBorder * 2 + hackToAccountForShadow);
return actualRect;
}
}
Example of using:
private void CaptureRegionButton_Click(object sender, RoutedEventArgs e)
{
CapturedImage.Source = ScreenCapture.CaptureRegion(100, 100, 500, 500, true);
}
private void CaptureScreenButton_Click(object sender, RoutedEventArgs e)
{
CapturedImage.Source = ScreenCapture.CaptureFullScreen(true);
}
I have a DrawingBrush with some vector graphics. I want to convert it to BitmapSource as an intermediate step to getting it to Bitmap. What's the (best) way to do this?
public static BitmapSource BitmapSourceFromBrush(Brush drawingBrush, int size = 32, int dpi = 96)
{
// RenderTargetBitmap = builds a bitmap rendering of a visual
var pixelFormat = PixelFormats.Pbgra32;
RenderTargetBitmap rtb = new RenderTargetBitmap(size, size, dpi, dpi, pixelFormat);
// Drawing visual allows us to compose graphic drawing parts into a visual to render
var drawingVisual = new DrawingVisual();
using (DrawingContext context = drawingVisual.RenderOpen())
{
// Declaring drawing a rectangle using the input brush to fill up the visual
context.DrawRectangle(drawingBrush, null, new Rect(0, 0, size, size));
}
// Actually rendering the bitmap
rtb.Render(drawingVisual);
return rtb;
}
I'm writing a program in WPF application that simulates the game of life.
How can I preform GDI+ like graphics operations to create an Image that contains the grid of cells?
(Normally, in WinForms, I would have know how to do this operation).
Edit:
I used this code:
WriteableBitmap wb = new WriteableBitmap(width * 5, height * 5, 100, 100, new PixelFormat(), new BitmapPalette(new List<Color> { Color.FromArgb(255, 255, 0, 0) }));
wb.WritePixels(new Int32Rect(0, 0, 5, 5), new IntPtr(), 3, 3);
Background.Source = wb;
Background is a System.Windows.Controls.Image Control
You could use a WriteableBitmap or use a WPF container such as a Grid or Canvas with a lot of rectangles in it. A lot depends on the size of the gameboard. A WriteableBitmap might be better suited for a huge map and a canvas or grid might be easier for smaller sizes.
Is this what you are looking for?
I think you're making things harder on yourself by using WriteableBitmap.WritePixel. You'll have a much better time drawing with Shapes or using RendterTargetBitmap and a DeviceContext.
Here's some code on how you would draw using this method.
MainForm's XAML:
<Grid>
<Image Name="Background"
Width="200"
Height="200"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</Grid>
MainForm's Code-Behind:
private RenderTargetBitmap buffer;
private DrawingVisual drawingVisual = new DrawingVisual();
public MainWindow()
{
InitializeComponent();
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
buffer = new RenderTargetBitmap((int)Background.Width, (int)Background.Height, 96, 96, PixelFormats.Pbgra32);
Background.Source = buffer;
DrawStuff();
}
private void DrawStuff()
{
if (buffer == null)
return;
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawRectangle(new SolidColorBrush(Colors.Red), null, new Rect(0, 0, 10, 10));
}
buffer.Render(drawingVisual);
}
Adjust the Width/Height of the Image to whatever you desire. All of your drawing logic should be inside of the using statement. You'll find the methods on DrawingContext are much more flexible and easier to understand than WritePixel. Call "DrawStuff" whenever you want to trigger a redraw.
My problem is that the image loading seems to be uncorrectly from application resources. This is code:
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri(#"pack://application:,,,/WpfApplication3;component/Resources/Images/16x16_incorrect.png", UriKind.Absolute);
bi.EndInit();
ImageSource s = bi;
Image file 16x16_incorrect.png is 16x16 32bpp PNG file, but after executing above code, s.Width = s.Height = 21,59729.... I also have another file - 16x16_correct.png, when it is loaded, both the ImageSource's Width and Height are equal to 16,002.
Other images each of them are loading incorrectly & it looks blurred (or smoothly), because system stretches it from 16x16 to 21x21.
correct image :
incorrect image :
What is causing this? If the problem in source image files, how can I change ImageSource.Width to desired size in order to use this files?
If you don't want to change DPI externally you can do it with this:
public static BitmapSource ConvertBitmapTo96DPI(BitmapImage bitmapImage)
{
double dpi = 96;
int width = bitmapImage.PixelWidth;
int height = bitmapImage.PixelHeight;
int stride = width * bitmapImage.Format.BitsPerPixel;
byte[] pixelData = new byte[stride * height];
bitmapImage.CopyPixels(pixelData, stride, 0);
return BitmapSource.Create(width, height, dpi, dpi, bitmapImage.Format, null, pixelData, stride);
}
If you just need correct values in Image.Source.Width/Height you can do something like I did:
this.myImage.Tag = new double[] { bitmapImage.DpiX, bitmapImage.DpiY };
this.myImage.Source = bitmapImage;
and resize it like so:
public static void ResizeImage(Image img, double maxWidth, double maxHeight)
{
if (img == null || img.Source == null)
return;
double srcWidth = img.Source.Width;
double srcHeight = img.Source.Height;
// Set your image tag to the sources DPI value for smart resizing if DPI != 96
if (img.Tag != null && img.Tag.GetType() == typeof(double[]))
{
double[] DPI = (double[])img.Tag;
srcWidth = srcWidth / (96 / DPI[0]);
srcHeight = srcHeight / (96 / DPI[1]);
}
double resizedWidth = srcWidth;
double resizedHeight = srcHeight;
double aspect = srcWidth / srcHeight;
if (resizedWidth > maxWidth)
{
resizedWidth = maxWidth;
resizedHeight = resizedWidth / aspect;
}
if (resizedHeight > maxHeight)
{
aspect = resizedWidth / resizedHeight;
resizedHeight = maxHeight;
resizedWidth = resizedHeight * aspect;
}
img.Width = resizedWidth;
img.Height = resizedHeight;
}
You need to set the image resolution to 96 DPI (currently it's 71.12 for the incorrect png).
You can do it using the free paint.net program ( http://getpaint.net ) from the Image menu select Canvas size and set the "resolution" field to 96
This is because of the DPI of the images. WPF renders default with 96 dpi. If you look at the dpi of the incorrect png image. You will see that it is set to 72. This causes WPF to scale the image to 96 DPI and keep the original size.
There are two solutions.
You can:
Modify the DPI using e.g XnView. Set it to 96.
Set the Width and Height properties to 16, and the Stretch property to Uniform
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Image x:Name="MyIncorrectImageFixed" Source="http://i.piccy.info/i5/24/41/504124/16x16_incorrect.png" Width="16" Height="16" Stretch="Uniform" />
<Image x:Name="MyIncorrectImage" Source="http://i.piccy.info/i5/24/41/504124/16x16_incorrect.png" Stretch="None" Grid.Row="1" />
<Image x:Name="MyCorrectImage" Source="http://i.piccy.info/i5/22/41/504122/16x16_correct.png" Stretch="None" Grid.Row="2" />
I'm trying to save a wpf control to file, but I'm applying a PixelShader effect to it, and when I try to save, the saved image is entirely white, black or red... deppends on the parameters of the effect.
I'm using the code here: WPF - Programmatic Binding on a BitmapEffect
how can I properly save it?
thanks!
UPDATE:
the code I'm using is:
BitmapSource bitmap = preview.Source as BitmapImage;
Rectangle r = new Rectangle();
r.Fill = new ImageBrush(bitmap);
r.Effect = effect;
Size sz = new Size(bitmap.PixelWidth, bitmap.PixelHeight);
r.Measure(sz);
r.Arrange(new Rect(sz));
var rtb = new RenderTargetBitmap(bitmap.PixelWidth, bitmap.PixelHeight, bitmap.DpiX, bitmap.DpiY, PixelFormats.Pbgra32);
rtb.Render(r);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(rtb));
Stream stm = File.Create("new.png");
png.Save(stm);
stm.Close();
Try this piece of code:
/// <summary>
/// Creates a screenshot of the given visual at the desired dots/inch (DPI).
/// </summary>
/// <param name="target">Visual component of which to capture as a screenshot.</param>
/// <param name="dpiX">Resolution, in dots per inch, in the X axis. Typical value is 96.0</param>
/// <param name="dpiY">Resolution, in dots per inch, in the Y axis. Typical value is 96.0</param>
/// <returns>A BitmapSource of the given Visual at the requested DPI, or null if there was an error.</returns>
public static BitmapSource CaptureScreen(Visual target, double dpiX, double dpiY)
{
if (target == null)
{
return null;
}
RenderTargetBitmap rtb = null;
try
{
// Get the actual size
Rect bounds = VisualTreeHelper.GetDescendantBounds(target);
rtb = new RenderTargetBitmap((int)(bounds.Width * dpiX / 96.0),
(int)(bounds.Height * dpiY / 96.0),
dpiX,
dpiY,
PixelFormats.Pbgra32);
DrawingVisual dv = new DrawingVisual();
using (DrawingContext ctx = dv.RenderOpen())
{
VisualBrush vb = new VisualBrush(target);
ctx.DrawRectangle(vb, null, new Rect(new Point(), bounds.Size));
}
rtb.Render(dv);
}
catch (Exception ex)
{
Console.Error.WriteLine("Error capturing image: " + ex.Message);
return null;
}
return rtb;
}