What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To add a text watermark to an uploaded image in Go, use net/http to receive and limit the upload, decode it with Go’s image packages, render text into a glyph mask, composite that mask onto a mutable image, and encode the result for the HTTP response. The standard library provides the HTTP handling and image compositing, but not a high-level function that lays out and rasterizes arbitrary text. This guide uses a font package for that missing step and returns PNG output.

What the HTTP handler does—and what it does not

An HTTP handler is the boundary of the service, not the watermark renderer. It accepts a request, checks its size and fields, decodes valid image bytes, invokes the drawing code, then sends an encoded image with the matching content type. Go’s net/http documentation describes the HTTP APIs, while the Go image/draw article explains drawing and compositing.

The example below accepts a multipart upload in a field named image and a required text field. It always returns PNG. It uses golang.org/x/image/font and golang.org/x/image/font/basicfont to rasterize text; these are additional dependencies, not a high-level watermark feature in net/http or image/draw.

Build a runnable multipart watermark endpoint

1. Create the module and install the font package

In an empty directory, initialize a Go module and add the font dependency:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
go mod init example.com/watermark

go get golang.org/x/image/font
go get golang.org/x/image/font/basicfont

The standard library’s image decoders are registered by importing their format packages. This example deliberately supports JPEG, PNG, and GIF input; it does not claim WebP support.

2. Save this server as main.go

package main

import (
    "bytes"
    "errors"
    "fmt"
    "image"
    "image/color"
    "image/draw"
    "image/gif"
    "image/jpeg"
    "image/png"
    "io"
    "log"
    "net/http"
    "strings"

    "golang.org/x/image/font"
    "golang.org/x/image/font/basicfont"
    "golang.org/x/image/math/fixed"
)

const maxUploadBytes = 10 << 20 // 10 MiB compressed request-body limit

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("POST /watermark", watermarkHandler)

    log.Println("listening on :8080")
    log.Fatal(http.ListenAndServe(":8080", mux))
}

func watermarkHandler(w http.ResponseWriter, r *http.Request) {
    // Apply the limit before multipart parsing or reading the body.
    r.Body = http.MaxBytesReader(w, r.Body, maxUploadBytes)
    if err := r.ParseMultipartForm(1 << 20); err != nil {
        var maxErr *http.MaxBytesError
        if errors.As(err, &maxErr) {
            http.Error(w, "request body exceeds the 10 MiB limit", http.StatusRequestEntityTooLarge)
            return
        }
        http.Error(w, "invalid multipart request", http.StatusBadRequest)
        return
    }
    if r.MultipartForm != nil {
        defer r.MultipartForm.RemoveAll()
    }

    text := strings.TrimSpace(r.FormValue("text"))
    if text == "" {
        http.Error(w, "form field text is required", http.StatusBadRequest)
        return
    }
    if len([]rune(text)) > 200 {
        http.Error(w, "watermark text must be 200 characters or fewer", http.StatusBadRequest)
        return
    }

    file, _, err := r.FormFile("image")
    if err != nil {
        http.Error(w, "multipart form field image is required", http.StatusBadRequest)
        return
    }
    defer file.Close()

    src, format, err := image.Decode(file)
    if err != nil {
        http.Error(w, "unsupported or invalid image; use JPEG, PNG, or GIF", http.StatusBadRequest)
        return
    }
    if format != "jpeg" && format != "png" && format != "gif" {
        http.Error(w, "unsupported image format", http.StatusBadRequest)
        return
    }

    // This example's explicit pixel policy is 20 million pixels. Choose a
    // limit appropriate to your service and memory budget.
    bounds := src.Bounds()
    width, height := bounds.Dx(), bounds.Dy()
    if width <= 0 || height <= 0 || int64(width)*int64(height) > 20_000_000 {
        http.Error(w, "image dimensions exceed the service limit", http.StatusRequestEntityTooLarge)
        return
    }

    // Decoders may return YCbCr or Paletted images rather than RGBA.
    dst := image.NewRGBA(image.Rect(0, 0, width, height))
    draw.Draw(dst, dst.Bounds(), src, bounds.Min, draw.Src)

    // Position the baseline near the lower-left, with a small image-relative margin.
    face := basicfont.Face7x13
    margin := max(8, width/40)
    baselineY := height - max(8, height/25)
    baselineX := margin
    d := &font.Drawer{
        Dst:  dst,
        Src:  image.NewUniform(color.NRGBA{R: 255, G: 255, B: 255, A: 210}),
        Face: face,
        Dot:  fixed.P(baselineX, baselineY),
    }
    d.DrawString(text)

    // PNG keeps source transparency. The endpoint always responds as PNG.
    var out bytes.Buffer
    if err := png.Encode(&out, dst); err != nil {
        http.Error(w, "could not encode watermarked image", http.StatusInternalServerError)
        return
    }
    w.Header().Set("Content-Type", "image/png")
    w.Header().Set("Content-Disposition", `inline; filename="watermarked.png"`)
    w.Header().Set("X-Input-Format", format)
    w.WriteHeader(http.StatusOK)
    if _, err := io.Copy(w, &out); err != nil {
        // The client may have disconnected; do not attempt to write a second response.
        log.Printf("writing image response: %v", err)
    }
}

