Load the photo and the watermark, size the watermark from the photo, and lay it on top with Composite, giving the position and an opacity. The program below puts a logo in the bottom-right corner of NASA’s “Blue Marble” photograph (public domain).
dotnet new console -o Watermark && cd Watermark dotnet add package RecursiveAI.OneImage --version 1.10.4 curl -L -o photo.jpg \ https://images-assets.nasa.gov/image/as17-148-22727/as17-148-22727~orig.jpg curl -L -o logo.png https://recursiveai.org/assets/brand/icon-512.png
OneImage needs the .NET 10 SDK. Put this in Program.cs and run dotnet run:
using OneImage; Image photo = Image.Load("photo.jpg"); // The logo at a sixth of the photo's width, so it stays in proportion on any image. Image logo = Image.Load("logo.png").Resize(width: photo.Width / 6); // Bottom-right, with a margin of 3% of the width, at 60% opacity. int margin = photo.Width * 3 / 100; int x = photo.Width - logo.Width - margin; int y = photo.Height - logo.Height - margin; photo.Composite(logo, x, y, opacity: 0.6f).Save("photo-logo.jpg"); Console.WriteLine($"photo-logo.jpg: {logo.Width}x{logo.Height} logo at ({x}, {y})");
photo-logo.jpg: 763x763 logo at (3679, 3679)The logo’s own transparency is respected, so only its shape shows. (margin, margin) is the top-left corner, and ((photo.Width - logo.Width) / 2, (photo.Height - logo.Height) / 2) centers it. The overlay is converted to the photo’s color profile, and photo and logo are decoded, blended and encoded in one pass at Save. Phone photos are loaded upright, so the corner is the corner people see.
A tiled, diagonal text watermark
Proofs repeat a translucent word across the whole picture, so cropping can’t remove it. Text needs the RecursiveAI.OneImage.Text package and a font file:
dotnet add package RecursiveAI.OneImage.Text --version 1.10.4 fonts=https://github.com/notofonts/notofonts.github.io/raw/main/fonts curl -L -o NotoSans-Regular.ttf $fonts/NotoSans/hinted/ttf/NotoSans-Regular.ttf
using System.Numerics; using OneImage; using OneImage.Drawing; using OneImage.Text; using Path = OneImage.Drawing.Path; Image photo = Image.Load("photo.jpg").Resize(width: 1500); FontFamily noto = new FontCollection().Add("NotoSans-Regular.ttf"); Font font = noto.CreateFont(photo.Width / 14f); var options = new TextOptions(font) { HorizontalAlignment = HorizontalAlignment.Center, // centered on (0, 0) VerticalAlignment = VerticalAlignment.Center, }; Path text = TextBuilder.GeneratePath("PREVIEW", options); // laid out once // Fill the outline on a grid, turned 30 degrees counterclockwise. Brush white = Brush.Solid(new Rgba32(255, 255, 255, 80)); Matrix3x2 turn = Matrix3x2.CreateRotation(-MathF.PI / 6); int step = photo.Width / 3, count = 0; for (int y = step / 2; y < photo.Height; y += step) { for (int x = step / 2; x < photo.Width; x += step, count++) { Matrix3x2 place = turn * Matrix3x2.CreateTranslation(x, y); photo = photo.Fill(white, text, transform: place); } } photo.Save("photo-tiled.jpg"); Console.WriteLine($"photo-tiled.jpg: {count} copies of PREVIEW");
photo-tiled.jpg: 9 copies of PREVIEWChange step for a denser grid. For a single line of text, such as a copyright notice in a corner, see Draw text on an image.
Limits
- A visible watermark discourages reuse, it doesn’t prevent it. A corner logo can be cropped off; a tiled mark is harder to remove.
- No fonts are bundled. Text needs a font file you ship, or an installed one through
SystemFonts. - Animations:
Compositeapplies to every frame, so an animated GIF stays animated with the mark on each frame.
To watermark images as they are served, leaving the originals unmarked, use the OnBeforeSaveAsync hook of OneImage.Web (Resize images on the fly in ASP.NET Core). Blend modes and compositing are in the guide.