win2D uwp won't save a highlight stroke in an inkcanvas - c#

I'm desperately trying to save a highlight stroke as a png. I'm using win2d for UWP to make this work.
It works well for strokes with 100% opacity, but when I set DrawAsHighlighter = true;, the saved png is empty, fully transparent.
Here's my code :
private void SetHighLight()
{
InkDrawingAttributes attributes = new InkDrawingAttributes();
attributes.DrawAsHighlighter = true;
attributes.PenTip = PenTipShape.Rectangle;
attributes.Size = new Size(4, 10);
attributes.Color = currentColor;
SetAttribute(attributes);
}
private void GetCanvasRender(out CanvasRenderTarget renderTarget)
{
CanvasDevice device = CanvasDevice.GetSharedDevice();
renderTarget = new CanvasRenderTarget(device, (int)ink.ActualWidth, (int)ink.ActualHeight, 96);
using (var ds = renderTarget.CreateDrawingSession())
{
ds.Clear(Colors.Transparent); //I already tried to delete this but it doesn't change anything
ds.DrawInk(ink.InkPresenter.StrokeContainer.GetStrokes());
}
}
private async void SavePicture()
{
CanvasRenderTarget renderTarget;
Image img = new Image();
GetCanvasRender(out renderTarget);
StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
StorageFile noteFile = await storageFolder.CreateFileAsync(i.ToString() + ".png", CreationCollisionOption.ReplaceExisting);
using (var fileStream = await noteFile.OpenAsync(FileAccessMode.ReadWrite))
await renderTarget.SaveAsync(fileStream, CanvasBitmapFileFormat.Png, 1f);
img.Source = new BitmapImage(new Uri(storageFolder.Path + "/" + i++ + ".png"));
img.VerticalAlignment = VerticalAlignment.Stretch;
ContainerCanvas.Children.Add(img);
Canvas.SetTop(img, ScrollViewerContainer.VerticalOffset);
Canvas.SetZIndex(img, 5);
}
I've read it might be because of the highlight not being present in the visual tree but I'm really not sure about it.
By the way when I try to change the opacity of a color(attributes.Color = Color.FromArgb(128, 255, 0, 0)), the inkcanvas doesn't apply the alpha, why ? Am I missing something ?

You can't save DrawAsHighlighter ink to a bitmap format like .png - that's just fundamentally not a meaningful operation to be attempting.
Regular non-highlighter ink is drawn using standard alpha blending, so it is reasonable to write just these ink shapes into a bitmap format. You can later blend that bitmap over some other background image, and get the same result as if the ink had been drawn directly over that background.
For highlighter ink, however, the "blend over background" is a more complex operation, not just standard sourceover blending. So there is no such thing as a bitmap image that contains just this ink - you also have to provide a background in order for the appropriate blend to be carried out.
You have three options here:
Don't use highligher ink mode.
Instead of saving out your ink as a bitmap image, save the original ink stroke data, and use inking APIs to later blend these strokes directly to their final location.
Include the background as well as the ink strokes in the same bitmap.

Try clearing the background of the canvas with:
ds.Clear(Colors.White);
The highlighter isn't visible on transparent backgrounds as it seems to multiply its value with the background color.

I finally figured out how to make it work.
I simply added a new layer before calling DrawInk and gave it an opacity, and got rid of the attributes.DrawAsHighlighter = true;. Instead, I've made 1 inkCanvas with 0.5 opacity specially for the highlighter, looking like you're using a highlighter.
Here's the code :
private void SetHighLight()
{
InkDrawingAttributes attributes = new InkDrawingAttributes();
attributes.PenTip = PenTipShape.Rectangle;
attributes.Size = new Size(4, 10);
attributes.Color = currentColor;
SetAttribute(attributes);
}
private void GetCanvasRender(out CanvasRenderTarget renderTarget, float opacity)
{
CanvasDevice device = CanvasDevice.GetSharedDevice();
renderTarget = new CanvasRenderTarget(device, (int)ink.ActualWidth, (int)ink.ActualHeight, 96);
using (var ds = renderTarget.CreateDrawingSession())
{
ds.Clear(Colors.Transparent);
using (ds.CreateLayer(opacity))
{
ds.DrawInk(ink.InkPresenter.StrokeContainer.GetStrokes());
}
}
}
private async void SavePicture(float opacity)
{
CanvasRenderTarget renderTarget;
Image img = new Image();
GetCanvasRender(out renderTarget, opacity);
StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
StorageFile noteFile = await storageFolder.CreateFileAsync(i.ToString() + ".png", CreationCollisionOption.ReplaceExisting);
using (var fileStream = await noteFile.OpenAsync(FileAccessMode.ReadWrite))
await renderTarget.SaveAsync(fileStream, CanvasBitmapFileFormat.Png, 1f);
img.Source = new BitmapImage(new Uri(storageFolder.Path + "/" + i++ + ".png"));
img.VerticalAlignment = VerticalAlignment.Stretch;
ContainerCanvas.Children.Add(img);
Canvas.SetTop(img, ScrollViewerContainer.VerticalOffset);
Canvas.SetZIndex(img, 5);
}