// Register the three decoders this endpoint accepts.
var _ = fmt.Sprintf
var _ = jpeg.Decode
var _ = gif.Decode

For clean imports, the explicit blank references above are not needed: the decoder packages register themselves through their import side effects. A more idiomatic form uses blank imports instead. Replace "fmt", "image/gif", and "image/jpeg" in the import block with _ "image/gif" and _ "image/jpeg", remove the three trailing var _ lines, and remove fmt. Keep image/png as a normal import because the handler calls png.Encode. The final import list should therefore contain blank imports for GIF and JPEG, and normal imports for PNG and the other packages.

3. Run it and send an upload

go run .

curl -i -X POST http://localhost:8080/watermark 
  -F 'image=@input.jpg' 
  -F 'text=Internal use'

A successful request returns status 200, Content-Type: image/png, and PNG bytes. Save them with curl using -o watermarked.png. Invalid multipart requests, missing fields, malformed images, and oversized requests return an HTTP error instead of an image.

How text rendering and compositing work

The handler copies the decoded source into a newly allocated *image.RGBA. This matters because decoders can return different concrete image types—for example, JPEG decoding may produce *image.YCbCr and GIF decoding may produce *image.Paletted. A new RGBA destination is predictable and mutable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Custom Logo Embosser Seal Stamp Your Own Design Personalized 1 x 5/8"
  • Custom Logo Embossing Stamp, Logo or Emblem Embosser, Wedding Logo Custom Embosser. Library Book Embosser, Logo Branding Embosser
  • Click on “Customize Now” to enter your details and upload your design or logo image.Best results are from a high resolution black and white jpeg or png.
  • Impression size approx. 1 5/8" " (Please do not upload photos) If you want a different design please message us.. Please ensure your image is clear and not too detailed. If so your order will be cancelled and you will need to reorder again. Please message if unsure.
  • Our Custom Personalized Embossers are made right here in the USA! We are FAST! Send us a message if you would like a different font, graphic image, larger or smaller seal etc.
  • FAST PRODUCTION! We can work together to make the best custom, personalized embosser for you!

The font drawer rasterizes glyphs and paints them through alpha masks. The drawing package combines the foreground with the destination using an operation such as draw.Over, so partially transparent watermark pixels layer over the image rather than replacing all underlying pixels. The Go image/draw article demonstrates this masked-compositing model. A bare image/draw call does not provide glyph selection, font parsing, sizing, text layout, or baselines; those belong to the font-rendering layer.

basicfont.Face7x13 is intentionally simple and fixed-size. For production, select a font package and licensed font suitable for your audience, then set font size, color, opacity, placement, and wrapping deliberately. The sample uses a translucent white mark near the bottom-left. A white mark can disappear on a bright image; contrast may require a dark outline, shadow, or contrasting backing. If you implement wrapping or alignment, calculate text bounds and keep the full mark inside the image.

Choose input, output, and upload policies deliberately

Formats

