Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Short answer: watermark the image first with an image-drawing library such as Six Labors ImageSharp.Drawing, then upload the encoded result with HttpClient. These are separate jobs: ImageSharp changes pixels; HttpClient sends an HTTP request. The receiving API determines whether the request body must be multipart form data, raw image bytes, or a stream.
What the workflow actually does
A reliable implementation has five stages:
- Load the source image.
- Apply EXIF orientation and resize it to its final dimensions.
- Lay out and draw the watermark text.
- Encode the processed image as PNG, JPEG, or WebP.
- Send that encoded data using the upload format documented by the destination endpoint.
HttpClient does not render text or edit pixels. Conversely, ImageSharp does not decide how an HTTP service wants its request body. Keeping those responsibilities separate makes it easier to test the image output independently from network failures.
Packages and project requirements
Create a current .NET application and add the ImageSharp packages from the versions approved for your project:
dotnet add package SixLabors.ImageSharp
dotnet add package SixLabors.ImageSharp.Drawing
ImageSharp.Drawing’s introduction states that projects directly depending on version 3.0.0 or later require a valid Six Labors license at build time. Check the current Six Labors licensing terms before shipping a production dependency; the applicable terms and price can change.
#1 Best Overall
You also need a font available to the application. A desktop machine may have system fonts, but a container or Linux deployment might not. Bundle a font file that your project is permitted to distribute, or choose a known installed font and fail clearly when it is absent.
Complete example: render the watermark, then upload it
The following example shows the full separation. It normalizes orientation, resizes before drawing, places a semitransparent white mark with a dark outline in the lower-right corner, writes a WebP file, and then uploads that file as multipart form data. Replace the endpoint and field names with the contract for your service.
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.Drawing;
using SixLabors.ImageSharp.PixelFormats;
using System.Net.Http.Headers;
const string inputPath = "original.jpg";
const string outputPath = "watermarked.webp";
const string watermarkText = "© Example Studio";
const string uploadUrl = "https://api.example.com/v1/images";
// Reuse this instance for the lifetime of the application.
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(90) };
using (var image = Image.Load<Rgba32>(inputPath))
{
// Orientation must be applied before positioning the mark.
image.Mutate(ctx => ctx.AutoOrient());
// Resize before drawing so the text is not softened by a later resample.
const int maxWidth = 2400;
if (image.Width > maxWidth)
{
var targetHeight = (int)Math.Round(image.Height * (maxWidth / (double)image.Width));
image.Mutate(ctx => ctx.Resize(maxWidth, targetHeight));
}
var fontFamily = SystemFonts.Families.FirstOrDefault(f => f.Name == "Arial")
?? SystemFonts.Families.First();
var font = fontFamily.CreateFont(Math.Max(18, image.Width / 45f), FontStyle.Bold);
var layout = new RichTextOptions(font)
{
Origin = new PointF(image.Width - 36, image.Height - 28),
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Bottom,
WrappingLength = image.Width * 0.75f,
LineSpacing = 1.1f
};
image.Mutate(ctx => ctx.DrawText(
layout,
watermarkText,
Brushes.Solid(Color.White.WithAlpha(0.72f)),
Pens.Solid(Color.Black.WithAlpha(0.75f), 2f)));
image.Save(outputPath, new WebpEncoder { Quality = 88 });
}
using var form = new MultipartFormDataContent();
await using var file = File.OpenRead(outputPath);
using var fileContent = new StreamContent(file);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/webp");
form.Add(fileContent, "file", Path.GetFileName(outputPath));
form.Add(new StringContent("catalog-image-123"), "asset_id");
using var response = await http.PostAsync(uploadUrl, form);
var responseBody = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode();
Console.WriteLine(responseBody);
The API may require an authorization header, a different multipart field (such as image), or additional metadata. Those names are examples, not universal values.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Positioning and text legibility
Use layout alignment instead of guessing text width
ImageSharp.Drawing’s text API uses Six Labors.Fonts. RichTextOptions carries the font, origin, wrapping length, and horizontal or vertical alignment. Anchoring at the lower-right lets the layout engine account for the actual string width, including when user-supplied text changes.
Choose contrast deliberately
A translucent white fill plus a darker outline works across many photographs. For mostly dark images, reverse the treatment or calculate a contrasting style. Keep the alpha value high enough to read but low enough that the underlying image remains visible.
Rank #2
Handle long or multilingual marks
Set WrappingLength when text can be long. Confirm that the selected font contains the required glyphs; a missing glyph can appear as a box. If the mark must never wrap, shorten or validate the input and reserve a safe margin around the anchor point.
Why orientation and resizing come first
Many camera images store their visual rotation in EXIF metadata rather than in already-rotated pixels. Calling AutoOrient() before drawing ensures “lower right” means the viewer’s lower right. Resizing after drawing forces the watermark through another interpolation step, which can make letter edges soft. Perform both operations before creating the font and layout values.
Recommended Free Tools
Choose the HTTP body that the endpoint specifies
Microsoft documents HttpContent as the representation of an HTTP entity body and its content headers. The relevant choices are distinct:
| Endpoint contract | C# content | Typical headers or shape |
|---|---|---|
| Multipart upload with fields | MultipartFormDataContent containing StreamContent or ByteArrayContent |
Boundary-managed multipart/form-data; include the field name and filename |
| Raw encoded image body | ByteArrayContent or StreamContent |
Content-Type: image/webp, image/jpeg, or image/png |
| JSON with base64 data | StringContent containing serialized JSON |
application/json; only use when the service explicitly requires it |
Do not assume multipart works for every API. Read the destination’s upload documentation, including its required field name, maximum size, authentication scheme, and accepted media types.
Raw-byte upload example
var bytes = await File.ReadAllBytesAsync("watermarked.webp");
using var content = new ByteArrayContent(bytes);
content.Headers.ContentType = new MediaTypeHeaderValue("image/webp");
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("UPLOAD_TOKEN"));
using var response = await http.PutAsync(
"https://api.example.com/v1/assets/catalog-image-123",
content);
response.EnsureSuccessStatusCode();
For large files, prefer StreamContent so the entire image does not need to be held in a second byte array.
HttpClient lifetime, cancellation and retries
Microsoft’s API documentation says: “HttpClient is intended to be instantiated once per application, rather than per-use.” Register it with IHttpClientFactory in ASP.NET Core or reuse a long-lived instance in a worker. Creating one per image can exhaust sockets and lose connection pooling.
Network operations are I/O-bound, so use asynchronous methods. Pass a cancellation token from the request or job:
using var request = new HttpRequestMessage(HttpMethod.Post, uploadUrl)
{
Content = form
};
using var response = await http.SendAsync(
request,
HttpCompletionOption.ResponseHeadersRead,
cancellationToken);
response.EnsureSuccessStatusCode();
Retry only transient failures such as connection resets, 408, 429, and selected 5xx responses. Use exponential backoff, honor Retry-After, and avoid retrying a request when the server may have accepted it unless the API provides an idempotency key or an upload-status check.
Format, quality and memory decisions
- PNG: lossless and suitable for screenshots, logos, or transparency, but often larger for photographs.
- JPEG: broadly accepted for photos; choose a quality setting and expect some recompression.
- WebP: often efficient for web delivery, provided the receiving API accepts it.
Encode once in the format the endpoint expects. Large full-resolution images consume memory during decode, mutation, and encode; enforce input-size and pixel-count limits for untrusted uploads. A maximum compressed file size alone is not enough because a highly compressed image can expand to a very large pixel buffer.
Troubleshooting common failures
The watermark is in the wrong corner
Apply AutoOrient() before layout and drawing. Also verify that your coordinate origin and alignment settings match the image dimensions after resizing.
Text is blurry
Resize and orient before drawing, not afterward. Ensure the output encoder is not applying an unexpectedly low quality setting, and avoid repeatedly decoding and re-encoding the same image.
Text is clipped or wraps unexpectedly
Increase the margin, set a suitable WrappingLength, and use alignment. Test the longest permitted string and fonts with non-Latin characters.
“Font family not found” or missing glyphs
System fonts differ between development machines and containers. Bundle and load a licensed font, or select from the fonts actually installed in the deployment image.
The server returns 400 or 415
A 400 commonly means a missing field, wrong field name, or invalid metadata. A 415 means the media type is unsupported. Compare the exact request body and Content-Type with the endpoint contract; do not change multipart to raw bytes (or vice versa) without checking the API.
The server returns 401 or 403
Check whether the service expects a bearer token, API-key header, query parameter, or a multipart field. Never log secret headers or embed credentials in source control.
Best Value
The request times out
Increase the timeout only when the endpoint’s documented processing time justifies it. Reduce image dimensions, stream the request, and inspect server-side limits. A timeout does not prove the server did not receive the image, so use an idempotency key or status endpoint before retrying.
ImageSharp throws on a hostile file
Validate extension and content type but do not rely on either alone. Set pixel and file limits, catch decode exceptions, and reject images that exceed your service’s resource budget.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup:
If your source is a webpage rather than a local camera file, ScreenshotNeo can return a screenshot through one GET request. It removes cookie/consent banners, newsletter popups and chat widgets before capture; bot checks, blank pages, failed loads and cache hits are not billed, with the result identified by X-Page-Verdict and X-Billed headers. Its MCP server lets Claude, Cursor and other MCP clients call take_screenshot, get_page_info and capture_pdf. You can then feed the returned image into the same ImageSharp watermark step above.
Free tools Windows power users keep installed
One-click scans. No signup required.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for parameters such as viewport, full-page capture, CSS selectors, custom CSS or JavaScript, waiting rules, headers, cookies, caching and asynchronous jobs.
The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account to get started.
Equivalent calls in Python and Node.js
Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', data);
Production checklist
- Confirm the ImageSharp.Drawing license terms for the version you deploy.
- Normalize orientation and resize before drawing.
- Use a font present and licensed in every deployment environment.
- Set margins, wrapping, alpha and outline for the longest expected watermark.
- Encode exactly once in an accepted format.
- Match
HttpContent, field names, headers and authentication to the endpoint contract. - Reuse
HttpClient, use async I/O and propagate cancellation. - Limit pixels and bytes for untrusted images.
- Log status, duration and a correlation ID, but never access tokens or image contents unless required.
FAQ
Can HttpClient add the watermark by itself?
No. It transports HTTP content; an image library must render the text into pixels first.
Should I use WebClient.UploadFile instead?
No for new development. Microsoft marks WebClient, WebRequest and related APIs obsolete and advises using HttpClient.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesIs ImageSharp.Drawing faster than SkiaSharp?
The cited documentation does not establish a fair benchmark or a universal winner. Select a library based on text layout, styling, formats, deployment support and licensing requirements, then benchmark your own image sizes and workload.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