Related

Add text label with semi transparent background to an image using Magick.NET

I have some C# code that adds a simple text overlay with a border and semi-transparent background to an image. It works great, but I'm trying to get an equivalent result using Magick.NET. (The straight C# code drops the XMP tags from the original image, and I haven't found a way to deal with that.) Magick.NET handles the XMP tags well, but I'm having trouble replicating the original output.
Original code follows:
using (Image i = Image.FromStream(stream))
{
int width = i.Width;
int height = i.Height;
using (Graphics graphics = Graphics.FromImage(i))
{
string measureString = "my string";
Size stringSize = graphics.MeasureString(measureString, stringFont).ToSize();
Point drawLocation = new Point(width - stringSize.Width - 15, height - stringSize.Height - 15);
Rectangle rect = new Rectangle(drawLocation.X, drawLocation.Y, stringSize.Width, stringSize.Height);
graphics.DrawRectangle(blackPen, rect);
graphics.FillRectangle(fillBrush, rect);
graphics.DrawString(measureString, stringFont, Brushes.Yellow, drawLocation);
}
i.Save(outputFolder + Path.GetFileName(imgFileName));
}
I cobbled this together based on the Magick.NET examples. This get close to what I'm looking for, but adding the border removes the transparency value, and I'm left with a dark gray background, instead of the transparency.
var settings = new MagickReadSettings{
Font = "Calibri",
FillColor=MagickColors.Yellow,
StrokeColor=MagickColors.Black,
TextGravity = Gravity.Center,
BackgroundColor = new MagickColor("#66666699"),
BorderColor = MagickColors.Black,
Height = 250, // height of text box
Width = 680 // width of text box
};
using (var image = new MagickImage(inputFile))
{
using (var caption = new MagickImage($"caption:{myString}", settings))
{
//adding this border removes transparency
// caption.BorderColor = MagickColors.Black;
// caption.Border(1);
image.Composite(caption, Gravity.Southeast, CompositeOperator.Over);
image.Write(outputFile);
}
}
In command line ImageMagick, this seems to work for me in that the background color is transparent gray. The following the result may be what you want:
convert -font ubuntu -fill yellow -stroke black -gravity center -background "#66666699" -bordercolor black -size 250x680 caption:"This Is Some Text" result.png
Note: I used -background, not -backgroundcolor. Also BorderColor is not the color for the outline of the text. That is the stroke. You have not used BorderColor, since you have not specified the Border amount (as in -border in command line), which would outline the image rectangle and not the text.
Due to time constraints with my project, I took a slightly different path to make this work. I wound up creating the transparent overlay using my original .NET drawing code, and passing that as a memory stream to Magick.NET to handle the merge.
Workaround:
string measureString = "build custom string here";
using (var tmpStreamImg = new MemoryStream())
{
// Call custom function to get length of my string
System.Drawing.Size stringSize = MeasureString(measureString, stringFont).ToSize();
Rectangle rect = new Rectangle(0, 0, stringSize.Width, stringSize.Height);
using (Bitmap overlay = new Bitmap(rect.Width, rect.Height))
{
overlay.SetResolution(350, 350);
using (Graphics overlayGraphic = Graphics.FromImage(overlay))
{
overlayGraphic.DrawRectangle(blackPen, rect);
overlayGraphic.FillRectangle(fillBrush, rect);
overlayGraphic.DrawString(measureString, stringFont, Brushes.Yellow, 3, 3);
}
overlay.Save(tmpStreamImg, ImageFormat.Png);
}
tmpStreamImg.Position= 0;
using (var originalImage = new MagickImage(imgFileName))
{
using (var overlayImage = new MagickImage(tmpStreamImg))
{
originalImage.Composite(overlayImage, Gravity.Southeast, CompositeOperator.Over);
originalImage.Write(outputFolder + Path.GetFileName(imgFileName));
}
}
}

