Documentation
¶
Overview ¶
Package overlay draws the post processing overlays on an extracted screenshot: a colour gradient at the bottom edge and a watermark in one of the corners.
Everything here is pure Go image work. The package neither starts processes nor knows about ffmpeg; the geometry is integer arithmetic so the results are exact and testable.
Index ¶
- Constants
- Variables
- func DecodeFile(path string) (image.Image, error)
- func DecodePNGFile(path string) (image.Image, error)
- func DrawGradient(img *image.NRGBA, g Gradient)
- func DrawVignette(img *image.NRGBA, mask []uint8)
- func DrawWatermark(img *image.NRGBA, logo image.Image, marginPercent, opacity int, ...)
- func Encode(w io.Writer, img image.Image, format clipper.Format, quality int) error
- func EncodeFile(path string, img image.Image, format clipper.Format, quality int) error
- func Fit(logo, frame image.Point, percent int) image.Point
- func FrameScale(frame image.Point) int
- func GradientBand(frame image.Point, percent int) int
- func ParseHexColor(s string) (color.RGBA, error)
- func Place(logo, frame image.Point, marginPercent int, position Position) image.Point
- func PositionList() string
- func ScaleLogo(logo image.Image, frame image.Point, sizePercent int) *image.NRGBA
- func VignetteMask(frame image.Point, v Vignette) []uint8
- type Compositor
- type Gradient
- type Position
- type Spec
- type Vignette
- type Watermark
Constants ¶
const MinWatermarkSize = 8
MinWatermarkSize is the smallest edge length a scaled logo may have. Below that nothing is recognisable any more, so the watermark is left out and reported.
Variables ¶
var ( // ErrUnsupportedFormat reports a format Go cannot write, which is webp. ErrUnsupportedFormat = errors.New("format cannot be written without ffmpeg") // ErrNotPNG reports a watermark file that is not a PNG. ErrNotPNG = errors.New("not a png file") )
Errors of the image codecs used here.
var ErrInvalidColor = errors.New("invalid colour")
ErrInvalidColor reports an unparsable colour.
var ErrUnknownPosition = errors.New("unknown watermark position")
ErrUnknownPosition reports an unusable watermark position.
var Positions = []Position{TopRight, TopLeft, BottomRight, BottomLeft}
Positions lists the supported positions in the order they are offered.
Functions ¶
func DecodePNGFile ¶
DecodePNGFile reads a PNG file and rejects every other format, so a mistyped watermark path fails with a clear message instead of silently working.
func DrawGradient ¶
DrawGradient blends the gradient into the image. The colour is mixed into the existing pixels rather than laid on top as a translucent layer, so the alpha channel of the frame is left exactly as it was.
The image is non premultiplied, which is what the blend formula expects: mixing premultiplied values while keeping the alpha would produce invalid pixels for frames that carry real transparency.
func DrawVignette ¶
DrawVignette darkens the image with a mask from VignetteMask. A mask that does not match the image is ignored rather than drawn wrongly.
Like the gradient the colour is mixed into the existing pixels and the alpha channel is left exactly as it was, so frames with real transparency stay valid.
func DrawWatermark ¶
func DrawWatermark(img *image.NRGBA, logo image.Image, marginPercent, opacity int, position Position)
DrawWatermark blends an already scaled logo into the frame. A nil logo or a zero opacity leaves the frame untouched.
Both images are non premultiplied, so the blend is a plain linear mix and the alpha channel of the frame survives unchanged.
func EncodeFile ¶
EncodeFile writes the image to path. It stages the data in a temporary file next to the target and moves it into place afterwards, so a failure never leaves a half written screenshot behind.
func Fit ¶
Fit returns the size the logo is drawn at. The logo is fitted into a square box with an edge of percent per cent of the frame scale, keeping its aspect ratio. It is never enlarged: a logo that already fits keeps its original size.
Because the box is square, the longer edge of the logo is the one that binds, so the rule reads simply: the long edge of the logo becomes percent per cent of the frame scale, in every format.
The box is capped at the shorter frame edge, so a logo never grows beyond the picture. That cap only bites at very large percentages, well above anything useful for a watermark: on 16:9 it starts at about 75 per cent.
func FrameScale ¶ added in v0.2.0
FrameScale returns the size of a frame as a single number: the geometric mean of its two edges, rounded. It is the reference the watermark is measured against.
Using one edge would tie the watermark to the shape of the frame rather than to its size. At ten per cent of the width a logo comes out 192 pixels wide on 1920x1080 but only 108 on 1080x1080, so the same setting would look different in every aspect ratio. The geometric mean depends on both edges equally, which makes the share of the picture the logo covers the same for 1:1, 9:16 and 16:9 alike.
func GradientBand ¶
GradientBand returns the height in pixels of the gradient band at the bottom of the frame, rounded down.
func ParseHexColor ¶
ParseHexColor reads a colour in hexadecimal notation. Accepted are #RRGGBB and the short #RGB form, with or without the leading hash. Transparency is not part of the colour; the gradient opacity has its own setting.
func Place ¶
Place returns the top left corner the logo is drawn at. The margin is a percentage of the frame scale, the same reference Fit uses, and is applied as an equal pixel distance on both axes so the gap looks square. Measuring it against a single edge would make the distance depend on the aspect ratio while the size no longer does. The result always stays inside the frame.
func PositionList ¶
func PositionList() string
PositionList returns the supported positions as a comma separated list, for help and error messages.
func ScaleLogo ¶
ScaleLogo fits the logo into the size box of a frame and returns the scaled copy. It returns nil when the result would be smaller than MinWatermarkSize, and it never returns the source image itself, so the caller cannot reach the shared source pixels.
func VignetteMask ¶
VignetteMask returns the coverage of every pixel of a frame, one byte per pixel in row major order, or nil when the vignette would draw nothing.
The mask depends only on the frame size and the settings, never on the picture, so it is the same for every screenshot of a resolution and worth caching. It is by far the expensive half of the effect; applying it is a table lookup.
Unlike the rest of the geometry this computes in floating point and rounds only once, at the end. The falloff has to be smooth across hundreds of pixels, and integer arithmetic would introduce exactly the steps the smoothstep is there to avoid.
Types ¶
type Compositor ¶
type Compositor struct {
// contains filtered or unexported fields
}
Compositor applies a Spec to extracted frames. It implements clipper.Compositor.
Everything that depends only on the frame size is cached per size: the scaled logo and the vignette mask. A run with many screenshots therefore scales the logo and builds the mask once per resolution instead of once per image. The cache is shared between the parallel workers and therefore guarded.
func (*Compositor) Active ¶
func (c *Compositor) Active() bool
Active reports whether this compositor changes anything beyond re-encoding.
func (*Compositor) Compose ¶
func (c *Compositor) Compose(req clipper.ComposeRequest) (clipper.ComposeResult, error)
Compose implements clipper.Compositor. It always consumes the raw frame, also when it fails, so no intermediate files are left behind.
func (*Compositor) Discard ¶
func (c *Compositor) Discard(raw string)
Discard implements clipper.Compositor. Removing a frame that is not there is not an error: the extraction may have failed before writing anything.
func (*Compositor) Staging ¶
func (c *Compositor) Staging(dest string) string
Staging implements clipper.Compositor. The raw frame sits next to its screenshot, is hidden by the leading dot and always ends in .png so ffmpeg picks the png muxer. The name carries the process id and a random part, so two clipper runs writing into the same directory cannot delete each other's intermediate.
The random part is hexadecimal and 32 bits wide rather than a full decimal uint64, which keeps the addition to the name at 23 characters instead of 36. That matters because a file name may only be 255 bytes long and the destination name is already part of it. Thirty two bits is ample here: the destination name and the process id do most of the separating, and the random part only has to cover two runs that share a process id, as two containers on one mounted volume can.
type Gradient ¶
type Gradient struct {
// Color is the colour faded in, usually black.
Color color.RGBA
// HeightPercent is the band height as a percentage of the frame height.
HeightPercent int
// Opacity is the coverage at the bottom row, from 1 to 100.
Opacity int
}
Gradient describes the colour band drawn at the bottom edge of a screenshot. The band is fully opaque at the very last row and fades linearly to fully transparent at its top edge, which keeps the transition invisible.
type Position ¶
type Position int
Position selects the corner the watermark is placed in. The zero value is TopRight.
func ParsePosition ¶
ParsePosition maps user input to a Position, case insensitively.
type Vignette ¶
type Vignette struct {
// RadiusPercent is the clear zone in the middle, measured as a percentage of
// the distance from the centre to a corner. At 100 the clear zone reaches the
// corner and nothing is darkened at all.
RadiusPercent int
// SoftnessPercent is the width of the transition in the same unit. Zero gives a
// hard edge. Radius plus softness may exceed 100, which simply means the
// falloff is still rising when it reaches the corner.
SoftnessPercent int
// Opacity is the coverage where the falloff has completed, from 1 to 100.
Opacity int
}
Vignette describes the darkening towards the edges of a screenshot. It is always black: a vignette imitates a lens, and a coloured one would be a different effect, so there is no colour to configure.
The darkening follows the shape of the frame rather than a circle, and the falloff is a smoothstep, which is what separates a vignette from a plain radial gradient.
type Watermark ¶
type Watermark struct {
// Logo is the decoded source image. It is only ever read.
Logo image.Image
// SizePercent is the share of the frame the logo is fitted into.
SizePercent int
// MarginPercent is the distance to the frame edges, as a percentage of the
// frame width.
MarginPercent int
// Opacity scales the alpha channel of the logo, from 1 to 100.
Opacity int
// Position is the corner the logo is placed in.
Position Position
}
Watermark describes the logo overlay.