The example accepts JPEG, PNG, and GIF via registered standard-library decoders, and always emits PNG. PNG output preserves alpha; JPEG output cannot preserve transparent pixels, so JPEG encoding requires a defined background/flattening policy. Do not infer format support from a filename or submitted MIME type. Decode the bytes and maintain an explicit allowlist.

Other libraries may advertise more formats, but actual decoding and encoding support depends on the chosen library, version, and its dependencies. The fzdwx/watermark package documentation lists JPEG, PNG, GIF, and WebP format constants; verify the current package version and its behavior before depending on that support.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #3
INFUNLY Vintage Number Clear Stamps for Scrapbooking 6.3x4.3 Retro Air Ticket Postmark Rubber Stamp Reusable Postage Transparent Stamps for Card Making Photo Journal Album Crafting Decoration
  • RICH IN PATTERNS: Clear Stamps includes various patterns, such as vintage numbers, mail postmark, postage, code, date, words and more to meet your stamping needs. Perfect for you DIY craft.
  • SIMPLE TO USE: The Clear Stamps for Card Making are easy to operate. Simply peel the silicone stamp from the sticker, and paste it on the acrylic plate, then apply the ink evenly on it, and last printed on the paper.
  • VERSATILE USE: This Silicone Stamps can be applied to decorate your photo albums, holiday cards, scrapbooks, notebooks, diaries. Different shape for your different need.
  • REUSABLE MATERIAL: This Vintage Clear Stamp is made of high quality PVC, which are reusable, flexible, and hard to fade or break, also can retain their words for a long time.
  • EXCELLENT DIY GIFT: This Vintage Clear Stamps for Scrapbooking are perfect gifts for your friends, family or anyone else who likes handicrafts, DIY and crafts.

Body size, memory, and dimensions

http.MaxBytesReader caps the incoming request body and reports an error when reads exceed the cap. The 10 MiB figure above is an example policy, not a universal safe limit. ParseMultipartForm(1 << 20) keeps up to 1 MiB of file parts in memory and can spill the remainder into temporary files; RemoveAll removes those files when the request completes. The net/http documentation also provides MultipartReader for handlers that need to process multipart data as a stream.

A compressed upload can expand into a very large pixel buffer, so request-byte limits are not enough. Apply dimension or pixel-count limits as a separate policy before allocating your destination. For stricter protection against decoder resource use, consider format-specific validation and a worker or process memory limit. Choose limits based on your service’s memory budget and expected images, not by copying the example value.

Request and response contract

  • Require POST with multipart/form-data, an image file part, and a non-empty text field.
  • Set the request-body cap before parsing, and reject invalid or unsupported image bytes with a client error.
  • Return an explicit output format and matching Content-Type; do not imply source-format preservation when the endpoint converts everything to PNG.
  • Check parse, file, decode, draw, encode, and response-write errors. Avoid returning internal error details to clients; log operational details safely.
  • Do not persist uploaded files unless required. If multipart parsing spills to disk, ensure temporary files are removed.

When a watermark library is a better fit

A library can save you from implementing font loading, positioning, alignment, and format handling yourself. The fzdwx/watermark documentation describes an AddTextMark API that accepts text, an io.Reader source, a format, and options, and returns image bytes and an error. Review its current version, maintenance status, supported options, format behavior, and MIT license before adopting it. These documented capabilities are not an independent performance or quality test.

For broader image transformations, govips documents a label operation with font, offsets, opacity, color, alignment, width, and height, plus compositing with the over blend mode. It binds to native libvips, which adds installation and deployment considerations compared with a pure-Go path. No objective throughput winner is established here, so benchmark with your image sizes and workload if performance is decisive.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
KWELLAM Ink Stamp Watermark Background Clear Stamps for Card Making Decoration and DIY Scrapbooking
  • - High-quality clear stamps for paper crafting
  • - Clear stamps are easy to use
  • - Just position them on an acrylic block (not included) and start stamping! Great for card making scrapbooking and other paper crafts
  • - Material: Silicone
  • - Size:11x16m

