v8.76.0

How to use shapes in the Markup Editor

Shapes are used in the Annotate, Decorate, and the Sticker plugins, they're also used to render image overlays in the willRenderCanvas hook of the editor.

The editor also uses shapes internally to draw parts of the user interface.

Working with Shapes

In the examples below we'll interact with the imageAnnotation property. In the same manner we can update the imageDecoration property and imageRedaction property.

We can set the imageAnnotation property to an array of shapes and the editor will draw the shapes to the screen.

This example will draw a red square in the top left corner of the image.

<!DOCTYPE html>

<head>
    <link rel="stylesheet" href="./pintura.css" />
</head>

<style>
    .pintura-editor {
        height: 600px;
    }
</style>

<div id="editor"></div>

<script type="module">
    import { appendDefaultEditor } from './pintura.js';

    const editor = appendDefaultEditor('#editor', {
        src: 'image.jpeg',
        imageAnnotation: [
            // a red square
            {
                x: 0,
                y: 0,
                width: 128,
                height: 128,
                backgroundColor: [1, 0, 0],
            },
        ],
    });
</script>

Let's add a green circle to the view.

<!DOCTYPE html>

<head>
    <link rel="stylesheet" href="./pintura.css" />
</head>

<style>
    .pintura-editor {
        height: 600px;
    }
</style>

<button type="button" id="buttonAddShape">Add green circle</button>

<div id="editor"></div>

<script type="module">
    import { appendDefaultEditor } from './pintura.js';

    const buttonAddShape = document.querySelector('#buttonAddShape');

    const editor = appendDefaultEditor('#editor', {
        src: 'image.jpeg',
        imageAnnotation: [
            // a red square
            {
                x: 0,
                y: 0,
                width: 128,
                height: 128,
                backgroundColor: [1, 0, 0],
            },
        ],
    });

    buttonAddShape.addEventListener('click', () => {
        // we merge our new circle shape with the existing list of shapes
        // it's added at the end of the list, so it will be drawn on top
        const updatedAnnotationList = [
            ...editor.imageAnnotation,
            {
                id: 'circle',
                x: 64,
                y: 64,
                rx: 128,
                ry: 128,
                backgroundColor: [0, 1, 0],
            },
        ];

        editor.imageAnnotation = updatedAnnotationList;
    });
</script>

Now le'ts change the color of the red square to blue.

<!DOCTYPE html>

<head>
    <link rel="stylesheet" href="./pintura.css" />
</head>

<style>
    .pintura-editor {
        height: 600px;
    }
</style>

<button type="button" id="buttonChangeColor">Change color</button>

<div id="editor"></div>

<script type="module">
    import { appendDefaultEditor } from './pintura.js';

    const buttonChangeColor = document.querySelector('#buttonChangeColor');

    const editor = appendDefaultEditor('#editor', {
        src: 'image.jpeg',
        imageAnnotation: [
            // a red square
            {
                x: 0,
                y: 0,
                width: 128,
                height: 128,
                backgroundColor: [1, 0, 0],
            },
        ],
    });

    buttonChangeColor.addEventListener('click', () => {
        const updatedAnnotationList = editor.imageAnnotation.map((shape) => {
            // if it's the square, copy and overwrite background color property
            if (shape.id === 'square') {
                return {
                    ...shape,
                    backgroundColor: [0, 0, 1],
                };
            }

            // else return shape
            return shape;
        });

        editor.imageAnnotation = updatedAnnotationList;
    });
</script>

Now let's remove the red square

<!DOCTYPE html>

<head>
    <link rel="stylesheet" href="./pintura.css" />
</head>

<style>
    .pintura-editor {
        height: 600px;
    }
</style>

<button type="button" id="buttonRemoveShape">Remove red square</button>

<div id="editor"></div>

<script type="module">
    import { appendDefaultEditor } from './pintura.js';

    const buttonRemoveShape = document.querySelector('#buttonRemoveShape');

    const editor = appendDefaultEditor('#editor', {
        src: 'image.jpeg',
        imageAnnotation: [
            // a red square
            {
                x: 0,
                y: 0,
                width: 128,
                height: 128,
                backgroundColor: [1, 0, 0],
            },
            // a green circle
            {
                id: 'circle',
                x: 64,
                y: 64,
                rx: 128,
                ry: 128,
                backgroundColor: [0, 1, 0],
            },
        ],
    });

    buttonRemoveShape.addEventListener('click', () => {
        // this filters out the red square from the shapes list
        const updatedAnnotationList = editor.imageAnnotation.filter(
            (shape) => shape.id !== 'square'
        );

        editor.imageAnnotation = updatedAnnotationList;
    });
