Png image processing in .NET - c#

I have the following task. Take a base image and overlay on it another one. The base image is 8b png as well as overlay.
Here are the base (left) and overlay (right) images.
Here is a result and how it must look.
The picture in the left is a screenshot when one picture is on top of another (html and positioning) and the second is the result of programmatic merging.
As you can see in the screenshot the borders of the text is darker. Also here are the sizes of the images
Base image 14.9 KB
Overlay image 6.87 KB
Result image 34.8 KB
The size of the resulting image is also huge
Here is my code that I use to merge those pictures
/*...*/
public Stream Concatinate(Stream baseStream, params Stream[] overlayStreams) {
var #base = Image.FromStream(baseStream);
var canvas = new Bitmap(#base.Width, #base.Height);
using (var g = canvas.ToGraphics()) {
g.DrawImage(#base, 0, 0);
foreach (var item in overlayStreams) {
using (var overlayImage = Image.FromStream(item)) {
try {
Overlay(#base as Bitmap, overlayImage as Bitmap, g);
} catch {
}
}
}
}
var ms = new MemoryStream();
canvas.Save(ms, ImageFormat.Png);
canvas.Dispose();
#base.Dispose();
return ms;
}
/*...*/
/*Tograpics extension*/
public static Graphics ToGraphics(this Image image,
CompositingQuality compositingQuality = CompositingQuality.HighQuality,
SmoothingMode smoothingMode = SmoothingMode.HighQuality,
InterpolationMode interpolationMode = InterpolationMode.HighQualityBicubic) {
var g = Graphics.FromImage(image);
g.CompositingQuality = compositingQuality;
g.SmoothingMode = smoothingMode;
g.InterpolationMode = interpolationMode;
return g;
}
private void Overlay(Bitmap source, Bitmap overlay, Graphics g) {
if (source.Width != overlay.Width || source.Height != overlay.Height)
throw new Exception("Source and overlay dimensions do not match");
var area = new Rectangle(0, 0, source.Width, source.Height);
g.DrawImage(overlay, area, area, GraphicsUnit.Pixel);
}
My questions are
What should I do in order to merge images to achieve the result as in the screenshot?
How can I lower the size of the result image?
Is the System.Drawing a suitable tool for this or is there any better tool for working with png for .NET?

The answers to your questions are:
1) Just call method ToGraphics with a argument CompositingQuality.Default instead of using default argument values like in the example:
using (var g = canvas.ToGraphics(compositingQuality: CompositingQuality.Default))
The problem is with CompositingQuality.HighQuality is that it makes a composition of both images into one, but you want to make an overlay, not to make a composition of two images.
2) The size will be similar to the one you specified and that can not be changed, it is due to a image format.
3) If you are programming in c# for desktop, than System.Drawing is the best choice as far as I know.

Related

C# Image GPS location property disappears after resizing image [duplicate]

