im attempting to convert a canvas to a image source for use as an OpacityMask, I want to save it into memory rather than save it as a file, i'm having trouble though. Below is my code, I think i'm going about it wrong!
Really, I need to get the image information as a Base64String, so somewhere between that I need to convert the RenderTargetBitmap!
public BitmapSource ExportToPng(Uri path, Canvas surface)
{
BitmapEncoder encoder = new PngBitmapEncoder();
System.IO.MemoryStream myStream = new System.IO.MemoryStream();
// Save current canvas transform
Transform transform = surface.LayoutTransform;
// reset current transform (in case it is scaled or rotated)
surface.LayoutTransform = null;
// Get the size of canvas
System.Windows.Size size = new System.Windows.Size(surface.ActualWidth, surface.ActualHeight);
// Measure and arrange the surface
// VERY IMPORTANT
surface.Measure(size);
surface.Arrange(new Rect(size));
// Create a render bitmap and push the surface to it
RenderTargetBitmap renderBitmap =
new RenderTargetBitmap(
(int)size.Width,
(int)size.Height,
96d,
96d,
PixelFormats.Pbgra32);
renderBitmap.Render(surface);
// push the rendered bitmap to it
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// save the data to the stream
encoder.Save(myStream);
// Restore previously saved layout
surface.LayoutTransform = transform;
var sr = new System.IO.StreamReader(myStream);
var myStr = sr.ReadToEnd();
var bytes = Convert.FromBase64String(myStr);
// Save to memory
/*Bitmap pg = new Bitmap("525, 350");
Graphics gr = Graphics.FromImage(pg);
gr.FillRectangle(new SolidBrush(System.Drawing.Color.FromArgb(255, 255, 255, 255)), 0, 0, (float)size.Width, (float)size.Height);
gr.DrawImage(System.Drawing.Bitmap.FromStream(myStream), 0, 0);*/
return BitmapFromBase64(myStr);
}
public static BitmapSource BitmapFromBase64(string base64String)
{
var bytes = Convert.FromBase64String(base64String);
using (var stream = new System.IO.MemoryStream(bytes))
{
return BitmapFrame.Create(stream,
BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
}
Edit:
Just found another possible way, however this creates a DrawingVisual, I need to convert that to a ImageBrush
C#
// Create a DrawingVisual that contains a rectangle.
private DrawingVisual CreateDrawingVisualRectangle(List<Rectangle> rectangles)
{
DrawingVisual drawingVisual = new DrawingVisual();
// Retrieve the DrawingContext in order to create new drawing content.
DrawingContext drawingContext = drawingVisual.RenderOpen();
// Create a rectangle and draw it in the DrawingContext.
foreach(Rectangle x in rectangles)
{
Rect rect = new Rect(new System.Windows.Point(x.X, x.Y), new System.Windows.Size(x.Width, x.Height));
drawingContext.DrawRectangle(System.Windows.Media.Brushes.Black, (System.Windows.Media.Pen)null, rect);
}
// Persist the drawing content.
drawingContext.Close();
return drawingVisual;
}
A UIElement takes any Brush as OpacityMask. You can simply create a VisualBrush from you Canvas, since the base class of every UIElement is SWM.Visual.
Canvas c = new Canvas();
element.OpacityMask = new VisualBrush( c );
Regards, Snowball
Related
I'm using this example - I found around here - to overlay two .png images and then save the result as a third .png image.
The input images are:
The output image should (in my dreams) be:
And instead I get this:
Here is the code:
public static void Test()
{
// Loads the images to tile (no need to specify PngBitmapDecoder, the correct decoder is automatically selected)
BitmapFrame frame1 = BitmapDecoder.Create(new Uri(#"D:\_tmp_\MaxMara\Test\Monoscope.png"), BitmapCreateOptions.None, BitmapCacheOption.OnLoad).Frames.First();
BitmapFrame frame2 = BitmapDecoder.Create(new Uri(#"D:\_tmp_\MaxMara\Test\OverlayFrame.png"), BitmapCreateOptions.None, BitmapCacheOption.OnLoad).Frames.First();
// Gets the size of the images (I assume each image has the same size)
int imageWidth = 1920;
int imageHeight = 1080;
// Draws the images into a DrawingVisual component
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawImage(frame1, new Rect(0, 0, imageWidth, imageHeight));
drawingContext.DrawImage(frame2, new Rect(0, 0, imageWidth, imageHeight));
}
// Converts the Visual (DrawingVisual) into a BitmapSource
RenderTargetBitmap bmp = new RenderTargetBitmap(imageWidth, imageHeight, 300, 300, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);
// Creates a PngBitmapEncoder and adds the BitmapSource to the frames of the encoder
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
// Saves the image into a file using the encoder
using (Stream stream = File.Create(#"D:\_tmp_\MaxMara\Test\Result.png"))
encoder.Save(stream);
}
Note: if i use 100 dpi as in:
RenderTargetBitmap bmp = new RenderTargetBitmap(imageWidth, imageHeight, 100, 100, PixelFormats.Pbgra32);
I get the correct result (meaning: the result I want).
I don't understand why. All images are 300 DPI
Can anyone shed some light on the topic please?
Thank you for your time
Orf
Do not use the PixelWidth and PixelHeight (i.e. your imageWidth and imageHeight values) of the bitmaps for drawing them into a DrawingContext.
Use their Width and Height values instead, because these give the bitmap size in device-independent units (1/96th inch per unit) as required for drawing.
using (var drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawImage(frame1, new Rect(0, 0, frame1.Width, frame1.Height));
drawingContext.DrawImage(frame2, new Rect(0, 0, frame2.Width, frame2.Height));
}
I want to write program that functions something like PhotoShop.
1.upload image
2. Then I want to do skew transform, But when I do transform I have a problem, my picture goes beyond the edge of the workspace.
How do I transform without this problem(I think I should create a new Image every time I do a transform).
Then I crop, but crop makes the picture without transform. I think if I create a new image every time I do a transform, the problem will be fixed.
How do I do this correctly?
How do I correctly create this image in WPF? How to do transform and save an image? I am using(System.Drawing.Bitmap, System.Windows.Media.Imaging) Maybe, can someone show me experiences, code or useful material?
For the skew tranformation you may use MatrixTranform. Basic idea is described here
Below is the code that transforms an image located at "D:\input.png", attaches transformation result to the source of Image that defined in the .xaml file:
<Image Name="imgProcess" />
and writes result to the file "D:\skew.png"
double skewX = .0;
double skewY = Math.Tan(Math.PI / 18);
MatrixTransform transformation = new MatrixTransform(1, skewY, skewX, 1, 0, 0)
BitmapImage image = new BitmapImage(new Uri(#"D:\input.png"));
var boundingRect = new Rect(0, 0, image.Width + image.Height * skewX, image.Height + image.Width * skewY);
DrawingGroup dGroup = new DrawingGroup();
using (DrawingContext dc = dGroup.Open())
{
dc.PushTransform(transformation);
dc.DrawImage(image, boundingRect);
}
DrawingImage imageSource = new DrawingImage(dGroup);
imgProcess.Source = imageSource;
SaveDrawingToFile(ToBitmapSource(imageSource), #"D:\skew.png", (int)boundingRect.Width, (int)boundingRect.Height);
private BitmapSource ToBitmapSource(DrawingImage source)
{
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
drawingContext.DrawImage(source, new Rect(new Point(0, 0), new Size(source.Width, source.Height)));
drawingContext.Close();
RenderTargetBitmap bmp = new RenderTargetBitmap((int)source.Width, (int)source.Height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);
return bmp;
}
private void SaveDrawingToFile(BitmapSource image, string fileName, int width, int height)
{
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(image));
using (var stream = new FileStream(fileName, FileMode.Create))
{
encoder.Save(stream);
}
}
Results
Going bonkers here trying to do this in WPF (with all the new image manipulation tools), but can't seem to find a working solution. So far, all the solutions are drawing them on the screen or making multiple saves, but I need to do this completely in memory.
Basically, I want to load a large jpeg into memory, resize it smaller (in memory), save as a small PNG file.
I can load the jpeg file into a BitMap object, fine. after that, I'm stumped.
I found this function that looks like it does the trick but it requires an ImageSource (unfortunately, I can't find a way to convert my in-memory BitMap object to an ImageSource that does not yield a NULL exception.)
private static BitmapFrame CreateResizedImage(ImageSource source, int width, int height, int margin)
{
dynamic rect = new Rect(margin, margin, width - margin * 2, height - margin * 2);
dynamic #group = new DrawingGroup();
RenderOptions.SetBitmapScalingMode(#group, BitmapScalingMode.HighQuality);
#group.Children.Add(new ImageDrawing(source, rect));
dynamic drawingVisual = new DrawingVisual();
using (drawingContext == drawingVisual.RenderOpen())
{
drawingContext.DrawDrawing(#group);
}
// Resized dimensions
// Default DPI values
dynamic resizedImage = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Default);
// Default pixel format
resizedImage.Render(drawingVisual);
return BitmapFrame.Create(resizedImage);
}
With WPF it's as easy as this:
private void ResizeImage(string inputPath, string outputPath, int width, int height)
{
var bitmap = new BitmapImage();
using (var stream = new FileStream(inputPath, FileMode.Open))
{
bitmap.BeginInit();
bitmap.DecodePixelWidth = width;
bitmap.DecodePixelHeight = height;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
}
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (var stream = new FileStream(outputPath, FileMode.Create))
{
encoder.Save(stream);
}
}
You may consider to only set one of DecodePixelWidth and DecodePixelHeight in order to preserve the original image's aspect ratio.
I want to have smaller size at image saved.
How can I resize it?
I use this code for redering the image:
Size size = new Size(surface.Width, surface.Height);
surface.Measure(size);
surface.Arrange(new Rect(size));
// Create a render bitmap and push the surface to it
RenderTargetBitmap renderBitmap =
new RenderTargetBitmap(
(int)size.Width,
(int)size.Height, 96d, 96d,
PixelFormats.Default);
renderBitmap.Render(surface);
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
// push the rendered bitmap to it
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// save the data to the stream
encoder.Save(outStream);
public static Bitmap ResizeImage(Bitmap imgToResize, Size size)
{
try
{
Bitmap b = new Bitmap(size.Width, size.Height);
using (Graphics g = Graphics.FromImage((Image)b))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.DrawImage(imgToResize, 0, 0, size.Width, size.Height);
}
return b;
}
catch
{
Console.WriteLine("Bitmap could not be resized");
return imgToResize;
}
}
The shortest way to resize a Bitmap is to pass it to a Bitmap-constructor together with the desired size (or width and height):
bitmap = new Bitmap(bitmap, width, height);
Does your "surface" visual have scaling capability? You can wrap it in a Viewbox if not, then render the Viewbox at the size you want.
When you call Measure and Arrange on the surface, you should provide the size you want the bitmap to be.
To use the Viewbox, change your code to something like the following:
Viewbox viewbox = new Viewbox();
Size desiredSize = new Size(surface.Width / 2, surface.Height / 2);
viewbox.Child = surface;
viewbox.Measure(desiredSize);
viewbox.Arrange(new Rect(desiredSize));
RenderTargetBitmap renderBitmap =
new RenderTargetBitmap(
(int)desiredSize.Width,
(int)desiredSize.Height, 96d, 96d,
PixelFormats.Default);
renderBitmap.Render(viewbox);
I am taking an image of a wpf control using this code:
BitmapEncoder imgEncoder = new PngBitmapEncoder();
RenderTargetBitmap bmpSource = new RenderTargetBitmap((int)element.ActualWidth, (int)element.ActualHeight, 150, 150, PixelFormats.Pbgra32);
bmpSource.Render(element);
imgEncoder.Frames.Add(BitmapFrame.Create(bmpSource));
using (MemoryStream ms = new MemoryStream())
{
imgEncoder.Save(ms);
bytes = ms.ToArray();
ms.Position = 0;
Image i = Image.FromStream(ms);
i.Save(#"C:\" + Guid.NewGuid().ToString() + "LARGE.png");
}
The trouble is ActualHeight/Width property gives the rendered height and width i.e. the displayed part. I want to save an image of the whole control even if some of the control is not visible on the screen i.e. it is placed in a scrollviewer.
How can I get the full size / height of a control so the bmpSource.Render() renders the whole control to an image?
private static void SaveUsingEncoder(string fileName, FrameworkElement UIElement, BitmapEncoder encoder)
{
int height = (int)UIElement.ActualHeight;
int width = (int)UIElement.ActualWidth;
// These two line of code make sure that you get completed visual bitmap.
// In case your Framework Element is inside the scroll viewer then some part which is not
// visible gets clip.
UIElement.Measure(new System.Windows.Size(width, height));
UIElement.Arrange(new Rect(new System.Windows.Point(), new Point(width, height)));
RenderTargetBitmap bitmap = new RenderTargetBitmap(width,
height,
96, // These decides the dpi factors
96,// The can be changed when we'll have preview options.
PixelFormats.Pbgra32);
bitmap.Render(UIElement);
SaveUsingBitmapTargetRenderer(fileName, bitmap, encoder);
}
private static void SaveUsingBitmapTargetRenderer(string fileName, RenderTargetBitmap renderTargetBitmap, BitmapEncoder bitmapEncoder)
{
BitmapFrame frame = BitmapFrame.Create(renderTargetBitmap);
bitmapEncoder.Frames.Add(frame);
// Save file .
using (var stream = File.Create(fileName))
{
bitmapEncoder.Save(stream);
}
}
Call this function as
SaveUsingEncoder(fileName, frameworkElement, new PngBitmapEncoder());
Hope this will help.
You could try temporarily taking the control out of its context (might cause problems if bound),transform it to a visivible point or scroll it into view for rendering.