I'm creating and application that needs to add and remove a lot of UIElement to a Canvas.
Basically a Canvas contains a collection of UIElement and automatically renders/updates it on the screen depending what it contains.
In order to avoid having a tons of UIElements who overlap each others on the screen I prefer to add all of them on a secondary Canvas then create a Image from it (thanks to WritableBitmap). Finally I add this Image on my current Canvas.
By allowing to have only few image on my Canvas I expect to have better performance.
Unfortunately it seems I can't delete completely the WritableBitmap, even if I set it to null.
The following code illustrates it :
//My constructor
public WP8Graphics()
{
//Here my collection DataBinded with the Canvas from the Mainpage
this.UIElements = new ObservableCollection<UIElement>();
//The secondary Canvas
GraphicCanvas = new Canvas();
GraphicCanvas.Height = MainPage.CurrentCanvasHeight;
GraphicCanvas.Width = MainPage.CurrentCanvasWidth;
}
///This method can be hit thousand times, it basically create a rectangle
public void fillRect(int x, int y, int width, int height)
{
// some code
// CREATE THE RECTANGLE rect
GraphicCanvas.Children.Add(rect); // My secondary Canvas
WriteableBitmap wb1 = new WriteableBitmap(GraphicCanvas, null);
wb1.Invalidate();
WriteableBitmap wb2 = new WriteableBitmap((int)MainPage.CurrentCanvasWidth, (int)MainPage.CurrentCanvasHeight);
for (int i = 0; i < wb2.Pixels.Length; i++)
{
wb2.Pixels[i] = wb1.Pixels[i];
}
wb2.Invalidate();
wb1 = null;
Image thumbnail = new Image();
thumbnail.Height = MainPage.CurrentCanvasHeight;
thumbnail.Width = MainPage.CurrentCanvasWidth;
thumbnail.Source = wb2;
this.UIElements.Add(thumbnail);
}
After something like 24 WriteableBitmap created a OutOfMemoryException appears.
I read many articles about this problem and in my case it seems the WriteableBitmap depends on my GraphicCanvas and remains because there still have a reference to it. I can't delete my Graphic Canvas nor set myImage source to null.
I have 2 question :
Is there another way to create an image from Canvas or a collection of UIElements ?
Is it possible to remove the reference who keeps that WriteableBitmap alive ?
I hope to be enough clear and easy to read.
Thank you for reading.
EDITED with atomaras suggestion, but still the same problem
WriteableBitmap wb1 = new WriteableBitmap(GraphicCanvas, null);
This line still throws OutOfMemoryException.
You need to copy the pixels of the original writeablebitmap (that will hold on to the GraphicsCanvas) to a new writeablebitmap.
Take a look at this great post http://www.wintellect.com/blogs/jprosise/silverlight-s-big-image-problem-and-what-you-can-do-about-it
Also why do you keep all of the writeablebitmaps in the UIElements collection? Wouldn't the latest one suffice? Cant you clear the UIElements collection right before adding the latest/new bitmap?
Related
I tried looking through a few questions on here already but none seemed to fit.
This is what i've tried:
listBox1.BackColor = Color.FromArgb(85, 200, 200, 200);
But at runtime, there's an error. It states that the component doesn't support transparency. I'm asking on here because there could be a workaround. If anyone could help, that'd be great. Thanks in advance!
I suggest going for a ListView in Details View instead.
This is a more modern control, much more powerful and also more supportive when it comes to adding some extra styling..
ListView has a BackgroundImage which alone may be good enough. It doesn't support transparency, though.
But with a few tricks you can make it fake it by copying the background that would shine through..:
void setLVBack(ListView lv)
{
int alpha = 64;
Point p1 = lv.Parent.PointToScreen(lv.Location);
Point p2 = lv.PointToScreen(Point.Empty);
p2.Offset(-p1.X, -p1.Y );
if (lv.BackgroundImage != null) lv.BackgroundImage.Dispose();
lv.Hide();
Bitmap bmp = new Bitmap(lv.Parent.Width, lv.Parent.Height);
lv.Parent.DrawToBitmap(bmp, lv.Parent.ClientRectangle);
Rectangle r = lv.Bounds;
r.Offset(p2.X, p2.Y);
bmp = bmp.Clone(r, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(bmp))
using (SolidBrush br = new SolidBrush(Color.FromArgb(alpha, lv.BackColor)))
{
g.FillRectangle(br, lv.ClientRectangle);
}
lv.BackgroundImage = bmp;
lv.Show();
}
A few notes:
I hide the listview for a short moment while getting the background pixels
I calculate an offset to allow borders; one could (and maybe should?) also use SystemInformation.Border3DSize.Height etc..
I crop the right area using a bitmap.Clone overload
finally I paint over the image with the background color of the LV, green in my case
you can set the alpha to control how much I paint over the image
Also note that I dispose of any previous image, so you can repeat the call when necessary, e.g. when sizes or positions change or the background etc..
The ListView overlaps a PictureBox (left) but sits on a TabPage with an image of its own.
Result:
Does anybody know of any ways to use an image as a mask for another image in UWP, the only masking function I can see is CompositionMaskBrush which I don't believe can achieve what I want.
An example of what I'm looking to achieve is the following.
I have a solid black PNG in the shape of a mobile phone case, the user adds their own image which is then clipped and masked to the dimensions of the solid black PNG - Resulting in the image below.
Any help whatsoever would be greatly appreciated. I've spent quite a while browsing for a solution.
Example Image Here
Just posting for anybody else who needs and answer to this, but I finally managed to find a solution using Win2D and an Imageloader.
Here is a link to the ImageLoader. Note that I had to roll back a few versions in order make it work how the documentation states. The link below is to the version that I'm using. Anything later than this version will not work with the sample code I'm going to post.
https://www.nuget.org/packages/Robmikh.Util.CompositionImageLoader/0.4.0-alpha
private Compositor _compositor;
private IImageLoader _imageLoader;
private CompositionEffectFactory _effectFactory;
private async void InitMask()
{
// Store our Compositor and create our ImageLoader.
_compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
_imageLoader = ImageLoaderFactory.CreateImageLoader(_compositor);
// Setup our effect definition. First is the CompositeEffect that will take
// our sources and produce the intersection of the images (because we selected
// the DestinationIn mode for the effect). Next we take our CompositeEffect
// and make it the source of our next effect, the InvertEffect. This will take
// the intersection image and invert the colors. Finally we take that combined
// effect and put it through a HueRotationEffect, were we can adjust the colors
// using the Angle property (which we will animate below).
IGraphicsEffect graphicsEffect = new HueRotationEffect
{
Name = "hueEffect",
Angle = 0.0f,
Source = new InvertEffect
{
Source = new CompositeEffect
{
Mode = CanvasComposite.DestinationIn,
Sources =
{
new CompositionEffectSourceParameter("image"),
new CompositionEffectSourceParameter("mask")
}
}
}
};
// Create our effect factory using the effect definition and mark the Angle
// property as adjustable/animatable.
_effectFactory = _compositor.CreateEffectFactory(graphicsEffect, new string[] { "hueEffect.Angle" });
// Create MangedSurfaces for both our base image and the mask we'll be using.
// The mask is a transparent image with a white circle in the middle. This is
// important since the CompositeEffect will use just the circle for the
// intersectionsince the rest is transparent.
var managedImageSurface = await _imageLoader.CreateManagedSurfaceFromUriAsync(new Uri("http://sendus.pics/uploads/" + ImagePass + "/0.png", UriKind.Absolute));
//var managedImageSurface = await _imageLoader.CreateManagedSurfaceFromUriAsync(new Uri("ms-appx:///Assets/colour.jpg", UriKind.Absolute));
var managedMaskSurface = await _imageLoader.CreateManagedSurfaceFromUriAsync(new Uri("ms-appx:///" + MaskImage, UriKind.Absolute));
// Create brushes from our surfaces.
var imageBrush = _compositor.CreateSurfaceBrush(managedImageSurface.Surface);
var maskBrush = _compositor.CreateSurfaceBrush(managedMaskSurface.Surface);
// Create an setup our effect brush.Assign both the base image and mask image
// brushes as source parameters in the effect (with the same names we used in
// the effect definition). If we wanted, we could create many effect brushes
// and use different images in all of them.
var effectBrush = _effectFactory.CreateBrush();
effectBrush.SetSourceParameter("image", imageBrush);
effectBrush.SetSourceParameter("mask", maskBrush);
// All that's left is to create a visual, assign the effect brush to the Brush
// property, and attach it into the tree...
var visual = _compositor.CreateSpriteVisual();
visual.Size = new Vector2(MaskH, MaskW);
visual.Offset = new Vector3(0, 300, 0);
visual.Brush = effectBrush;
ElementCompositionPreview.SetElementChildVisual(this, visual);
}
Here's my buffer for an animation:
Bitmap PixBuffer;
Here's how I create it:
PixBuffer = new Bitmap(ClientRectangle.Width, ClientRectangle.Height, PixelFormat.Format32bppArgb);
Here's how I draw on it:
Graphics Renderer { get { return Graphics.FromImage(PixBuffer); } }
To make long story short. It works. I draw. I see changes. I use the bitmap as BackgroundImage for a window. Since the window has DoubleBuffered = true, it's silky smooth and fast.
OK, and the WTF part. I try to clone a slice of my bitmap, or even whole thing:
PixBuffer = (Bitmap)PixBuffer.Clone();
It doesn't make much sense, it should do nothing with what's displayed. But guess what - the clone is EMPTY! Exactly the same result if I try to draw PixBuffer on a new bitmap. The contents of PixBuffer is displayed. It can be even stretched as windows background. But I see no way to copy it. RotateFlip has no effect too.
What am I doing wrong? How to get pixel data of what I drew?
Freeze = (Bitmap)PixBuffer.Clone();
using (var g = Graphics.FromImage(Freeze)) {
g.FillRectangle(BrushF, 0, 0, 100, 100);
g.CompositingMode = CompositingMode.SourceOver;
g.Dispose();
}
var test = (Bitmap)Freeze.Clone();
BackgroundImage = test;
When I set PixBuffer as BackgroundImage - I get my drawn image. When I set Freeze as BackgroundImage - I get a square.
Then, if I clone Freeze to let's say Freeze1 - I still get my square, so cloning actually works on some bitmaps. But on PixBuffer NO JOY!
PixBuffer is not drawn in one frame. It is drawn as progressing animation during ca 30s. After animation completes - I have still screen - this screen I want to have as a normal bitmap to manipulate (like scaling and such). It seems like PixBuffer is write-only. I can still draw on it, but I can't copy anything from it.
I even tried to convert it to Icon and then back to Bitmap - but it's exactly the same like I was doing operations on empty Bitmap object.
But it IS NOT EMPTY! I tested it. I removed the BackgroundImage. I set another image in its place. And then I set PixBuffer as BacgroundImage again - and it is not empty, there is all I drew.
I'm missing something.
It's one of those very nasty bugs in code.
void RenderFrame(object sender, EventArgs e) {
using (var r = Graphics.FromImage(PixBuffer)) r.Clear(Color.Transparent);
var end = false;
for (var i = 0; i < Speed; i++) if (end = !UnmaskOne()) break;
RenderText();
RenderPattern();
if (end) FreezeContent = true;
Refresh();
}
I tried to copy my bitmap in UnmaskOne() method, which is called directly after clearing the frame, since this method can detect, if there's nothing more to unmask. However I had to wait with copying the bitmap - it should be drawn first with RenderText() and RenderPattern() methods. No magic here. Just plain human error.
how can I merge canvas in one image ? I need do this because I want to save merge image.
Here is my code:
WriteableBitmap wb = new WriteableBitmap(50, 50);
wb.LoadJpeg(stream);
Image t = new Image();
t.Source = wb;
Canvas.SetLeft(t, 130);
Canvas.SetTop(t, 130);
canvas1.Children.Add(t);
So now I want to merge these two images into one and use my save function.
You can use Graphics.FromImage() and Graphics.DrawImage()
http://msdn.microsoft.com/en-us/library/system.drawing.graphics.fromimage%28v=vs.110%29.aspx
http://msdn.microsoft.com/en-us/library/42807xh1%28v=vs.110%29.aspx
// Pseudo ...
using (Graphics gfx = Graphics.FromImage(image1))
{
gfx.DrawImage(image2, new Point(0, 0));
}
// image1 is now merged with image 2
You have not specified in details what do you mean by "merge". Do you mean, overlay the images on top of each other (if that's the case, what overlay mode? add? multiply? normal?) or merge the images side by side into a larger image (like taking 3 shots with a camera and then combining them into one long photo)? Either way, you will want to look at the System.Drawing namespace.
Assuming the latter one is the case. Here's what you'll do:
Image a = ...;
Image b = ...;
//assuming equal height, and I forget whether the ctor is width first or height first
Image c = new Image(a.Width + b.Width, a.Height);
Graphics g = Graphics.FromImage(c);
g.DrawImage(...); //a lot of overloads, better check out the documentation
SaveImage(c); //depending on how you want to save it
g.Dispose();
You need a third-party library, like WriteableBitmapEx. Look at the Blit method.
I've modified the SuperContextMenuStrip found at CodeProject to meet some of my projects needs. I'm using it as a tooltip for map markers on a GMap.NET Map Control. Here is a sample of what it looks like:
What I would like to do is pretty this up a little by making it look more like a bubble. Similar to an old Google Maps stytle tooltip:
I've spent some time searching on control transparency and I know this isn't an easy thing. This SO question in particular illustrates that.
I have considered overriding the OnPaint method of the SuperContextMenuStrip to draw a background of the GMap.NET control that is underneath the SuperContextMenuStrip, but even that would fail in cases where the marker is hanging off the GMap.NET control:
What is the correct way to create the type of transparency I am looking for?
In Windows Forms, you achieve transparency (or draw irregularly shaped windows) by defining a region. To quote MSDN
The window region is a collection of pixels within the window where
the operating system permits drawing.
In your case, you should have a bitmap that you will use as a mask. The bitmap should have at least two distinct colors. One of these colors should represent the part of the control that you want to be transparent.
You would then create a region like this:
// this code assumes that the pixel 0, 0 (the pixel at the top, left corner)
// of the bitmap passed contains the color you wish to make transparent.
private static Region CreateRegion(Bitmap maskImage) {
Color mask = maskImage.GetPixel(0, 0);
GraphicsPath grapicsPath = new GraphicsPath();
for (int x = 0; x < maskImage.Width; x++) {
for (int y = 0; y < maskImage.Height; y++) {
if (!maskImage.GetPixel(x, y).Equals(mask)) {
grapicsPath.AddRectangle(new Rectangle(x, y, 1, 1));
}
}
}
return new Region(grapicsPath);
}
You would then set the control’s Region to the Region returned by the CreateRegion method.
this.Region = CreateRegion(YourMaskBitmap);
to remove the transparency:
this.Region = new Region();
As you can probably tell from the code above, creating regions is expensive resource-wise. I'd advice saving regions in variables should you need to use them multiple times. If you use cached regions this way, you'd soon experience another problem. The assignment would work the first time but you would get an ObjectDisposedException on subsequent calls.
A little investigation with refrector would reveal the following code within the set accessor of the Region Property:
this.Properties.SetObject(PropRegion, value);
if (region != null)
{
region.Dispose();
}
The Region object is disposed after use!
Luckily, the Region is clonable and all you need to do to preserve your Region object is to assign a clone:
private Region _myRegion = null;
private void SomeMethod() {
_myRegion = CreateRegion(YourMaskBitmap);
}
private void SomeOtherMethod() {
this.Region = _myRegion.Clone();
}