How to extract a System.Drawing.Rectangle from a BitmapImage object? [duplicate] - wpf

This question already has answers here:
WPF - How to zoom specific area in an image
(1 answer)
How to effectively crop and scale image data
(1 answer)
Closed 5 years ago.
I'm stuck and I need advice, I've gone down multiple rabbit holes and haven't found any rabbits... I don't know if this is a conversion issue, resolution issue, or I just need to use different object types, or what. I'm new to working with images. Please help me!
I have a WPF application which loads in images of various types, and it loads them as BitmapImage objects. They are displayed in an image control:
<Image Width="70" Height="90" Source="{Binding Path=Source}" />
This application provides the ability for the user to draw a rectangle around part of the image, and then the application saves that rectangle as a separate image to disk. I'm using System.Drawing.Rectangle to store the rectangle object. Here's a code snippet, which leaves me with just a much smaller portion of the image than the actual rectangle is.
Bitmap segment = new Bitmap(item.Width, item.Height);
var g = Graphics.FromImage(segment);
g.DrawImage(source, 0, 0, new Rectangle(item.X, item.Y, item.Width, item.Height), GraphicsUnit.Pixel);
segment.Save(imagePath, ImageFormat.Jpeg);
The problem occurs when I go to write the rectangle out to a file, because Rectangle is compatible with Bitmap, but not with BitmapImage, at least when it comes to sizing. When I load my images into BitmapImage it changes their size with the PixelHeight and PixelWidth properties. It seems to roughly double in size. I don't know why it does that. But BitmapImage is more compatible with WPF, so I'm using that. The straight Bitmap object doesn't have those pixel properties. And the rectangle is like the regular size (not the doubled up Pixel size).
So I tried using CroppedBitmap since that's compatible with BitmapImage, to get the selected image. So I got the size increase ratio and increased my rectangle size by that, to create the CroppedBitmap, but the sizing is a little too small. It's cutting off the outer edges. "item" is my rectangle. Here's the code sample for that. Should I stick with CroppedBitmap and just throw in some padding? Or is there something else I should be doing?
double heightFactor = source.PixelHeight / source.Height;
double widthFactor = source.PixelWidth / source.Width;
CroppedBitmap segment = new CroppedBitmap(source, new Int32Rect(item.X, item.Y, Convert.ToInt32(Math.Ceiling(item.Width * widthFactor)), Convert.ToInt32(Math.Ceiling(item.Height * heightFactor))));

Related

when to use WriteableBitmap and BitmapImage in silverlight

I am trying to display image using BitmapImage for some time and it worked.I have changed the image and it stopped working.
For Bitmapimage I was using this code:
`ms.Seek(0, SeekOrigin.Begin); // ms is memory stream
BitmapImage b = new BitmapImage();
b.SetSource(ms);
image.ImageSource = b;`
I have ran into piece of code where it was checking if the length of the bytes[] ==14400
if(bytes.length == 14400)
{
var bmp = new WriteableBitmap(width, height);
Buffer.BlockCopy(buffer, 0, bmp.Pixels, 0, buffer.Length);
}
I want to know when to use WriteableBitmap and BitmapImage .
From iProgrammer:
Bitmaps are generally implemented as immutable objects. What this means is that once you create a bitmap you can't make any changes to it. You can manipulate bitmaps by creating new versions, which then immediately become immutable....
The WriteableBitmap, as its name suggests, isn't immutable and you can get at its individual pixels and manipulate them as much as you want. This is the ideal way to work when you need dynamic bitmaps.
iProgrammer - WriteableBitmap
From MSDN:
"Use the WriteableBitmap class to update and render a bitmap on a per-frame basis..." MSDN - WriteableBitmap Class
The Examples section of the MSDN article also shows how to update a WritableBitmap image when responding to mouse events. The code in the example erases pixels of the image by setting the pixel's ARGB values to 0 when the mouse's right button is down. The code also shows how to update individual pixels in the image where the mouse's left button is down. Essentially the code shows a rudimentary pixel image editor.
The point, however, is that you can't change image data when using regular bitmap - you have to use WritableBitmap instead. You can, however, render both if you wish.

WPF: Get 1:1 pixel rendering in Image whose size is modified with a LayoutTransform