</script>

Types

Pintura Image Editor determines the type of a shape (and how to draw and interact with it) based on the defined props on the shape.

Name Requirements
Path Has points property.
Line Has x1, y1, x2, and y2 property.
Rectangle Has width, height properties. Or a width and height can be derived from the bounds properties x, y, top, right, bottom, and left.
Ellipse Has rx and ry property.
Text Has text property.
Triangle Has x1, y1, x2, y2, x3, and y3 property

Styles

Layout related styles can be defined in pixels or in percentages.

When defined in percentages the Pintura Image Editor will calculate the pixel value automatically relative to the shape parent context.

When annotating an image the parent context is the image itself, when decorating a crop, the parent context is the crop selection.

const absoluteShape = {
    x: 0,
    y: 0,
    // ...other styles
};

const relativeShape = {
    x: '10%',
    y: '15%',
    // ...other styles
};

Shared

Property Description
x The x position of the shape. Either in pixels or a percentage.
y The y position of the shape. Either in pixels or a percentage.
rotation Shape rotation in radians.
backgroundColor Array of 3 or 4 color values ranging between 0 and 1.
opacity The opacity of the shape. Value between 0 and 1
alwaysOnTop If set to true the shape will be sorted to appear above other shapes.
aboveFrame If set to true the shape will be drawn on top of the frame.
selectionStyle The outline style to use when selected, set to 'hook' for hook style or undefined for default style.

rotation

The rotation of the shape in radians.

const shape = {
    rotation: Math.PI / 4, // 45 degrees
    // ...other styles
};

If you want to set the rotation in degrees you can use the degToRad method.

import { degToRad } from './pintura.js';

const shape = {
    rotation: degToRad(45),
    // ...other styles
};

alwaysOnTop

If set to true the shape will be sorted to appear above other shapes.

When multiple shapes have this property set to true the order of the shapes in the shape array is retained.

Shape draw order runs from top to bottom. In the shape list below the green circle is drawn first, then the blue square and then the red square.

const shapes = [
    // 2. a blue square
    {
        id: 'square',
        x: 32,
        y: 32,
        width: 128,
        height: 128,
        backgroundColor: [0, 0, 1],
        alwaysOnTop: true,
    },
    // 3. a red square
    {
        id: 'square',
        x: 0,
        y: 0,
        width: 128,
        height: 128,
        backgroundColor: [1, 0, 0],
        alwaysOnTop: true,
    },
    // 1. a green circle
    {
        id: 'circle',
        x: 64,
        y: 64,
        rx: 128,
        ry: 128,
        backgroundColor: [0, 1, 0],
    },
];

aboveFrame

When set to true the shape will be drawn on top of the frame, useful for example to draw annotations on a Polaroid.

Eraser

const shape = {
    eraseRadius: 0,
};
Property Description
eraseRadius The radius around the erase position to remove shapes in. Increase to make it easier to remove small shapes.

Rectangle

const shape = {
    x: 0,
    y: 0,
    width: 128,
    height: 128,
    backgroundColor: [1, 0, 0],
};
Property Description
width The width of the rectangle.
height The height of the rectangle.
left The position of the rectangle relative to the left of its context.
top The position of the rectangle relative to the top of its context.
right The position of the rectangle relative to the right of its context.
bottom The position of the rectangle relative to the bottom of its context.
cornerRadius The rectangle corner radius.
backgroundImage The resource to use as a background image for this shape.
backgroundSize How the image is fitted in the rectangle, either undefined, 'contain', 'cover', or a size in pixels { width: 128, height: 128 }.
backgroundPosition How the image is positioned in the rectangle, either undefined, or a position in pixels { x: 128, y: 128 }.
backgroundRepeat Set to 'repeat' to use the backgroundImage as a background pattern, set to undefined to stretch image to fit shape.
aspectRatio The aspect ratio the rectangle should adhere to.
strokeColor Array of 3 or 4 color values ranging between 0 and 1.
strokeWidth The width of the stroke to use for the rectangle.
feather The feather radius around the edges of the shape, useful to blend shapes with their background.
status Set to 'error' to show error state, set to 'loading' to show loading state.

backgroundSize

When set to undefined the shape will be forced to match the backgroundImage aspect ratio.