Yes yes... I've seen other posts related to this issue, and yes... I've googled about it.
But so far, I was not able to get to the result I need.
I'm loading a large image taken in 300 dpi, and I need to resize it.
I know... I know... dpi is relative and doesn't really matter... what matters are the dimensions in pixels:
DPI is essentially the number of pixels that correspond to an inch when the image is printed not when it is viewed on a screen. Therefore by increasing the DPI of the image, you do not increase the size of the image on the screen. You only increase the quality of print.
Even though the DPI information stored in the EXIF of an image is somewhat useless, it is causing me problems.
The image I'm resizing is losing the original exif information, including the horizontal and vertical resolution (dpi), and thus it is saving with a default of 96 dpi. Possible reason to this is that only JPEG and another format can hold metadata information.
The end image result is should look like this: 275x375 at 300dpi
Instead is looking like this: 275x375 at 96dpi
You can argue that they are they same, and I agree, but we have a corel draw script that used to load these images, and since this dpi information is different, it places it in different sizes on the document.
Here's what I'm using for resizing:
public System.Drawing.Bitmap ResizeImage(System.Drawing.Image image, int width, int height)
{
Bitmap result = new Bitmap(width, height);
// set the resolutions the same to avoid cropping due to resolution differences
result.SetResolution(image.HorizontalResolution, image.VerticalResolution);
//use a graphics object to draw the resized image into the bitmap
using (Graphics graphics = Graphics.FromImage(result))
{
//set the resize quality modes to high quality
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//draw the image into the target bitmap
graphics.DrawImage(image, 0, 0, result.Width, result.Height);
}
//return the resulting bitmap
return result;
}
That does the work very well, but loses the EXIF information.
Setting the SetResolution to SetResolution(300, 300) does not work!
I looked at reading and changing the EXIF information of an image, and I've tried:
public void setImageDpi(string Filename, string NewRes)
{
Image Pic;
PropertyItem[] PropertyItems;
byte[] bDescription = new Byte[NewRes.Length];
int i;
string FilenameTemp;
System.Drawing.Imaging.Encoder Enc = System.Drawing.Imaging.Encoder.Transformation;
EncoderParameters EncParms = new EncoderParameters(1);
EncoderParameter EncParm;
ImageCodecInfo CodecInfo = GetEncoderInfo("image/jpeg");
// copy description into byte array
for (i = 0; i < NewRes.Length; i++) bDescription[i] = (byte)NewRes[i];
// load the image to change
Pic = Image.FromFile(Filename);
foreach (PropertyItem item in Pic.PropertyItems)
{
if (item.Id == 282 || item.Id == 283)
{
PropertyItem myProperty = item;
myProperty.Value = bDescription;
myProperty.Type = 2;
myProperty.Len = NewRes.Length;
Pic.SetPropertyItem(item);
Console.WriteLine(item.Type);
}
}
// we cannot store in the same image, so use a temporary image instead
FilenameTemp = Filename + ".temp";
// for lossless rewriting must rotate the image by 90 degrees!
EncParm = new EncoderParameter(Enc, (long)EncoderValue.TransformRotate90);
EncParms.Param[0] = EncParm;
// now write the rotated image with new description
Pic.Save(FilenameTemp, CodecInfo, EncParms);
// for computers with low memory and large pictures: release memory now
Pic.Dispose();
Pic = null;
GC.Collect();
// delete the original file, will be replaced later
System.IO.File.Delete(Filename);
// now must rotate back the written picture
Pic = Image.FromFile(FilenameTemp);
EncParm = new EncoderParameter(Enc, (long)EncoderValue.TransformRotate270);
EncParms.Param[0] = EncParm;
Pic.Save(Filename, CodecInfo, EncParms);
// release memory now
Pic.Dispose();
Pic = null;
GC.Collect();
// delete the temporary picture
System.IO.File.Delete(FilenameTemp);
}
That didn't work either.
I tried looking and changing the EXIF information for DPI (282 and 283) later in the process as such:
Encoding _Encoding = Encoding.UTF8;
Image theImage = Image.FromFile("somepath");
PropertyItem propItem282 = theImage.GetPropertyItem(282);
propItem282.Value = _Encoding.GetBytes("300" + '\0');
theImage.SetPropertyItem(propItem282);
PropertyItem propItem283 = theImage.GetPropertyItem(283);
propItem283.Value = _Encoding.GetBytes("300" + '\0');
theImage.SetPropertyItem(propItem283);
theImage.Save("somepath");
But the program crashes saying that Property Cannot be Found.
If the property doesn't exist, apparently I can't add it:
A PropertyItem is not intended to be used as a stand-alone object. A PropertyItem object is intended to be used by classes that are derived from Image. A PropertyItem object is used to retrieve and to change the metadata of existing image files, not to create the metadata. Therefore, the PropertyItem class does not have a defined Public constructor, and you cannot create an instance of a PropertyItem object.
I'm stuck... all I need is a resized image with a dpi set to 300, it shouldn't be so hard.
Any help much appreciated. Thanks
The following code worked for me:
const string InputFileName = "test_input.jpg";
const string OutputFileName = "test_output.jpg";
var newSize = new Size(640, 480);
using (var bmpInput = Image.FromFile(InputFileName))
{
using (var bmpOutput = new Bitmap(bmpInput, newSize))
{
foreach (var id in bmpInput.PropertyIdList)
bmpOutput.SetPropertyItem(bmpInput.GetPropertyItem(id));
bmpOutput.SetResolution(300.0f, 300.0f);
bmpOutput.Save(OutputFileName, ImageFormat.Jpeg);
}
}
When I inspect the output file I can see EXIF data and the DPI has been changed to 300.

