i'am making a programm where i want my RichTextBox
content (text+images) to be saved as an image (jpg/png). I tried to use this solution
but i get only black filled image from
SaveUIAsGraphicFile()
I also tried to create FormattedText from my rtb control, printing it works fine, but its not possible to insert images in there. Maybe it is possible to print FlowDocument somehow?
You could use something like the following method to create a bitmap from a FlowDocument:
public BitmapSource FlowDocumentToBitmap(FlowDocument document, Size size)
{
document = CloneDocument(document);
var paginator = ((IDocumentPaginatorSource)document).DocumentPaginator;
paginator.PageSize = size;
var visual = new DrawingVisual();
using (var drawingContext = visual.RenderOpen())
{
// draw white background
drawingContext.DrawRectangle(Brushes.White, null, new Rect(size));
}
visual.Children.Add(paginator.GetPage(0).Visual);
var bitmap = new RenderTargetBitmap((int)size.Width, (int)size.Height,
96, 96, PixelFormats.Pbgra32);
bitmap.Render(visual);
return bitmap;
}
public FlowDocument CloneDocument(FlowDocument document)
{
var copy = new FlowDocument();
var sourceRange = new TextRange(document.ContentStart, document.ContentEnd);
var targetRange = new TextRange(copy.ContentStart, copy.ContentEnd);
using (var stream = new MemoryStream())
{
sourceRange.Save(stream, DataFormats.XamlPackage);
targetRange.Load(stream, DataFormats.XamlPackage);
}
return copy;
}
and then use it like shown below to save a RichTextBox's Document to an image file.
var doc = richTextBox.Document;
var bm = FlowDocumentToBitmap(doc, new Size(richTextBox.ActualWidth, richTextBox.ActualHeight));
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bm));
using (var stream = new FileStream("doc.jpg", FileMode.Create))
{
encoder.Save(stream);
}
You can spend HOURS chasing around trying to figure out why the width is wrong when in reality its trying to paginate in columns. Set the document's columnwidth to the full width of your output bitmap.
public Bitmap FlowDocumentToBitmap(FlowDocument document, Size size)
{
document = CloneDocument(document);
document.ColumnWidth = size.Width;// <- Add this line
Related
I have a function in my code behind that does what I want. What I like about this is no matter how the user expand his drawing out of the InkCanvas boundries, the image will be resized when saved.
private void exportCanvasToBitmap(object sender, EventArgs e) {
RenderTargetBitmap rtb = new
RenderTargetBitmap((int)signatureCanvas.ActualWidth,
(int)signatureCanvas.ActualHeight, 96, 96, PixelFormats.Default);
rtb.Render(signatureCanvas);
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
FileStream fs = File.Open(#".\images\"+ signatureFileName + ".jpg", FileMode.Create);
encoder.Save(fs);
fs.Close();
(sourceWindow as MainWindow).updateSignatureImage(signatureType, signatureFileName);
}
I wanted to move this to the ViewModel and I didn't know how to make it does the same as the one I have before. I don't want to usertb.Render(inkCanvas) in the ViewModel. After some search I binded a StrokeCollection to my view and used this function.
public void convertStrokestoImage() {
StrokeCollection sc = strokes;
byte[] inkData = null;
using (MemoryStream inkMemStream = new MemoryStream())
{
sc.Save(inkMemStream);
inkData = inkMemStream.ToArray();
}
byte[] gifData = null;
using (Microsoft.Ink.Ink ink2 = new Microsoft.Ink.Ink())
{
ink2.Load(inkData);
gifData = ink2.Save(Microsoft.Ink.PersistenceFormat.Gif);
}
File.WriteAllBytes("./Src/strokes.png", gifData);
}
The problem with this is that if the user draws out of the InkCanvas' boundaries, the image scales " which I don't want". I am not really sure how to do that since both of the function I found online as solutions in other questions.
My initial thought is that I have to do something similar for what I had before where I had a RenderTargetBitmap that has the actual width and height of my InkCanvas. The issue is I don't know how to obtain this without using InkCanvas object itself.
any tips?
I solved the issue like this.
First I converted the byte[] to an Image and then I used a helper function to resize the image.
public void convertStrokestoImage()
{
StrokeCollection sc = strokes;
byte[] inkData = null;
using (MemoryStream inkMemStream = new MemoryStream())
{
sc.Save(inkMemStream);
inkData = inkMemStream.ToArray();
}
byte[] gifData = null;
using (Microsoft.Ink.Ink ink2 = new Microsoft.Ink.Ink())
{
ink2.Load(inkData);
gifData = ink2.Save(Microsoft.Ink.PersistenceFormat.Gif);
}
MemoryStream ms = new MemoryStream(gifData);
Image image = Image.FromStream(ms);
image = resizeImage(image, 100, 150);
image.Save("./Src/strokes.png");
}
You can find alot of functions to resize images in the following question.
Resize an Image C#
What I don't like about this is the image file size become bigger!
For example what was 5kb became 30kb.
I would like to convert the WPF display into a PNG file.
This is being used to create an invoice for a holiday. The code I currently have creates the PNG file but it creates a blank document.
Here is what I am trying to do:
var screenCap = new RenderTargetBitmap((int)this.Width, (int)this.Height, 96d, 96d, PixelFormats.Default);
var encoder = new PngBitmapEncoder();
var outputScreen = BitmapFrame.Create(screenCap);
encoder.Frames.Add(outputScreen);
using (var file = File.OpenWrite(#"Invoice.png"))
{
encoder.Save(file);
}
You need to call screenCap.Render(this) after you've constructed the object, but before you call BitmapFrame.Create(screenCap)
The following code will capture a specified element from the display and write it to a specified path:
private void SaveImage(FrameworkElement targetElement, string savePath)
{
var bmp = new RenderTargetBitmap((int)targetElement.ActualWidth, (int)targetElement.ActualHeight, 96, 96, PixelFormats.Default);
bmp.Render(targetElement);
using (var stream = new MemoryStream())
{
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
encoder.QualityLevel = 100;
encoder.Save(stream);
File.WriteAllBytes(savePath, stream.ToArray());
}
}
I have a WPF control in my PowerPoint add-in that hosts an image that I want to be able to drag & drop onto the active slide. I can get the image to appear on the slide, but the transparent areas are rendered in black.
My code to initialize the drag from my attached behavior:
var targetBitmap = new RenderTargetBitmap(
(int) MyWpfControl.ActualWidth,
(int) MyWpfControl.ActualHeight,
96d, 96d, PixelFormats.Default);
targetBitmap.Render(MyWpfControl);
var dataObject = new DataObject(
DataFormats.Bitmap,
targetBitmap);
DragDrop.DoDragDrop(MyWpfControl, dataObject, DragDropEffects.Copy)
Thinking that maybe I needed to pass a System.Drawing.Image, I attempted this modification, which only resulted in the transparent areas being rendered in gray:
var targetBitmap = new RenderTargetBitmap(
(int) MyWpfControl.ActualWidth,
(int) MyWpfControl.ActualHeight,
96d, 96d, PixelFormats.Default);
targetBitmap.Render(MyWpfControl);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(targetBitmap));
var ms = new MemoryStream();
encoder.Save(ms);
var dataObject = new DataObject(DataFormats.Bitmap, Image.FromStream(ms, true))
DragDrop.DoDragDrop(MyWpfControl, dataObject, DragDropEffects.Copy)
I did a test where I replaced the memory stream with a file stream, and the image that was written did indeed have the correct transparency.
So what am I missing here? How can I maintain transparency?
I was able to resolve this by following the instructions in this blog post. The solution was to use the EnhancedMetafile DataFormat in my DataObject.
Edit:
Here’s the code that initiates the drag operation.
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Bitmap bitmap = ImageToBitmap(e.Source as System.Windows.Controls.Image);
DataObject data = new DataObject(DataFormats.EnhancedMetafile, MakeMetafileStream(bitmap));
DragDrop.DoDragDrop((DependencyObject)e.Source, data, DragDropEffects.Copy);
}
This makes use of a utility function to convert the Image to a Bitmap:
private Bitmap ImageToBitmap(System.Windows.Controls.Image image)
{
RenderTargetBitmap rtBmp = new RenderTargetBitmap((int)image.ActualWidth, (int)image.ActualHeight,
96.0, 96.0, PixelFormats.Pbgra32);
image.Measure(new System.Windows.Size((int)image.ActualWidth, (int)image.ActualHeight));
image.Arrange(new Rect(new System.Windows.Size((int)image.ActualWidth, (int)image.ActualHeight)));
rtBmp.Render(image);
PngBitmapEncoder encoder = new PngBitmapEncoder();
MemoryStream stream = new MemoryStream();
encoder.Frames.Add(BitmapFrame.Create(rtBmp));
// Save to memory stream and create Bitamp from stream
encoder.Save(stream);
return new System.Drawing.Bitmap(stream);
}
This also requires a utility function that converts a Bitmap to a stream containing a Metafile, taken from Stack Overflow.
// From Convert an image into WMF with .NET?
private MemoryStream MakeMetafileStream(Bitmap image)
{
Graphics graphics = null;
Metafile metafile = null;
var stream = new MemoryStream();
try
{
using (graphics = Graphics.FromImage(image))
{
var hdc = graphics.GetHdc();
metafile = new Metafile(stream, hdc);
graphics.ReleaseHdc(hdc);
}
using (graphics = Graphics.FromImage(metafile))
{ graphics.DrawImage(image, 0, 0); }
}
finally
{
if (graphics != null)
{ graphics.Dispose(); }
if (metafile != null)
{ metafile.Dispose(); }
}
return stream;
}
I'm trying to save an UIElement created programmatically in a JPG/PNG/BMP image in a Windows Phone 8.1 (C#) application.
I'm using the class RenderTargetBitmap using the method RenderAsync() but it only works with UI elements created in the XAML code. When I use it on UI elements created directly in C# I have this exception: "System.ArgumentException (Value does not fall within the expected range.)"
Am I doing something wrong or this class doesn't allow rendering of UIElement(s) created programmatically? Is there any way to do this on Windows Phone 8.1? Thank you!
Here's the code I use:
private static async void RenderText(string text, int width, int height, int fontsize, string imagename)
{
RenderTargetBitmap b = new RenderTargetBitmap();
var canvas = new Grid();
canvas.Width = width;
canvas.Height = height;
var background = new Canvas();
background.Height = width;
background.Width = height;
SolidColorBrush backColor = new SolidColorBrush(Colors.Red);
background.Background = backColor;
var textBlock = new TextBlock();
textBlock.Text = text;
textBlock.FontWeight = FontWeights.Bold;
textBlock.TextAlignment = TextAlignment.Left;
textBlock.HorizontalAlignment = HorizontalAlignment.Center;
textBlock.VerticalAlignment = VerticalAlignment.Stretch;
textBlock.Margin = new Thickness(35);
//textBlock.Width = b.PixelWidth - textBlock.Margin.Left * 2;
textBlock.TextWrapping = TextWrapping.Wrap;
textBlock.Foreground = new SolidColorBrush(Colors.White); //color of the text on the Tile
textBlock.FontSize = fontsize;
canvas.Children.Add(textBlock);
await b.RenderAsync(background);
await b.RenderAsync(canvas);
// Get the pixels
var pixelBuffer = await b.GetPixelsAsync();
// Get the local folder.
StorageFolder local = Windows.Storage.ApplicationData.Current.LocalFolder;
// Create a new folder name DataFolder.
var dataFolder = await local.CreateFolderAsync("DataFolder",
CreationCollisionOption.OpenIfExists);
StorageFile file = await dataFolder.CreateFileAsync(imagename, CreationCollisionOption.ReplaceExisting);
// Encode the image to the selected file on disk
using (var fileStream = await file.OpenStreamForWriteAsync())
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, fileStream.AsRandomAccessStream());
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
(uint)b.PixelWidth,
(uint)b.PixelHeight,
DisplayInformation.GetForCurrentView().LogicalDpi,
DisplayInformation.GetForCurrentView().LogicalDpi,
pixelBuffer.ToArray());
await encoder.FlushAsync();
}
}
It is not working only with elements created in XAML but with these that (as MSDN says) are in VisualTree:
Renders a snapshot of a UIElement visual tree to an image source.
So your method will work if you add your elements for example to your current Page:
LayoutRoot.Children.Add(canvas);
Your UIElemet Height and Width values will be 0 when you create them using code. If you want the image in a particular resolution, try to assign the same height and width to the UIElement as well.
The code shown below worked well for me.
public static void SaveElementAsJPG(FrameworkElement element, string ImageName)
{
WriteableBitmap wBitmap = new WriteableBitmap(element, null);
using (MemoryStream stream = new MemoryStream())
{
wBitmap.SaveJpeg(stream, (int)element.ActualWidth, (int)element.ActualHeight, 0, 100);
wBitmap = null;
//Use can either save the file to isolated storage or media library.
//Creates file in Isolated Storage.
using (var local = new IsolatedStorageFileStream(ImageName, FileMode.Create, IsolatedStorageFile.GetUserStoreForApplication()))
{
local.Write(stream.GetBuffer(), 0, stream.GetBuffer().Length);
}
//Creates file in Media Library.
var lib = new MediaLibrary();
var picture = lib.SavePicture(ImageName, stream.GetBuffer());
}
}
I'm trying to set a Grid's backgroud through code.
The Grid has 1000 x 1000 size, the same size of the loaded PNG.
I load this PNG into a Stream, then execute the following code:
using (StreamWrapper wrapper = new StreamWrapper(streamImg))
using (BinaryReader reader = new BinaryReader(wrapper))
{
originalImage = new BitmapImage();
originalImage.BeginInit();
originalImage.CacheOption = BitmapCacheOption.OnLoad;
originalImage.StreamSource = reader.BaseStream;
originalImage.EndInit();
originalImage.Freeze();
}
ib = new ImageBrush() { ImageSource = ConvertToGrayScale(ref originalImage) };
grdQuebraCabeça.Background = ib;
private FormatConvertedBitmap ConvertToGrayScale(ref BitmapImage image)
{
FormatConvertedBitmap grayImage = new FormatConvertedBitmap();
grayImage.BeginInit();
grayImage.Source = image;
grayImage.DestinationFormat = PixelFormats.Gray32Float;
grayImage.EndInit();
return grayImage;
}
The following screenshot is the result:
You can see black blocks on lower and right sides.
I want to center the image in the grid.
Is that possible? How can I achieve this result?