# 稿定抠图编辑器

## 使用方法

```bash
npm install matting-editor
```

```html
<template>
    <matting-editor
        ref="mattingEditor"
        :options="mattingEditorOptions"
        @update="onMattingUpdate"
        @error="onMattingError"
    >
        <div slot="logo">logo 注入</div>
        <div class="user-tips">支持外部组件注入（需自定义 CSS）</div>
    </matting-editor>
</template>

<script>
import Vue from 'vue';
import MattingEditor from 'matting-editor';

Vue.use(MattingEditor);
</script>
```

## 开发模式

```bash
npm run dev
# visit page on localhost:8080
```

## 关于懒加载

使用懒加载可以在我们需要抠图 SDK 的时候，再进行代码加载。

```javascript
// install-matting-editor.js
import Vue from 'vue';

let promise;

export default () => {
    if(!promise) {
        promise = new Promise(resolve => {
            // 引入 matting-editor 组件
            require(['matting-editor'], resolve);
        })
        .then(mattingModule => {
            // 调用 matting-editor 的 install 方法
            // 参数是 Vue
            mattingModule.default.install(Vue);

            return Promise.delay(0);
        });
    }

    return promise;
};

// index.js
import installMattingEditor from './install-matting-editor';

installMattingEditor()
.then(() => {
    // ...
});
```

## User canceled

用户连续进行抠图操作，若上一次抠图未处理完，将会抛出 `User canceled` 错误

```javascript
err.name; // CancellationError
err.canceled; // true
```

## 配置参数

### options:

默认参数如下，包含钩子的默认实现：

```javascript
mattingEditorOptions = {
    // 图片最大高度
    maxImageHeight: 1000,

    // 图片最大宽度
    maxImageWidth: 1000,

    maxFileSize: 9 * 1024 * 1024,

    // 画布背景色值
    canvasBackgroundColor: "#e6e6e6",

    // 笔刷颜色对象
    brushColorsMap: {
        // 保留刷子颜色
        keep: "#196cfa",

        // 剔除刷子颜色值
        drop: "#f73d64"
    },

    // 背景色列表
    swatches: [
        {
            // 颜色值 null 表示透明
            value: null,

            // text tips
            text: '透明'
        },
        {
            value: '#000000',
            text: '黑色'
        },
        {
            value: '#666666',
            text: '灰色'
        },
        {
            value: '#ffffff',
            text: '白色'
        },
        {
            value: '#d8090b',
            text: '红色'
        },
        {
            value: '#01a1ff',
            text: '蓝色'
        }
    ],

    // 默认刷子类型
    defaultTool: 'keep',

    // 工具栏开关
    showToolbar: true,

    // 抠图 loading 图片延迟时间
    loadingDelay: 500,

    // 第一次使用的用户(通过 localStorage = matting_guided_new_user 缓存) 显示新手引导
    guiderable: true,

    // 第一次使用的用户(通过 localStorage = matting_popper_store 缓存) 显示指引性引导
    // 用户在画布上画完一笔后 3s 后在相对的笔刷上出现 popper 提示选择另外一个画笔
    // 用户选择另外笔刷在画布上画完 3s 后在背景选择位置出现 popper 提示可以选择其他颜色
    popperGuiderable: true,

    // 新手帮助
    enableHelp: false,

    // 保存钩子，编辑器会在用户操作、抠图结果更新时调用此钩子
    // 入参为编辑器内部维护 `matting` 数据
    // 返回 `Promise`，结果需要返回一个 `matting` 对象
    saveMatting(matting) {
        return Promise.try(() => {
            if (matting.id) {
                if (matting.$update) {
                    return matting.$update();
                }

                return axios.put(`/api/mattings/${matting.id}`, matting);
            }

            if (matting.$save) {
                return matting.$save();
            }

            return axios.post('/api/mattings', matting);
        })
        .then(res => {
            return res.data || res;
        });
    },

    // 抠图钩子，编辑器会在用户操作后调用此钩子
    // 入参分别为 `mattingData`, `matting`
    // 返回 `Promise`，结果内至少需要包含 `edge_paths`, `mask_url` 字段
    mattingImage(matting) {
        const apiUrl = `/api/mattings/${matting.id}/images`;

        return axios.post(apiUrl, {
            content: matting.content
        })
        .then(res => {
            return res.data;
        });
    },

    // 图片上传钩子
    // 入参为图片 blob 数据，可以直接通过 oss 等方式上传
    // 返回 `Promise`，结果至少需要返回 `url` 字段
    uploadImage(imageBlob, matting) {
        return {
            url: URL.createObjectURL(imageBlob)
        };
    }
}
```