Let me start by saying I have searched extensively on this and have found partial answers, but nothing that works all the way.
I need to display bitmap images in my WPF application that are not scaled. I want to map 1 pixel of the bitmap to 1 pixel of the display. I do intend to support multiple resolutions by shipping multiple versions of my bitmaps. But I want to know that, when a particular bitmap has been chosen, it will be rendered EXACTLY as it has been designed.
My strategy for overcoming the automatic scaling that happens in WPF is to look at what is being applied automatically (by virtue of the OS DPI setting), and then apply a LayoutTransform that is the inverse, to the outermost container of my window.
This ensures that, no matter what the user's DPI settings are, the app renders the contents of the window a 1:1 ratio of WPF pixels to hardware pixels. So far, so good.
That code looks like this. (Presume this is called with an argument of 1.0).
private void SetScale(double factor)
{
// First note the current window transform factor.
// This is the factor being applied to the entire window due to OS DPI settings.
Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice;
double currentWindowTransformFactorX = m.M11;
double currentWindowTransformFactorY = m.M22;
// Now calculate the inverse.
double currentWindowTransformInverseX = (1 / m.M11);
double currentWindowTransformInverseY = (1 / m.M22);
// This factor will put us "back to 1.0" in terms of a device-independent-pixel to physical pixel mapping.
// On top of this, we can apply our caller-specified factor.
double transformFactorX = currentWindowTransformInverseX * factor;
double transformFactorY = currentWindowTransformInverseY * factor;
// Apply the transform to the registered target container
ScaleTransform dpiTransform = new ScaleTransform(transformFactorX, transformFactorY);
if (dpiTransform.CanFreeze)
dpiTransform.Freeze();
this.pnlOutermost.LayoutTransform = dpiTransform;
}
Up to here, everything works great. No matter what I set my Windows DPI to, the contents of that main container are always exactly the same size, and the bitmaps are rendered precisely.
Now comes the fun part. I want to support different screen resolutions by providing resolution-specific artwork, and scaling my entire UI as appropriate.
It turns out that LayoutTransform works really well for this. So if I call the above method with 1.25 or 1.5 or whatever, the entire UI scales and everything looks perfect...except my images, which are back to looking stretched and crappy, even when I change the source to be an image that is exactly the right size for the new, scaled dimensions.
For example, suppose I have an image that is 100x100 in the XAML. My artwork comes in three flavors: 100x100, 125x125, and 150x150. When I scale the container that houses the image, I also change the source of that image to the appropriate one.
Interestingly, if the image object is sitting at a position that, when scaled by the factor, yields integral results, then the scaled image looks fine. That is to say, suppose the image has the following properties:
Canvas.Left = 12
Canvas.Top = 100
When we apply a factor of 1.25, this yields 15 and 125, and the image looks great. But if the image is moved by one pixel, to say:
Canvas.Left = 13
Canvas.Top = 100
Now when we apply a factor of 1.25, we get 15.25 and 125, and the result looks crappy.
Clearly, this looks like some kind of rounding issue or something like that. So I've tried:
UseLayoutRounding="True"
SnapsToDevicePixels="True"
RenderOptions.EdgeMode="Aliased"
RenderOptions.BitmapScalingMode="NearestNeighbor"
I've tried these in the window, in the container being scaled, and in the image object. And nothing works. And the BitmapScalingMode doesn't really make sense anyway, because the image should not be being scaled at all.
Eternal thanks to anyone who can shed some light on this.
I had the exact same problem so it looks like this has not been fixed in the framework as of 2019.
I managed to solve the issue using a three step approach.
Enable layout rounding on my top level UI element
<UserControl ... UseLayoutRounding="True">
Apply the inverse LayoutTransform to my Imageobjects (the LayoutTransformwas applied to the parent ListBox).
<Image ... LayoutTransform="{Binding Path=LayoutTransform.Inverse,
Mode=OneTime,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}}">
Subclass Imageand add a custom override for OnRender.
internal class CustomImage: Image {
private PresentationSource presentationSource;
public CustomImage() => Loaded += OnLoaded;
protected override void OnRender(DrawingContext dc) {
if (this.Source == null) {
return;
}
var offset = GetOffset();
dc.DrawImage(this.Source, new Rect(offset, this.RenderSize));
}
private Point GetOffset() {
var offset = new Point(0, 0);
var root = this.presentationSource?.RootVisual;
var compositionTarget = this.presentationSource?.CompositionTarget;
if (root == null || compositionTarget == null) {
return offset;
}
// Transform origin to device (pixel) coordinates.
offset = TransformToAncestor(root).Transform(offset);
offset = compositionTarget.TransformToDevice.Transform(offset);
// Round to nearest integer value.
offset.X = Math.Round(offset.X);
offset.Y = Math.Round(offset.Y);
// Transform back to local coordinate system.
offset = compositionTarget.TransformFromDevice.Transform(offset);
offset = root.TransformToDescendant(this).Transform(offset);
return offset;
}
private void OnLoaded(object sender, RoutedEventArgs e) {
this.presentationSource = PresentationSource.FromVisual(this);
InvalidateVisual();
}
}
}
The code from step 3 is based on this blogpost.
By using the CustomImage class in my XAML instead of Image and binding to a BitmapSource that will return a properly sized image based on the current scale factor, I managed to achieve great looking images without any unwanted scaling.
Note that you might need to call InvalidateVisual on your images when they need to be re-rendered.