C# Image Resizing - Losing EXIF

Yes yes... I've seen other posts related to this issue, and yes... I've googled about it.
But so far, I was not able to get to the result I need.
I'm loading a large image taken in 300 dpi, and I need to resize it.
I know... I know... dpi is relative and doesn't really matter... what matters are the dimensions in pixels:
DPI is essentially the number of pixels that correspond to an inch when the image is printed not when it is viewed on a screen. Therefore by increasing the DPI of the image, you do not increase the size of the image on the screen. You only increase the quality of print.
Even though the DPI information stored in the EXIF of an image is somewhat useless, it is causing me problems.
The image I'm resizing is losing the original exif information, including the horizontal and vertical resolution (dpi), and thus it is saving with a default of 96 dpi. Possible reason to this is that only JPEG and another format can hold metadata information.
The end image result is should look like this: 275x375 at 300dpi
Instead is looking like this: 275x375 at 96dpi
You can argue that they are they same, and I agree, but we have a corel draw script that used to load these images, and since this dpi information is different, it places it in different sizes on the document.
Here's what I'm using for resizing:
public System.Drawing.Bitmap ResizeImage(System.Drawing.Image image, int width, int height)
{
Bitmap result = new Bitmap(width, height);
// set the resolutions the same to avoid cropping due to resolution differences
result.SetResolution(image.HorizontalResolution, image.VerticalResolution);
//use a graphics object to draw the resized image into the bitmap
using (Graphics graphics = Graphics.FromImage(result))
{
//set the resize quality modes to high quality
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//draw the image into the target bitmap
graphics.DrawImage(image, 0, 0, result.Width, result.Height);
}
//return the resulting bitmap
return result;
}
That does the work very well, but loses the EXIF information.
Setting the SetResolution to SetResolution(300, 300) does not work!
I looked at reading and changing the EXIF information of an image, and I've tried:
public void setImageDpi(string Filename, string NewRes)
{
Image Pic;
PropertyItem[] PropertyItems;
byte[] bDescription = new Byte[NewRes.Length];
int i;
string FilenameTemp;
System.Drawing.Imaging.Encoder Enc = System.Drawing.Imaging.Encoder.Transformation;
EncoderParameters EncParms = new EncoderParameters(1);
EncoderParameter EncParm;
ImageCodecInfo CodecInfo = GetEncoderInfo("image/jpeg");
// copy description into byte array
for (i = 0; i < NewRes.Length; i++) bDescription[i] = (byte)NewRes[i];
// load the image to change
Pic = Image.FromFile(Filename);
foreach (PropertyItem item in Pic.PropertyItems)
{
if (item.Id == 282 || item.Id == 283)
{
PropertyItem myProperty = item;
myProperty.Value = bDescription;
myProperty.Type = 2;
myProperty.Len = NewRes.Length;
Pic.SetPropertyItem(item);
Console.WriteLine(item.Type);
}
}
// we cannot store in the same image, so use a temporary image instead
FilenameTemp = Filename + ".temp";
// for lossless rewriting must rotate the image by 90 degrees!
EncParm = new EncoderParameter(Enc, (long)EncoderValue.TransformRotate90);
EncParms.Param[0] = EncParm;
// now write the rotated image with new description
Pic.Save(FilenameTemp, CodecInfo, EncParms);
// for computers with low memory and large pictures: release memory now
Pic.Dispose();
Pic = null;
GC.Collect();
// delete the original file, will be replaced later
System.IO.File.Delete(Filename);
// now must rotate back the written picture
Pic = Image.FromFile(FilenameTemp);
EncParm = new EncoderParameter(Enc, (long)EncoderValue.TransformRotate270);
EncParms.Param[0] = EncParm;
Pic.Save(Filename, CodecInfo, EncParms);
// release memory now
Pic.Dispose();
Pic = null;
GC.Collect();
// delete the temporary picture
System.IO.File.Delete(FilenameTemp);
}
That didn't work either.
I tried looking and changing the EXIF information for DPI (282 and 283) later in the process as such:
Encoding _Encoding = Encoding.UTF8;
Image theImage = Image.FromFile("somepath");
PropertyItem propItem282 = theImage.GetPropertyItem(282);
propItem282.Value = _Encoding.GetBytes("300" + '\0');
theImage.SetPropertyItem(propItem282);
PropertyItem propItem283 = theImage.GetPropertyItem(283);
propItem283.Value = _Encoding.GetBytes("300" + '\0');
theImage.SetPropertyItem(propItem283);
theImage.Save("somepath");
But the program crashes saying that Property Cannot be Found.
If the property doesn't exist, apparently I can't add it:
A PropertyItem is not intended to be used as a stand-alone object. A PropertyItem object is intended to be used by classes that are derived from Image. A PropertyItem object is used to retrieve and to change the metadata of existing image files, not to create the metadata. Therefore, the PropertyItem class does not have a defined Public constructor, and you cannot create an instance of a PropertyItem object.
I'm stuck... all I need is a resized image with a dpi set to 300, it shouldn't be so hard.
Any help much appreciated. Thanks
The following code worked for me:
const string InputFileName = "test_input.jpg";
const string OutputFileName = "test_output.jpg";
var newSize = new Size(640, 480);
using (var bmpInput = Image.FromFile(InputFileName))
{
using (var bmpOutput = new Bitmap(bmpInput, newSize))
{
foreach (var id in bmpInput.PropertyIdList)
bmpOutput.SetPropertyItem(bmpInput.GetPropertyItem(id));
bmpOutput.SetResolution(300.0f, 300.0f);
bmpOutput.Save(OutputFileName, ImageFormat.Jpeg);
}
}
When I inspect the output file I can see EXIF data and the DPI has been changed to 300.

