Background
My project is urgent and requires that I iterate a large XML file and return Base64 encoded images.
Each image must be inserted into an MS Word doc, and I am using the DocX library for that.
I am converting the Base64 strings to bitmap with no problem.
Problem
For the life of me, I can't seem to get the bitmaps into a Novacode.Image object which can then be inserted to the document. NOTE: I already know how to convert to System.Drawing.Image format. It is Novacode.Image format (DocX) that is giving me grief.
If I try to convert a la (Novacode.Image)somebitmap; I get Can not cast expression of type Image to Bitmap. If I try to initialize a new Novacode.Image object I get Can not access internal constructor Image here.
Using C#, .NET 4, Forms App, lots of coffee.
Question
Only Novacode.Image objects can be inserted into the MS Word doc via the library, so how the heck do I get my bitmap in there??
I am bleary-eyed at this point so perhaps I am just missing something simple.
You have to use the DocX.AddImage() method to create a Novacode.Image object.
Follow these 5 steps to add a image to a word document:
Save your picture into a memory stream.
Create the Novacode.Image object by calling AddImage() method.
Create a picture by calling CreatePicture() on the Novacode.Image object created in step 2.
Set the shape of the picture (if needed).
Insert your picture into a pragraph.
The sample below shows how to insert a image into a word document:
using (DocX doc = DocX.Create(#"Example.docx"))
{
using (MemoryStream ms = new MemoryStream())
{
System.Drawing.Image myImg = System.Drawing.Image.FromFile(#"test.jpg");
myImg.Save(ms, myImg.RawFormat); // Save your picture in a memory stream.
ms.Seek(0, SeekOrigin.Begin);
Novacode.Image img = doc.AddImage(ms); // Create image.
Paragraph p = doc.InsertParagraph("Hello", false);
Picture pic1 = img.CreatePicture(); // Create picture.
pic1.SetPictureShape(BasicShapes.cube); // Set picture shape (if needed)
p.InsertPicture(pic1, 0); // Insert picture into paragraph.
doc.Save();
}
}
Hope, this helps.
Thanks Hans and Martin, I was able to use this as a basis for ensuring large image files (photos) are always sized to fit on the page. The max width and max height can be changed depending on your page size.
using (MemoryStream ms = new MemoryStream())
{
System.Drawing.Image myImg = System.Drawing.Image.FromFile(imageDirectory + i.FileName);
double xScale = 1;
double yScale = 1;
double maxWidthInches = 6.1; // Max width to fit on a page
double maxHeightInches = 8.66; // Max height to fit on a page
// Normalise the Horizontal and Vertical scale for different resolutions
double hScale = ((double)96 / myImg.HorizontalResolution);
double vScale = ((double)96 / myImg.VerticalResolution);
// Scaling required to fit in x direction
double imageWidthInches = myImg.Width / myImg.HorizontalResolution; // in inches using DPI
if (imageWidthInches > maxWidthInches)
xScale = maxWidthInches / imageWidthInches * hScale;
// Scaling required to fit in y direction
double imageHeightInches = myImg.Height / myImg.VerticalResolution;
if (imageHeightInches > maxHeightInches)
yScale = maxHeightInches / imageHeightInches * vScale;
double finalScale = Math.Min(xScale, yScale); // Use the smallest of the two scales to ensure the picture will fit in both directions
myImg.Save(ms, myImg.RawFormat); // Save your picture in a memory stream.
ms.Seek(0, SeekOrigin.Begin);
Novacode.Image img = document.AddImage(ms); // Create image.
Paragraph p = document.InsertParagraph();
Picture pic = img.CreatePicture(); // Create picture.
//Apply final scale to height & width
double width = Math.Round((double)myImg.Width * finalScale);
double height = Math.Round((double)myImg.Height * finalScale);
pic.Width = (int)(width);
pic.Height = (int)(height);
p.InsertPicture(pic); // Insert picture into paragraph.
}
Thanks Hans. I had an Issue where the Image is inserted at the wrong size based on the DPI so I used this to scale the image based on DPI, 96 dpi seemed to be the basis of the scaled image in word:
using (MemoryStream ms = new MemoryStream())
{
System.Drawing.Image myImg = System.Drawing.Image.FromFile(path);
//Calculate Horizontal and Vertical scale
float Hscale = ((float)96 / myImg.HorizontalResolution);
float Vscale = ((float)96 / myImg.VerticalResolution );
myImg.Save(ms, myImg.RawFormat); // Save your picture in a memory stream.
ms.Seek(0, SeekOrigin.Begin);
Novacode.Image img = proposal.AddImage(ms); // Create image.
Picture pic1 = img.CreatePicture(); // Create picture.
//Apply scale to height & width
pic1.Height = (int)(myImg.Height * Hscale);
pic1.Width = (int)(myImg.Width * Vscale);
a.InsertPicture(pic1, 0); // Insert picture into paragraph.
}
Related
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!
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 have done image resizing while allowing user to upload a specific size image and then crop them to different dimension i have also used jCrop in project to allow users to upload a image of specific size and then select the image area & crop it accordingly.
In new project i have a requirement where user can upload any size image which is at least larger than 500Px in width and then i have to allow user to select the part of image using jCrop and then save image in different dimension of 475x313 , 310x205 while maintaining the aspect ration.
I can do it with if i allow the used to upload a fixed size image but i am not sure how i can handle variable size image.
I also need to display the image uploaded before cropping in a fixed size box.. let us say 300x200. in this area i have to allow the user to select the part of the image before i can crop.
Issue i am facing is how to handle variable length image and show it is a fixed image box of 300x200px.
I wrote an article on using jCrop with dynamically-resized uploaded images, which seems to be what you're needing.
If you're looking for an open-source ASP.NET control that does it for you, check out cropimage.net.
Want to going to by programmatically than you can try this :
if you are using file upload for upload images
string path = Path.GetFileName(fileuploaderID.PostedFile.FileName);
ConvertThumbnails(width, height, fileuploaderID.FileBytes, path);
your function
public void ConvertThumbnails(int width, int height, byte[] filestream, string path)
{
// create an image object, using the filename we just retrieved
var stream = new MemoryStream(filestream);
System.Drawing.Image image = System.Drawing.Image.FromStream(stream);
try
{
int fullSizeImgWidth = image.Width;
int fullSizeImgHeight = image.Height;
float imgWidth = 0.0F;
float imgHeight = 0.0F;
imgWidth = width;
imgHeight = height;
Bitmap thumbNailImg = new Bitmap(image, (int)imgWidth, (int)imgHeight);
MemoryStream ms = new MemoryStream();
// Save to memory using the Jpeg format
thumbNailImg.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
// read to end
byte[] bmpBytes = ms.GetBuffer();
item.Attachments.Add(path, bmpBytes);
thumbNailImg.Dispose();
ms.Close();
}
catch (Exception)
{
image.Dispose();
}
}
So, I have an app that takes an original image, gets the new new cropped region, and then saves the cropped version of the image as a new file. It works perfectly with one major drawback. The new image is, on average, 4x larger than the original image. In my test, I have a photograph that has a size on disk of ~4.5MB, and the cropped version (which is properly cropped and looks fine) is ~21MB on disk. The code is as follows:
var originalImage = new Bitmap(imagePath);
var fWidth = originalImage.PhysicalDimension.Width;
var fHeight = originalImage.PhysicalDimension.Height;
float calculatedWidth = GetCroppedWidth();
float calculatedHeight = GetCroppedHeight();
//Draw the image by centering the cropped region on the original
var heightOffset = (fHeight - calculatedHeight) / 2;
var widthOffset = (fWidth - calculatedWidth) / 2;
var sourceRectF = new RectangleF(widthOffset, heightOffset, calculatedWidth, calculatedHeight);
var croppedImage = originalImage.Clone(sourceRectF, originalImage.PixelFormat);
//Save the image
croppedImage.Save(croppedFileName);
It sounds like the image you are loading is some other format than BMP (e.g. PNG or JPG).
Use another overload of Bitmap.Save that specified an ImageFormat
Look at the Bitmap.Save overload that lets you choose the output format.
By default, it is Bmp i guess, which has no compression.
so, in your case use
croppedImage.Save(croppedFileName, originalImage.RawFormat);
I'm developing a service in which users can upload images and save them in a library. I want to preserve the original file (so people can upload high resolution images), but also make a copy of the image to use as a thumbnail.
The problem I have is that the thumbnail "weights" much more than the original file, proportionally. When I check the basic properties of each file (in XnView), I can see that the original files are for instance saved with 32 bits per Plane, whereas the source file will have, for instance, only 24 bits per plane.
What would be the correct way to make a copy of the original file while still using compression? This is an excerpt of the code:
private void ResizeImage(string originalFile, string NewFile, int NewWidth, int MaxHeight, bool OnlyResizeIfWider, string directory)
{
System.Drawing.Image FullsizeImage = System.Drawing.Image.FromFile(originalFile);
// Prevent using images internal thumbnail
FullsizeImage.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);
FullsizeImage.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);
if (OnlyResizeIfWider)
{
if (FullsizeImage.Width <= NewWidth)
{
NewWidth = FullsizeImage.Width;
}
}
int NewHeight = FullsizeImage.Height * NewWidth / FullsizeImage.Width;
if (NewHeight > MaxHeight)
{
// Resize with height instead
NewWidth = FullsizeImage.Width * MaxHeight / FullsizeImage.Height;
NewHeight = MaxHeight;
}
System.Drawing.Image NewImage = FullsizeImage.GetThumbnailImage(NewWidth, NewHeight, null, IntPtr.Zero);
// Clear handle to original file so that we can overwrite it if necessary
FullsizeImage.Dispose();
// Save resized picture
NewImage.Save(directory + Path.DirectorySeparatorChar + NewFile);
}
I would suggest you save the image as a PNG or JPEG stream. That will be much more efficient. Considering that the thumbnail is not the original data, it is probably OK to apply strong compression on it (QF < 60). I once managed to get useful JPEG images in less than 1000 bytes. At that point, they're so small you can consider putting them in a database rather than on disk.
EDIT: (after reading the question fully :) )
And to answer the question, the result of GetThumbnailForImage(), is again an Image so you can call the Save method on it, one of the overloads allows you to specify which compression to use.
Rather than calling GetThumbnailImage, you can create a new image of the size you want and the PixelFormat that you want, and then scale the original image into that.
System.Drawing.Image FullsizeImage = System.Drawing.Image.FromFile(originalFile);
// Create a bitmap that's NewWidth x NewHeight
Bitmap bmp = new Bitmap(NewWidth, NewHeight, PixelFormat.Format24bppRgb);
// Get a Graphics object for that image and draw the original image into it
using (Graphics g = Graphics.FromImage(bmp))
{
g.DrawImage(FullsizeImage, 0, 0, NewWidth, NewHeight);
}
// you now have a 24bpp thumbnail image