WPF RenderTargetBitmap Missing Elements

I have a TreeView with small icons displayed in the data template. I'm trying to save the Treeview as a PNG using RenderTargetBitmap.
The image saves correctly on small data sets. However, if the data set becomes too large, some of the icons are excluded from the final image. The magic number seems to be 200 items. It doesn't seem to matter if the tree is deep or wide, after 200 items, the icons are not rendered.
Added Code
So here is my code that I'm using to create an image.
RenderTargetBitmap targetBitmap = new RenderTargetBitmap(
(int)_treeView.ActualWidth,
(int)_treeView.ActualHeight,
96, 96, PixelFormats.Default);
targetBitmap.Render(_treeView);
Added Screen Shot
Notice the missing icons way over on the right side of the tree.
Now if I collapse a few branches, thus hiding some of the other icons, then these icons are included. It's almost like RenderTargetBitmap.Render doesn't have the power to render all of the icons. Or it may have something to do with virtual panels.
Here is a closer look.
What I immediately noticed that you have HUGE image. Width 12000. I am surprised that you even got that close.
As MSDN states, the texture width/height are limited by DirectX texture limits.
The maximum rendered size of a XAML visual tree is restricted by the maximum dimensions of a Microsoft DirectX texture; for more info see Resource Limits (Direct3D). This limit can vary depending on the hardware whre the app runs. Very large content that exceeds this limit might be scaled to fit. If scaling limits are applied in this way, the rendered size after scaling can be queried using the PixelWidth and PixelHeight properties. For example, a 10000 by 10000 pixel XAML visual tree might be scaled to 4096 by 4096 pixels, an example of a particular limit as forced by the hardware where the app runs.
http://msdn.microsoft.com/library/windows/apps/dn298548
I suspect these things:
Virtualization cutting off some things - I've had the exact problem in past with DataGrid, and the problem was virtualization. Your case doesn't seem like one though.
Too big texture can cause undefined behaviour.
You can try disabling hardware acceleration. The thing causes quite few hardcore bugs. http://msdn.microsoft.com/en-us/library/system.windows.media.renderoptions.processrendermode.aspx
Other than that - it will be tricky, but I am pretty sure that it will work beautifully:
1) start with the root object, and traverse the root object childrens recursively, until you find an object that is less than 1000 x 1000. Take picture of it using RenderTargetBitmap(BMP) and merge it to IN-MEMORY-BMP. Do it for each children.
You should be able to calculate all this stuff.
For the records: there's a workaround.
Instead of rendering your Visual directly with RenderTargetBitmap, use an interim DrawingVisual. Paint your Visual into the DrawingVisual using a VisualBrush and then use RenderTargetBitmap with the DrawingVisual.
Like this:
public BitmapSource RenderVisualToBitmap(Visual visual)
{
var contentBounds = VisualTreeHelper.GetContentBounds(visual);
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
var visualBrush = new VisualBrush(visual);
drawingContext.DrawRectangle(visualBrush, null, contentBounds);
}
var renderTargetBitmap = new RenderTargetBitmap((int)contentBounds.Width, (int)contentBounds.Height, 96, 96, PixelFormats.Default);
renderTargetBitmap.Render(drawingVisual);
return renderTargetBitmap;
}
Note however that as your VisualBrush gets bigger the resulting image gets more and more fuzzy (when rendering with high DPI). To work around this problem use a series of smaller VisualBrush "tiles" as described here:
https://srndolha.wordpress.com/2012/10/16/exported-drawingvisual-quality-when-using-visualbrush/

Create and resize an image in WPF from System.Drawing.Bitmap areas