## 主要数据结构

### mattingData

`mattingData` 对象是 `matting` 对象中的核心部分，用于存储抠图的主要信息。

```javascript
defaultMattingData = {
    sourceImage: '',        // 抠图图片，必须为 URL 地址
    imageHeight: 0,         // 图片的高度
    imageWidth: 0,          // 图片的宽度
    backgroundColor: null,  // 抠图的背景颜色，null为透明，需要为Hex颜色值
    featheringRadius: 0,    // 画笔边缘平滑像素
    brushSize: 30,          // 画笔大小像素
    lines: []               // 存储笔画路径信息，笔画数据结构在后面说明
}
```

### line

`line` 对象记录每个笔画的信息，存储在 `mattingData.line` 中，每个 `line` 对应一个笔画。

```javascript
line = {
    action: 'keep',         // keep 为保留笔画，drop 为丢弃笔画
    alpha: 0.8,             // alpha 通道
    color: 1666298,        // hex 颜色值的十进制值
    points: [0, 1, 3, 5]    // 存储笔画路径，绘制方式请参考源码
}
```

### matting

`matting` 对象，对外的主要数据结构。

```javascript
matting = {
    content: JSON.string(mattingData)   // matting 只需将被JSON序列化后的 mattingData 存储在content即可，可以根据需要添加其他字段
}
```

## 主要接口

### importMatting()

用于导入一个已经存在的 `matting` 对象，同时初始化编辑器

```javascript
mattingEditor.importMatting({
    id: 1,
    content: '{"sourceImage":"https://xxx.jpg","brushSize":30,"lines":[...]}',
});
```

### importMattingByImage()

通过图片 url 创建抠图，先调用 `saveMatting` 钩子函数，创建 `matting` 对象，并初始化编辑器。若图片宽高超过限制，会先压缩，再调用 uploadImage 钩子函数上传图片

```javascript
const imageUrl = 'https://xxx.jpg';
const extendData = {
    scene: 'koutu'    
}; // 添加 matting 属性

mattingEditor.importMattingByImage(imageUrl, extendData)
.then(matting => {
    // matting 对象

    console.log(matting.scene); // koutu
});
```

### importMattingByFile()

通过图片 file 文件创建抠图，先调用 `saveMatting` 钩子函数，创建 `matting` 对象, 并初始化编辑器。若图片宽高超过限制，会先压缩，再调用 uploadImage 钩子函数上传图片

```javascript
const extendData = {
    scene: 'koutu'
}; // 添加 matting 属性

mattingEditor.importMattingByFile(file, extendData)
.then(matting => {
    // matting 对象

    console.log(matting.scene); // koutu
});
```

### createMatting()

用于创建 `matting` 对象。需要注意的是，创建后的 `matting` 对象的 `content` 字段为字符串，如果需要修改 `mattingData`，需要先 `JSON.parse` 解析后再操作

```javascript
// 创建默认 matting
let matting = mattingEditor.createMatting();

// 根据已有的 mattingData 创建，该函数会用默认值填充 mattingData 的空字段
let matting = mattingEditor.createMatting(mattingData);

// 还可以为 matting 添加自定义字段
let extendData = {
    extend: 'extend'
};
let matting = mattingEditor.createMatting(mattingData, extendData);
/**
 * {
 *     content: ...,
 *     extend: 'extend'
 * } 
 */
```

### getMattingData()

获取当前抠图数据对象

```javascript
let mattingData = mattingEditor.getMattingData();

// mattingData 结构如下
mattingData = {
    // 抠图插件版本号
    version: '1.0.0',

    // 原图片地址
    sourceImage: 'http://123.png',

    // 当前图片宽度
    imageWidth: 100,

    // 当前图片高度
    imageHeight: 100,

    // 结果图的背景颜色, "#000"
    backgroundColor: null,

    // 笔刷直径
    brushSize: 30,

    // 边缘平滑度
    featheringRadius: 0,

    // 当前笔刷数组对象
    lines: [
        {
            // 线条类型
            action: 'keep',

            // 线条透明图(0-1)
        	alpha: 0.8,

            // 颜色值
            color: 0xff0000

            // 线条坐标 n: 代表x坐标 n+1代表y坐标
            points: [],

            // 笔刷直径 / zoom
            size: 30
        }
    ]
}
```

### getMatting()

获取当前抠图 `matting` 对象

```javascript
let matting = mattingEditor.getMatting();

// matting 结构如下
matting = {
    id: 1,
    content: '{"sourceImage":"https://xx.jpg","lines":[]}',
    result_image: 'https://xxx.jpg'
};
```


