Recommended Free Tools
Generate an Open Graph image in ASP.NET Core by rendering page data onto a nonzero-size canvas, encoding the result as PNG, JPEG, or WebP, publishing it at a stable public URL, and placing that URL in the page’s og:image metadata. The image-generation step and the image-delivery step are separate: SkiaSharp can create and encode pixels, while ASP.NET Core static-file middleware or an endpoint can make those bytes reachable by URL.
The complete pipeline
A reliable implementation has five stages:
- Prepare data: choose the title, author, category, brand colors, and any other values that belong in the graphic. Validate and constrain untrusted input before drawing it.
- Render: create a canvas with nonzero dimensions and draw the background, typography, logo, and page-specific values.
- Encode: convert the raster image to PNG, JPEG, or WebP.
- Deliver: save the bytes to a stable public path or return them from a dedicated response endpoint.
- Publish metadata: emit the required Open Graph properties and associate optional image metadata with the correct image.
Open Graph defines og:title, og:type, og:image, and og:url as required properties. When a page specifies an image, the protocol says it should also specify og:image:alt. Width, height, and MIME type are optional structured properties.
Choose an image architecture
Generate at request time
A route can render an image from current page data whenever a crawler requests it. This keeps content fresh and avoids storing files, but repeated requests consume rendering resources. Add input limits, an appropriate cache policy, and protection against unbounded text or expensive requests.
Pre-generate and serve a file
Generate during publishing or in a background job, then write the result under the web root. Static-file middleware serves the completed file efficiently. This approach makes cache behavior and failures easier to reason about, but you must handle regeneration, invalidation, storage, and stable naming.
#1 Best Overall
| Decision | Request-time route | Pre-generated file |
|---|---|---|
| Freshness | Reflects current data when rendered | Requires regeneration after changes |
| Compute | Work occurs during requests | Work occurs during publishing or jobs |
| Cacheability | Requires response and intermediary caching | Static assets are straightforward to cache |
| Storage | Usually stores no image file | Needs a storage location and cleanup policy |
| URL stability | Stable route is possible | Stable filename or versioning is required |
Install and configure SkiaSharp
SkiaSharp provides the drawing abstraction used below. Its SKImage content is immutable, and the documented image or surface creation paths return no image when a requested dimension is zero. Validate width and height before allocating a bitmap or surface.
Add the SkiaSharp package appropriate to the .NET and operating-system combination used by your application. Package versions and native-runtime requirements vary, so pin and verify the version in your own project rather than assuming a version here.
dotnet add package SkiaSharp
The following service illustrates the core operation. It draws a dark background and wraps a title into several lines. The text, font family, dimensions, and colors are implementation choices; adjust them to your design and target consumers.
Rank #2
using SkiaSharp;
public sealed class OgImageRenderer
{
public byte[] Render(string title, string? category)
{
const int width = 1200;
const int height = 630;
if (width <= 0 || height <= 0)
throw new ArgumentOutOfRangeException("Image dimensions must be positive.");
using var bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var canvas = new SKCanvas(bitmap);
canvas.Clear(new SKColor(18, 24, 38));
using var accent = new SKPaint { Color = new SKColor(72, 187, 255), IsAntialias = true };
canvas.DrawRect(new SKRect(0, 0, width, 18), accent);
using var categoryPaint = new SKPaint
{
Color = SKColors.LightSkyBlue,
TextSize = 34,
IsAntialias = true,
Typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Bold)
};
canvas.DrawText(category ?? "ARTICLE", 72, 100, categoryPaint);
using var titlePaint = new SKPaint
{
Color = SKColors.White,
TextSize = 64,
IsAntialias = true,
Typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Bold)
};
var lines = Wrap(title ?? string.Empty, titlePaint, width - 144);
var y = 205f;
foreach (var line in lines.Take(4))
{
canvas.DrawText(line, 72, y, titlePaint);
y += 78;
}
using var image = SKImage.FromBitmap(bitmap);
using var data = image.Encode(SKEncodedImageFormat.Png, 100);
return data.ToArray();
}
private static IEnumerable<string> Wrap(string text, SKPaint paint, float maxWidth)
{
var words = text.Split(' ', StringSplitOptions.RemoveEmptyEntries);
var line = string.Empty;
foreach (var word in words)
{
var candidate = line.Length == 0 ? word : $"{line} {word}";
if (paint.MeasureText(candidate) <= maxWidth)
line = candidate;
else
{
if (line.Length > 0) yield return line;
line = word;
}
}
if (line.Length > 0) yield return line;
}
}
Long words, missing fonts, right-to-left scripts, emoji, and text that exceeds four lines need deliberate handling. You can reduce type size, truncate with an ellipsis, reserve a fixed number of lines, or select a font with the required glyph coverage. Do not let page authors choose arbitrary font files or dimensions without limits.
Free tools Windows power users keep installed
One-click scans. No signup required.
Encode PNG, JPEG, or WebP
SkiaSharp’s encoding APIs support JPEG, PNG, and WebP, including stream-oriented overloads. PNG preserves transparency and is lossless. JPEG has no alpha channel, so flatten the design onto an opaque background before encoding. WebP can offer a smaller file with configurable quality and compression, but verify that every target consumer accepts the format you choose.
using var image = SKImage.FromBitmap(bitmap);
using var png = image.Encode(SKEncodedImageFormat.Png, 100);
await System.IO.File.WriteAllBytesAsync(path, png.ToArray());
// JPEG: use an opaque canvas, then choose a quality value.
using var jpeg = image.Encode(SKEncodedImageFormat.Jpeg, 85);
// WebP: quality is encoder-dependent; verify output with your consumers.
using var webp = image.Encode(SKEncodedImageFormat.Webp, 80);
Keep the extension, response content type, and encoded bytes consistent. Inspect the resulting bytes during testing rather than trusting a filename alone.
Publish a stable image URL
Static files from the web root
ASP.NET Core static-file middleware can serve files from the application’s web root at host-based URLs. In a typical application, save the generated file beneath wwwroot/og and enable static files in the request pipeline.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseStaticFiles();
app.MapControllers();
app.Run();
A generated file at wwwroot/og/article-123.png can then be referenced as https://your-host.example/og/article-123.png, provided the host is publicly reachable and the file is not behind authentication.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Return the image from an endpoint
For request-time generation, expose a route that validates an identifier, loads the corresponding page data, renders the image, and returns the bytes with the correct content type. Keep the route deterministic so the same page has a stable URL and can be cached.
[ApiController]
public sealed class OgController : ControllerBase
{
[HttpGet("/og/{slug}.png")]
public IActionResult Get(string slug)
{
// Look up slug from a trusted data source and enforce length/character limits.
var renderer = new OgImageRenderer();
var bytes = renderer.Render("Example article", "Guides");
Response.Headers.CacheControl = "public,max-age=3600";
return File(bytes, "image/png");
}
}
The example uses placeholder lookup data intentionally. In production, return not-found for an unknown slug, avoid reflecting unsanitized HTML into the image, and choose cache headers that match your invalidation strategy.
Emit Open Graph metadata
Render these tags in the page’s HTML <head>. Use an absolute URL that a crawler can reach without a login, VPN, or internal DNS. That public-access requirement is an engineering consequence of og:image being an image URL; verify the behavior of each platform you target.
<meta property="og:title" content="A page-specific title">
<meta property="og:type" content="website">
<meta property="og:url" content="https://example.com/article/example">
<meta property="og:image" content="https://example.com/og/example.png">
<meta property="og:image:type" content="image/png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="A concise description of the image content">
Put structured properties immediately after their corresponding root image property. If you list multiple og:image values, repeat the associated type, dimensions, and alt properties in the intended order. The protocol recognizes width and height as pixel values but does not establish one universal mandatory size. Treat dimensions as a design decision and verify current requirements for every platform you support.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsBest Value
- Applying all key ASP.NET Core components, including MVC for HTML generation, .NET Core, EF Core, ASP.NET Identity, dependency injection, and more
- Integrating ASP.NET Core with leading client-side frameworks, including Bootstrap
- ASP.NET Core code for implementing business logic and data transformations
- Handling configuration, routing, controllers, views, and common tasks (including posting forms and presenting data)
- Performing complementary tasks: error handling, logging, application design, authentication, localization, and more
Validation and troubleshooting
The image is blank or missing
- Confirm width and height are greater than zero before creating the bitmap or surface.
- Check that drawing coordinates fall inside the canvas and that paint colors have visible alpha.
- Open the returned bytes locally and verify they begin as a valid image, not an exception page or empty response.
The URL returns 404
- Confirm the generated file is under the configured web root and that
UseStaticFiles()runs in the pipeline. - Check filename casing, URL encoding, and deployment output.
- For an endpoint, verify route constraints and that the request reaches the correct application.
The crawler cannot fetch it
- Use the public HTTPS host, not localhost or an internal hostname.
- Remove authentication requirements and check firewall, robots, proxy, and certificate behavior.
- Inspect the image response status, content type, and cache headers from outside your network.
Text is clipped or unreadable
- Measure each candidate line before drawing it and apply a maximum line count.
- Reserve space for category labels, logos, and safe margins.
- Install or package a font with the glyphs your content requires, and test non-Latin text.
Updates do not appear in previews
Ensure the URL changes when an image must be invalidated, or use an appropriate cache policy. Social previews can be cached independently; consult the current documentation for each target platform before relying on a particular refresh procedure.
Test the complete path
- Render a page and inspect its final HTML, not only a server-side template.
- Verify all four required properties and an alt value for every declared image.
- Request the image URL from an unauthenticated external client.
- Check status,
Content-Type, byte length, and that an image decoder can open the bytes. - Test short, long, empty, multilingual, and punctuation-heavy titles.
- Re-run after deployment because native image dependencies, fonts, proxy rules, and static-file paths can differ between environments.
Or skip the browser setup
If your actual need is a clean screenshot of an existing page rather than a custom server-side graphic, ScreenshotNeo provides a one-call website screenshot API. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing result in headers.
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
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}`);
See the ScreenshotNeo documentation for parameters and response details. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. Every feature is available on every plan; 1,000 screenshots per month are free with no card, and paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.
Frequently Asked Questions
Does Open Graph require a particular image size?
The protocol documentation identifies width and height as optional pixel metadata but does not define one universal size. Choose dimensions for your design and verify current requirements for each platform you target.
Should I generate the image in a controller or a background job?
Use request-time generation when freshness and personalization matter; use pre-generation when predictable latency, static delivery, and simpler caching are more important.
Can I use JPEG when my design has transparency?
No. JPEG does not preserve alpha transparency. Flatten the design onto an opaque background or use PNG or a compatible WebP configuration.
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.

