zhengyiming
11 小时以前 97f29024ce18babeb4b635c5d73f907ac493976e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
<!--
 * @Author: 秦少卫
 * @Date: 2023-04-06 23:04:38
 * @LastEditors: 秦少卫
 * @LastEditTime: 2024-10-07 17:19:51
 * @Description: 图片滤镜
-->
 
<template>
  <div v-if="isOne && state.type === 'image'" class="box">
    <Divider plain orientation="left">
      <h4>图片滤镜</h4>
    </Divider>
    <Collapse>
      <Panel name="1">
        {{ $t('filters.simple') }}
        <template #content>
          <div class="filter-box">
            <!-- 无参数滤镜 -->
            <div class="filter-item" v-for="(value, key) in state.noParamsFilters" :key="key">
              <img
                :src="getImageUrl(key)"
                alt=""
                @click="changeFilters(key, !noParamsFilters[key])"
              />
              <Checkbox
                v-model="state.noParamsFilters[key]"
                @on-change="(val) => changeFilters(key, val)"
              >
                {{ $t('filters.' + key) }}
              </Checkbox>
            </div>
          </div>
        </template>
      </Panel>
      <Panel name="2">
        {{ $t('filters.complex') }}
        <template #content>
          <!-- 有参数滤镜与组合参数滤镜 -->
          <div>
            <div
              class="filter-item has-params"
              v-for="item in [...state.paramsFilters, ...state.combinationFilters]"
              :key="item.type"
            >
              <Checkbox v-model="item.status" @on-change="changeFiltersByParams(item.type)">
                {{ $t('filters.' + item.type) }}
              </Checkbox>
              <div v-if="item.status" class="content">
                <div class="content slider-box" v-for="info in item.params" :key="info">
                  <div v-if="info.uiType === uiType.SELECT">
                    <RadioGroup v-model="info.value" @on-change="changeFiltersByParams(item.type)">
                      <Radio :label="listItem" v-for="listItem in info.list" :key="listItem">
                        {{ $t('filters.' + item.type + 'List.' + listItem) }}
                      </Radio>
                    </RadioGroup>
                  </div>
                  <div v-if="info.uiType === uiType.NUMBER">
                    <Slider
                      v-model="info.value"
                      :max="info.max"
                      :min="info.min"
                      :step="info.step"
                      @on-input="changeFiltersByParams(item.type)"
                    ></Slider>
                  </div>
                  <div v-if="info.uiType === uiType.COLOR">
                    <ColorPicker
                      v-model="info.value"
                      alpha
                      size="small"
                      @on-change="changeFiltersByParams(item.type)"
                    />
                  </div>
                </div>
              </div>
            </div>
          </div>
        </template>
      </Panel>
    </Collapse>
  </div>
</template>
 
<script name="Filter" setup>
import useSelect from '@/hooks/select';
import { uiType, paramsFilters, combinationFilters } from '@/config/constants/filter';
 
const { fabric, isOne, canvasEditor } = useSelect();
const update = getCurrentInstance();
// 无参数滤镜
const noParamsFilters = {
  BlackWhite: false,
  Brownie: false,
  Vintage: false,
  Kodachrome: false,
  technicolor: false,
  Polaroid: false,
  Invert: false,
  Sepia: false,
};
 
const state = reactive({
  uiType,
  noParamsFilters,
  paramsFilters: [...paramsFilters],
  combinationFilters: [...combinationFilters],
  type: '',
});
 
// 无参数滤镜修改状态
const changeFilters = (type, value) => {
  const activeObject = canvasEditor.canvas.getActiveObjects()[0];
  state.noParamsFilters[type] = value;
  if (value) {
    const itemFilter = _getFilter(activeObject, type);
    if (!itemFilter) {
      _createFilter(activeObject, type);
    }
  } else {
    _removeFilter(activeObject, type);
  }
};
// 有参数与组合滤镜修改
const changeFiltersByParams = (type) => {
  const activeObject = canvasEditor.canvas.getActiveObjects()[0];
  const filtersAll = [...state.paramsFilters, ...state.combinationFilters];
  const moduleInfo = filtersAll.find((item) => item.type === type);
  if (moduleInfo.status) {
    // 组合参数滤镜修改
    if (moduleInfo.handler) {
      _changeAttrByHandler(moduleInfo);
    } else {
      // 有参数滤镜修改
      moduleInfo.params.forEach((paramsItem) => {
        _changeAttr(type, paramsItem.key, paramsItem.value);
      });
    }
  } else {
    _removeFilter(activeObject, type);
  }
};
 