### downloadImage([imageOptions])

下载结果图。返回 `Promise`

```javascript
mattingEditor.downloadImage({
    // 导出图片类型 ('png', 'jpeg')
    type: 'jpeg',

    // 图片质量只有 `jpeg` 设置有效，数值 0-1
    quality: 0.8
});
```

### exportImage([imageOptions])

获取结果图。返回 `Promise`，结果返回 `DataURL`

```javascript
mattingEditor.exportImage({
    // 导出图片类型 ('png', 'jpeg')
    type: 'jpeg',

    // 图片质量只有 `jpeg` 设置有效，数值 0-1
    quality: 0.8
})
.then(dataURL => {
    // dataURL = "data:image/png;base64,iVBORw0KGgxxxxxx"
});
```

### updateViewSize()

更新画布位置，尺寸

```javascript

mattingEditor.updateViewSize();

```

### setBrushSize(brushSize)

设置刷子尺寸

```javascript

mattingEditor.setBrushSize(30);

```

### setFeatheringRadius(featheringRadius)

设置边缘平滑

```javascript

mattingEditor.setFeatheringRadius(2);

```

### setBackgroundColor(backgroundColor)

设置输出图背景

```javascript

mattingEditor.setBackgroundColor("#000");

```

### setOptions(options)

设置 mattingOptions

```javascript

mattingEditor.setOptions({
    canvasBackgroundColor: "#e6e6e6"
});

```

### undo()

撤销

```javascript

mattingEditor.undo();

```

### redo()

重做

```javascript

mattingEditor.redo();

```

### zoomIn()

放大

```javascript

mattingEditor.zoomIn();

```

### zoomOut()

缩小

```javascript

mattingEditor.zoomOut();

```

### fitZoom()

适应画布

```javascript

mattingEditor.fitZoom();

```

### setZoom(zoom)

设置图片比例 注：最大 4 倍，最小 20px

```javascript

mattingEditor.setZoom(2);

```

### showGuider()

显示抠图引导弹窗 不受配置参数 `guiderable` 影响

```javascript

mattingEditor.showGuider();

```

### initPopperGuider()

初始化指引性引导 不受配置参数 `popperGuiderable` 影响 
注：请在 dom 加载完成后初始化 (通过 localStorage = matting_popper_store 缓存) 重复调用无效

```javascript

// 用户在画布上画完一笔后 3s 后在相对的笔刷上出现 popper 提示选择另外一个画笔
// 用户选择另外笔刷在画布上画完 3s 后在背景选择位置出现 popper 提示可以选择其他颜色

mattingEditor.initPopperGuider();

```

## 稿定抠图生命周期事件

### ready()

插件准备就绪，图片已载入 canvas

```javascript
<matting-editor
    @ready="ready"
/>

export default {
    methods: {
        ready() {}
    }
}
```

### change(mattingData)

笔刷发生了变动，需要注意的是笔刷发生了变动不一定会请求抠图API

```javascript
<matting-editor
    @change="change"
/>

export default {
    methods: {
        change(mattingData) {
            // mattingData 可参考上面结构
        }
    }
}
```

### beforeMatting(mattingData)

准备请求抠图API前

```javascript
<matting-editor
    @before-matting="beforeMatting"
/>

export default {
    methods: {
        beforeMatting(mattingData) {
            // mattingData 可参考上面结构
        }
    }
}
```

### mattinged(mattingResult, mattingData)

图片抠图完成, 未调用 `uploadImage()`，需要注意的是此时调用`exportImage()`可能导致导出的Data URL的为空图片

```javascript
<matting-editor
    @mattinged="mattinged"
/>

export default {
    methods: {
        mattinged(mattingResult, mattingData) {
            // mattingData 可参考上面结构
            // 抠图处理接口的返回结果
            mattingResult = {
                edge_paths: ["M0 450 ... 3z"],
                mask_url: "http://xxx.json"
            }
        }
    }
}
```

### update(mattingData)

抠图的结果图片已经上传完成，此时调用`exportImage()`能得到正确的结果

```javascript
<matting-editor
    @update="update"
/>

export default {
    methods: {
        update(mattingData) {
            // mattingData 可参考上面结构
        }
    }
}
```

### error(err)

抠图失败，带err参数

```javascript
<matting-editor
    @error="error"
/>

export default {
    methods: {
        error(err) {
            // err 错误信息
        }
    }
}
```

## TODO

- [] 去除Jquery依赖
- [] 错误管理
- [] 添加自定义组件