I have a big svg image.
I would like to crop it to a rectangle, using coordinates, and convert it to png image.
I have to say that I'm not used to drawing with c#.
Surface, Canvas and other notions are new to me.
I have figured out how to load the svg, using SkiaShark and SkiaShark.Svg:
var svg = new SkiaSharp.Extended.Svg.SKSvg();
svg.Load(tmpPath);
And found a gist that saves a png. But this is "Chinese" for me.
var imageInfo = new SKImageInfo(100, 100);
using (var surface = SKSurface.Create(imageInfo))
using (var canvas = surface.Canvas)
{
// calculate the scaling need to fit to screen
var scaleX = 100 / svg.Picture.CullRect.Width;
var scaleY = 100 / svg.Picture.CullRect.Height;
var matrix = SKMatrix.CreateScale((float)scaleX, (float)scaleY);
// draw the svg
canvas.Clear(SKColors.Transparent);
canvas.DrawPicture(svg.Picture, ref matrix);
canvas.Flush();
using (var data = surface.Snapshot())
using (var pngImage = data.Encode(SKEncodedImageFormat.Png, 100))
{
tmpPath = Path.ChangeExtension(tmpPath, "PNG");
using var imageStream = new FileStream(tmpPath, FileMode.Create);
pngImage.SaveTo(imageStream);
}
}
If someone could show me the directions, it would be much appreciated.
EDIT
I've come to this implentation myself, though it is not working... The result bitmap is transparent and empty.
private string ConvertSVGToPNGRectangleWithSkiaSharpExtended(string path, double left, double top, double right, double bottom)
{
var svg = new SkiaSharp.Extended.Svg.SKSvg();
var picture = svg.Load(path);
// Get the initial map size
var source = new SKRect(picture.CullRect.Left, picture.CullRect.Top, picture.CullRect.Width, picture.CullRect.Height);
// Cropping Rect
var cropRect = new SKRect((int)left, (int)top, (int)right, (int)bottom);
var croppedBitmap = new SKBitmap((int)cropRect.Width, (int)cropRect.Height);
using var canvas = new SKCanvas(croppedBitmap);
canvas.Clear(SKColors.Transparent);
canvas.DrawBitmap(croppedBitmap, source, cropRect);
var data = croppedBitmap.Encode(SKEncodedImageFormat.Png, 100);
path = Path.ChangeExtension(path, "PNG");
using var imageStream = new FileStream(path, FileMode.Create);
data.SaveTo(imageStream);
return path;
}
EDIT 2:
I'm sorry , if it is not clear. You can refer to this question for doing so on Android.
Disclaimer: I'm not an expert with SkiaSharp, but I do know about drawing and canvases and I looked up the documentation at https://learn.microsoft.com/en-us/dotnet/api/skiasharp.skcanvas.drawpicture
I think the problem is this line:
canvas.DrawBitmap(croppedBitmap, source, cropRect);
I think you need:
canvas.DrawPicture(picture, -cropRect.Left, -cropRect.Top);
canvas.Flush();
I might have the coordinates wrong, but the problem behind your failure is that you are never drawing picture to the canvas.
To help with your confusion about what a canvas is: SKCanvas(croppedBitmap) is making it so that every time you draw to canvas, what you are actually drawing on is the bitmap. So in order to draw your picture onto the bitmap, you have to draw picture onto canvas. Using negative coordinates make it so the top and left of the svg picture are above and to the left of the top-left corner of the bitmap, which is the equivalent of having the bitmap start somewhere in the middle of the svg picture.
Hope this helps!
Related
I'm trying to crop an image from webcam and display right next to the camera preview.
Cropping an image should be carried with 3 considerations.
Output of cropped image should be in form of VideoFrame
The above output, VideoFrame, needs to be displayed (on XAML)
The target crop image is in the middle of original image
I found RenderTargetBitmap would help me to get an cropped image.
But still I have no idea how to display VideoFrame (without saving an image), set the position where to crop.
I got stuck below...
public async Task<VideoFrame> CroppingImage(Grid grid)
{
RenderTargetBitmap renderBitmap = new RenderTargetBitmap();
await renderBitmap.RenderAsync(grid);
var buffer = await renderBitmap.GetPixelsAsync();
var softwareBitmap = SoftwareBitmap.CreateCopyFromBuffer(buffer, BitmapPixelFormat.Bgra8, renderBitmap.PixelWidth, renderBitmap.PixelHeight, BitmapAlphaMode.Ignore);
buffer = null;
renderBitmap = null;
VideoFrame vf = VideoFrame.CreateWithSoftwareBitmap(softwareBitmap);
await CropAndDisplayInputImageAsync(vf);
return cropped_vf;
}
private async Task CropAndDisplayInputImageAsync(VideoFrame inputVideoFrame)
{
//some cropping algorithm here
//i have a rectangle on a canvas(camera preview is on CaptureElement)
//I know the left top position and width and height but no idea how to use
}
Any help?
This is what i found and done :)
(assume that there is a videoframe which name is croppedface)
croppedFace = new VideoFrame(BitmapPixelFormat.Bgra8, (int)width, (int)height, BitmapAlphaMode.Ignore);
await inputVideoFrame.CopyToAsync(croppedFace, cropBounds, null);
SoftwareBitmap asdf = croppedFace.SoftwareBitmap;
asdf = SoftwareBitmap.Convert(asdf, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore);
var qwer = new SoftwareBitmapSource();
await qwer.SetBitmapAsync(asdf);
CroppedFaceImage.Source = qwer;
But still I have no idea how to display VideoFrame(without saving an image), set the position where to crop.
If you want to show the frame on the xaml, you need to convert the frame to a displayable format and rendering it to the screen. Please check the FrameRender class in the official Camera frames sample. It has a ConvertToDisplayableImage method that should be what you want.
Then, you could show it in Image control. After that, you could use Image.Clip to set the position where you want to crop.
I'm trying to create a level editor using Windows Forms for my monogame project and need to draw small pixel based images to a picture box with no quality loss when scaled. In monogame when I need to do this I can just set the draw type to PointClamp and then each pixel is drawn as is instead of being pixelated when zoomed; I was hoping for something like this via a picturebox. Right now it looks like this But I'd prefer a more crisp and clean image like this (The second is how it'll appear in monogame). I haven't uploaded any code for this, but just assume I grabbed an image from the filestream and used the bitmap constructor to scale it up (don't think that's relevent but I'll just put it out there).
Image croppedImage, image = tileMap.tileBox.Image;
var brush = new SolidBrush(Color.Black);
try { croppedImage = CropImage(image, tileMap.highlightedRect); } catch {
return; // If crop target is outside bounds of image then return
}
float scale = Math.Min(higlightedTileBox.Width / croppedImage.Width, higlightedTileBox.Height / image.Height);
var scaleWidth = (int)(higlightedTileBox.Width * scale);
var scaleHeight = (int)(higlightedTileBox.Height * scale);
try { higlightedTileBox.Image = new Bitmap(croppedImage, new Size(scaleWidth, scaleHeight)); } catch {
return; // Image couldn't be scaled or highlighted tileBox couldn't be set to desired image
}
CropImage:
private static Image CropImage(Bitmap img, Rectangle cropArea) {
return img.Clone(cropArea, img.PixelFormat);
}
private static Image CropImage(Image img, Rectangle cropArea) {
return CropImage(new Bitmap(img), cropArea);
}
The code above is my current method in it's entirety. tileMap is a form and tilebox is the picturebox within that form.image is the full spritesheet texture before being cropped to what the user has highlighted. After being cropped I attempt to set the current picturebox (highlightedTileBox's) image to a scaled up version of the cropped image.
So I got a solution by trying around a bit.
It looks like scaling images directly by size is using some sort of interpolation.
To try different interpolation modes supported by Winforms, I created a little demo.
As you can see, every label contains the name of the InterpolationMode and is followed by its resulting image. The original bitmap I used is the small one at the top.
From your question, it looks like you would like to achieve something like NearestNeighbour.
Following code scales bmp and the result is stored in bmp2. Try if that's what you want. Consider building a proper implementation if you're using this as solution (disposing unused bitmaps etc.).
I hope it helps.
Bitmap bmp = new Bitmap("test.bmp");
Bitmap bmp2;
Graphics g = Graphics.FromImage(bmp2=new Bitmap(bmp.Width * 2, bmp.Height * 2));
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
g.DrawImage(bmp, 0, 0, bmp.Width * 2, bmp.Height * 2);
g.Dispose();
I've searched a bit around the discussions\forums/StackOverflow/Official documentation, but i couldn't find much information about how to achieve what i'm trying. Most of the official documentation covers the command-line version of ImageMagick.
I'll describe what i'm trying to do:
I have a image loaded that i would like to paste into a larger one.
Ex: the image i loaded has 9920 width, 7085 height. I would like to place it in the middle of a larger one (10594 width, 7387 height). I do have all border calculation ready ([larger width - original width / 2] , same goes for height).
But i don't know how to do it using MagickImage. Here's the max i got:
private void drawInkzone(MagickImage loadedImage, List<string>inkzoneAreaInformation, string filePath)
{
unitConversion converter = new unitConversion();
List<double> inkZoneInfo = inkZoneListFill(inkzoneAreaInformation);
float DPI = getImageDPI(filePath);
double zoneAreaWidth_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(4), DPI);
double zoneAreaHeight_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(5), DPI);
using (MagickImage image = new MagickImage(MagickColor.FromRgb(255, 255, 255), Convert.ToInt32(zoneAreaWidth_Pixels), Convert.ToInt32(zoneAreaHeight_Pixels)))
{
//first: defining the larger image, with a white background (must be transparent, but for now its okay)
using (MagickImage original = loadedImage.Clone())
{
//Cloned the original image (already passed as parameter)
}
}
Here's the max i got. In order to achieve this, i used the following post:
How to process only one part of image by ImageMagick?
And i'm not using GDI+ because i'll be always working with larger TIFF files (big resolutions), and GDI+ tends to throw exceptions (Parameter not valid, out of memory) when it can't handle everything (i loaded three images with an resolution like that, and got out of memory).
Any help will be kindly appreciate, thanks.
Pablo.
You could either Composite the image on top of a new image with the required background or you could Clone and Extent if with the required background. In the answer from #Pablo Costa there is an example for Compositing the image so here is an example on how you could extent the image:
private void drawInkzone(MagickImage loadedImage, List<string> inkzoneAreaInformation, string filePath)
{
unitConversion converter = new unitConversion();
List<double> inkZoneInfo = inkZoneListFill(inkzoneAreaInformation);
float DPI = getImageDPI(filePath);
double zoneAreaWidth_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(4), DPI);
double zoneAreaHeight_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(5), DPI);
using (MagickImage image = loadedImage.Clone())
{
MagickColor background = MagickColors.Black;
int width = (int)zoneAreaWidth_Pixels;
int height = (int)zoneAreaHeight_Pixels;
image.Extent(width, height, Gravity.Center, background);
image.Write(#"C:\DI_PLOT\whatever.png");
}
}
I managed to accomplish what i needed.
Cool that i didn't had to calculate borders.
Here's the code:
private void drawInkzone(MagickImage loadedImage, List<string>inkzoneAreaInformation, string filePath)
{
unitConversion converter = new unitConversion();
List<double> inkZoneInfo = inkZoneListFill(inkzoneAreaInformation); //Larger image information
float DPI = getImageDPI(filePath);
double zoneAreaWidth_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(4), DPI); //Width and height for the larger image are in mm , converted them to pixel
double zoneAreaHeight_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(5), DPI);//Formula (is: mm * imageDPI) / 25.4
using (MagickImage image = new MagickImage(MagickColor.FromRgb(0, 0, 0), Convert.ToInt32(zoneAreaWidth_Pixels), Convert.ToInt32(zoneAreaHeight_Pixels)))
{
//first: defining the larger image, with a white background (must be transparent, but for now its okay)
using (MagickImage original = loadedImage.Clone())
{
//Cloned the original image (already passed as parameter)
image.Composite(loadedImage, Gravity.Center);
image.Write(#"C:\DI_PLOT\whatever.png");
}
}
Hope this helps someone :)
I'm trying to print image from Silverlight application. I have pretty good quality scans (TIFF) with resolution 1696x2200
When I print - I get PrintableArea from PrintDocument and it's 816x1056
What I do - I resize bitmap to Printable area (to fit document to page) and result I get is blurry image. I understand this is scaling problem (most likely), but how do I scale properly so it looks good? When I display document inside Image and just set image size - it looks good.
For resizing I'm using WriteableBitmapEx extensions and tried both types of resize (Nearest neighbor and bilinear)
Code:
var printDocument = new PrintDocument();
printDocument.PrintPage += (s, ea) =>
{
var printableArea = ea.PrintableArea;
var bitmap = this.currentPreviewPage.FullBitmap.Resize((int)printableArea.Width, (int)printableArea.Height, WriteableBitmapExtensions.Interpolation.Bilinear);
var image = new Image { Source = bitmap };
var canvas = new Canvas { Width = bitmap.PixelWidth, Height = bitmap.PixelHeight };
canvas.Children.Add(image);
ea.PageVisual = canvas;
ea.HasMorePages = false;
};
printDocument.PrintBitmap("Silverlight Bitmap Print");
How document looks on screen (inside Image)
And this is printed:
Rather than using the WriteableBitmapEx extensions, when declaring your Image element, try setting the Stretch property so that it stretches based on your maximum specified dimensions:
var image = new Image { Source = bitmap, Stretch = Stretch.UniformToFill };
Blilinear filter tends to blur images.You may want to try WriteableBitmapExtensions.Interpolation.NearestNeighbor instead to see if you get better results
In my case it was enough to set UseLayoutRounding="True".
I have a TextBox that I allow my users to rotate. But what I would LOVE for my users is to have their Cursor rotate to the same angle that the TextBox was rotated at. For example, if they rotated the TextBox to 28°, then when the Cursor enters that TextBox the Cursor should also rotate itself to 28°.
You can rotate your cursor using the System.Drawing.Icon class from WinForms in combination with WPF's bitmap rotation ability.
The way to do this is to load the icon, convert it to a BitmapSource, use Image and RenderTargetBitmap to rotate it, convert it back to an Icon, save it, and finally update bytes 2, 10, and 11 that make it a .cur instead of a .ico.
Here's what the code looks like:
public Cursor GetRotatedCursor(byte[] curFileBytes, double rotationAngle)
{
// Load as Bitmap, convert to BitmapSource
var origStream = new MemoryStream(curFileBytes);
var origBitmap = new System.Drawing.Icon(origStream).ToBitmap();
var origSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(origBitmap.GetHBitmap());
// Construct rotated image
var image = new Image
{
BitmapSource = origSource,
RenderTransform = new RotateTransform(rotationAngle)
};
// Render rotated image to RenderTargetBitmap
var width = origBitmap.Width;
var height = origBitmap.Height;
var resultSource = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
resultSource.Render(image);
// Convert to System.Drawing.Bitmap
var pixels = new int[width*height];
resultSource.CopyPixels(pixels, width, 0);
var resultBitmap = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppPargb);
for(int y=0; y<height; y++)
for(int x=0; x<width; x++)
resultBitmap.SetPixel(x, y, Color.FromArgb(pixels[y*width+x]));
// Save to .ico format
var resultStream = new MemoryStream();
new System.Drawing.Icon(resultBitmap.GetHIcon()).Save(resultStream);
// Convert saved file into .cur format
resultStream.Seek(2); resultStream.WriteByte(curFileBytes, 2, 1);
resultStream.Seek(10); resultStream.WriteByte(curFileBytes, 10, 2);
resultStream.Seek(0);
// Construct Cursor
return new Cursor(resultStream);
}
If you want to avoid the loop, you can replace it with a small bit of usafe code to call the System.Drawing.Bitmap constructor that takes initialization data:
fixed(int* bits = pixels)
{
resultBitmap = new System.Drawing.Bitmap(width, height, width, System.Drawing.Imaging.PixelFormat.Format32bppPargb, new IntPtr(bits));
}
You'll need to call this every time your TextBox rotation changes. This can be done either from the code that rotates your TextBox, or from a PropertyChangedCallback on a value that is bound to the TextBox's rotation.
mmm I'm not sure... but since the cursor is managed by Windows.. I guess you would need to hide the cursor when it enters the textbox and draw your own (which would be easy to rotate since you are rotating the other controls).
Heh, Googling for a way to do this, the first result was naturally from SO, you might wanna check the accepted answer (if you are using wpf):
Custom cursor in WPF?