How do I overlay an image in .NET

I have a .png image i wish to overlay on a base image.
My overlay image contains just a red slant line. I need to get the red line overlayed on the base image at the same co-ordinate as it is in overlay image.
The problem is I do not have the co-ordinates location.
I need to find it programmatically with C#. The overlay image will always be transparent or of white background. What code to find the line co-ordinates from overlay image?
You can create new image, render background image first and then render overlay image over it. Since overlay has alpha channel and line is placed where it should be (i mean there is opaque space on top and left side of line) you do not need coordinates. Illustration code:
Image imageBackground = Image.FromFile("bitmap1.png");
Image imageOverlay = Image.FromFile("bitmap2.png");
Image img = new Bitmap(imageBackground.Width, imageBackground.Height);
using (Graphics gr = Graphics.FromImage(img))
{
gr.DrawImage(imageBackground, new Point(0, 0));
gr.DrawImage(imageOverlay, new Point(0, 0));
}
img.Save("output.png", ImageFormat.Png);
If you are using WPF, why not use a path for your overlay instead of an image if it is a simple line? This would allow it to scale to any size, and has methods for manipulating its dimensions.
If you are using Winforms, there are some similar graphics drawing capabilities you might leverage. Getting the dimensions of the image should be easy, assuming you're using a PictureBox, the following properties should suffice:
myPictureBox.Top
myPictureBox.Bottom
myPictureBox.Left
myPictureBox.Right
Similarly, for a WPF Image:
myImage.Margin
I already needed to create a blank space around an image and I used the ImageFactory library to do that.
Here is the code. I guess you are capable to figure out how to adjust to your needs.
public static Image ResizedImage(Image image, int desiredWidth, int desiredHeight)
{
Image res = (Bitmap)image.Clone();
Image resizedImage;
ImageLayer imageLayer = new ImageLayer();
try
{
if (res != null)
{
//white background
res = new Bitmap(desiredWidth, desiredHeight, res.PixelFormat);
//image to handle
using (var imgf = new ImageFactory(true))
{
imgf
.Load(image)
.Resize(new ResizeLayer(new Size(desiredWidth, desiredHeight),
ResizeMode.Max,
AnchorPosition.Center,
false));
resizedImage = (Bitmap)imgf.Image.Clone();
}
//final image
if (resizedImage != null)
{
imageLayer.Image = resizedImage;
imageLayer.Size = new Size(resizedImage.Width, resizedImage.Height);
imageLayer.Opacity = 100;
using (var imgf = new ImageFactory(true))
{
imgf
.Load(res)
.BackgroundColor(Color.White)
.Overlay(imageLayer);
res = (Bitmap)imgf.Image.Clone();
}
}
}
}
catch (Exception ex)
{
ex.Message;
}
return res;
}