Is there a way to lower the resolution of a UWP InkCanvas to 28x28?

In an attempt to create a neural network program to detect handwritten digits, I first need to capture the handwritten digit on an InkCanvas. The problem is that when I save the InkCanvas as a Bitmap, the resolution is much greater than 28x28. I'm favoring 28x28 because the input layer to my neural network will have 784 nodes. The size of my input layer will skyrocket if I use the full resolution of the InkCanvas.
As it stands, I draw the handwritten digits in a 28x28 canvas within MS Paint.
I then save the image as a bmp file in my programs project folder.
From there, my program sees the bmp file and processes it accordingly.
I just need help lowering the resolution of the UWP InkCanvas.
I do not simply want to edit the width and height, but the actual pixel count/zoom.
Main Page
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
// sets initial window size
ApplicationView.PreferredLaunchViewSize = new Windows.Foundation.Size(1000, 800);
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;
// sets supported inking device types
inkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse;
// sets initial ink stroke attributes
InkDrawingAttributes drawingAttributes = new InkDrawingAttributes();
drawingAttributes.Color = Windows.UI.Colors.White;
drawingAttributes.Size = new Windows.Foundation.Size(2, 2);
drawingAttributes.IgnorePressure = false;
drawingAttributes.FitToCurve = true;
inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttributes);
}
private async void saveInkCanvasBitmap()
{
RenderTargetBitmap bitmap = new RenderTargetBitmap();
await bitmap.RenderAsync(grid);
var pixelBuffer = await bitmap.GetPixelsAsync();
byte[] pixels = pixelBuffer.ToArray();
var displayInformation = DisplayInformation.GetForCurrentView();
StorageFolder pictureFolder = KnownFolders.SavedPictures;
var file = await pictureFolder.CreateFileAsync("image.bmp", CreationCollisionOption.ReplaceExisting);
using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.BmpEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
(uint)bitmap.PixelWidth,
(uint)bitmap.PixelHeight,
displayInformation.RawDpiX,
displayInformation.RawDpiY,
pixels);
await encoder.FlushAsync();
}
}
private void clearButton_Click(object sender, RoutedEventArgs e)
{
// clears the ink canvas of any pen strokes
inkCanvas.InkPresenter.StrokeContainer.Clear();
}
private void submitButton_Click(object sender, RoutedEventArgs e)
{
saveInkCanvasBitmap(); // try to lower resolution of ink canvas to 28 x 28
// other stuff
}
}
Based on the description of BitmapEncoder.SetPixelData, the parameter called dpiX and dpiY are the resolution values for the bitmap. Please try to change your code like
encoder.SetPixelData(BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
(uint)bitmap.PixelWidth,
(uint)bitmap.PixelHeight,
28,
28,
pixels);

UWP / C# Rotating BMP

