J Benjamin J Benjamin - 2 months ago 7x
C# Question

write a watermark image to an uploaded image (in MVC controller)

I would like to accept an image file upload from a user, write a little 16x16 icon in the corner, then save the image to my Azure cloud storage.

I've found some good posts about this topic but I'm not quite getting it. I thought I almost had it but was getting OutOfMemory exceptions on the TextureBrush line.

Please let me know if there is any additional info that I need to add and thanks for any assistance!

Here is my work so far (AddWaterMark is the part that is crapping out):

// add watermark before uploading to cloud server
Stream stream = _fileManager.AddWaterMark(file.InputStream);

cloudBlockBlob.UploadFromStream(stream, null, null, null);
fileUrl = cloudBlockBlob.Uri.ToString();

And my AddWaterMark method:

public Stream AddWaterMark(Stream stream)

var bytes = Convert.FromBase64String(Settings.Default.PartnifyWatermark);
Image watermarkImage;
using (var ms = new MemoryStream(bytes))
watermarkImage = Image.FromStream(ms);

using (var image = Image.FromStream(stream))
using (var imageGraphics = Graphics.FromImage(image))
using (var watermarkBrush = new TextureBrush(watermarkImage))
var x = (image.Width - 16);
var y = (image.Height - 16);
watermarkBrush.TranslateTransform(x, y);
imageGraphics.FillRectangle(watermarkBrush, new Rectangle(new Point(x, y), new Size(watermarkImage.Width + 1, watermarkImage.Height)));
image.Save(stream, ImageFormat.Png);
return stream;


------------------- WORKING SOLUTION BELOW -------

Initially I wanted to watermark my uploaded image mid-stream before sending it off to my azure storage. I think I was really close but I couldn't quite get that to work with everything else. My uploaded images are coming in one at a time and < 2M so I settled for a solution that saves the image, stamps it, and continues on. Here is the working solution, comments welcome:

Note: I have placed the company watermark in the app Settings as a base64 string so the first couple lines are just pulling that out. After return, the calling method just grabs the clone, uploads, and removes the clone. Obviously there are a few more elegant ways to handle the image but for the purpose of demonstrating the marking, this is acceptable.

public void AddWaterMark(string filepath)
//var outStream = new MemoryStream();
var watermarkImageBase64 = Settings.Default.CompanyWatermark;
var data = Convert.FromBase64String(watermarkImageBase64);
using (var streamWatermark = new MemoryStream(data, 0, data.Length))
using (var watermark = Image.FromStream(streamWatermark))
using (var targetImage = Image.FromFile(filepath))
using (var g = Graphics.FromImage(targetImage))
var destX = (targetImage.Width - watermark.Width) - 10;
var destY = 10;

g.DrawImage(watermark, new Rectangle(destX, destY, watermark.Width, watermark.Height));

using (var cloneImage = (Image) targetImage.Clone())

One thing I don't like about this approach....incoming images are already converted to png at the watermarking point but the watermark is 16x16 and appears smaller or larger depending on the dimensions of the target image. Would prefer it "appears" around the same size whatever the resolution of the target image..should be easy enough.


You can watermark an image more simply by drawing the image to the target. A TextureBrush would work to tile the image over the entirety of the target.

This is using larger watermark image (125x125) but that doesnt matter because you could thumbnail it to the desired size:

// show before
pbWM.Image = Image.FromFile(@"C:\Temp\horus_w.png");

using (Image watermark = Image.FromFile(@"C:\Temp\horus_w.png"))
using (Image TargetImg = Image.FromFile(@"C:\Temp\BigHead.jpg"))
using (Graphics g = Graphics.FromImage(TargetImg))
    var destX = (TargetImg.Width - watermark.Width) - 10;
    var destY = (TargetImg.Height - watermark.Height) - 10;

    g.DrawImage(watermark, new Rectangle(destX,
    // display a clone for demo purposes
    pb2.Image = (Image)TargetImg.Clone();

enter image description here

The rosy image is the bare watermark image, the other is the end result. Using a TexturedBrush you can do things like used tiled, angled text with a low opacity as the watermark:

enter image description here

You can get that error sometimes when creating a TextureBrush, I avoid it by specifying the size:

using (TextureBrush br = new TextureBrush(wmImg,
            new Rectangle(0, 0, wmImg.Width - 1, wmImg.Height - 1)))
    g.FillRectangle(br, 0, 0, Marked.Width, Marked.Height);

As an aside, if the watermark image/logo is the same, I would store it in a variable and use the same image over and over. The code in the question is creating a new watermarkImage each time and not disposing of it.