const handleSelectOne = () => {
  const activeObject = canvasEditor.canvas.getActiveObjects()[0];
  if (activeObject) {
    state.type = activeObject.type;
    if (state.type === 'image') {
      // 无参数滤镜回显
      Object.keys(noParamsFilters).forEach((type) => {
        state.noParamsFilters[type] = !!_getFilter(activeObject, type);
        update?.proxy?.$forceUpdate();
      });
      // 有参数滤镜回显
      paramsFilters.forEach((filterItem) => {
        const moduleInfo = state.paramsFilters.find((item) => item.type === filterItem.type);
        const filterInfo = _getFilter(activeObject, filterItem.type);
        moduleInfo.status = !!filterInfo;
        moduleInfo.params.forEach((paramsItem) => {
          paramsItem.value = filterInfo ? filterInfo[paramsItem.key] : paramsItem.value;
        });
      });
 
      // 组合滤镜回显
      combinationFilters.forEach((filterItem) => {
        const moduleInfo = state.combinationFilters.find((item) => item.type === filterItem.type);
        const filterInfo = _getFilter(activeObject, filterItem.type);
        moduleInfo.status = !!filterInfo;
        // 不回显具体参数
      });
    }
    update?.proxy?.$forceUpdate();
  }
};
 
onMounted(() => {
  canvasEditor.on('selectOne', handleSelectOne);
});
 
onBeforeUnmount(() => {
  canvasEditor.off('selectOne', handleSelectOne);
});
 
// 图片地址拼接
function getImageUrl(name) {
  return new URL(`../assets/filters/${name}.png`, import.meta.url).href;
}
 
// 设置滤镜值
function _changeAttr(type, key, value) {
  const activeObject = canvasEditor.canvas.getActiveObjects()[0];
  const itemFilter = _getFilter(activeObject, type);
  if (itemFilter) {
    itemFilter[key] = value;
  } else {
    const imgFilter = _createFilter(activeObject, type);
    imgFilter[key] = value;
  }
  activeObject.applyFilters();
  canvasEditor.canvas.renderAll();
}
 
function _changeAttrByHandler(moduleInfo) {
  const activeObject = canvasEditor.canvas.getActiveObjects()[0];
  // 删除
  _removeFilter(activeObject, moduleInfo.type);
  // 创建
  const params = moduleInfo.params.map((item) => item.value);
  _createFilter(activeObject, moduleInfo.type, moduleInfo.handler(...params));
}
 
/**
 * Create filter instance
 * @param {fabric.Image} sourceImg - Source image to apply filter
 * @param {string} type - Filter type
 * @param {Object} [options] - Options of filter
 * @returns {Object} Fabric object of filter
 * @private
 */
function _createFilter(sourceImg, type, options = null) {
  let filterObj;
  // capitalize first letter for matching with fabric image filter name
  const fabricType = _getFabricFilterType(type);
  const ImageFilter = fabric.Image.filters[fabricType];
  if (ImageFilter) {
    filterObj = new ImageFilter(options);
    filterObj.options = options;
    sourceImg.filters.push(filterObj);
  }
  sourceImg.applyFilters();
  canvasEditor.canvas.renderAll();
  return filterObj;
}
/**
 * Get applied filter instance
 * @param {fabric.Image} sourceImg - Source image to apply filter
 * @param {string} type - Filter type
 * @returns {Object} Fabric object of filter
 * @private
 */
function _getFilter(sourceImg, type) {
  let imgFilter = null;
 
  if (sourceImg) {
    const fabricType = _getFabricFilterType(type);
    const { length } = sourceImg.filters;
    let item, i;
 
    for (i = 0; i < length; i += 1) {
      item = sourceImg.filters[i];
      if (item.type === fabricType) {
        imgFilter = item;
        break;
      }
    }
  }
 
  return imgFilter;
}
/**
 * Remove applied filter instance
 * @param {fabric.Image} sourceImg - Source image to apply filter
 * @param {string} type - Filter type
 * @private
 */
function _removeFilter(sourceImg, type) {
  const fabricType = _getFabricFilterType(type);
  sourceImg.filters = sourceImg.filters.filter((value) => value.type !== fabricType);
  sourceImg.applyFilters();
  canvasEditor.canvas.renderAll();
}
/**
 * Change filter class name to fabric's, especially capitalizing first letter
 * @param {string} type - Filter type
 * @example
 * 'grayscale' -> 'Grayscale'
 * @returns {string} Fabric filter class name
 */
function _getFabricFilterType(type) {
  return type.charAt(0).toUpperCase() + type.slice(1);
}
</script>
 
<style scoped lang="less">
.filter-box {
  overflow: hidden;
  .filter-item {
    float: left;
    cursor: pointer;
    width: 50%;
    margin-bottom: 10px;
    img {
      width: 90%;
      height: auto;
    }
  }
}
.has-params {
  display: inline-block;
  margin-bottom: 10px;
  width: 50%;
  .content {
    width: 90%;
  }
  cursor: none;
}
.box {
  margin-bottom: 12px;
}
</style>