Documentation
¶
Overview ¶
Package effects defines post- and pre-render visual filters for 2D drawing operations. These filters operate directly on *image.RGBA buffers, modifying pixel data to simulate optical effects such as shadows, blurs, glow, and color transformations.
This file implements a *drop shadow* effect similar to CSS’s `box-shadow`, applying a blurred, offset shadow below rendered content. The effect uses the existing alpha channel of the layer as a shape mask and tints it with a specified shadow color, spread, and blur radius.
High-level algorithm
- Extract the alpha channel from the destination buffer (dst) to form a mask representing the visible shape of the layer.
- Optionally expand (spread) the mask to enlarge the opaque region before blur.
- Apply a separable box blur to the alpha mask to soften the edges.
- Tint the blurred alpha mask with the shadow color and multiply it by overall opacity. RGB values are premultiplied by alpha to maintain correct compositing.
- Render the resulting shadow offset by (x, y) *below* the original content, then composite the source image back over it.
Notes and constraints
- Image format: The function expects *image.RGBA with premultiplied alpha. Alpha blending is handled manually by setting premultiplied RGB values.
- Spread: Expands alpha before blur, similar to CSS `box-shadow spread-radius`.
- Blur: Implemented as a separable two-pass box blur for performance. The result approximates a Gaussian blur with radius `blur`.
- Opacity: The `opacity` parameter multiplies the color alpha and scales the shadow’s final visibility. Range [0..1].
- Composition order: The shadow is drawn first, then the original layer is overlaid using Porter-Duff `draw.Over` blending.
- Performance: O(W*H*blur) complexity. For large radii, consider downsampling.
- Thread-safety: Concurrent writes to the same RGBA buffer are unsafe.
Example usage:
shadow := effects.NewDropShadow(4, 4, 6, 0, color.NRGBA{0, 0, 0, 255}, 0.6)
shadow.Apply(layer)
This produces a smooth, semi-transparent shadow shifted 4px down and right, visually equivalent to CSS `box-shadow: 4px 4px 6px rgba(0,0,0,0.6);`.
Package effects defines interfaces and utilities for 2D image effects that can be applied to rendered layers before or after drawing operations. These effects modify *image.RGBA buffers directly to simulate phenomena like shadows, blur, glow, or color overlays.
This file contains the base Effect interface shared by all post-processing filters, as well as helper functions for working with alpha masks such as extracting or softening alpha channels.
Overview
- The Effect interface provides a unified contract for any visual filter that modifies an RGBA image, including its name and processing phase.
- The helper functions (`extractAlpha`, `featherMask`) operate on alpha masks to facilitate effects that depend on object silhouettes or smooth transparency gradients.
Notes:
- Effects may be either pre-render (IsPre == true) or post-render (IsPre == false). Pre effects modify the layer before content is drawn (e.g., tint, blur base), while post effects process the result afterward (e.g., shadow, glow).
- All effects work in-place on the provided image buffer and must ensure bounds safety themselves.
- Helper functions are CPU-based and not optimized for real-time pipelines, but they are suitable for procedural rendering or offline composition.
Package effects provides post- and pre-render image effects that can be attached to drawable instructions and compositing stages. These effects modify pixel data to simulate optical phenomena such as shadows, lighting, texture overlays, or atmospheric depth. All operations are performed directly on CPU-based *image.RGBA buffers for deterministic, hardware-independent rendering.
The effect implemented in this file simulates a CSS-like *inset box-shadow* (inner shadow). It darkens the interior edges of a rendered shape according to its alpha mask, creating the perception of depth or recessed surfaces.
High-level algorithm
- Extract the alpha channel of the destination image (dst). The alpha defines the shape mask to which the inner shadow will be applied.
- Invert the alpha mask so that the area outside the shape becomes opaque. This inverted mask defines the region where the shadow will originate.
- Offset the inverted mask in the opposite direction of the desired shadow (negative offset) to simulate a light source casting from the given direction. For example, positive offsetX and offsetY produce a lower-right inner shadow.
- Apply a box blur of radius `blur` to soften the shadow edges. The blur kernel is separable and applied in-place for efficiency.
- Multiply the blurred mask by the original alpha, ensuring the shadow only affects pixels *inside* the shape’s visible area.
- Tint destination RGB channels toward the shadow color with opacity-scaled interpolation. The alpha channel remains unmodified to match CSS behavior.
Notes and constraints
- Image format: dst must be *image.RGBA with premultiplied alpha. The effect reads and writes pixel data directly through Pix/PixOffset. Alpha blending is simulated via per-channel linear interpolation in sRGB space.
- Opacity: final opacity equals `effect.opacity * color.A/255`. Setting either to 0 disables the shadow. Opacity values are clamped to [0, 1].
- Coordinate system: offsets use CSS-style semantics. Positive offsetX/offsetY move the *light source* down and right, which pushes the shadow upward/left.
- Performance: the implementation is linear in the number of pixels. For large images, consider parallelization or reusing intermediate buffers.
- Thread-safety: concurrent modification of the same *image.RGBA is unsafe. Guard dst if the effect is applied concurrently across tiles or regions.
Example usage:
shadow := effects.NewInnerShadowEffect(4, 4, 6, patterns.Color{R: 0, G: 0, B: 0, A: 128})
shadow.SetOpacity(0.8)
shadow.Apply(target)
The resulting image will appear as if the drawn shape has a soft, recessed inner shadow consistent with Figma or CSS `box-shadow: inset ...` behavior.
Package effects defines visual post-processing effects for 2D drawing instructions. These effects operate directly on image buffers (*image.RGBA) after or before the main rendering pass, allowing simulation of optical phenomena such as blur, shadows, glow, or texture overlays.
This file implements a *layer blur* effect equivalent to Figma’s “Layer Blur”. Unlike a background blur, this effect acts solely on the content of the current layer — it diffuses the layer’s own pixels without sampling or blending the background underneath.
High-level algorithm
- Copy the current layer (dst) into a temporary buffer to preserve original data.
- Apply one or more passes of separable box blur to spread pixel values. - In constant mode, a uniform blur radius is used for the entire image. - In progressive mode, blur radius is linearly interpolated along the Y-axis, producing a gradient blur (useful for atmospheric or focus effects).
- Optionally multiply all alpha values by `opacity` ∈ [0, 1] to attenuate the blurred result.
Implementation details
- Blur kernel: A three-pass box filter is used to approximate Gaussian blur. Each pass performs horizontal and vertical averaging within ±radius.
- Progressive blur: Implemented by reusing `boxBlurLine` on each row with a per-line radius computed via linear interpolation between `radiusStart` and `radiusEnd`.
- Image format: Operates in sRGB 8-bit space directly. Alpha is blurred along with RGB. The result remains premultiplied-compatible.
- Performance: Complexity is O(W*H*radius). For large images or high radii, consider splitting into tiles or parallelizing.
- Thread-safety: Concurrent writes to the same *image.RGBA are unsafe. Synchronize access when applying effects in parallel.
Example usage:
blur := effects.NewLayerBlurEffect(8).SetOpacity(0.8) blur.Apply(layer) // Progressive blur from 4px (top) to 16px (bottom) blur.SetProgressive(4, 16).Apply(layer)
Both modes produce results visually similar to Figma’s layer blur.
Package effects implements pre- and post-render effects for 2D drawing instructions. This file defines a noise overlay effect similar to Figma’s “Noise” fill. It modifies RGB values of the destination image using random sampling and blending.
Algorithm summary
- Iterate over all pixels of the target image.
- For each pixel, with probability equal to `density`, apply a random noise color according to the selected noise type (mono, duo, or multi).
- Blend the noise color into the pixel’s RGB channels using linear interpolation by `opacity`.
The alpha channel remains unchanged. The effect is post-applied (IsPre() == false).
Parameters:
- noiseType — type of noise pattern (mono/duo/multi).
- density — probability of applying noise at each pixel in [0, 1].
- opacity — blend factor in [0, 1].
- colorA, colorB — base colors for duo mode.
- blendMode — reserved for future blending options.
Notes:
- Non-deterministic: Uses math/rand global RNG. Seed externally if deterministic output is needed.
- Complexity: O(W×H) with very low per-pixel cost.
- Safe for any *image.RGBA buffer. No allocations.
Package effects provides post- and pre-render image effects that can be attached to drawable instructions. The effect in this file simulates Figma-like texture overlay (procedural roughness) by tinting destination pixels with a noisy mask.
High-level algorithm
- For each destination pixel (x, y) compute a smooth pseudo-random value n in [0, 1] using a sum of sine products (see mathSinNoise). The evaluation domain is scaled by the user-provided `scale` so bigger values produce wider, lower-frequency waves; smaller values produce finer grain.
- Clamp and scale n by `intensity` in [0, 1]. Intensity = 0 disables the effect; 1 uses the full amplitude of the procedural noise.
- Linearly interpolate the destination RGB channels toward the effect color using t = n * opacity, while leaving the alpha channel unchanged. Interpolation is performed in 8-bit sRGB space for performance: C_out = Lerp(C_dst, C_tex, t)
- The effect runs as a post effect (IsPre() == false). It expects that the base content has already been drawn into dst.
Notes and constraints
- Image format: dst must be *image.RGBA. The buffer stores premultiplied alpha, but this effect does not modify A and blends only RGB in-place. This matches a simple "overlay tint" and is fast. If you need accurate alpha-aware compositing, convert to linear space and back.
- Determinism: mathSinNoise includes calls to math/rand for slight phase jitter. Using the package-level RNG makes results depend on the global seed. If you need reproducibility across runs, seed math/rand yourself (e.g., rand.Seed(0)) before applying the effect, or rewrite the noise to be hash-based and seedable per-effect.
- Performance: The implementation iterates pixels and writes directly into dst.Pix via PixOffset to avoid allocations. For very large images consider tiling or parallelization.
- Thread-safety: Package-level functions in math/rand are safe for concurrent use, but modifying the same *image.RGBA from multiple goroutines is not. Guard dst with your own synchronization if you parallelize.
Index ¶
- type DropShadowEffect
- type Effect
- type InnerShadowEffect
- type LayerBlurEffect
- type NoiseEffect
- type NoiseType
- type TextureEffect
- func (e *TextureEffect) Apply(dst *image.RGBA)
- func (e *TextureEffect) IsPre() bool
- func (e *TextureEffect) Name() string
- func (e *TextureEffect) SetIntensity(v float64) *TextureEffect
- func (e *TextureEffect) SetOpacity(v float64) *TextureEffect
- func (e *TextureEffect) SetScale(v float64) *TextureEffect
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type DropShadowEffect ¶
type DropShadowEffect struct {
// contains filtered or unexported fields
}
DropShadowEffect adds a blurred, offset shadow below the rendered object. It works similarly to CSS `box-shadow` for rasterized layers, using the layer’s alpha channel as the shape mask.
func NewDropShadow ¶
func NewDropShadow(x, y, blur, spread float64, col color.Color, opacity float64) *DropShadowEffect
NewDropShadow creates a configured drop shadow effect using offset, blur radius, spread, color, and opacity.
Arguments:
- x, y: shadow offset in pixels (positive = right/down).
- blur: blur radius for edge softness.
- spread: expansion radius before blurring.
- col: shadow color (any color.Color).
- opacity: final opacity factor [0..1].
func (*DropShadowEffect) Apply ¶
func (e *DropShadowEffect) Apply(dst *image.RGBA)
Apply draws the drop shadow under the current image content.
The shadow is composited below existing pixels based on their alpha, blurred, tinted, and offset by (x, y). The destination buffer (dst) is modified in place.
func (*DropShadowEffect) IsPre ¶
func (e *DropShadowEffect) IsPre() bool
IsPre indicates whether this effect should be applied before drawing. Drop shadows are post effects, so it always returns false.
func (*DropShadowEffect) Name ¶
func (e *DropShadowEffect) Name() string
Name returns the effect identifier.
type Effect ¶
type Effect interface {
// Apply performs the effect’s operation on the provided RGBA image.
// The modification is in-place; implementations must handle bounds safety.
Apply(dst *image.RGBA)
// Name returns a short, human-readable identifier for this effect,
// typically used for debugging or logging.
Name() string
// IsPre reports whether this effect should be applied before the layer’s
// content is drawn. Pre-effects modify the base buffer; post-effects apply
// after the main drawing pass.
IsPre() bool
}
Effect defines the universal interface for any image-based visual effect.
An Effect operates directly on an *image.RGBA buffer, optionally modifying pixels in-place to produce a visual transformation such as blur, shadow, glow, or color grading.
type InnerShadowEffect ¶
type InnerShadowEffect struct {
// contains filtered or unexported fields
}
InnerShadowEffect simulates a CSS-like "inset box-shadow" effect. It darkens the interior edges of a shape based on its alpha channel, producing the illusion of depth or an inner cavity.
Parameters:
- offsetX / offsetY: shadow displacement in pixels (positive = right/bottom).
- blur: blur radius in pixels for soft edges.
- color: shadow tint color.
- opacity: overall transparency factor (0..1), multiplied by the color alpha.
func NewInnerShadowEffect ¶
func NewInnerShadowEffect(offsetX, offsetY, blur float64, c patterns.Color) *InnerShadowEffect
NewInnerShadowEffect constructs a new inner shadow effect using the given offset, blur, and color. The initial opacity is derived from the color's alpha.
func (*InnerShadowEffect) Apply ¶
func (e *InnerShadowEffect) Apply(dst *image.RGBA)
Apply renders the inner shadow into the destination RGBA image (dst).
The effect is achieved in four stages:
- Invert the alpha channel to create an outer mask.
- Shift the mask in the opposite direction of the offset (positive offset moves the light source down/right).
- Blur the shifted mask for smooth transitions.
- Multiply the blurred mask by the original alpha to limit the shadow to inside the shape, tint it by the shadow color, and blend it back.
The resulting appearance mimics CSS `box-shadow: inset ...`.
func (*InnerShadowEffect) IsPre ¶
func (e *InnerShadowEffect) IsPre() bool
IsPre reports whether this effect should be applied before drawing content. Inner shadows are post-render effects, so this always returns false.
func (*InnerShadowEffect) Name ¶
func (e *InnerShadowEffect) Name() string
Name returns a human-readable identifier for this effect.
func (*InnerShadowEffect) SetOpacity ¶
func (e *InnerShadowEffect) SetOpacity(v float64) *InnerShadowEffect
SetOpacity sets a custom opacity factor (0..1) and returns the same effect for chaining. The final opacity is the product of this value and the color alpha.
type LayerBlurEffect ¶
type LayerBlurEffect struct {
// contains filtered or unexported fields
}
LayerBlurEffect applies a blur to the pixels of the current layer itself.
It can operate either with a constant blur radius or progressively — increasing or decreasing blur intensity along the Y-axis.
The implementation uses a simple multi-pass box blur approximation of a Gaussian blur, applied directly to RGBA pixel data.
Parameters:
- radiusStart, radiusEnd — starting and ending radius of blur (for progressive mode).
- progressive — when true, interpolates radius vertically across image height.
- opacity — final opacity multiplier applied to the blurred layer.
Notes:
- Works as a post-effect (IsPre() == false).
- Computational cost grows linearly with radius and pixel count.
- Not thread-safe when used on the same *image.RGBA concurrently.
func NewLayerBlurEffect ¶
func NewLayerBlurEffect(radius float64) *LayerBlurEffect
NewLayerBlurEffect creates a new layer blur with constant radius.
Example:
e := effects.NewLayerBlurEffect(8).SetOpacity(0.8) layer.AddEffect(e)
func (*LayerBlurEffect) Apply ¶
func (e *LayerBlurEffect) Apply(dst *image.RGBA)
Apply executes the blur operation on the destination image.
Steps:
- Copy the current layer to a temporary source buffer.
- Apply either a uniform blur (boxBlur) or progressive per-line blur (boxBlurLine).
- If opacity < 1, scale the alpha channel accordingly.
The blur uses three passes of box filtering (horizontal + vertical) to approximate Gaussian blur.
func (*LayerBlurEffect) IsPre ¶
func (e *LayerBlurEffect) IsPre() bool
IsPre indicates whether this effect runs before drawing. LayerBlurEffect is post-applied, so this returns false.
func (*LayerBlurEffect) Name ¶
func (e *LayerBlurEffect) Name() string
Name returns the human-readable identifier of this effect.
func (*LayerBlurEffect) SetOpacity ¶
func (e *LayerBlurEffect) SetOpacity(v float64) *LayerBlurEffect
SetOpacity sets the layer’s alpha scaling factor in [0,1]. This reduces the overall strength of the blurred image.
func (*LayerBlurEffect) SetProgressive ¶
func (e *LayerBlurEffect) SetProgressive(start, end float64) *LayerBlurEffect
SetProgressive enables progressive blur and sets start/end radii.
When active, blur strength varies linearly from start at the top to end at the bottom. Returns the receiver for chaining.
type NoiseEffect ¶
type NoiseEffect struct {
// contains filtered or unexported fields
}
NoiseEffect represents a noise overlay applied as a post-processing step. The noise modifies RGB channels based on the selected NoiseType.
func NewNoiseEffect ¶
func NewNoiseEffect(nt NoiseType, density float64) *NoiseEffect
NewNoiseEffect creates a new NoiseEffect with the given type and density. Density defines how frequently noise is applied to pixels (0=no noise, 1=full coverage). Opacity defaults to 1.0; colors default to white and black.
Example:
e := effects.NewNoiseEffect(effects.NoiseDuo, 0.3). SetColors(colors.White, colors.Black). SetOpacity(0.5) shape.AddEffect(e)
func (*NoiseEffect) Apply ¶
func (e *NoiseEffect) Apply(dst *image.RGBA)
Apply executes the noise effect on the given RGBA image.
For each pixel within bounds:
- With probability = density, generate noise according to the selected type.
- Linearly interpolate destination RGB channels toward the noise color using the specified opacity.
Alpha channel remains unchanged.
func (*NoiseEffect) IsPre ¶
func (e *NoiseEffect) IsPre() bool
IsPre indicates whether the effect should be applied before drawing. NoiseEffect is always post-applied, so this returns false.
func (*NoiseEffect) Name ¶
func (e *NoiseEffect) Name() string
Name returns the effect identifier. Implements the Effect interface.
func (*NoiseEffect) SetColors ¶
func (e *NoiseEffect) SetColors(a, b patterns.Color) *NoiseEffect
SetColors sets the two colors used by NoiseDuo mode. Returns the receiver for chaining.
func (*NoiseEffect) SetOpacity ¶
func (e *NoiseEffect) SetOpacity(v float64) *NoiseEffect
SetOpacity sets the blending opacity for noise application in [0,1]. 0 disables blending, 1 applies full strength. Returns the receiver for chaining.
type NoiseType ¶
type NoiseType int
NoiseType defines the noise generation mode. It determines how pixel colors are chosen for the noise pattern.
const ( // NoiseMono — uniform grayscale noise, each selected pixel takes a random // brightness value from 0 to 255, applied equally to R, G, and B channels. NoiseMono NoiseType = iota // NoiseDuo — binary color noise. Each pixel randomly picks between colorA and colorB. NoiseDuo // NoiseMulti — full RGB noise, each pixel gets a completely random color. NoiseMulti )
type TextureEffect ¶
type TextureEffect struct {
// contains filtered or unexported fields
}
TextureEffect simulates a texture/roughness overlay similar to Figma's "Texture" fill.
The effect tints destination pixels toward a given color using a smooth, procedural, sine-based noise field. The alpha channel is preserved.
Parameters:
- scale: Spatial scale of the noise in pixels. Larger values produce broader features. Clamped to [1, 100].
- intensity: Strength of the noise modulation in [0, 1]. Acts like an amplitude for the noise field before opacity is applied.
- opacity: Final opacity of the tint in [0, 1]. Multiplies the noise value.
- color: Target tint color in sRGB 8-bit channels.
- blendMode: Reserved for future compositing modes; currently unused.
Implementation detail: The type conforms to the project’s Effect interface via Name, IsPre, and Apply.
func NewTextureEffect ¶
func NewTextureEffect(scale, intensity float64, c patterns.Color) *TextureEffect
NewTextureEffect constructs a new TextureEffect with clamped parameters.
scale is clamped to [1, 100]. intensity is clamped to [0, 1]. opacity defaults to 1.
Example:
e := effects.NewTextureEffect(24, 0.6, colors.Silver).SetOpacity(0.5) shape.AddEffect(e)
The effect is post-applied (IsPre() == false).
func (*TextureEffect) Apply ¶
func (e *TextureEffect) Apply(dst *image.RGBA)
Apply runs the texture overlay over the entire destination image.
Preconditions:
- dst must be non-nil and of type *image.RGBA.
- The base content should already be composited into dst.
Behavior:
- If opacity == 0, the method is a no-op.
- For each pixel, compute a smooth noise value in [0, 1], scale it by intensity, then linearly interpolate each RGB channel toward e.color with t = noise * opacity. Alpha is left unchanged.
Complexity: O(W×H) time, O(1) extra space.
Warning: The interpolation is done in sRGB byte space for speed and may not match physically correct results in linear RGB.
func (*TextureEffect) IsPre ¶
func (e *TextureEffect) IsPre() bool
IsPre reports whether the effect should run before the main draw pass.
TextureEffect is a post effect, so this method returns false. Satisfies the Effect interface.
func (*TextureEffect) Name ¶
func (e *TextureEffect) Name() string
Name returns the human-readable effect name. Satisfies the Effect interface.
func (*TextureEffect) SetIntensity ¶
func (e *TextureEffect) SetIntensity(v float64) *TextureEffect
SetIntensity sets the strength of the noise field in [0, 1].
intensity scales the noise before opacity is applied. Returns the receiver for chaining.
func (*TextureEffect) SetOpacity ¶
func (e *TextureEffect) SetOpacity(v float64) *TextureEffect
SetOpacity sets the final blend opacity in [0, 1].
The effective per-pixel blend factor becomes noise * opacity. Returns the receiver for chaining.
func (*TextureEffect) SetScale ¶
func (e *TextureEffect) SetScale(v float64) *TextureEffect
SetScale sets the spatial scale of the noise and clamps it to [1, 100].
Larger values yield lower-frequency textures. Returns the receiver for chaining.