Desktop capture in png format

I will like to get a screen capture and save it in the format of png of the entire screen. How can I do that?
Could I use the Snipping Tool library to accomplish this? There are some tutorials on the internet that show you how to do this with windows forms and the image is in the format of bitmap.
Here's a little method to capture the contents of any screen.
private static void CaptureScreen(Screen window, string file)
{
try
{
Rectangle s_rect = window.Bounds;
using (Bitmap bmp = new Bitmap(s_rect.Width, s_rect.Height))
{
using (Graphics gScreen = Graphics.FromImage(bmp))
gScreen.CopyFromScreen(s_rect.Location, Point.Empty, s_rect.Size);
bmp.Save(file, System.Drawing.Imaging.ImageFormat.Png);
}
}
catch (Exception) { /*TODO: Any exception handling.*/ }
}
Example of usage:
CaptureScreen(Screen.PrimaryScreen, #"B:\exampleScreenshot.png");
EDIT: Coming back to this later I realized it's probably more useful to return an Image object from the function so you can choose how to use the captured bitmap.
I've also made the function a bit more robust now so that it can capture multiple screens (i.e. in a multi-monitor setup). It should accommodate screens of varying heights, but I can't test this myself.
public static Image CaptureScreens(params Screen[] screens) {
if (screens == null || screens.Length == 0)
throw new ArgumentNullException("screens");
// Order them in logical left-to-right fashion.
var orderedScreens = screens.OrderBy(s => s.Bounds.Left).ToList();
// Calculate the total width needed to fit all the screen into a single image
var totalWidth = orderedScreens.Sum(s => s.Bounds.Width);
// In order to handle screens of different sizes, make sure to make the Bitmap large enough to fit the tallest screen
var maxHeight = orderedScreens.Max(s => s.Bounds.Top + s.Bounds.Height);
var bmp = new Bitmap(totalWidth, maxHeight);
int offset = 0;
// Copy each screen to the bitmap
using (var g = Graphics.FromImage(bmp)) {
foreach (var screen in orderedScreens) {
g.CopyFromScreen(screen.Bounds.Left, screen.Bounds.Top, offset, screen.Bounds.Top, screen.Bounds.Size);
offset += screen.Bounds.Width;
}
}
return bmp;
}
New example:
// Capture all monitors and save them to file
CaptureScreens(Screen.AllScreens).Save(#"C:\Users\FooBar\screens.png");

Most Efficient Way To Watermark Image C# On The Fly?

I have an ecommerce store built in asp.net c# (Webforms) and a lot of the new product images are very hard to source, so I'd like to watermark them with our logo or domain name.
There are too many products to just download the images and add the watermark, and users with limited image editing experience will be uploading new ones (So they won't have a clue how to add the watermark).
So I guess this just leaves me with using a HttpHandler? Yes / No? If so can you provide some insight (Preferably code samples in C#) into the most efficient way of adding the watermark, considering some pages will have around 20 images (Jpegs) on (All of which need to be watermarked)
I would obtain the Graphicsobject to the jpeg, and then draw the watermark on top of that item, and save it again with the watermark:
using (Image image = Image.FromFile("myImage.jpg"))
using(Graphics g = Graphics.FromImage( image)){
g.DrawImage( myWaterMarkImage, myPosition);
image.Save(myFilename);
}
This looks like it could be helpful:
http://www.switchonthecode.com/tutorials/csharp-snippet-tutorial-how-to-draw-text-on-an-image
While it focus's on text, I am sure with a little modification you could add a graphic in also.
Once you have an implementation you could either call it once per view or when adding prior to saving the file.
Here is a sample HttpHandler
/// <summary>
/// Summary description for $codebehindclassname$
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ImageHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string imageName = string.Empty;
string physicalPath = string.Empty;
Image image = null;
Image thumbnailImage = null;
Bitmap bitmap = null;
using (MemoryStream memoryStream = new MemoryStream())
{
string actionName = context.Request.QueryString["Image"];
string opacity = context.Request.QueryString["Opacity"];
int opacityPercent = int.Parse(opacity);
Color waterMarkColor = Color.Gray;
switch (actionName)
{
case "BlueHills":
string myCompany = "My Company Name";
Font font = new Font("Times New Roman", 8f);
context.Response.ContentType = "image/png";
bitmap = Resources.Resources.BlueHills;
Graphics g = Graphics.FromImage(bitmap);
Brush myBrush = new SolidBrush(Color.FromArgb(opacityPercent, waterMarkColor));
SizeF sz = g.MeasureString(myCompany, font);
int X = (int)(bitmap.Width - sz.Width) / 2;
int Y = (int)(sz.Height) / 2;
g.DrawString(myCompany, font, myBrush, new Point(X, Y));
bitmap.Save(memoryStream, ImageFormat.Png);
break;
default:
string test = actionName;
break;
}
context.Response.BinaryWrite(memoryStream.GetBuffer());
memoryStream.Close();
if (image != null) { image.Dispose(); }
if (thumbnailImage != null) { thumbnailImage.Dispose(); }
if (bitmap != null) { bitmap.Dispose(); }
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
and can be called like such:
<asp:Image ID="Image1" runat="server" ImageUrl="~/ImageHandler.ashx?Image=BlueHills&Opacity=50" />
I suggest you to take a look to WPF classes to do this job (GDI+ are deprecated in a web context).
The way (I don't know if is THE BEST way, but I've already done this and works pretty fine) is something similar to:
// Load the original image
BitmapImage image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = new Uri(physical_imagepath);
image.EndInit();
// Create a final render image
RenderTargetBitmap final = new RenderTargetBitmap(yourNeededWidth, yourNeededHeight, yourDpiDefault, yourDpiDefault, PixelFormats.Default);
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
Rect rectImage = new Rect(0, 0, (double)image.PixelWidth, (double)image.PixelHeight);
dc.DrawImage(image, rectImage);
// Load the bitmap of the watermark
BitmapImage watermark = new BitmapImage();
watermark.BeginInit();
watermark.CacheOption = BitmapCacheOption.OnLoad;
watermark.UriSource = new Uri(physical_logopath);
watermark.EndInit();
// Defines the watermark box
Rect rectWatermark = new Rect(0, 0, (double)watermark.PixelWidth, (double)watermark.PixelHeight);
/* use rectWatermark.X and rectWatermark.Y to move your watermark box around on the final image */
dc.DrawImage(watermark, rectWatermark);
}
final.Render(dv);
// And then serve the final Bitmap to the client
Of course all written as HttpHandler.
The code above is not tested.
(little ads: I've published a CodeCanyon Item that do a similar job).
This isn't an answer so much as a few tips:
Jpeg doesn't support transparency, the best you can probably do is add the watermark image and make it a very light grey color.
Use the generic handler (.ashx), it's very lightweight and prevents you from having to add anything to your web.config file.
If there are going to be upwards of 20 images per page, then I would recommend adding the watermark as you get the images. This is a one-time cost per image and will make loading pages with the images faster.
I can't vouch for the most efficient way of adding a watermark, but if you go with tip #3, it becomes less important as you will only be performing the operation once ever per image. I would probably just use the System.Drawing namespace to do this; just be sure to dispose of the resources you use (Image, Graphics, etc), though I'm sure there are libraries out there that would work much better.
The obvious optimization to any 'on the fly image wartermarking' is to cache the 'watermarked' image if you can afford the storage cost. So, the efficiency of the wartermarking operation itself do not matters much.

Categories