The question seems to be asked already, however I cannot find a relevant answer.
I am loading a BMP image to memory in a UWP app, and I would like to rotate it by either 90, 180 or 270, but I just cannot find the way to do this.
The imgSource.rotate() does not seem to exist anymore
The RotateTransform works with xaml
....
Could anyone add the missing code by a chance please?
public async Task LoadImage()
{
StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync("test.bmp");
using (var stream = await file.OpenAsync(FileAccessMode.Read))
{
var decoder = await BitmapDecoder.CreateAsync(stream);
bitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
var imgSource = new WriteableBitmap(bitmap.PixelWidth, bitmap.PixelHeight);
// Code to rotate image by 180 to be added
bitmap.CopyToBuffer(imgSource.PixelBuffer);
}
}
The RotateTransform works with xaml
As you known, RotateTransform is for rotate transform in uwp app XAML. A RotateTransform is defined by an Angle that rotates an object through an arc around the point CenterX, CenterY. But a transform is typically used to fill the UIElement.RenderTransform property, so if you load the image source to an ImageControl, you can rotate the ImageControl since it is a UIElement. For example, if we have ImageControl as follows:
<Image x:Name="PreviewImage" Height="400" Width="300" AutomationProperties.Name="Preview of the image" Stretch="Uniform" HorizontalAlignment="Center" VerticalAlignment="Center"/>
We can simply rotate it by angle property by code as:
RotateTransform m_transform = new RotateTransform();
PreviewImage.RenderTransform = m_transform;
m_transform.Angle = 180;
If you need rotate an image file not a UIElement, you may need to decode the image file as what you already did and then encode the file with setting the BitmapTransform.Rotation property. Code as follows:
double m_scaleFactor;
private async void btnrotatefile_Click(object sender, RoutedEventArgs e)
{
StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync("test.bmp");
using (IRandomAccessStream fileStream = await file.OpenAsync(FileAccessMode.ReadWrite),
memStream = new InMemoryRandomAccessStream())
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
uint originalWidth = decoder.PixelWidth;
uint originalHeight = decoder.PixelHeight;
BitmapEncoder encoder = await BitmapEncoder.CreateForTranscodingAsync(memStream, decoder);
if (m_scaleFactor != 1.0)
{
encoder.BitmapTransform.ScaledWidth = (uint)(originalWidth * m_scaleFactor);
encoder.BitmapTransform.ScaledHeight = (uint)(originalHeight * m_scaleFactor);
encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;
}
//Rotate 180
encoder.BitmapTransform.Rotation = BitmapRotation.Clockwise180Degrees;
await encoder.FlushAsync();
memStream.Seek(0);
fileStream.Seek(0);
fileStream.Size = 0;
await RandomAccessStream.CopyAsync(memStream, fileStream);
}
}
More features about the image file rotation you can use other APIS under Windows.Graphics.Imaging namespace. And the scenario 2 of SimpleImaging official sample provides a complete sample about image rotation you can reference.

Can I create Icons on the fly in WPF?

Since I am not getting anywhere with my previous question, I would like to know, are there any ways I can create icons on the fly on WPF?
You don't need WPF.
Usign GDI+ (System.Drawing.dll), you can create a 16x16 Bitmap, then call Icon.FromHandle(bitmap.GetHicon()).
You can use WritableBitmap for this.
You can use Taskbar icon progress bar...Sure U have seen, most of the application if doing any scanning or progress things, it displays progress actions on Icon.
Do this in your main form where u included the Icon
<Window x:Class="CCTrayHelper.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Icon="/CCTrayHelper;component/Images/CCTrayHelperIcon.png">
<Window.TaskbarItemInfo>
<TaskbarItemInfo />
</Window.TaskbarItemInfo>
Trig it from code behind
private void OnProgress(object sender, EventArgs args)
{
Dispatcher.Invoke(DispatcherPriority.Send, (Action)delegate() { TaskbarItemInfo.ProgressState = TaskbarItemProgressState.None; });
// Use here your progress type
}
This is what I ended up doing, I don't fully understand the details, if you find anything that can be improved, please do comment. Thanks for all the answers and comments.
public static ImageSource GetIconWithText(int digit)
{
BitmapImage myBitmapImage = new BitmapImage(new Uri(#"Images\PomoDomo.ico",
UriKind.RelativeOrAbsolute));
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
// Draw image
drawingContext.DrawImage(myBitmapImage, new Rect(0, 0, myBitmapImage.Width,
myBitmapImage.Height));
var typeFace = new Typeface(new FontFamily("Verdana"), FontStyles.Normal,
FontWeights.ExtraBold, FontStretches.UltraCondensed);
var formatedText = new FormattedText(digit.ToString(),
CultureInfo.InvariantCulture,
FlowDirection.LeftToRight,
typeFace,
40,
System.Windows.Media.Brushes.White);
//Center the text on Image
int pointY = (int)(myBitmapImage.Height - formatedText.Height) / 2;
int pointX = (int)(myBitmapImage.Width - formatedText.Width) / 2;
drawingContext.DrawText(formatedText, new Point(pointX, pointY));
}
RenderTargetBitmap finalBitmap = new RenderTargetBitmap((int)myBitmapImage.Width,
(int)myBitmapImage.Height, myBitmapImage.DpiX, myBitmapImage.DpiY,
PixelFormats.Pbgra32);
finalBitmap.Render(drawingVisual);
return finalBitmap;
}
private static void SaveImage(RenderTargetBitmap returnBitmap, string pngFileName)
{
string fileName = string.Format("{0}.png", pngFileName)
PngBitmapEncoder image = new PngBitmapEncoder();
image.Frames.Add(BitmapFrame.Create(returnBitmap));
using (Stream fs = File.Create(fileName))
{
image.Save(fs);
}
}
You can find a method here and here to render text on writeablebitmap

