Adding watermark to an image is very widely used in websites in order to protect the content owner's copyright, such as a blog system like this website. In traditional ASP.NET (.NET Framework), we could use System.Web.Helpers.WebImage to add text watermark like this:

var image = new WebImage(imageBytes);
image.AddTextWatermark(
    Settings.Instance.WatermarkText, "White", Settings.Instance.WatermarkFontSize,
    opacity: Settings.Instance.WatermarkTextOpacityPercentage
    );

But in .NET Core, there's no WebImage class for us to use. How can we accomplish this task?

Let's begin with the image upload process. In ASP.NET Core, we use IFormFile for file upload, which includes images. For more details, you can see Microsoft official document here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-2.1 

In my blog, I wrote an Action for uploading the image, and put the image into a MemoryStream object so that my underlying image storage service can save it to target location:

[Route("image/upload")]
public async Task<IActionResult> UploadImageAsync(IFormFile file)
{
...
    using (var stream = new MemoryStream())
    {
        await file.CopyToAsync(stream);
        ... // call underlying image storage service
    }
...
}

To add watermark, we have to modify the image stream.

By default, .NET Core doesn't have the ability to process the image because System.Drawing has very few APIs. We need to use a Microsoft official NuGet package to bring back some familiar APIs as they were in .NET Framework:

Install-Package System.Drawing.Common -Version 4.5.1

Now, we have access to types like System.Drawing.Image and System.Drawing.Graphics. 

The following code will use these classes to add a text watermark onto the uploaded image's stream:

// Add watermark
var watermarkedStream = new MemoryStream();
using (var img = Image.FromStream(stream))
{
    using (var graphic = Graphics.FromImage(img))
    {
        var font = new Font(FontFamily.GenericSansSerif, 20, FontStyle.Bold, GraphicsUnit.Pixel);
        var color = Color.FromArgb(128, 255, 255, 255);
        var brush = new SolidBrush(color);
        var point = new Point(img.Width - 120, img.Height - 30);

        graphic.DrawString("edi.wang", font, brush, point);
        img.Save(watermarkedStream, ImageFormat.Png);
    }
}

Here's the result:

There are a few things to point out:

1. You can't modify the original stream, if you try to save the image and override the original stream like this, it won't work:

img.Save(stream, ImageFormat.Png);

This is why I have to define another watermarkedStream object.

2. For the watermark position, which is the point object. The calculation I use is for adding the watermark on the bottom right corner of the image, you need to modify this to meet your own requirement.

3. I would suggest using a font that is cross-platform because .NET Core can be deployed not only to Windows.

Finally, the complete sample code for my blog's image upload and watermark action:

[Authorize]
[HttpPost]
[Route("image/upload")]
public async Task<IActionResult> UploadImageAsync(IFormFile file)
{
    try
    {
        if (null == file)
        {
            Logger.LogError("file is null.");
            return BadRequest();
        }

        if (file.Length > 0)
        {
            var name = Path.GetFileName(file.FileName);
            if (name != null)
            {
                using (var stream = new MemoryStream())
                {
                    await file.CopyToAsync(stream);

                    // Add watermark
                    var watermarkedStream = new MemoryStream();
                    using (var img = Image.FromStream(stream))
                    {
                        using (var graphic = Graphics.FromImage(img))
                        {
                            var font = new Font(FontFamily.GenericSansSerif, 20, FontStyle.Bold, GraphicsUnit.Pixel);
                            var color = Color.FromArgb(128, 255, 255, 255);
                            var brush = new SolidBrush(color);
                            var point = new Point(img.Width - 120, img.Height - 30);

                            graphic.DrawString("edi.wang", font, brush, point);
                            img.Save(watermarkedStream, ImageFormat.Png);
                        }
                    }

                    var response = await _imageStorageProvider.InsertAsync(name, watermarkedStream.ToArray());
                    Logger.LogInformation("Image Upload: " + JsonConvert.SerializeObject(response));

                    if (response.IsSuccess)
                    {
                        string refPath = "/image/" + response.Item;
                        return Json(new { location = refPath });
                    }
                    Logger.LogError(response.Message);
                    return StatusCode(StatusCodes.Status500InternalServerError);
                }
            }
        }
        return BadRequest();
    }
    catch (Exception e)
    {
        Logger.LogError(e, $"Error uploading image.");
        return StatusCode(StatusCodes.Status500InternalServerError);
    }
}