v8.90.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 let's 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. This shape is deprecated and is replaced with Path.

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

A rectangle or square shape. Stickers drawn with rectangle shapes that have a backgroundImage applied.

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, set to 'complete' to not automatically show loading state while loading backgroundImage.

backgroundImage

The backgroundImage to apply to the rectangle.

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

Used to draw circles and ellipses.

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

The Line shape is used to draw lines and arrows. An arrow is a line with a lineStart and/or lineEnd style.

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.
lineEndScalar The scale factor to use when drawing the decorative shape at the ends of the line, defaults to 5.
lineStartScalar The scale factor to use when drawing the decorative shape at the start of the line, defaults to 5.

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

Draw text and emoji with the Text shape.

If the text shape doesn't have width, it'll automatically expand. If it does have a width property it'll wrap. If it has a width and height property it'll render a resizable text box.

Set the disableTextScale property to false to allow scaling/resizing auto-width text shapes.

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

Draw paths using the Path shape. Paths can be closed with the pathClose property.

If the Path bitmap property is set to true the path is drawn as a canvas element and can receive additional styles like strokeJoin, strokeCap, and strokeDash.

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.

points

The array of points that describe the polygon shape to draw.

const shape = {
    points: [
        { x: 0, y: 0 },
        { x: 128, y: 128 },
        { x: 256, y: 128 },
    ];
}

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].

State

Shape state properties give information on the current state of the shape.

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

The shape right management properties can be used for templating. Among other thigns we can control which styles the user can set, if shapes can be removed, selected, rotated, or erased.

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.
disableTextScale Prevents scaling auto width text.
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.
disableShowPoints Disables drawing the corner points of a path.
disableAcceptSnap Removes shape from snap targets list, other shapes no longer snap to this shape.

disableSelect

When set to true prevents the user from selecting the shape.

disableRemove

Set to true to prevent the user from removing this shape.

disableDuplicate

When set to true this prevents duplicating the shape.

disableErase

When set to true this prevents erasing the shape with the eraser tool.

disableManipulate

Prevents manipulating the shape in any way.

disableMove

Prevents moving the shape.

disableResize

Prevents resizing the shape.

disableRotate

Prevents rotating the shape.

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'],
};

disableTextScale

Prevents scaling auto width text boxes. Defaults to true, set to false on default text shape style to allow scaling text shapes.

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, ''),
};

disableAcceptSnap

Removes shape from snap targets list, other shapes no longer snap to this shape.

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.