azureblur

The triple box blur implementation from Firefox’s moz2d / Azure, with Python bindings.

This library only supports 2D raster images with a single channel of one byte per pixel. Such images are typically used as a mask that affects the alpha (transparency) channel of some other source.

See for example the code that generates the image below, using this library for blurring the shadow and the cairocffi 2D graphics library.

_images/sample.png

Documentation

class azureblur.AlphaBoxBlur(pointer)

Implementation of a triple box blur approximation of a Gaussian blur.

A Gaussian blur is good for blurring because, when done independently in the horizontal and vertical directions, it matches the result that would be obtained using a different (rotated) set of axes. A triple box blur is a very close approximation of a Gaussian.

This is a “service” class; the constructors set up all the information based on the values and compute the minimum size for an 8-bit alpha channel context. The callers are responsible for creating and managing the backing surface and passing the pointer to the data to the Blur() method. This class does not retain the pointer to the data outside of the blur() call.

A spread N makes each output pixel the maximum value of all source pixels within a square of side length 2N+1 centered on the output pixel.

classmethod from_radiuses(rect, spread_radius, blur_radius, dirty_rect=None, skip_rect=None)

Constructs a box blur and computes the backing surface size.

Parameters:
  • rect – The coordinates of the surface to create in device units. A (x, y, width, height) tuple.
  • blur_radius – The blur radius in pixels. This is the radius of the entire (triple) kernel function. Each individual box blur has radius approximately 1/3 this value, or diameter approximately 2/3 this value. This parameter should nearly always be computed using calculate_blur_radius(), below. A (width, height) tuple.
  • dirty_rect – An optional dirty rect, measured in device units, if available. This will be used for optimizing the blur operation. It is safe to pass nullptr here. A (x, y, width, height) tuple, or None.
  • skip_rect – An optional rect, measured in device units, that represents an area where blurring is unnecessary and shouldn’t be done for speed reasons. A (x, y, width, height) tuple, or None.
classmethod from_sigma(rect, stride, sigma_x, sigma_y)

Constructs a box blur and computes the backing surface size.

Parameters:rect – The coordinates of the surface to create in device units. A (x, y, width, height) tuple.

TODO: document other parameters.

get_size()

Return the size, in pixels, of the 8-bit alpha surface we’d use.

get_stride()

Return the stride, in bytes, of the 8-bit alpha surface we’d use.

get_rect()

Returns the device-space rectangle the 8-bit alpha surface covers.

get_surface_allocation_size()

Return the minimum buffer size that should be given to Blur() method. If zero, the class is not properly setup for blurring. Note that this includes the extra three bytes on top of the stride * width, where something like Gecko’s gfxImageSurface::GetDataSize() would report without it, even if it happens to have the extra bytes.

blur_array(data_array)

Perform the blur in-place on the surface backed by specified 8-bit alpha surface data.

Parameters:data_array – A array.array of at least get_surface_allocation_size() byte-sized items.
Raises:ValueError if the the array is too small or its items are not byte-sized.

On PyPy, because of the moving garbage collector, array.array.buffer_info() is the only reliable way to get a pointer to a chuck of writable memory allocated from Python.

Usage example:

import azureblur
blur = azureblur.AlphaBoxBlur.from_radiuses(...)
alloc_size = blur.get_surface_allocation_size()
data = array.array('B', b'\x00' * alloc_size)
blur.blur_array(data)
blur_pointer(data_pointer)

Perform the blur in-place on the surface backed by specified 8-bit alpha surface data. The size must be at least that returned by get_surface_allocation_size() or bad things will happen.

Parameters:data_pointer – A uint8_t* pointer as a CFFI CData object.
azureblur.calculate_blur_radius(standard_deviation_x, standard_deviation_y)

Calculates a blur radius that, when used with box blur, approximates a Gaussian blur with the given standard deviation. The result of this function should be used as the blur_radius parameter to AlphaBoxBlur.from_radiuses().

Returns:A blur radius as a (width, height) tuple.

Example with cairocffi

The code that generates the image above is reproduced below.

# coding: utf8
import os
import array
from azureblur import AlphaBoxBlur


def sample(filename):
    import cairocffi

    background_color = (1, 1, 1)
    font_family = 'Chancery URW'
    font_size = 100
    text_color = (0, .5, 1)
    shadow_color = (.5, .5, .5)
    shadow_offset_x = 5
    shadow_offset_y = 5
    shadow_blur_radius = (5, 5)
    shadow_spread_radius = (0, 0)

    surface = cairocffi.ImageSurface(cairocffi.FORMAT_RGB24, 410, 100)
    context = cairocffi.Context(surface)

    context.set_source_rgb(*background_color)
    context.paint()

    context.select_font_face(font_family)
    context.set_font_size(font_size)
    context.set_source_rgb(*text_color)
    context.move_to(10, 80)  # Left of the text’s baseline
    context.show_text('Azure ')

    blurred_text = 'blur'

    # (x, y, width, height) rectangle just big enough for the shadowed text,
    # positioned with the left of the text’s baseline at coordinates (0, 0).
    blurred_text_rect = context.text_extents(blurred_text)[:4]

    blur = AlphaBoxBlur.from_radiuses(
        blurred_text_rect, shadow_spread_radius, shadow_blur_radius)

    # blurred_text_rect inflated to leave space for spread and blur.
    mask_x, mask_y, mask_width, mask_height = blur.get_rect()

    mask_data = array.array('B', b'\x00' * blur.get_surface_allocation_size())

    mask_surface = cairocffi.ImageSurface(
        cairocffi.FORMAT_A8,  # Single channel, one byte per pixel.
        mask_width, mask_height, mask_data, blur.get_stride())
    mask_context = cairocffi.Context(mask_surface)
    mask_context.move_to(-mask_x, -mask_y)  # Left of the text’s baseline
    mask_context.select_font_face(font_family)
    mask_context.set_font_size(font_size)
    mask_context.show_text(blurred_text)

    blur.blur_array(mask_data)

    # Position after showing 'Azure ': right of its baseline.
    current_x, current_y = context.get_current_point()

    context.set_source_rgb(*shadow_color)
    context.mask_surface(
        mask_surface,
        current_x + shadow_offset_x + mask_x,
        current_y + shadow_offset_x + mask_y)

    context.set_source_rgb(*text_color)
    context.show_text(blurred_text)
    surface.write_to_png(filename)


if __name__ == '__main__':
    sample(
        os.path.join(os.path.dirname(os.path.dirname(__file__)),
        'sample.png'))

Table Of Contents

This Page