Integrating tui-image-editor for Image Editing in Vue.js Applications
Install the tui-image-editor package using npm:
npm install tui-image-editor
Create a Vue copmonent to embed the image editor:
<template>
<el-dialog append-to-body :visible.sync="editorVisible" :title="imageTitle" width="75%">
<div class="editor-wrapper">
<div id="image-editor-container"></div>
</div>
<div class="action-buttons">
<el-button size="mini" @click="exportImage">Download</el-button>
<el-button size="mini" @click="saveToServer">Save to Server</el-button>
</div>
</el-dialog>
</template>
<script>
import { convertBase64ToBlob, uploadBase64Image } from '@/utils/image-utils'
import { downloadBlob } from '@/utils'
import 'tui-image-editor/dist/tui-image-editor.css'
import 'tui-color-picker/dist/tui-color-picker.css'
import ImageEditor from 'tui-image-editor'
const chineseLocale = {
ZoomIn: 'Zoom In',
ZoomOut: 'Zoom Out',
Hand: 'Hand Tool',
History: 'History',
Resize: 'Adjust Dimensions',
Crop: 'Crop',
DeleteAll: 'Clear All',
Delete: 'Delete',
Undo: 'Undo',
Redo: 'Redo',
Reset: 'Reset',
Flip: 'Mirror',
Rotate: 'Rotate',
Draw: 'Draw',
Shape: 'Shape Annotation',
Icon: 'Icon Annotation',
Text: 'Text Annotation',
Mask: 'Mask',
Filter: 'Filter',
Bold: 'Bold',
Italic: 'Italic',
Underline: 'Underline',
Left: 'Align Left',
Center: 'Center',
Right: 'Align Right',
Color: 'Color',
'Text size': 'Font Size',
Custom: 'Custom',
Square: 'Square',
Apply: 'Apply',
Cancel: 'Cancel',
'Flip X': 'Flip X',
'Flip Y': 'Flip Y',
Range: 'Range',
Stroke: 'Stroke',
Fill: 'Fill',
Circle: 'Circle',
Triangle: 'Triangle',
Rectangle: 'Rectangle',
Free: 'Freehand',
Straight: 'Straight Line',
Arrow: 'Arrow',
'Arrow-2': 'Arrow 2',
'Arrow-3': 'Arrow 3',
'Star-1': 'Star 1',
'Star-2': 'Star 2',
Polygon: 'Polygon',
Location: 'Location',
Heart: 'Heart',
Bubble: 'Bubble',
'Custom icon': 'Custom Icon',
'Load Mask Image': 'Load Mask Image',
Grayscale: 'Grayscale',
Blur: 'Blur',
Sharpen: 'Sharpen',
Emboss: 'Emboss',
'Remove White': 'Remove White',
Distance: 'Distance',
Brightness: 'Brightness',
Noise: 'Noise',
'Color Filter': 'Color Filter',
Sepia: 'Sepia',
Sepia2: 'Sepia 2',
Invert: 'Invert',
Pixelate: 'Pixelate',
Threshold: 'Threshold',
Tint: 'Tint',
Multiply: 'Multiply',
Blend: 'Blend',
Width: 'Width',
Height: 'Height',
'Lock Aspect Ratio': 'Lock Aspect Ratio'
}
const customThemeConfig = {
"common.bi.image": "",
"common.bisize.width": "0px",
"common.bisize.height": "0px",
"common.backgroundImage": "none",
"common.backgroundColor": "#f3f4f6",
"common.border": "1px solid #444",
"header.backgroundImage": "none",
"header.backgroundColor": "#f3f4f6",
"header.border": "0px",
"loadButton.backgroundColor": "#fff",
"loadButton.border": "1px solid #ddd",
"loadButton.color": "#222",
"loadButton.fontFamily": "NotoSans, sans-serif",
"loadButton.fontSize": "12px",
"loadButton.display": "none",
"downloadButton.backgroundColor": "#fdba3b",
"downloadButton.border": "1px solid #fdba3b",
"downloadButton.color": "#fff",
"downloadButton.fontFamily": "NotoSans, sans-serif",
"downloadButton.fontSize": "12px",
"downloadButton.display": "none",
"menu.normalIcon.color": "#8a8a8a",
"menu.activeIcon.color": "#555555",
"menu.disabledIcon.color": "#434343",
"menu.hoverIcon.color": "#e9e9e9",
"submenu.normalIcon.color": "#8a8a8a",
"submenu.activeIcon.color": "#e9e9e9",
"menu.iconSize.width": "24px",
"menu.iconSize.height": "24px",
"submenu.iconSize.width": "32px",
"submenu.iconSize.height": "32px",
"submenu.backgroundColor": "#1e1e1e",
"submenu.partition.color": "#858585",
"submenu.normalLabel.color": "#858585",
"submenu.normalLabel.fontWeight": "lighter",
"submenu.activeLabel.color": "#fff",
"submenu.activeLabel.fontWeight": "lighter",
"checkbox.border": "1px solid #ccc",
"checkbox.backgroundColor": "#fff",
"range.pointer.color": "#fff",
"range.bar.color": "#666",
"range.subbar.color": "#d1d1d1",
"range.disabledPointer.color": "#414141",
"range.disabledBar.color": "#282828",
"range.disabledSubbar.color": "#414141",
"range.value.color": "#fff",
"range.value.fontWeight": "lighter",
"range.value.fontSize": "11px",
"range.value.border": "1px solid #353535",
"range.value.backgroundColor": "#151515",
"range.title.color": "#fff",
"range.title.fontWeight": "lighter",
"colorpicker.button.border": "1px solid #1e1e1e",
"colorpicker.title.color": "#fff"
}
export default {
props: {
storageBucket: {
type: String,
default: 'bridge'
}
},
data() {
return {
editorInstance: null,
imageSource: '',
imagePath: '',
imageTitle: 'Image',
editorVisible: false
}
},
methods: {
launchEditor() {
this.editorVisible = true
this.$nextTick(() => {
this.editorInstance = new ImageEditor(document.querySelector('#image-editor-container'), {
includeUI: {
loadImage: {
path: this.imagePath,
name: 'image'
},
menu: ['resize', 'crop', 'rotate', 'draw', 'shape', 'icon', 'text', 'filter'],
initMenu: 'draw',
menuBarPosition: 'bottom',
locale: chineseLocale,
theme: customThemeConfig
},
cssMaxWidth: 1000,
cssMaxHeight: 600
})
document.getElementsByClassName('tui-image-editor-main')[0].style.top = '0px'
})
},
exportImage() {
const base64Data = this.editorInstance.toDataURL()
const blobFile = convertBase64ToBlob(base64Data, this.imageTitle)
downloadBlob([blobFile], this.imageTitle)
},
async saveToServer() {
const base64Data = this.editorInstance.toDataURL()
const fileIdentifier = await uploadBase64Image(base64Data, this.storageBucket)
this.$emit('imageUploaded', fileIdentifier)
this.editorVisible = false
}
}
}
</script>
<style lang="css" scoped>
.editor-wrapper {
height: 90vh;
text-align: center;
}
.action-buttons {
text-align: right;
padding-top: 20px;
padding-right: 20px;
}
</style>
Utility functions for hadnling image data:
export const convertBase64ToBlob = (base64Data, fileName) => {
const parts = base64Data.split(',')
const mimeType = parts[0].match(/:(.*?);/)[1]
const binaryData = atob(parts[1])
const length = binaryData.length
const uintArray = new Uint8Array(length)
for (let i = 0; i < length; i++) {
uintArray[i] = binaryData.charCodeAt(i)
}
const blob = new Blob([uintArray], { type: mimeType })
blob.lastModifiedDate = new Date()
blob.name = fileName
return blob
}
export const uploadBase64Image = (base64String, bucketName) => {
const payload = {
strBase64Image: base64String,
bucketName: bucketName
}
return request({
url: '/filemgr/uploadFileBase64',
method: 'post',
data: payload
}).then(({ data }) => {
if (data.code === 0) {
return data.data
}
})
}
export function downloadBlob(dataArray, fileName) {
const blob = new Blob(dataArray)
const link = document.createElement('a')
const url = window.URL.createObjectURL(blob)
link.href = url
link.download = decodeURIComponent(fileName)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
}