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>
import '@pqina/pintura/pintura.css';
import './App.css';
import { PinturaEditor } from '@pqina/react-pintura';
import { getEditorDefaults } from '@pqina/pintura';
const editorDefaults = getEditorDefaults();
function App() {
return (
<div className="App">
<PinturaEditor
{...editorDefaults}
src={'image.jpeg'}
imageAnnotation={[
// a red square
{
x: 0,
y: 0,
width: 128,
height: 128,
backgroundColor: [1, 0, 0],
},
]}
/>
</div>
);
}
export default App;
.pintura-editor {
height: 600px;
}
<template>
<div>
<PinturaEditor
v-bind="editorDefaults"
src="image.jpeg"
:imageAnnotation="imageAnnotation"
/>
</div>
</template>
<script>
import { PinturaEditor } from '@pqina/vue-pintura';
import { getEditorDefaults } from '@pqina/pintura';
export default {
name: 'App',
components: {
PinturaEditor,
},
data() {
return {
editorDefaults: getEditorDefaults(),
imageAnnotation: [
// a red square
{
x: 0,
y: 0,
width: 128,
height: 128,
backgroundColor: [1, 0, 0],
},
],
};
},
methods: {},
};
</script>
<style>
@import '@pqina/pintura/pintura.css';
.pintura-editor {
height: 600px;
}
</style>
<script>
import { PinturaEditor } from '@pqina/svelte-pintura';
import { getEditorDefaults } from '@pqina/pintura';
import '@pqina/pintura/pintura.css';
let editorDefaults = getEditorDefaults();
</script>
<div>
<PinturaEditor
{...editorDefaults}
src={'image.jpeg'}
imageAnnotation={[
// a red square
{
x: 0,
y: 0,
width: 128,
height: 128,
backgroundColor: [1, 0, 0],
},
]}
/>
</div>
<style>
div :global(.pintura-editor) {
height: 600px;
}
</style>
import { Component } from '@angular/core';
import { getEditorDefaults } from '@pqina/pintura';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
editorDefaults: any = getEditorDefaults();
imageAnnotation: any = [
// a red square
{
x: 0,
y: 0,
width: 128,
height: 128,
backgroundColor: [1, 0, 0],
},
];
}
<pintura-editor
[options]="editorDefaults"
src="image.jpeg"
[imageAnnotation]="imageAnnotation"
></pintura-editor>
::ng-deep .pintura-editor {
height: 600px;
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { AngularPinturaModule } from '@pqina/angular-pintura';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AngularPinturaModule],
exports: [AppComponent],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
<!DOCTYPE html>
<head>
<link rel="stylesheet" href="./pintura/pintura.css" />
</head>
<script src="./jquery.js"></script>
<script src="./jquery-pintura/useEditorWithJQuery-iife.js"></script>
<script src="./pintura/pintura-iife.js"></script>
<style>
.pintura-editor {
height: 600px;
}
</style>
<div id="editor"></div>
<script>
useEditorWithJQuery(jQuery, pintura);
$(function () {
var editor = $('#editor').pinturaDefault({
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>
import '@pqina/pintura/pintura.css';
import './App.css';
import { useRef } from 'react';
import { PinturaEditor } from '@pqina/react-pintura';
import { getEditorDefaults } from '@pqina/pintura';
const editorDefaults = getEditorDefaults();
function App() {
const editorRef = useRef(null);
const handleButtonClick = () => {
// 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 = [
...editorRef.current.editor.imageAnnotation,
{
id: 'circle',
x: 64,
y: 64,
rx: 128,
ry: 128,
backgroundColor: [0, 1, 0],
},
];
editorRef.current.editor.imageAnnotation = updatedAnnotationList;
};
return (
<div className="App">
<button type="button" onClick={handleButtonClick}>
Add green circle
</button>
<PinturaEditor
ref={editorRef}
{...editorDefaults}
src={'image.jpeg'}
imageAnnotation={[
// a red square
{
x: 0,
y: 0,
width: 128,
height: 128,
backgroundColor: [1, 0, 0],
},
]}
/>
</div>
);
}
export default App;
.pintura-editor {
height: 600px;
}
<template>
<div>
<button type="button" v-on:click="handleButtonClick($event)">
Add green circle
</button>
<PinturaEditor
ref="editor"
v-bind="editorDefaults"
src="image.jpeg"
:imageAnnotation="imageAnnotation"
/>
</div>
</template>
<script>
import { PinturaEditor } from '@pqina/vue-pintura';
import { getEditorDefaults } from '@pqina/pintura';
export default {
name: 'App',
components: {
PinturaEditor,
},
data() {
return {
editorDefaults: getEditorDefaults(),
imageAnnotation: [
// a red square
{
x: 0,
y: 0,
width: 128,
height: 128,
backgroundColor: [1, 0, 0],
},
],
};
},
methods: {
handleButtonClick: function () {
// 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 = [
...this.$refs.editor.editor.imageAnnotation,
{
id: 'circle',
x: 64,
y: 64,
rx: 128,
ry: 128,
backgroundColor: [0, 1, 0],
},
];
this.$refs.editor.editor.imageAnnotation = updatedAnnotationList;
},
},
};
</script>
<style>
@import '@pqina/pintura/pintura.css';
.pintura-editor {
height: 600px;
}
</style>
<script>
import { PinturaEditor } from '@pqina/svelte-pintura';
import { getEditorDefaults } from '@pqina/pintura';
import '@pqina/pintura/pintura.css';
let editorDefaults = getEditorDefaults();
let editor;
const handleButtonClick = () => {
// 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>
<div>
<button type="button" on:click={handleButtonClick}>Add green circle</button>
<PinturaEditor
bind:this={editor}
{...editorDefaults}
src={'image.jpeg'}
imageAnnotation={[
// a red square
{
x: 0,
y: 0,
width: 128,
height: 128,
backgroundColor: [1, 0, 0],
},
]}
/>
</div>
<style>
div :global(.pintura-editor) {
height: 600px;
}
</style>
import { Component, ViewChild } from '@angular/core';
import { getEditorDefaults } from '@pqina/pintura';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
@ViewChild('buttonAddShape') buttonAddShape?: any;
@ViewChild('editor') editor?: any;
editorDefaults: any = getEditorDefaults();
imageAnnotation: any = [
// a red square
{
x: 0,
y: 0,
width: 128,
height: 128,
backgroundColor: [1, 0, 0],
},
];
handleButtonClick(): void {
// 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 = [
...this.editor.editor.imageAnnotation,
{
id: 'circle',
x: 64,
y: 64,
rx: 128,
ry: 128,
backgroundColor: [0, 1, 0],
},
];
this.editor.editor.imageAnnotation = updatedAnnotationList;
}
}
<button #buttonAddShape type="button" (click)="handleButtonClick()">
Add green circle
</button>
<pintura-editor
#editor
[options]="editorDefaults"
src="image.jpeg"
[imageAnnotation]="imageAnnotation"
></pintura-editor>
::ng-deep .pintura-editor {
height: 600px;
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { AngularPinturaModule } from '@pqina/angular-pintura';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AngularPinturaModule],
exports: [AppComponent],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
<!DOCTYPE html>
<head>
<link rel="stylesheet" href="./pintura/pintura.css" />
</head>
<script src="./jquery.js"></script>
<script src="./jquery-pintura/useEditorWithJQuery-iife.js"></script>
<script src="./pintura/pintura-iife.js"></script>
<style>
.pintura-editor {
height: 600px;
}
</style>
<button type="button" id="buttonAddShape">Add green circle</button>
<div id="editor"></div>
<script>
useEditorWithJQuery(jQuery, pintura);
$(function () {
var buttonAddShape = $('#buttonAddShape');
var editor = $('#editor').pinturaDefault({
src: 'image.jpeg',
imageAnnotation: [
// a red square
{
x: 0,
y: 0,
width: 128,
height: 128,
backgroundColor: [1, 0, 0],
},
],
});
buttonAddShape.on('click', function () {
// 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).pintura('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>
import '@pqina/pintura/pintura.css';
import './App.css';
import { useRef } from 'react';
import { PinturaEditor } from '@pqina/react-pintura';
import { getEditorDefaults } from '@pqina/pintura';
const editorDefaults = getEditorDefaults();
function App() {
const editorRef = useRef(null);
const handleButtonClick = () => {
const updatedAnnotationList =
editorRef.current.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;
});
editorRef.current.editor.imageAnnotation = updatedAnnotationList;
};
return (
<div className="App">
<button type="button" onClick={handleButtonClick}>
Change color
</button>
<PinturaEditor
ref={editorRef}
{...editorDefaults}
src={'image.jpeg'}
imageAnnotation={[
// a red square
{
x: 0,
y: 0,
width: 128,
height: 128,
backgroundColor: [1, 0, 0],
},
]}
/>
</div>
);
}
export default App;
.pintura-editor {
height: 600px;
}
<template>
<div>
<button type="button" v-on:click="handleButtonClick($event)">
Change color
</button>
<PinturaEditor
ref="editor"
v-bind="editorDefaults"
src="image.jpeg"
:imageAnnotation="imageAnnotation"
/>
</div>
</template>
<script>
import { PinturaEditor } from '@pqina/vue-pintura';
import { getEditorDefaults } from '@pqina/pintura';
export default {
name: 'App',
components: {
PinturaEditor,
},
data() {
return {
editorDefaults: getEditorDefaults(),
imageAnnotation: [
// a red square
{
x: 0,
y: 0,
width: 128,
height: 128,
backgroundColor: [1, 0, 0],
},
],
};
},
methods: {
handleButtonClick: function () {
const updatedAnnotationList =
this.$refs.editor.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;
});
this.$refs.editor.editor.imageAnnotation = updatedAnnotationList;
},
},
};
</script>
<style>
@import '@pqina/pintura/pintura.css';
.pintura-editor {
height: 600px;
}
</style>
<script>
import { PinturaEditor } from '@pqina/svelte-pintura';
import { getEditorDefaults } from '@pqina/pintura';
import '@pqina/pintura/pintura.css';
let editorDefaults = getEditorDefaults();
let editor;
const handleButtonClick = () => {
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>
<div>
<button type="button" on:click={handleButtonClick}>Change color</button>
<PinturaEditor
bind:this={editor}
{...editorDefaults}
src={'image.jpeg'}
imageAnnotation={[
// a red square
{
x: 0,
y: 0,
width: 128,
height: 128,
backgroundColor: [1, 0, 0],
},
]}
/>
</div>
<style>
div :global(.pintura-editor) {
height: 600px;
}
</style>
import { Component, ViewChild } from '@angular/core';
import { getEditorDefaults } from '@pqina/pintura';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
@ViewChild('buttonChangeColor') buttonChangeColor?: any;
@ViewChild('editor') editor?: any;
editorDefaults: any = getEditorDefaults();
imageAnnotation: any = [
// a red square
{
x: 0,
y: 0,
width: 128,
height: 128,
backgroundColor: [1, 0, 0],
},
];
handleButtonClick(): void {
const updatedAnnotationList = this.editor.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;
}
);
this.editor.editor.imageAnnotation = updatedAnnotationList;
}
}
<button #buttonChangeColor type="button" (click)="handleButtonClick()">
Change color
</button>
<pintura-editor
#editor
[options]="editorDefaults"
src="image.jpeg"
[imageAnnotation]="imageAnnotation"
></pintura-editor>
::ng-deep .pintura-editor {
height: 600px;
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { AngularPinturaModule } from '@pqina/angular-pintura';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AngularPinturaModule],
exports: [AppComponent],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
<!DOCTYPE html>
<head>
<link rel="stylesheet" href="./pintura/pintura.css" />
</head>
<script src="./jquery.js"></script>
<script src="./jquery-pintura/useEditorWithJQuery-iife.js"></script>
<script src="./pintura/pintura-iife.js"></script>
<style>
.pintura-editor {
height: 600px;
}
</style>
<button type="button" id="buttonChangeColor">Change color</button>
<div id="editor"></div>
<script>
useEditorWithJQuery(jQuery, pintura);
$(function () {
var buttonChangeColor = $('#buttonChangeColor');
var editor = $('#editor').pinturaDefault({
src: 'image.jpeg',
imageAnnotation: [
// a red square
{
x: 0,
y: 0,
width: 128,
height: 128,
backgroundColor: [1, 0, 0],
},
],
});
buttonChangeColor.on('click', function () {
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).pintura('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>
import '@pqina/pintura/pintura.css';
import './App.css';
import { useRef } from 'react';
import { PinturaEditor } from '@pqina/react-pintura';
import { getEditorDefaults } from '@pqina/pintura';
const editorDefaults = getEditorDefaults();
function App() {
const editorRef = useRef(null);
const handleButtonClick = () => {
// this filters out the red square from the shapes list
const updatedAnnotationList =
editorRef.current.editor.imageAnnotation.filter(
(shape) => shape.id !== 'square'
);
editorRef.current.editor.imageAnnotation = updatedAnnotationList;
};
return (
<div className="App">
<button type="button" onClick={handleButtonClick}>
Remove red square
</button>
<PinturaEditor
ref={editorRef}
{...editorDefaults}
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],
},
]}
/>
</div>
);
}
export default App;
.pintura-editor {
height: 600px;
}
<template>
<div>
<button type="button" v-on:click="handleButtonClick($event)">
Remove red square
</button>
<PinturaEditor
ref="editor"
v-bind="editorDefaults"
src="image.jpeg"
:imageAnnotation="imageAnnotation"
/>
</div>
</template>
<script>
import { PinturaEditor } from '@pqina/vue-pintura';
import { getEditorDefaults } from '@pqina/pintura';
export default {
name: 'App',
components: {
PinturaEditor,
},
data() {
return {
editorDefaults: getEditorDefaults(),
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],
},
],
};
},
methods: {
handleButtonClick: function () {
// this filters out the red square from the shapes list
const updatedAnnotationList =
this.$refs.editor.editor.imageAnnotation.filter(
(shape) => shape.id !== 'square'
);
this.$refs.editor.editor.imageAnnotation = updatedAnnotationList;
},
},
};
</script>
<style>
@import '@pqina/pintura/pintura.css';
.pintura-editor {
height: 600px;
}
</style>
<script>
import { PinturaEditor } from '@pqina/svelte-pintura';
import { getEditorDefaults } from '@pqina/pintura';
import '@pqina/pintura/pintura.css';
let editorDefaults = getEditorDefaults();
let editor;
const handleButtonClick = () => {
// this filters out the red square from the shapes list
const updatedAnnotationList = editor.imageAnnotation.filter(
(shape) => shape.id !== 'square'
);
editor.imageAnnotation = updatedAnnotationList;
};
</script>
<div>
<button type="button" on:click={handleButtonClick}>Remove red square</button
>
<PinturaEditor
bind:this={editor}
{...editorDefaults}
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],
},
]}
/>
</div>
<style>
div :global(.pintura-editor) {
height: 600px;
}
</style>
import { Component, ViewChild } from '@angular/core';
import { getEditorDefaults } from '@pqina/pintura';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
@ViewChild('buttonRemoveShape') buttonRemoveShape?: any;
@ViewChild('editor') editor?: any;
editorDefaults: any = getEditorDefaults();
imageAnnotation: any = [
// 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],
},
];
handleButtonClick(): void {
// this filters out the red square from the shapes list
const updatedAnnotationList = this.editor.editor.imageAnnotation.filter(
(shape) => shape.id !== 'square'
);
this.editor.editor.imageAnnotation = updatedAnnotationList;
}
}
<button #buttonRemoveShape type="button" (click)="handleButtonClick()">
Remove red square
</button>
<pintura-editor
#editor
[options]="editorDefaults"
src="image.jpeg"
[imageAnnotation]="imageAnnotation"
></pintura-editor>
::ng-deep .pintura-editor {
height: 600px;
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { AngularPinturaModule } from '@pqina/angular-pintura';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AngularPinturaModule],
exports: [AppComponent],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
<!DOCTYPE html>
<head>
<link rel="stylesheet" href="./pintura/pintura.css" />
</head>
<script src="./jquery.js"></script>
<script src="./jquery-pintura/useEditorWithJQuery-iife.js"></script>
<script src="./pintura/pintura-iife.js"></script>
<style>
.pintura-editor {
height: 600px;
}
</style>
<button type="button" id="buttonRemoveShape">Remove red square</button>
<div id="editor"></div>
<script>
useEditorWithJQuery(jQuery, pintura);
$(function () {
var buttonRemoveShape = $('#buttonRemoveShape');
var editor = $('#editor').pinturaDefault({
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.on('click', function () {
// this filters out the red square from the shapes list
const updatedAnnotationList = editor.imageAnnotation.filter(
(shape) => shape.id !== 'square'
);
$(editor).pintura('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>
import '@pqina/pintura/pintura.css';
import './App.css';
import { PinturaEditor } from '@pqina/react-pintura';
import {
getEditorDefaults,
createMarkupEditorToolStyle,
createMarkupEditorToolStyles,
} from '@pqina/pintura';
const editorDefaults = getEditorDefaults();
function App() {
return (
<div className="App">
<PinturaEditor
{...editorDefaults}
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',
}),
})}
/>
</div>
);
}
export default App;
.pintura-editor {
height: 600px;
}
<template>
<div>
<PinturaEditor
v-bind="editorDefaults"
src="image.jpeg"
:markupEditorToolStyles="markupEditorToolStyles"
/>
</div>
</template>
<script>
import { PinturaEditor } from '@pqina/vue-pintura';
import {
getEditorDefaults,
createMarkupEditorToolStyle,
createMarkupEditorToolStyles,
} from '@pqina/pintura';
export default {
name: 'App',
components: {
PinturaEditor,
},
data() {
return {
editorDefaults: getEditorDefaults(),
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',
}),
}),
};
},
methods: {},
};
</script>
<style>
@import '@pqina/pintura/pintura.css';
.pintura-editor {
height: 600px;
}
</style>
<script>
import { PinturaEditor } from '@pqina/svelte-pintura';
import {
getEditorDefaults,
createMarkupEditorToolStyle,
createMarkupEditorToolStyles,
} from '@pqina/pintura';
import '@pqina/pintura/pintura.css';
let editorDefaults = getEditorDefaults();
</script>
<div>
<PinturaEditor
{...editorDefaults}
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',
}),
})}
/>
</div>
<style>
div :global(.pintura-editor) {
height: 600px;
}
</style>
import { Component } from '@angular/core';
import {
getEditorDefaults,
createMarkupEditorToolStyle,
createMarkupEditorToolStyles,
} from '@pqina/pintura';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
editorDefaults: any = getEditorDefaults();
markupEditorToolStyles: any = 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',
}),
});
}
<pintura-editor
[options]="editorDefaults"
src="image.jpeg"
[markupEditorToolStyles]="markupEditorToolStyles"
></pintura-editor>
::ng-deep .pintura-editor {
height: 600px;
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { AngularPinturaModule } from '@pqina/angular-pintura';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AngularPinturaModule],
exports: [AppComponent],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
<!DOCTYPE html>
<head>
<link rel="stylesheet" href="./pintura/pintura.css" />
</head>
<script src="./jquery.js"></script>
<script src="./jquery-pintura/useEditorWithJQuery-iife.js"></script>
<script src="./pintura/pintura-iife.js"></script>
<style>
.pintura-editor {
height: 600px;
}
</style>
<div id="editor"></div>
<script>
useEditorWithJQuery(jQuery, pintura);
$(function () {
var { createMarkupEditorToolStyle, createMarkupEditorToolStyles } =
$.fn.pintura;
var editor = $('#editor').pinturaDefault({
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.