Set the value to 'contain' to center the backgroundImage in the rectangle.

Setting the value to 'cover' will cover the rectangle with the backgroundImage.

Setting the value to 'force' will skew the backgroundImage to fit the shape.

Settings the value to { width: 128, height: 128 } will resize the background to that size.

backgroundPosition

Sets the position of the background, defaults to undefined.

Settings the value to { x: 128, y: 128 } will offset the background to that position.

backgroundRepeat

By default a background will stretch to fill the shape. We can set the backgroundRepeat property to 'repeat' to have the background repeat to fill the shape.

We can adjust the size of the backgroundImage using the backgroundSize property.

Ellipse

const shape = {
    x: 32,
    y: 32,
    rx: 64,
    ry: 64,
    backgroundColor: [1, 0, 0],
};
Property Description
rx The radius of the ellipse on the x axis.
ry The radius of the ellipse on the y axis.
strokeColor Array of 3 or 4 color values ranging between 0 and 1.
strokeWidth The width of the stroke to use for the ellipse.

Line

const shape = {
    x1: 0,
    y1: 0,
    x2: 128,
    y2: 128,
    strokeColor: [1, 0, 0],
};
Property Description
x1 The start position of the line on the x axis.
y1 The start position of the line on the y axis.
x2 The end position of the line on the x axis.
y2 The end position of the line on the y axis.
strokeColor Array of 3 or 4 color values ranging between 0 and 1.
strokeWidth The width of the stroke to use for the line.
lineStart The decorative shape to use for the start of the line.
lineEnd The decorative shape to use for the end of the line.

If we're not using one of the default editor factory methods we need to set set the shapePreprocessor property to render lineStart and lineEnd styles.

For the possible lineStart and lineEnd values see lineStart and lineEnd styles

Text

const shape = {
    x: 0,
    y: 0,
    width: 128,
    height: 128,
    text: 'Hello World',
    fontWeight: 'bold',
    color: [1, 0, 0],
};
Property Description
format Defaults to text, set to 'html' to enable rich text controls.
fontSize The font size to use for the text.
fontFamily The font family to use for the text.
fontWeight The font weight to use for the text.
fontStyle The font style to use for the text.
fontVariant The font variant to use for the text.
lineHeight The line height of the text.
color Array of 3 or 4 color values ranging between 0 and 1.
backgroundColor Background color of the text box. Set to a default value like [0, 0, 0, 0] to enable background color control.
textAlign Alignment of the text, "left", "center", or "right", defaults to "left".
textOutline Outine around text, defaults to undefined. Set to a default value like ['0%', [1, 1, 1]] to enable text outline control.
textShadow Shadow behind text, defaults to undefined. Set to a default value like ['0%', '0%', '0%', [0, 0, 0, .5]] to enable text shadow control.
letterSpacing Spacing between letters, defaults to undefined.
text The text to show.

The following list of styles isn't enabled on the default text shape.

  • backgroundColor
  • textOutline
  • textShadow

We can enable these styles and add additional features to the default text field like by updating the markupEditorToolStyles property.

<!DOCTYPE html>

<head>
    <link rel="stylesheet" href="./pintura.css" />
</head>

<style>
    .pintura-editor {
        height: 600px;
    }
</style>

<div id="editor"></div>

<script type="module">
    import {
        appendDefaultEditor,
        createMarkupEditorToolStyle,
        createMarkupEditorToolStyles,
    } from './pintura.js';

    const editor = appendDefaultEditor('#editor', {
        src: 'image.jpeg',
        markupEditorToolStyles: createMarkupEditorToolStyles({
            text: createMarkupEditorToolStyle('text', {
                // Set default text shape background to transparent
                backgroundColor: [0, 0, 0, 0],

                // Set default text shape outline to 0 width and white
                textOutline: ['0%', [1, 1, 1]],

                // Set default text shadow, shadow will not be drawn if x, y, and blur are 0.
                textShadow: ['0%', '0%', '0%', [0, 0, 0, 0.5]],

                // Allow newlines in inline text
                disableNewline: false,

                // Align to left by default, this triggers always showing the text align control
                textAlign: 'left',

                // Enable text formatting
                format: 'html',
            }),
        }),
    });