Performance, reliability, and security considerations

  • Memory: Decoded pixels, the RGBA destination, and encoded output may coexist in memory. A buffered PNG response simplifies error handling but adds an output buffer; large workloads may require streaming or bounded worker concurrency.
  • Latency: Decode, glyph rendering, and encoding determine processing time. Avoid claims about speed without measurements on your formats, image dimensions, hardware, and concurrency.
  • Availability: Treat image processing as fallible. A decoder can reject malformed bytes, an encoder can fail, and a client can disconnect while the response is being written.
  • Abuse resistance: Keep body and pixel limits, cap watermark text length, validate required fields, and consider request timeouts and concurrency limits in the server configuration.
  • Privacy: Avoid retaining uploads or watermark text longer than necessary. Handle temporary files and logs with the same care as the original image.
  • Legal meaning: A visible overlay can identify or brand an image, but the implementation itself does not establish ownership or prevent copying.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting common failures

“invalid multipart request” or HTTP 400

Send the request as multipart/form-data, include a file part named exactly image, and include a non-empty text field. When using curl, use -F rather than manually setting a multipart content type without its boundary.

HTTP 413 or “request body exceeds the limit”

The complete multipart request—including fields and boundaries—exceeds the configured 10 MiB example cap. Raise the cap only if your service can safely accept larger uploads, or resize/compress the image before sending it.

“unsupported or invalid image”

The bytes are malformed, the format is not registered, or the upload is not one of the accepted formats. Do not trust a .jpg filename or a client-supplied content type. Add a decoder intentionally if you need another format, then test both input and output behavior.

Image rejected for dimensions

The decoded image exceeds the example’s 20-million-pixel policy. Lower-resolution input may be appropriate; otherwise change the policy after considering memory use and concurrent requests. This is separate from the compressed request-size cap.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
DECORA 6 Pieces Floral Pattern Round Wooden Rubber Stamp for Scrapbooking
  • ❤WIDE APPLICATION-Great for polymer clay,envelope,handmade cards, school holiday card and gift tags;deal for decorating Scrapbooking and wedding invitation cards etc; A great Christmas and New Year Gifts for kids, definitely a big hit among children!
  • ❤EASY TO USE-Detail stands out very nice; The design goes right to the edge of the stamp making it easy to see where the placement should be;Clean it with a baby wipe and damp sponge
  • ❤DIY-How they really shine is creating your own background; Using Distress rubber stamp and tool, or Pan Pastels, or even water colors, you can easily create a own stamp painting;Beautiful lines, creates beautiful under brush for paintings on card fronts very natural
  • ❤DIY-How they really shine is creating your own background; Using Distress rubber stamp and tool, or Pan Pastels, or even water colors, you can easily create a own stamp painting;Beautiful lines, creates beautiful under brush for paintings on card fronts very natural

Watermark is invisible, clipped, or hard to read

The fixed-size basic font may not suit the image dimensions, the white text may blend into a light background, or a long string may extend past the right edge. Use a scalable font renderer, calculate the text bounds, adjust size and margins, and choose contrast deliberately.

Output looks different from the source

The endpoint converts all inputs to PNG, which may change file size and metadata. If switching to JPEG output, define how transparent areas are flattened; JPEG has no alpha channel. Test animated GIF behavior too: the simple decode path works with an image frame, not a promise to preserve animation.

Or skip the browser setup

This Go handler is for watermarking uploads on your own server. If your actual task is taking website screenshots, ScreenshotNeo is a different tool: it returns screenshots or PDFs through an API and MCP server. One GET request can capture a URL; for example:

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 API documentation for request options. It removes cookie/consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, and cache hits are not billed. Its MCP server lets AI agents use screenshot tools. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. For a text-watermark endpoint, continue with the Go implementation above; for website captures, learn about ScreenshotNeo and sign up free for 1,000 screenshots a month with no card.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Does the example preserve animated GIFs?

No. The decode-and-draw flow processes an image frame and returns a single PNG; preserving animation requires handling each frame and timing explicitly.

Can I use this endpoint to prove who owns an image?

No. A rendered watermark can communicate attribution, but this code does not establish legal ownership or prevent removal.

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.