I'm trying to implement a function that takes a System.Drawing.Bitmap object and renders it on a WPF Canvas. The bitmap has to be cropped and joined a few times before rendering.
Environment: WPF application running on .NET 3.5 SP1
Input: System.Drawing.Bitmap object, of size 800x600 and pixel format RGB24
Goal: to display an image which is composed of two stripes of the input bitmap (on one line). The stripes are two bitmap halves - (0,0,800,300) and (0,300,800,600). Later on I want to be able to scale the image up or down.
I've already implemented a solution with GDI and Graphics.DrawImage (that renders into a Bitmap object), but I want to improve performance (this function could be called 30 times per second).
Is there a faster way to implement this with WPF, assuming I want to render the image on a WPF window?
The best solution I found so far is using WriteableBitmap, something like this:
void Init()
{
m_writeableBitmap = new WriteableBitmap(DesiredWidth, DesiredHeight, DesiredDpi, DesiredDpi, PixelFormats.Pbgra32, null);
{
void CopyPixels(System.Drawing.Bitmap frame, Rectangle source, Point destBegin)
{
var bmpData = frame.LockBits(source, ImageLockMode.ReadOnly, frame.PixelFormat);
m_writeableBitmap.Lock();
var dest = new Int32Rect(destBegin.X, destBegin.Y, bmpData.Width, bmpData.Height);
m_writeableBitmap.WritePixels(dest, bmpData.Scan0, bmpData.Stride * bmpData.Height, bmpData.Stride);
m_writeableBitmap.Unlock();
frame.UnlockBits(bmpData);
}
CopyPixels would be called twice for the use case I described in my question (two stripes).

Resizing images in Silverlight 3 using WriteableBitmap

This is my first day with Silverlight. I’m trying to prototype an application which (among other functions) should be able to resize user supplied images. It should be able to handle and display several resized images at once. The most obviously approaches I've tried seem to "leak" memory in the sense that the original bitmaps are still being referenced in some way which causes Silverlight to allocate hundreds of megabytes of memory after a while. I just want to be able to load the images one by one, resize them and keep the small versions.
To be precise, I've tried the following:
Creating a list of System.Windows.Controls.Image's (and scaling them). I'm not surprised that this has not worked.
Creating a list of rectangles filled by image brushes. I'm not surprised either.
Rendering the bitmaps into System.Windows.Media.Imaging.WriteableBitmap. I expected this to perform well; I assumed that the bitmaps are really just drawn directly and not referenced in any way. However, the memory consumption indicated otherwise.
Here is a snippet of the relevant piece of code:
// create image source
Stream stream = file.OpenRead();
BitmapImage bmpImg = new BitmapImage();
bmpImg.SetSource(stream);
stream.Close();
// create temporary image from it
Image tmpImg = new Image();
tmpImg.Source = bmpImg;
// this is required by WriteableBitmap
tmpImg.Measure(new Size(100, 100));
tmpImg.Arrange(new Rect(0, 0, 100, 100));
// prepare scaling to 100x100
ScaleTransform scaleTrans = new ScaleTransform();
double scale = (double)100 / (double)Math.Max(bmpImg.PixelHeight, bmpImg.PixelWidth);
scaleTrans.CenterX = 0;
scaleTrans.CenterY = 0;
scaleTrans.ScaleX = scale;
scaleTrans.ScaleY = scale;
// render
WriteableBitmap writeableBitmap = new WriteableBitmap(100, 100);
writeableBitmap.Render(tmpImg, scaleTrans);
writeableBitmap.Invalidate();
// final image
Image img = new Image();
img.Source = writeableBitmap;
I hope I'm not missing anything silly, but it looks to OK to me and does the right thing (except the memory problem). Please also bear in mind that code quality is not supposed to be a production quality; it’s just a quick and dirty prototype.
I noticed that I’m not alone; I found questions related to image manipulation in Silverlight. I’m also aware of the fact that I could use some third party library, do processing on the server or write something even myself, but I’m surprised that Silverlight does not offer any basic image manipulation functions. It does not seem to be an uncommon requirement given where Silverlight positions itself.
Have you looked at the WriteableBitmapEx project? It's an open source project with a tonne of extension methods for the WriteableBitmap class. Here's how you resize:
BitmapImage image = new BitmapImage();
image.SetSource(dialog.File.OpenRead());
WriteableBitmap bitmap = new WriteableBitmap(image);
WriteableBitmap resizedBitmap = bitmap.Resize(500, 500, WriteableBitmapExtensions.Interpolation.Bilinear);
// For uploading
byte[] data = resizedBitmap.ToByteArray();
I don't know about the specifics here, but if you're leaking resources - you might look at which of your objects implements the IDisposable interface. I would guess that the Stream and Image classes implement this interface. And if they do - calling Dispose() on them (or wrapping their usage in a "Using" statement) will cause them to free their resources immediately rather than waiting for the garbage collector to kick in eventually.

Resources