Meme Generator — your image, your words, your browser
This meme generator composites caption text onto a picture you supply from your own device. You load the image, type a top caption, a bottom caption, or a free-floating block you drag wherever you want it, and the tool draws the classic heavy outlined lettering onto a copy of the photo using a <canvas> element on this page. When it looks right, you download it as a PNG or a JPEG. Nothing is uploaded, because there is no server here to upload it to.
You bring the image — there are no templates
There is no template gallery in this tool, and there is not going to be one. The familiar meme formats are other people's photographs, film stills and television screencaps, with real copyright holders attached. Shipping them here would mean this site redistributing those images from its own domain to everyone who visits, and an attribution line does not change that. So the picture comes from you: the file picker, a drag onto the preview, or a screenshot pasted straight from your clipboard. Whatever you hold the rights to use, you can caption.
How the caption is measured and fitted
The part that actually takes work is the text layout. Character widths are never assumed here, because they cannot be: the same string at the same nominal size is a different number of pixels on Windows, macOS and Android, depending on which typeface in the fallback stack the device really has. Every wrapping decision is made by calling measureText() against the exact font that is about to be drawn, on the same canvas that will draw it.
Font size then follows from that measurement rather than from a slider you have to nudge. The tool searches for the largest whole-pixel size at which the wrapped caption fits its box in both directions, and it re-wraps the text at every size it tries — shrink the letters and short lines merge into longer ones, so a size judged against a wrap computed at some other size is simply the wrong answer. If a caption will not fit even at the smallest size allowed, it is drawn at that size and the tool says so underneath, rather than quietly clipping words off the bottom.
A word too long for its box is broken inside the word, and the break lands on a grapheme cluster boundary. That matters more than it sounds: splitting a string by character cuts emoji in half and produces mojibake, and splitting by code point tears apart flags, skin-tone sequences and combining accents. Languages written without spaces, such as Chinese and Japanese, arrive as one enormous word and fall through the same path, which is what gives them sensible lines.
The outlined lettering
The recognisable look is a light fill with a dark outline, and the order of operations is what makes it work. The outline is stroked first and filled second; doing it the other way round paints the outline on top of the letterform and eats into the glyph until it looks spindly. The stroke width is a fraction of the font size rather than a fixed pixel count, so it stays proportional — a fixed stroke is a hairline on a 4K photo and a blob on a thumbnail.
Uppercase is a toggle, not a forced transform, and it is applied only at the moment of drawing. Your typed text is stored exactly as you entered it, so switching uppercase back off returns your original casing intact — which matters for text where uppercasing loses information, such as German ß or Turkish dotted and dotless i.
Preview, export and privacy
The preview is the product. The composite is measured once in image coordinates and drawn from that single plan at both preview scale and full resolution, so the file you download is the picture you were looking at. The dashed caption boxes shown while you are editing are drawn onto the on-screen canvas only and never reach the exported bitmap. Export is written with toBlob at the source resolution by default, the download filename is rebuilt from an allowlist rather than taken from your caption verbatim, and every object URL and decoded bitmap is released when you swap images or leave the page.