Documentation
¶
Overview ¶
Package thumb makes a small image out of a large one.
It exists because a 48 pixel tile for a two megabyte screenshot used to cost two megabytes, and a page of twenty image results cost forty. The bytes a browser is asked to move are not visible in any latency measurement of the JSON that named them, which is how a slow page stayed slow while every number on the dashboard was inside its budget.
This is the first package that decodes a file the corpus handed us, so it is written on the assumption that some of those files are hostile. Three rules follow from that and none of them is optional.
The header is read before the pixels are. image.DecodeConfig reports the dimensions without allocating the image, so a file claiming sixty thousand pixels on a side is refused for the price of reading its first few bytes rather than for the price of thirteen gigabytes.
Concurrency is capped, because the memory ceiling is not per request. A decoded image costs four bytes a pixel and the real bound is that number times how many decodes are in flight, so one semaphore holds the worst case to MaxPixels times four times Concurrency, which is a few hundred megabytes rather than however many requests happened to arrive at once.
Every refusal is the same refusal. A hostile image, a text file with a .png on the end and a format nobody taught us to read all produce ErrUnrenderable, and the endpoint above turns that into the same 404 a document that does not exist produces.
Index ¶
Constants ¶
const Concurrency = 4
Concurrency is how many images may be decoded at once.
Four rather than one because a thumbnail is generated once and then served from a cache, so the queue is short and a single decoder would make the first visit to a page of images serial. Four rather than the number of cores because the bound being protected is memory rather than time.
const MaxPixels = 24_000_000
MaxPixels is the largest source image that will be decoded.
Twenty four megapixels is more than any photograph in a corpus and a hundred times less than the largest number a PNG header can claim. A decoded image of that size is ninety six megabytes, which times Concurrency is the worst case this package can cost.
Variables ¶
var ErrUnrenderable = errors.New("thumb: nothing to render")
ErrUnrenderable is every refusal.
A file that is not an image, an image in a format we do not read, an image with no pixels and an image that claims more pixels than MaxPixels all produce this one error, because a caller who can tell those apart learns something about a document from an endpoint whose job is to say nothing.
var Sizes = []int{48, 96, 256}
Sizes are the widths a thumbnail may be asked for, in pixels.
Three fixed sizes rather than a number in the query string, so the cache holds three entries per document instead of one per distinct request, and so a request cannot ask the server to do a piece of work of its own choosing. A list tile asks for 48, or 96 where the device pixel ratio is two, and a grid cell asks for 256.
Functions ¶
Types ¶
type Thumbnail ¶
Thumbnail is a rendered image and the box it occupies.
func Render ¶
Render scales raw down so that its longest side is size pixels.
The result is a PNG, whatever the source was. A thumbnail at these sizes is a few kilobytes either way, PNG is lossless so a diagram with text in it stays readable, and one output format means the caller has one content type to declare rather than a mapping to keep in step with the decoders above.
It never scales up. An image already smaller than size is re-encoded at its own dimensions, because returning a blurry enlargement of a 32 pixel icon would be worse than returning the icon.