</script>
const myMarkupEditorToolStyles = createMarkupEditorToolStyles({
    text: createMarkupEditorToolStyle('text', {
        // Set default text shape background to transparent
        backgroundColor: [0, 0, 0, 0],

        // Set default text shape outline to 0 width and white
        textOutline: ['0%', [1, 1, 1]],

        // Set default text shadow to transparent
        textShadow: ['0%', '0%', '0%', [0, 0, 0, 0]],

        // Allow newlines in inline text
        disableNewline: false,

        // Align to left by default, this triggers always showing the text align control
        textAlign: 'left',

        // Enable text formatting
        format: 'html',
    }),
});

Path

const shape = {
    points: [
        {
            x: 0,
            y: 0,
        },
        {
            x: 128,
            y: 0,
        },
        {
            x: 128,
            y: 128,
        },
        {
            x: 0,
            y: 128,
        },
    ],
    strokeColor: [1, 0, 0],
    pathClose: false,
};
Property Description
points Array of { x, y } coordinates.
strokeColor Array of 3 or 4 color values ranging between 0 and 1.
strokeWidth The width of the stroke to use for the path.
pathClose This will draw a line between the first and last point in the points array.
cursorStyle The style of the cursor, defaults to undefined, can be set to 'ellipse'.
cursorSize When the cursor style is 'ellipse' this determines the size. Can be set to a fixed value or to a the name of a property, for example 'strokeWidth'.
bitmap When set to true the path will be rendered with a bitmap shape resulting in a better look at some performance cost.
strokeJoin When bitmap is set to true we can set strokeJoin to either 'miter', 'bevel', or 'round'
strokeCap When bitmap is set to true we can set strokeCap to either 'miter', 'bevel', or 'round'
strokeDash When bitmap is set to true we can set this to an array of numbers or percentages to draw dashed lines, for example [10,10].
selectedPoint The index of the currently selected point.

State

Property Description
isSelected The shape is currently selected.
isDraft The shape is in draft state, this is true when the shape is being created.
isComplete Is true when all shape resources have loaded.
isError Something went wrong while loading shape resources.
isLoading Currently loading shape resources.
isEditing Currently editing this shape.
isFormatted The shape has been formatted by Pintura Image Editor.

Right management

Property Description
disableStyle Prevents changing the styles of the shape or disables a selection of styles.
disableErase Prevents erasing the shape with eraser.
disableSelect Prevents selecting the shape.
disableRemove Prevents removing the shape.
disableDuplicate Prevents duplicating the shape.
disableReorder Prevents changing the position of the shape in the shapes list.
disableFlip Prevents flipping the shape.
disableInput Prevents updating the text of the shape or filters the text input.
disableTextLayout Prevents switching text layout.
disableNewline Prevents inserting new line.
disableManipulate Prevents manipulating the shape in any way.
disableMove Prevents moving the shape.
disableResize Prevents resizing the shape.
disableRotate Prevents rotating the shape.
disableAddPoints Prevents adding and removing points from a path.

disableTextLayout

If set to true all text layout options are locked. Set to an array to block specific layout options. Valid values are 'fixed-size', 'auto-width', and 'auto-height'

This example prevents the text layout from being set to auto height.

const shape = {
    x: 0,
    y: 0,
    width: 128,
    height: 128,
    text: 'Hello World',
    disableTextLayout: ['auto-height'],
};

disableStyle

If set to true all styles are locked.

Alternatively it can be set to an array of disabled style names.

const shape = {
    x: 0,
    y: 0,
    width: 128,
    height: 128,
    backgroundColor: [1, 0, 0],
    strokeColor: [1, 1, 1],
    strokeWidth: 20,
    disableStyle: ['strokeWidth', 'strokeColor'],
};

disableInput

Only applies to Text shapes. If set to true will prevent the user from updating the text value.

Can alternatively be set to a function in which case the function receives a string and can return a formatted string. In the example below we disallow any numeric input.

const shape = {
    x: 0,
    y: 0,
    width: 128,
    height: 128,
    text: 'Hello World',
    disableInput: (text) => text.replace(/[0-9]/g, ''),
};

Colors

Shape colors are defined with an array of 3 or 4 color values.

The array format is [R, G, B] or [R ,G ,B ,A] each value ranges between 0 and 1.

Setting the color red using the RGB array.

const shape = {
    // red
    backgroundColor: [1, 0, 0],
    // ...other styles
};

Setting an RGBA value.

// red with 50% opacity
const shape = {
    backgroundColor: [1, 0, 0, 0.5],
    // ...other styles
};

We can automatically create these arrays from more familiar CSS color values by using the colorStringToColorArray method exported by the editor.