Copy UI element with adorner - c#

I am working on taking screenshot of UI Element (WPF) in various size and i'm able to achieve this using "RenderTargetBitmap. But the UIElement which has an Adorner part is not coming while take copy. What should i do to achieve this. Any reference or code snippet?

As far as I know, elements don't have direct references to their adorners. Adorners do reference their element through AdornedElement though, so you could search for adorners assigned to your element like this:
var layer = AdornerLayer.GetAdornerLayer(element);
var adorners = layer.GetVisualChildren().Cast<Adorner>().Where(a => a.AdornedElement == element);
Here GetVisualChildren is an extension method which is defined as:
public static IEnumerable<DependencyObject> GetVisualChildren(this DependencyObject current) {
return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(current)).Select(i => VisualTreeHelper.GetChild(current, i));
}
The size of the adorner seems to include the size of the adorned element (although I'm not sure if this is always the case), so if there is only one adorner, that is your screenshot size. If there is more than one adorner, you would need to find the maximum for each bound (left, top, right, bottom) to calculate the screenshot region.
You will need to capture the AdornerDecorator which contains both the elements being adorned and the AdornerLayer (layer in the above code). That would be the visual parent of the layer:
var container = VisualTreeHelper.GetParent(layer) as Visual;
Once you have the container, you can render it with RenderTargetBitmap and crop it to the screenshot region.
For the screenshot region, you need the element bounds relative to the container. First, get the non-relative bounds:
var elementBounds = element.RenderTransform.TransformBounds(new Rect(element.RenderSize));
Then get those bounds relative to the container:
var relativeElementBounds = element.TransformToAncestor(container).TransformBounds(elementBounds);
As I mentioned above, you will need to do this for the element as well as each of its adorners and combine the maximum bounds into one final Rect which is just large enough to contain all of them.
Finally, use CroppedBitmap to get a cropped version of the RenderTargetBitmap:
var croppedBitmap = new CroppedBitmap(renderTargetBitmap, new Int32Rect(left, top, width, height));
CroppedBitmap and RenderTargetBitmap both inherit from BitmapSource, so you should be able to save it the same way.

You can use the native WPF Printing name space to print to an XPS file and this will include the adorner in the result (I tested it successfully)...
using System.Windows.Controls;
private void ExecutePrintCommand(object obj)
{
PrintDialog printDialog = new PrintDialog();
if (printDialog.ShowDialog() == true)
{
printDialog.PrintVisual(_mainWindow, "Main Window with Adorner");
}
}
If you did not want to use the PrintDialog (which actually opens a dialog box). You can use the XpsDocumentWriter class to programmatically control the process. The enabling snippet for this is...
XpsDocumentWriter xpsdw = PrintQueue.CreateXpsDocumentWriter(q);
xpsdw.Write(viewer.Document);
...which was extracted from here: Print FixedDocument programmatically And there are more articles about fine-tuning the process if that is part of your requirements. NOTE that the XPS file is actually a 'zip' file masquerading as an 'xps' file, so you can unzip it by changing the extension to see if the contents are of any use.
Secondarily, I tested saving a window with an adorner on a TextBox with this code...
private void SaveWithAdorner()
{
RenderTargetBitmap rtb = RenderVisaulToBitmap(_mainWindow, 500, 300);
MemoryStream file = new MemoryStream();
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
encoder.Save(file);
using (FileStream fstream = File.OpenWrite("Myimage.jpg"))
{
file.WriteTo(fstream);
fstream.Flush();
fstream.Close();
}
}
...with good results. I.e., the adorner appeared in the saved bitmap with its red border. This might differ from your code because I use a Png encoder (but saved to a 'jpg' file).
Although I have tested both approaches successfully, you'll need to check them on your hardware.
And lastly, as a last-ditch resort, you can deactivate WPF's hardware rendering mode and set it to software rendering...
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
...for which there's a nice SO thread here: Software rendering mode - WPF

In my case all I needed was call the AdornerLayer class like so:
public void GetScreenshotWithAdorner(Canvas canvas, string filename)
{
AdornerLayer adornerlayer = AdornerLayer.GetAdornerLayer(canvas);
RenderTargetBitmap rtb = new RenderTargetBitmap(
(int)canvas.ActualWidth,
(int)canvas.ActualHeight,
96, //dip X
96, //dpi Y
PixelFormats.Pbgra32);
rtb.Render(canvas); //renders the canvas screen first...
rtb.Render(adornerlayer); //... then it renders the adorner layer
SaveRTBAsPNG(rtb, filename);
}
private void SaveRTBAsPNG(RenderTargetBitmap bmp, string filename)
{
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(BitmapFrame.Create(bmp));
using (var filestream = System.IO.File.Create(filename))
{
pngImage.Save(filestream);
}
}
This works if you want to include ALL adorners in your canvas.

Related

Tile an image in PDF using iText7

I would like to be able to fill graphics objects using the tiling pattern functionality that pdf offers. For example, I would like to be able to draw something like this:
iText7 has a few objects related to patterns that could be useful, but I am having trouble figuring out how to use them and it is exceedingly difficult to find examples of similar code online.
iText7 provides the following classes that may be useful:
PdfPattern.Tiling
PatternColor
PdfPatternCanvas
It looks like you should be able to create a PdfPattern.Tiling object which references an image in some way and then create a PatternColor from that tiling object. Then you can set your canvas' fill color to the PatternColor you just created. An example of a function that does this is:
private void SetImageTilingFill(PdfCanvas canvas, PdfImageXObject img)
{
PdfPattern.Tiling tiling = new PdfPattern.Tiling((float)Inches2Points(img.GetHeight() / 96), (float)Inches2Points(img.GetWidth() / 96)); // create tiling object with width and height the size of the img
tiling.GetResources().AddImage(img);// add the image as a resource?
canvas.SetFillColor(new PatternColor(tiling)); // set fill color to PatternColor?
}
So far this approach has not been successful, my rectangle ends up solid black. Any suggestions would be much appreciated.
Using an image as a tile requires setting up the tile (it has a canvas to draw on). And then using it as a fill when drawing.
try (PdfWriter writer = new PdfWriter("tiling.pdf");
PdfDocument document = new PdfDocument(writer)) {
// creating image
ImageData imageData = ImageDataFactory.create("wavy.png");
PdfImageXObject imageXo = new PdfImageXObject(imageData);
// setting up the tile
PdfPattern.Tiling imageTile = new PdfPattern.Tiling(100, 100);
new PdfPatternCanvas(imageTile, document)
.addXObjectFittedIntoRectangle(imageXo, new Rectangle(0, 0, 100, 100))
.release();
PdfPage page = document.addNewPage();
PdfCanvas canvas = new PdfCanvas(page);
//using the tile
canvas.setFillColor(new PatternColor(imageTile));
canvas.rectangle(10, 10, 500, 900).fill();
canvas.release();
}

Image is not drawn at the correct spot

Bitmap image = ReadBitmap("image.png");
Bitmap imageCopy = new Bitmap(image);
Bitmap canvas = new Bitmap(imageCopy.Width+100, imageCopy.Height);
// From this bitmap, the graphics can be obtained, because it has the right PixelFormat
using(Graphics g = Graphics.FromImage(canvas))
{
// Draw the original bitmap onto the graphics of the new bitmap
g.DrawImage(image, 0, 0);
}
// Use tempBitmap as you would have used originalBmp
InputPictureBox.Image = image;
OutputPictureBox.Image = canvas;
I haven't understood the output of this c# code.
The original image is not placed at the correct position. It should have been at (0, 0).
Also, I need a black background.
So, what is going on and how to correct this?
You are loading an Image, then a copy of this source is created using:
Bitmap bitmap = new Bitmap();
When you create a copy of an Image this way, you sacrifice/alter some details:
Dpi Resolution: if not otherwise specified, the resolution is set to the UI resolution. 96 Dpi, as a standard; it might be different with different screen resolutions and scaling. The System in use also affects this value (Windows 7 and Windows 10 will probably/possibly provide different values)
PixelFormat: If not directly copied from the Image source or explicitly specified, the PixelFormat is set to PixelFormat.Format32bppArgb.
From what you were saying, you probably wanted something like this:
var imageSource = Image.FromStream(new MemoryStream(File.ReadAllBytes(#"[SomeImageOfLena]"))), true, false)
var imageCopy = new Bitmap(imageSource.Width + 100, imageSource.Height, imageSource.PixelFormat))
imageCopy.SetResolution(imageSource.HorizontalResolution, imageSource.VerticalResolution);
using (var g = Graphics.FromImage(imageCopy)) {
g.Clear(Color.Black);
g.CompositingMode = CompositingMode.SourceCopy;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(imageSource, (imageCopy.Width - imageSource.Width) / 2, 0);
pictureBox1.Image?.Dispose();
pictureBox2.Image?.Dispose();
pictureBox1.Image = imageSource;
pictureBox2.Image = imageCopy;
}
This is the result:
(The upper/lower frame black color is actually the Picturebox background color)
When the original Image Dpi Resolution is different from the base Dpi Resolution used when creating an Image copy with new Bitmap(), your results may be different from what is expected.
This is what happens with a source Image of 150, 96 and 72 Dpi in the same scenario:
Another important detail is the IDisposable nature of the Image object.
When you create one, you have to Dispose() of it; explicitly, calling the Dispose method, or implicitly, enclosing the Image contructor in a Using statement.
Also, possibly, don't assign an Image object directly loaded from a FileStream.
GDI+ will lock the file, and you will not be able to copy, move or delete it.
With the file, all resources tied to the Images will also be locked.
Make a copy with new Bitmap() (if you don't care of the above mentioned details), or with Image.Clone(), which will preserve the Image Dpi Resolution and PixelFormat.
I am not completely clear on what you are actually needing to do. But anyway, here is a WPF-friendly example of how to draw an image at a specific position inside another image.
Note if all you want to do is display the image in different size and/or put a black border around it, there are much simpler ways to do simply that, without having to create a second image, such as just laying out the image inside a panel that already has the border style you want.
Notice that I am using classes from the System.Windows.Media namespace because that is what WPF uses. These don't mix easily with the older classes from System.Drawing namespace (some of the class names conflict, and Microsoft's .Net framework lacks built-in methods for converting objects between those types), so normally one needs to simply decide whether to use one or the other sets of drawing tools. I assume you have been trying to use System.Drawing. Each has its own pros and cons that would take too long to explain here.
// using System.Windows.Media;
// using System.Windows.Media.Imaging;
private void DrawTwoImages()
{
// For InputPictureBox
var file = new Uri("C:\\image.png");
var inputImage = new BitmapImage(file);
// If your image is stored in a Resource Dictionary, instead use:
// var inputImage = (BitmapImage) Resources["image.png"];
InputPicture.Source = inputImage;
// imageCopy isn't actually needed for this example.
// But since you had it in yours, here is how it's done, anyway.
var imageCopy = inputImage.Clone();
// Parameters for setting up our output picture
int leftMargin = 50;
int topMargin = 5;
int rightMargin = 50;
int bottomMargin = 5;
int width = inputImage.PixelWidth + leftMargin + rightMargin;
int height = inputImage.PixelHeight + topMargin + bottomMargin;
var backgroundColor = Brushes.Black;
var borderColor = (Pen) null;
// Use a DrawingVisual and DrawingContext for drawing
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
// Draw the black background
dc.DrawRectangle(backgroundColor, borderColor, new Rect(0, 0, width, height));
// Copy input image onto output image at desired position
dc.DrawImage(inputImage, new Rect(leftMargin, topMargin,
inputImage.PixelWidth, inputImage.PixelHeight));
}
// For displaying output image
var rtb = new RenderTargetBitmap( width, height, 96, 96, PixelFormats.Pbgra32 );
rtb.Render(dv);
OutputPicture.Source = rtb;
}

How to overlap images and store them in new image objects

I have two images,now i want to overlay the images,such that the other image appears on center or left corner of the other image,and then when finally both the images are overlayed i can store it in another new image object,and i want to all this in code behind only not xaml,how to do this?
if (((Grid)sender).Children.Count > 0)
{
gridBackground = (ImageBrush)(((Grid)sender).Background);
gridBackImage = new System.Windows.Controls.Image();
gridBackImage.Source = gridBackground.ImageSource;
}
System.Windows.Controls.Image imgRejectIcon;
if (((Grid)sender).Children.Count > 0)
{
imgRejectIcon = (System.Windows.Controls.Image)(((Grid)sender).Children[0]);
}
Now i want to merge gridBackImage and imgRejection and store it in new image object
You can arrange your two Images in any way that you want and I'll leave that code for you to do (it'd much simpler to do in XAML). In WPF, all UI controls extend the Visual class. This is very useful for making BitmapImages from UI controls, when used with the RenderTargetBitmap.Render method.
First, arrange your two Image controls into a Grid, or other container control, and then you can pass the container control to the RenderTargetBitmap.Render method and create an Image something like this:
RenderTargetBitmap renderTargetBitmap =
new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(yourContainerControl);
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
using (Stream fileStream = File.Create(filePath))
{
pngImage.Save(fileStream);
}

How can I render text on a WriteableBitmap on a background thread, in Windows Phone 7?

I am trying to render text on a bitmap in a Windows Phone 7 application.
Code that looks more or less like the following would work fine when it's running on the main thread:
public ImageSource RenderText(string text, double x, double y)
{
var canvas = new Canvas();
var textBlock = new TextBlock { Text = text };
canvas.Children.Add(textBloxk);
Canvas.SetLeft(textBlock, x);
Canvas.SetTop(textBlock, y);
var bitmap = new WriteableBitmap(400, 400);
bitmap.Render(canvas, null);
bitmap.Invalidate();
return bitmap;
}
Now, since I have to render several images with more complex stuff, I would like to render the bitmap on a background thread to avoid an unresponsive UI.
When I use a BackgroundWorker to do so, the constructor for TextBlock throws an UnauthorizedAccessException claiming that this is an invalid cross-thread access.
My question is: how can I render text on a bitmap without blocking the UI?
Please don't suggest using a web service to do the rendering. I need to render a large number of images and the bandwidth cost is not acceptable for my needs, and the ability to work offline is a major requirement.
The solution doesn't necessarily has to use WriteableBitmap or UIElements, if there is another way to render text.
EDIT
Another thought: does anyone know if it should be possible to run a UI message loop in another thread, and then have that thread do the work? (instead of using a BackgroundWorker)?
EDIT 2
To consider alternatives to WriteableBitmap, the features I need are:
Draw a background image.
Measure the width and height of a 1-line string, given a font familiy and size (and preferably style). No need for word wrapping.
Draw a 1-line string, with given font family, size, style, at a given coordinate.
Text rendering should support a transparent background. I.e. you should see the background image between the characters.
This method copies the letters from an pre-made image instead of using TextBlock, it's based on my answer to this question. The main limitation is requiring a different image for each font and size needed. A size 20 Font needed about 150kb.
Using SpriteFont2 export the font and the xml metrics file in the sizes you require. The code assumes they're named "FontName FontSize".png and "FontName FontSize".xml add them to your project and set the build action to content. The code also requires WriteableBitmapEx.
public static class BitmapFont
{
private class FontInfo
{
public FontInfo(WriteableBitmap image, Dictionary<char, Rect> metrics, int size)
{
this.Image = image;
this.Metrics = metrics;
this.Size = size;
}
public WriteableBitmap Image { get; private set; }
public Dictionary<char, Rect> Metrics { get; private set; }
public int Size { get; private set; }
}
private static Dictionary<string, List<FontInfo>> fonts = new Dictionary<string, List<FontInfo>>();
public static void RegisterFont(string name,params int[] sizes)
{
foreach (var size in sizes)
{
string fontFile = name + " " + size + ".png";
string fontMetricsFile = name + " " + size + ".xml";
BitmapImage image = new BitmapImage();
image.SetSource(App.GetResourceStream(new Uri(fontFile, UriKind.Relative)).Stream);
var metrics = XDocument.Load(fontMetricsFile);
var dict = (from c in metrics.Root.Elements()
let key = (char) ((int) c.Attribute("key"))
let rect = new Rect((int) c.Element("x"), (int) c.Element("y"), (int) c.Element("width"), (int) c.Element("height"))
select new {Char = key, Metrics = rect}).ToDictionary(x => x.Char, x => x.Metrics);
var fontInfo = new FontInfo(new WriteableBitmap(image), dict, size);
if(fonts.ContainsKey(name))
fonts[name].Add(fontInfo);
else
fonts.Add(name, new List<FontInfo> {fontInfo});
}
}
private static FontInfo GetNearestFont(string fontName,int size)
{
return fonts[fontName].OrderBy(x => Math.Abs(x.Size - size)).First();
}
public static Size MeasureString(string text,string fontName,int size)
{
var font = GetNearestFont(fontName, size);
double scale = (double) size / font.Size;
var letters = text.Select(x => font.Metrics[x]).ToArray();
return new Size(letters.Sum(x => x.Width * scale),letters.Max(x => x.Height * scale));
}
public static void DrawString(this WriteableBitmap bmp,string text,int x,int y, string fontName,int size,Color color)
{
var font = GetNearestFont(fontName, size);
var letters = text.Select(f => font.Metrics[f]).ToArray();
double scale = (double)size / font.Size;
double destX = x;
foreach (var letter in letters)
{
var destRect = new Rect(destX,y,letter.Width * scale,letter.Height * scale);
bmp.Blit(destRect, font.Image, letter, color, WriteableBitmapExtensions.BlendMode.Alpha);
destX += destRect.Width;
}
}
}
You need to call RegisterFont once to load the files then you call DrawString. It uses WriteableBitmapEx.Blit so if your font file has white text and a transparent background alpha is handled correctly and you can recolour it. The code does scale the text if you draw at a size you didn't load but the results aren't good, a better interpolation method could be used.
I tried drawing from a different thread and this worked in the emulator, you still need to create the WriteableBitmap on the main thread. My understanding of your scenario is that you want to scroll through tiles similar to how mapping apps work, if this is the case reuse the old WriteableBitmaps instead of recreating them. If not the code could be changed to work with arrays instead.
I'm not sure if this will fully resolve your issues, but there are 2 tools that I use in my comic book reader (I won't shamelessly plug it here, but I'm tempted.. a hint if you are searching for it.. it is "Amazing"). There are times where I need to stitch together a bunch of images. I use Rene Schulte's (and a bunch of other contributors) WriteableBitmapExtensions (http://writeablebitmapex.codeplex.com/). I have been able to offload rendering/stitching of an image to a background thread and then set the resulting WriteableBitmap as the source of some image on the UI thread.
Another up and comer in this space is the .NET Image Tools (http://imagetools.codeplex.com/). They have a bunch of utilities for saving/reading various image formats. They also have a few of the low levels, and I wish there were an easy way to use both (but there isn't).
All of the above work in WP7.
I guess the major difference is with these tools you won't be using XAML you will be writing directly to your image (so you may need to do size detection of your text and stuff like that).
The very nature of UI elements requires interaction with them on the UI thread. Even if you could create them on a background thread, when you came to try to render them into the WriteableBitmap you'd get a similar exception, and even then if it allowed you to do that, the elements wouldn't actually have a visual representation until they were added into the visual tree. You might need to use a generic image manipulation library instead of using UI elements.
Perhaps you could describe your scenario on a wider basis, we might have a better solution for you :)
First off, are you sure about rendering this as a bitmap? How about generating a Canvas with an image and TextBlock?
I need to render a large number of images
I have a feeling that this generating will kill phone performance. Generally, for bitmap mainupulation, the best way is to use XNA. Some parts of the XNA framework do a great job Silverlight projects. (BTW the refreshed Windows Phone Developer Tools will allow Silverlight and XNA coexist in the same project)
I would step back and think about this feature. Developing something like this for a week and then end up with unacceptable performance would make me a sad panda.
EDIT
As far I understand you need some kind of popup with image as a background and the message.
Make a Canvas with TextBlock but hide it.
<Canvas x:Name="userInfoCanvas" Height="200" Width="200" Visibility="Collapsed">
<Image x:Name="backgroundImage"> </Image>
<TextBlock x:Name="messageTextBlock" Canvas.ZIndex="3> </TextBlock> <!--ZIndex set the order of elements -->
</Canvas>
When you got the new message, show the Canvas to the user (a opacity animation would be nice), when you finish rendering on the background thread.
messageTextBlock.Text = message;
backgroundImage.Source = new BitmapImage(renderedImage);
Obviouslly, here is a problem with update. UIelements can be updated only form the UI Thread, hence update must be queue with Dispatcher
Dispatcher.BeginInvoke(DispatcherPriority.Background, messageUpdate); //messageUpdate is an Action or anthing that can be infered to Delegate
PS. didn't compile, this is more pseudocode.
You can draw on WriteableBitmap in thread, but You have to
create WriteableBitmap in main UI thread
do draw work in background thread
assign BitmapSource in main UI thread
i'll agree with Derek's answer: you're trying to use UI controls without a UI.
If you want to render a bitmap you need to stick to classes for drawing text on bitmaps.
i presume Windows Phone 7 has the .NET Compact Framework.
psudeo-code:
public Bitmap RenderText(string text, double x, double y)
{
Bitmap bitmap = new Bitmap(400, 400);
using (Graphics g = new Graphics(bitmap))
{
using (Font font = SystemFonts....)
{
using (Brush brush = new SolidColorBrush(...))
{
g.DrawString(text, font, brush, new Point(x, y));
}
}
}
return bitmap;
}

Clipboard.SetImage with WPF canvas leaves clipboard empty for large images

I'm rendering a WPF canvas to an image and sticking it on the clipboard.
If the canvas is small (< 900px square) it all works fine.
I have a larger canvas (3000+px square) and the clipboard is empty (paste option disabled in photoshop/word etc)
var transform = canvas.LayoutTransform;
canvas.LayoutTransform = null;
var size = new Size(canvas.Width, canvas.Height);
canvas.Measure(size);
canvas.Arrange(new Rect(size));
var renderBitmap = new RenderTargetBitmap((int) size.Width, (int) size.Height, 96d, 96d, PixelFormats.Pbgra32);
renderBitmap.Render(canvas);
canvas.LayoutTransform = transform;
Clipboard.SetImage(renderBitmap);
I've not found if there is a threshold size that causes this to break.
3140 x 1903 doesnt work, 3140 x 317 does
Whats going on?
Thanks
It turns out that in order to store an image to the clipboard, the image is automatically converted into uncompressed bitmaps in several formats (BMP, DIB, etc.). So when you have a 10MP image that takes up 40MB uncompressed (8-bit RGBA), it could take 200MB of memory to store on the clipboard -- just in case somebody might want it in one of those other formats.
What you might be able to do is put it on the clipboard yourself without as much overhead. If you use Reflector, you'll see that Clipboard.SetImage looks like this:
public static void SetImage(Image image)
{
if (image == null)
{
throw new ArgumentNullException("image");
}
IDataObject data = new DataObject();
data.SetData(DataFormats.Bitmap, true, image); // true means autoconvert
Clipboard.SetDataObject(data, true); // true means copy
}
If you make your own version of the SetImage function with one or both of the true instances set to false, you may be able to overcome some of the unnecessary copying and be able to put a larger image on the clipboard.

Categories