How do I print an Image from a Uri?

I am attempting to print a JPEG file that I reference using a Uri object and am having some difficulties. I found that while the image was printing, it was cropped slightly and was flipped and mirrored. I'm guessing that the crop was caused by a size not being set properly but have no idea why it's being flipped and rotated. Assuming that this was a natural oddity, I attempted to resolve the issue by applying a transform to the drawingContext object but this results a blank page being printed. Here is my code:
public void Print(List<Uri> ListToBePrinted)
{
XpsDocumentWriter writer =
PrintQueue.CreateXpsDocumentWriter(this.SelectedPrinter.PrintQueue);
PrintCapabilities printerCapabilities =
this.SelectedPrinter.PrintQueue.GetPrintCapabilities();
Size PageSize =
new Size(printerCapabilities.PageImageableArea.ExtentWidth,
printerCapabilities.PageImageableArea.ExtentHeight);
foreach (Uri aUri in ListToBePrinted)
{
BitmapImage anImage = new BitmapImage(aUri);
//create new visual which would be initialized by image
DrawingVisual drawingVisual = new DrawingVisual();
//create a drawing context so that image can be rendered to print
DrawingContext drawingContext = drawingVisual.RenderOpen();
// Flips along X and Y axis (flips and mirrors)
drawingContext.PushTransform(new ScaleTransform(-1, -1));
drawingContext.DrawImage(anImage, new Rect(PageSize));
drawingContext.Close();
writer.Write(drawingVisual);
}
}
Any help would be greatly appreciated - thank you!
Here's what I ended up with:
public void Print(List<Uri> ListToBePrinted)
{
XpsDocumentWriter writer =
PrintQueue.CreateXpsDocumentWriter(this.SelectedPrinter.PrintQueue);
PrintCapabilities printerCapabilities =
this.SelectedPrinter.PrintQueue.GetPrintCapabilities();
Size PrintableImageSize =
new Size(printerCapabilities.PageImageableArea.ExtentWidth,
printerCapabilities.PageImageableArea.ExtentHeight);
foreach (Uri aUri in ListToBePrinted)
{
DrawingVisual drawVisual = new DrawingVisual();
ImageBrush imageBrush = new ImageBrush();
imageBrush.ImageSource = new BitmapImage(aUri);
imageBrush.Stretch = Stretch.Fill;
imageBrush.TileMode = TileMode.None;
imageBrush.AlignmentX = AlignmentX.Center;
imageBrush.AlignmentY = AlignmentY.Center;
using (DrawingContext drawingContext = drawVisual.RenderOpen())
{
// Flips along X and Y axis (flips and mirrors)
drawingContext.PushTransform(new ScaleTransform(-1, 1, PrintableImageSize.Width / 2, PrintableImageSize.Height / 2));
drawingContext.PushTransform(new RotateTransform(180, PrintableImageSize.Width / 2, PrintableImageSize.Height / 2)); // Rotates 180 degree
drawingContext.DrawRectangle(imageBrush, null, new Rect(25, -25, PrintableImageSize.Width, PrintableImageSize.Height));
}
writer.Write(drawVisual);
}
}
The image is a little fuzzy but is certainly acceptable. I'm still not sure why my image needed to be flipped or mirrored.
Could you do something like:
BitmapImage anImage = new BitmapImage(aUri);
Image image = new Image();
image.BeginInit();
image.Source = anImage;
image.EndInit();
image.Measure(PageSize);
image.InvalidateVisual();
Then just print the Image object since it derives from Visual...
You need to call InvalidateVisual so that OnRender will be called, if you didn't it would result in a blank image...

Categories

Resources