深度画布bug
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# VITE_APP_URL = http://192.168.31.82:8771
|
||||
VITE_APP_URL = http://18.167.251.121:10015
|
||||
# VITE_APP_URL = http://192.168.31.118:8080
|
||||
VITE_APP_URL = http://192.168.31.82:8755
|
||||
# VITE_APP_URL = http://192.168.31.82:8755
|
||||
VITE_GOOGLE_CLIENT_ID = 216037134725-7q8vqp0ohtmohlosltkfg7bd2v29rm5a.apps.googleusercontent.com
|
||||
|
||||
@@ -103,3 +103,12 @@ body,
|
||||
border-radius: 0.4rem;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.mosaic-bg {
|
||||
--mosaic-bg-size: 1rem;
|
||||
--mosaic-bg-color1: #efefef;
|
||||
--mosaic-bg-color2: #fff;
|
||||
background-image: repeating-conic-gradient(var(--mosaic-bg-color1) 0% 25%, var(--mosaic-bg-color2) 0% 50%);
|
||||
background-repeat: repeat;
|
||||
background-position: 50% 50%;
|
||||
background-size: var(--mosaic-bg-size) var(--mosaic-bg-size);
|
||||
}
|
||||
|
||||
@@ -117,3 +117,13 @@ body,
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.mosaic-bg{
|
||||
--mosaic-bg-size: 1rem;
|
||||
--mosaic-bg-color1: #efefef;
|
||||
--mosaic-bg-color2: #fff;
|
||||
background-image: repeating-conic-gradient(var(--mosaic-bg-color1) 0% 25%, var(--mosaic-bg-color2) 0% 50%);
|
||||
background-repeat: repeat;
|
||||
background-position: 50% 50%;
|
||||
background-size: var(--mosaic-bg-size) var(--mosaic-bg-size);
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
<brush-control-panel v-if="show" :currentTool="currentTool2" style="top: 14rem" />
|
||||
<brush-control-panel v-if="show" :currentTool="currentTool" style="top: 14rem" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -40,7 +40,6 @@
|
||||
[OperationType.AISELECT_DRAW]: OperationType.ERASER,
|
||||
[OperationType.AISELECT_ERASER]: OperationType.ERASER
|
||||
}
|
||||
const currentTool2 = computed(() => tool2[props.currentTool] || props.currentTool)
|
||||
const show = computed(() => stateManager.aiSelectboxToolManager.tools.includes(props.currentTool))
|
||||
const list = ref([
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, computed, watch } from 'vue'
|
||||
import { ref, inject, computed, watch, nextTick } from 'vue'
|
||||
import depthSlider from './tools/depth-slider.vue'
|
||||
import { OperationType } from '../tools/layerHelper'
|
||||
const props = defineProps({
|
||||
@@ -40,20 +40,27 @@
|
||||
})
|
||||
const stateManager = inject('stateManager') as any
|
||||
const brushState = computed(() => stateManager.brushManager.brushStore.state)
|
||||
const showTools = [OperationType.DRAW, OperationType.ERASER]
|
||||
const showTools = [
|
||||
OperationType.DRAW,
|
||||
OperationType.ERASER,
|
||||
OperationType.AISELECT_DRAW,
|
||||
OperationType.AISELECT_ERASER
|
||||
]
|
||||
const show = computed(() => showTools.includes(props.currentTool))
|
||||
const brushSize = ref(40)
|
||||
const brushOpacity = ref(100)
|
||||
const brushColor = ref('#000000')
|
||||
const updateBrushState = () => {
|
||||
brushSize.value = brushState.value.size
|
||||
brushOpacity.value = brushState.value.opacity
|
||||
brushColor.value = brushState.value.color
|
||||
const updateBrushState = async () => {
|
||||
await nextTick()
|
||||
brushSize.value = stateManager.brushManager.brushStore.state.size
|
||||
brushOpacity.value = stateManager.brushManager.brushStore.state.opacity
|
||||
brushColor.value = stateManager.brushManager.brushStore.state.color
|
||||
}
|
||||
updateBrushState()
|
||||
watch(() => brushState.value.size, updateBrushState)
|
||||
watch(() => brushState.value.opacity, updateBrushState)
|
||||
watch(() => brushState.value.color, updateBrushState)
|
||||
watch(() => props.currentTool, updateBrushState)
|
||||
|
||||
const onInputSize = (value: number) => {
|
||||
stateManager.brushManager.setBrushSize(value)
|
||||
|
||||
@@ -68,12 +68,25 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="select">
|
||||
<div class="title">{{ $t('DepthCanvas.blendMode') }}</div>
|
||||
<div class="content">
|
||||
<depth-select
|
||||
v-model="data.globalCompositeOperation"
|
||||
:list="layerCompositeOptions"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, computed, nextTick, onBeforeUnmount, reactive, watch } from 'vue'
|
||||
import DepthInput from '../tools/depth-input.vue'
|
||||
import DepthSelect from '../tools/depth-select.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
const objectManager = inject('objectManager') as any
|
||||
const stateManager = inject('stateManager') as any
|
||||
const props = defineProps({
|
||||
@@ -90,7 +103,8 @@
|
||||
width: 0,
|
||||
height: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
scaleY: 1,
|
||||
globalCompositeOperation: 'source-over'
|
||||
})
|
||||
const updateData = async () => {
|
||||
await nextTick()
|
||||
@@ -100,6 +114,7 @@
|
||||
data.height = Math.round(props.object.height)
|
||||
data.scaleX = Math.round(props.object.scaleX * 100)
|
||||
data.scaleY = Math.round(props.object.scaleY * 100)
|
||||
data.globalCompositeOperation = props.object.globalCompositeOperation
|
||||
}
|
||||
updateData()
|
||||
watch(() => props.object, updateData)
|
||||
@@ -121,6 +136,94 @@
|
||||
stateManager.event.remove('canvas:undo', updateData)
|
||||
stateManager.event.remove('canvas:redo', updateData)
|
||||
})
|
||||
|
||||
const layerCompositeOptions = ref([
|
||||
{
|
||||
value: 'source-over',
|
||||
label: t('DepthCanvas.compositeNormal'),
|
||||
tip: t('DepthCanvas.compositeNormalTip')
|
||||
}, // 正常
|
||||
{
|
||||
value: 'darken',
|
||||
label: t('DepthCanvas.compositeDarken'),
|
||||
tip: t('DepthCanvas.compositeDarkenTip')
|
||||
}, // 变暗
|
||||
{
|
||||
value: 'multiply',
|
||||
label: t('DepthCanvas.compositeMultiply'),
|
||||
tip: t('DepthCanvas.compositeMultiplyTip')
|
||||
}, // 正片叠底
|
||||
{
|
||||
value: 'color-burn',
|
||||
label: t('DepthCanvas.compositeColorBurn'),
|
||||
tip: t('DepthCanvas.compositeColorBurnTip')
|
||||
}, // 颜色加深
|
||||
|
||||
{
|
||||
value: 'lighten',
|
||||
label: t('DepthCanvas.compositeLighten'),
|
||||
tip: t('DepthCanvas.compositeLightenTip')
|
||||
}, // 颜色减淡
|
||||
{
|
||||
value: 'screen',
|
||||
label: t('DepthCanvas.compositeScreen'),
|
||||
tip: t('DepthCanvas.compositeScreenTip')
|
||||
}, // 滤色
|
||||
{
|
||||
value: 'color-dodge',
|
||||
label: t('DepthCanvas.compositeColorDodge'),
|
||||
tip: t('DepthCanvas.compositeColorDodgeTip')
|
||||
}, // 颜色减淡
|
||||
{
|
||||
value: 'lighter',
|
||||
label: t('DepthCanvas.compositeLighter'),
|
||||
tip: t('DepthCanvas.compositeLighterTip')
|
||||
}, // 颜色减淡
|
||||
|
||||
{
|
||||
value: 'overlay',
|
||||
label: t('DepthCanvas.compositeOverlay'),
|
||||
tip: t('DepthCanvas.compositeOverlayTip')
|
||||
}, // 叠加
|
||||
{
|
||||
value: 'soft-light',
|
||||
label: t('DepthCanvas.compositeSoftLight'),
|
||||
tip: t('DepthCanvas.compositeSoftLightTip')
|
||||
}, // 柔光
|
||||
{
|
||||
value: 'hard-light',
|
||||
label: t('DepthCanvas.compositeHardLight'),
|
||||
tip: t('DepthCanvas.compositeHardLightTip')
|
||||
}, // 强光
|
||||
|
||||
{
|
||||
value: 'difference',
|
||||
label: t('DepthCanvas.compositeDifference'),
|
||||
tip: t('DepthCanvas.compositeDifferenceTip')
|
||||
}, // 差值
|
||||
{
|
||||
value: 'exclusion',
|
||||
label: t('DepthCanvas.compositeExclusion'),
|
||||
tip: t('DepthCanvas.compositeExclusionTip')
|
||||
}, // 排除
|
||||
|
||||
{ value: 'hue', label: t('DepthCanvas.compositeHue'), tip: t('DepthCanvas.compositeHueTip') }, // 色相
|
||||
{
|
||||
value: 'saturation',
|
||||
label: t('DepthCanvas.compositeSaturation'),
|
||||
tip: t('DepthCanvas.compositeSaturationTip')
|
||||
}, // 饱和度
|
||||
{
|
||||
value: 'color',
|
||||
label: t('DepthCanvas.compositeColor'),
|
||||
tip: t('DepthCanvas.compositeColorTip')
|
||||
}, // 颜色
|
||||
{
|
||||
value: 'luminosity',
|
||||
label: t('DepthCanvas.compositeLuminosity'),
|
||||
tip: t('DepthCanvas.compositeLuminosityTip')
|
||||
} // 亮度
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -137,9 +240,9 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
--depth-input-height: 2.4rem;
|
||||
--depth-input-bg-color: rgba(249, 249, 250, 1);
|
||||
--depth-input-border-color: rgba(230, 230, 231, 1);
|
||||
--depth-input-decorate-color: rgba(69, 71, 84, 0.1);
|
||||
// --depth-input-bg-color: rgba(249, 249, 250, 1);
|
||||
// --depth-input-border-color: rgba(230, 230, 231, 1);
|
||||
// --depth-input-decorate-color: rgba(69, 71, 84, 0.1);
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="fill-repeat h">
|
||||
<div>
|
||||
<div class="title">{{ $t('DepthCanvas.image') }}</div>
|
||||
<div class="content">
|
||||
<div class="content mosaic-bg">
|
||||
<img :src="object.info.fill.source" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -138,9 +138,9 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
--depth-input-height: 2.4rem;
|
||||
--depth-input-bg-color: rgba(249, 249, 250, 1);
|
||||
--depth-input-border-color: rgba(230, 230, 231, 1);
|
||||
--depth-input-decorate-color: rgba(69, 71, 84, 0.1);
|
||||
// --depth-input-bg-color: rgba(249, 249, 250, 1);
|
||||
// --depth-input-border-color: rgba(230, 230, 231, 1);
|
||||
// --depth-input-decorate-color: rgba(69, 71, 84, 0.1);
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="layer-item">
|
||||
<div class="item" @click="onClickLayer">
|
||||
<div class="drag"><svg-icon name="dc-drag" size="18" /></div>
|
||||
<div class="thumb">
|
||||
<div class="thumb mosaic-bg">
|
||||
<img v-if="layer.thumbnail" :src="layer.thumbnail" />
|
||||
</div>
|
||||
<div class="name">
|
||||
@@ -16,6 +16,7 @@
|
||||
:value="layer.info.name"
|
||||
@blur="onChangeName"
|
||||
@keyup.enter="onChangeName"
|
||||
@keydown.stop
|
||||
/>
|
||||
</div>
|
||||
<div class="icons">
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(v) => {
|
||||
console.log(v)
|
||||
value.value = v
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { fabric } from 'fabric-with-all'
|
||||
import { OperationType } from '../tools/layerHelper'
|
||||
import { getObjectAlphaToCanvas, traceImageContour, cloneObjects } from '../tools/canvasMethod'
|
||||
import { getObjectAlphaToCanvas, traceImageContour, clipCanvasTransparent, cloneObjects } from '../tools/canvasMethod'
|
||||
import { getSegAnythingImage } from '@/api/depth-canvas'
|
||||
import { useGlobalStore, useUserInfoStore } from '@/stores'
|
||||
|
||||
@@ -256,28 +256,46 @@ export class AISelectboxToolManager {
|
||||
if (!this.demoObject) return
|
||||
const fobject = this.demoObject
|
||||
this.clearDemoObject()
|
||||
const tcanvas = await this.createStaticCanvas(fobject)
|
||||
const canvas = getObjectAlphaToCanvas(tcanvas, null, 0, { r: 255, g: 0, b: 0, a: 255 });
|
||||
const arr = traceImageContour(canvas);
|
||||
const scaleY = fobject.scaleY
|
||||
const scaleX = fobject.scaleX
|
||||
const top = fobject.top
|
||||
const left = fobject.left
|
||||
let minX = fobject.width;
|
||||
let minY = fobject.height;
|
||||
const str = arr.map((v) => {
|
||||
if (v.x < minX) minX = v.x;
|
||||
if (v.y < minY) minY = v.y;
|
||||
return `${v.x} ${v.y}`
|
||||
}).join(" L ");
|
||||
const path = new fabric.Path(`M ${str} z`);
|
||||
const tcanvas = await this.createStaticCanvas(fobject)
|
||||
const canvas = getObjectAlphaToCanvas(tcanvas, null, 0, { r: 255, g: 255, b: 255, a: 255 });
|
||||
|
||||
/** 路径裁剪法 */
|
||||
// const arr = traceImageContour(canvas);
|
||||
// const str = arr.map((v) => {
|
||||
// if (v.x < minX) minX = v.x;
|
||||
// if (v.y < minY) minY = v.y;
|
||||
// return `${v.x} ${v.y}`
|
||||
// }).join(" L ");
|
||||
// const path = new fabric.Path(`M ${str} z`);
|
||||
// path.set({
|
||||
// left: left + minX,
|
||||
// top: top + minY,
|
||||
// scaleX: scaleX,
|
||||
// scaleY: scaleY,
|
||||
// ...this.selectionStyle,
|
||||
// });
|
||||
|
||||
/** 图片裁剪法 */
|
||||
const info = clipCanvasTransparent(canvas)
|
||||
minX = info.minX
|
||||
minY = info.minY
|
||||
const path = new fabric.Image(info.canvas)
|
||||
path.set({
|
||||
left: left + minX,
|
||||
top: top + minY,
|
||||
scaleX: scaleX,
|
||||
scaleY: scaleY,
|
||||
...this.selectionStyle,
|
||||
});
|
||||
absolutePositioned: true,
|
||||
})
|
||||
|
||||
|
||||
// 创建分组层
|
||||
const group = await this.layerManager.createGroupLayer({
|
||||
clipPath: path,
|
||||
top: path.top + path.height / 2,
|
||||
|
||||
@@ -189,16 +189,9 @@ export class CanvasManager {
|
||||
const objects = this.getObjects().filter((v: any) => v.type !== "group" && !!v.info?.id);
|
||||
for (let i = 0; i < objects.length; i++) {
|
||||
let object = objects[i]
|
||||
if (object.clipPath) object.set({ clipPath: null })
|
||||
let group = this.getObjectById(object.info.parentId)
|
||||
if (!group) continue
|
||||
let path = group.clipPath
|
||||
let path = this.getObjectById(object.info.parentId)?.clipPath
|
||||
if (!path) continue
|
||||
let clipPath = await cloneObjects([path]).then((v) => v[0])
|
||||
clipPath.set({
|
||||
absolutePositioned: true,
|
||||
})
|
||||
object.set({ clipPath })
|
||||
object.set({ clipPath: path })
|
||||
}
|
||||
this.renderAll()
|
||||
}
|
||||
|
||||
@@ -32,7 +32,30 @@ export class LayerManager {
|
||||
}
|
||||
}
|
||||
setActiveFirstLayer() {
|
||||
this.setActiveID(this.layers.value[0]?.info?.id || "")
|
||||
const layer = this.layers.value[0]
|
||||
const id = layer.type === "group" ? layer.children[0]?.info?.id : layer.info?.id
|
||||
this.setActiveID(id || "")
|
||||
}
|
||||
|
||||
// 更新图层列表
|
||||
async updateLayers(isSort = false) {
|
||||
const objects = this.canvasManager.getObjects().map(v => v.toObject()).filter(v => !!v.info?.id).reverse()
|
||||
objects.forEach(v => {
|
||||
if (v.type === "group") {
|
||||
if (!v.children) v.children = []
|
||||
return;
|
||||
}
|
||||
const parentId = v.info?.parentId
|
||||
if (!parentId) return
|
||||
objects.forEach((obj: any) => {
|
||||
if (obj.info?.id !== parentId) return
|
||||
if (!obj.children) obj.children = []
|
||||
obj.children.push(v)
|
||||
})
|
||||
})
|
||||
const layers = objects.filter(v => !v.info?.parentId)
|
||||
this.layers.value = layers
|
||||
if (isSort) await this.sortLayers()
|
||||
}
|
||||
|
||||
getActiveLayer() {
|
||||
@@ -168,26 +191,6 @@ export class LayerManager {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新图层列表
|
||||
async updateLayers(isSort = false) {
|
||||
const objects = this.canvasManager.getObjects().map(v => v.toObject()).filter(v => !!v.info?.id).reverse()
|
||||
objects.forEach(v => {
|
||||
if (v.type === "group") {
|
||||
if (!v.children) v.children = []
|
||||
return;
|
||||
}
|
||||
const parentId = v.info?.parentId
|
||||
if (!parentId) return
|
||||
objects.forEach((obj: any) => {
|
||||
if (obj.info?.id !== parentId) return
|
||||
if (!obj.children) obj.children = []
|
||||
obj.children.push(v)
|
||||
})
|
||||
})
|
||||
const layers = objects.filter(v => !v.info?.parentId)
|
||||
this.layers.value = layers
|
||||
if (isSort) await this.sortLayers()
|
||||
}
|
||||
|
||||
/** 设置图层位置-不设置默认居中 */
|
||||
setLayerPosition(object, options?: any) {
|
||||
@@ -455,7 +458,7 @@ export class LayerManager {
|
||||
const objects = this.canvasManager.getObjects()
|
||||
objects.forEach((item: any) => {
|
||||
item.set({
|
||||
erasable: (item.info.id === this.activeID.value && item.type !== "group")
|
||||
erasable: (item?.info?.id === this.activeID.value && item.type !== "group")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@ export class ToolManager {
|
||||
setTool(value: string) {
|
||||
const tool = this.tools.find((t) => t.name === value)
|
||||
if (!tool) return console.warn(`工具${tool}不存在`)
|
||||
this.brushManager?.handleToolChange(tool.name)
|
||||
const oldTool = this.currentTool.value
|
||||
this.currentTool.value = tool.name
|
||||
this.canvasManager.canvas.defaultCursor = tool.cursor
|
||||
@@ -124,7 +125,6 @@ export class ToolManager {
|
||||
this.canvasManager.canvas.isDrawingMode = !!tool.isDrawingMode;// 绘制模式
|
||||
if (!tool.isDrawingMode) this._disableBrushIndicator()// 非绘制模式,禁用笔刷指示器
|
||||
|
||||
|
||||
if (tool.setup) tool.setup()
|
||||
|
||||
this.stateManager?.aiSelectboxToolManager?.handleToolChange?.(oldTool, tool.name)
|
||||
@@ -160,16 +160,13 @@ export class ToolManager {
|
||||
const brushStore = this.brushManager?.brushStore
|
||||
if (brushStore) {
|
||||
// 同步基本属性
|
||||
// this.brushManager.setBrushSize(brushStore.state.size);
|
||||
// this.brushManager.setBrushColor(brushStore.state.color);
|
||||
// this.brushManager.setBrushOpacity(brushStore.state.opacity);
|
||||
this.brushManager.setBrushSize(brushStore.state.size);
|
||||
this.brushManager.setBrushColor(brushStore.state.color);
|
||||
this.brushManager.setBrushOpacity(brushStore.state.opacity);
|
||||
|
||||
// 同步笔刷类型 - 修复方法名,使用正确的setBrushType方法
|
||||
this.brushManager.setBrushType("pencil");
|
||||
}
|
||||
this.brushManager.setBrushSize(5);
|
||||
this.brushManager.setBrushColor("#000");
|
||||
this.brushManager.setBrushOpacity(1);
|
||||
|
||||
// 更新应用到画布
|
||||
this.brushManager.updateBrush();
|
||||
@@ -185,11 +182,12 @@ export class ToolManager {
|
||||
if (!this.canvasManager.canvas) return;
|
||||
|
||||
// 确保有笔刷管理器
|
||||
if (this.brushManager) {
|
||||
const brushStore = this.brushManager?.brushStore
|
||||
if (brushStore) {
|
||||
this.brushManager.createEraser();
|
||||
this.brushManager.setBrushSize(brushStore.state.size);
|
||||
}
|
||||
|
||||
this.brushManager.setBrushSize(5);
|
||||
this.stateManager.layerManager.setActiveObjectErasable()
|
||||
// 启用笔刷指示器
|
||||
this._enableBrushIndicator();
|
||||
@@ -206,10 +204,16 @@ export class ToolManager {
|
||||
console.warn("画笔正在更新中,请稍候...");
|
||||
return;
|
||||
}
|
||||
this.brushManager.setBrushSize(5);
|
||||
this.brushManager.setBrushColor("rgb(255, 0, 0)");
|
||||
this.brushManager.setBrushOpacity(0.5);
|
||||
this.brushManager.setBrushType("pencil");
|
||||
const brushStore = this.brushManager?.brushStore
|
||||
if (brushStore) {
|
||||
// 同步基本属性
|
||||
this.brushManager.setBrushSize(brushStore.state.size);
|
||||
this.brushManager.setBrushColor(brushStore.state.color);
|
||||
this.brushManager.setBrushOpacity(brushStore.state.opacity);
|
||||
|
||||
// 同步笔刷类型 - 修复方法名,使用正确的setBrushType方法
|
||||
this.brushManager.setBrushType("pencil");
|
||||
}
|
||||
// 更新应用到画布
|
||||
this.brushManager.updateBrush();
|
||||
}
|
||||
@@ -222,9 +226,10 @@ export class ToolManager {
|
||||
if (!this.canvasManager.canvas) return;
|
||||
|
||||
// 确保有笔刷管理器
|
||||
if (this.brushManager) {
|
||||
const brushStore = this.brushManager?.brushStore
|
||||
if (brushStore) {
|
||||
this.brushManager.createEraser();
|
||||
this.brushManager.setBrushSize(5);
|
||||
this.brushManager.setBrushSize(brushStore.state.size);
|
||||
}
|
||||
|
||||
this.stateManager.layerManager.setAllObjectsErasable(false)
|
||||
|
||||
@@ -3,13 +3,13 @@ import { reactive, readonly } from "vue";
|
||||
class texturePresetManager { }
|
||||
|
||||
export class BrushState {
|
||||
constructor(options) {
|
||||
constructor(options = {}) {
|
||||
this.state = reactive({
|
||||
// 笔刷基础属性
|
||||
size: 5, // 笔刷大小
|
||||
color: "#000000", // 笔刷颜色
|
||||
opacity: 1, // 笔刷透明度
|
||||
type: "pencil", // 当前笔刷类型
|
||||
size: options.size || 5, // 笔刷大小
|
||||
color: options.color || "#000000", // 笔刷颜色
|
||||
opacity: options.opacity || 1, // 笔刷透明度
|
||||
type: options.type || "pencil", // 当前笔刷类型
|
||||
|
||||
// 阴影相关属性
|
||||
shadowEnabled: false, // 是否启用阴影
|
||||
|
||||
@@ -21,6 +21,8 @@ import { EraserStateManager } from "../EraserStateManager.js";
|
||||
import { SprayBrush } from "./types/SprayBrush";
|
||||
// import { SketchyBrush } from "./types/SketchyBrush";
|
||||
// import { SpraypaintBrush } from "./types/SpraypaintBrush";
|
||||
import { OperationType } from '../../tools/layerHelper'
|
||||
|
||||
|
||||
/**
|
||||
* 笔刷管理器
|
||||
@@ -37,7 +39,17 @@ export class BrushManager {
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
this.canvas = options.canvas;
|
||||
this.brushStore = new BrushState();
|
||||
this.brushStoreList = {
|
||||
[OperationType.DRAW]: new BrushState(),
|
||||
[OperationType.ERASER]: new BrushState(),
|
||||
[OperationType.AISELECT_DRAW]: new BrushState({
|
||||
color: "rgb(255, 0, 0)",
|
||||
opacity: 0.5,
|
||||
}),
|
||||
[OperationType.AISELECT_ERASER]: new BrushState(),
|
||||
}
|
||||
this.brushStore = this.brushStoreList[OperationType.DRAW];
|
||||
|
||||
this.layerManager = options.layerManager; // 添加图层管理器引用
|
||||
this.brushIndicator = options.brushIndicator; // 添加笔刷指示器引用
|
||||
// this.t = options.t
|
||||
@@ -55,7 +67,15 @@ export class BrushManager {
|
||||
this.isErasingActive = false;
|
||||
this.currentErasedObjects = []; // 当前擦除会话中被影响的对象
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换工具时,更新当前活动笔刷
|
||||
* @param {string} toolName 当前工具名称
|
||||
*/
|
||||
handleToolChange(toolName) {
|
||||
const store = this.brushStoreList[toolName];
|
||||
if (!store) return;
|
||||
this.brushStore = store;
|
||||
}
|
||||
/**
|
||||
* 注册默认笔刷
|
||||
* @private
|
||||
|
||||
@@ -11,7 +11,6 @@ export class KeyEventManager {
|
||||
/** 处理键盘事件 */
|
||||
_handleKeyDown: any
|
||||
handleKeyDown(event: any) {
|
||||
event.preventDefault()
|
||||
const activeID = this.stateManager.layerManager.activeID.value
|
||||
const ctrl = event.ctrlKey ? 'ctrl-' : "";
|
||||
const shift = event.shiftKey ? 'shift-' : "";
|
||||
@@ -24,6 +23,7 @@ export class KeyEventManager {
|
||||
{ key: "ctrl-s", handler: () => this.onWorkbench() },
|
||||
{ key: "ctrl-shift-z", handler: () => this.stateManager.redoState() },
|
||||
]
|
||||
if (list.some((v) => reg.test(v.key))) event.preventDefault()
|
||||
list.forEach((v: any) => {
|
||||
if (reg.test(v.key)) v.handler(event)
|
||||
})
|
||||
|
||||
@@ -259,4 +259,41 @@ export function traceImageContour(canvas) {
|
||||
);
|
||||
|
||||
return contour;
|
||||
}
|
||||
}
|
||||
|
||||
/** 裁剪画布透明像素 */
|
||||
export function clipCanvasTransparent(canvas) {
|
||||
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const data = imageData.data;
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
var minX = width;
|
||||
var minY = height;
|
||||
var maxX = 0;
|
||||
var maxY = 0;
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
const index = (y * width + x) * 4;
|
||||
if (data[index + 3] > 0) {
|
||||
if (x < minX) minX = x;
|
||||
if (y < minY) minY = y;
|
||||
if (x > maxX) maxX = x;
|
||||
if (y > maxY) maxY = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
const newCanvas = document.createElement("canvas");
|
||||
newCanvas.width = maxX - minX + 1;
|
||||
newCanvas.height = maxY - minY + 1;
|
||||
const newCtx = newCanvas.getContext("2d");
|
||||
newCtx.drawImage(canvas, minX, minY, maxX - minX + 1, maxY - minY + 1, 0, 0, maxX - minX + 1, maxY - minY + 1);
|
||||
return {
|
||||
minX,
|
||||
minY,
|
||||
maxX,
|
||||
maxY,
|
||||
canvas: newCanvas,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import { fabric } from 'fabric-with-all'
|
||||
import { createStaticCanvas } from './canvasFactory'
|
||||
import { getObjectsBoundingBox, cloneObjects } from './canvasMethod'
|
||||
/** 导出画布为图片 */
|
||||
export async function exportCanvasToImage(canvas) {
|
||||
/** 导出画布为图片
|
||||
* @param {fabric.Canvas} canvas - 画布对象
|
||||
* @param {boolean} isDisscar - 是否丢弃选中对象
|
||||
* @returns {Promise<string>} - 图片URL
|
||||
*/
|
||||
export async function exportCanvasToImage(canvas, isDisscar = true) {
|
||||
if (isDisscar) {
|
||||
canvas.discardActiveObject()
|
||||
canvas.renderAll()
|
||||
}
|
||||
const clonedObjects = await cloneObjects(canvas.getObjects())
|
||||
const staticCanvas = createStaticCanvas(document.createElement('canvas'))
|
||||
const width = canvas.clipPath.width
|
||||
|
||||
@@ -254,7 +254,43 @@ export default {
|
||||
brush: 'Brush',
|
||||
erase: 'Erase',
|
||||
create: 'Create',
|
||||
reset: 'Reset'
|
||||
reset: 'Reset',
|
||||
// 混合模式
|
||||
blendMode: 'Blend Mode',
|
||||
compositeNormal: 'Normal',
|
||||
compositeNormalTip: 'Normal: Default, new graphics cover original content',
|
||||
compositeDarken: 'Darken',
|
||||
compositeDarkenTip: 'Darken: Take the darkest color',
|
||||
compositeMultiply: 'Multiply',
|
||||
compositeMultiplyTip: 'Multiply: Darken the image',
|
||||
compositeColorBurn: 'Color Burn',
|
||||
compositeColorBurnTip: 'Color Burn: Increase contrast and darken the bottom color',
|
||||
compositeLighten: 'Lighten',
|
||||
compositeLightenTip: 'Lighten: Take the brightest color',
|
||||
compositeScreen: 'Screen',
|
||||
compositeScreenTip: 'Screen: Lighten the image',
|
||||
compositeColorDodge: 'Color Dodge',
|
||||
compositeColorDodgeTip: 'Color Dodge: Reduce contrast and lighten the bottom color',
|
||||
compositeLighter: 'Color Dodge (Add)',
|
||||
compositeLighterTip: 'Color Dodge (Add): Add the brightness of the overlapping parts',
|
||||
compositeOverlay: 'Overlay',
|
||||
compositeOverlayTip: 'Overlay: Highlight effect',
|
||||
compositeSoftLight: 'Soft Light',
|
||||
compositeSoftLightTip: 'Soft Light: Blend effect',
|
||||
compositeHardLight: 'Hard Light',
|
||||
compositeHardLightTip: 'Hard Light: Highlight effect',
|
||||
compositeDifference: 'Difference',
|
||||
compositeDifferenceTip: 'Difference: Take the color difference between the two images',
|
||||
compositeExclusion: 'Exclusion',
|
||||
compositeExclusionTip: 'Exclusion: Take the absolute value of the color difference between the two images',
|
||||
compositeHue: 'Hue',
|
||||
compositeHueTip: 'Hue: Preserve the original image color and change the hue of the new image',
|
||||
compositeSaturation: 'Saturation',
|
||||
compositeSaturationTip: 'Saturation: Preserve the original image hue and change the saturation of the new image',
|
||||
compositeColor: 'Color',
|
||||
compositeColorTip: 'Color: Preserve the original image saturation and change the color of the new image',
|
||||
compositeLuminosity: 'Luminosity',
|
||||
compositeLuminosityTip: 'Luminosity: Preserve the original image color and change the luminosity of the new image',
|
||||
},
|
||||
clipDialog: {
|
||||
title: 'Upload your profile photo',
|
||||
|
||||
@@ -249,7 +249,43 @@ export default {
|
||||
brush: '画笔',
|
||||
erase: '擦除',
|
||||
create: '创建',
|
||||
reset: '重置'
|
||||
reset: '重置',
|
||||
// 混合模式
|
||||
blendMode: '混合模式',
|
||||
compositeNormal: '正常',
|
||||
compositeNormalTip: '正常:默认,新图形覆盖原内容',
|
||||
compositeDarken: '变暗',
|
||||
compositeDarkenTip: '变暗:取暗部颜色',
|
||||
compositeMultiply: '正片叠底',
|
||||
compositeMultiplyTip: '正片叠底:图像变暗',
|
||||
compositeColorBurn: '颜色加深',
|
||||
compositeColorBurnTip: '颜色加深:增加对比度,变暗底层颜色',
|
||||
compositeLighten: '变亮',
|
||||
compositeLightenTip: '变亮:取亮部颜色',
|
||||
compositeScreen: '滤色',
|
||||
compositeScreenTip: '滤色:图像变亮',
|
||||
compositeColorDodge: '颜色减淡',
|
||||
compositeColorDodgeTip: '颜色减淡:降低对比度,加亮底层颜色',
|
||||
compositeLighter: '颜色减淡(添加)',
|
||||
compositeLighterTip: '颜色减淡(添加):重叠部分亮度叠加',
|
||||
compositeOverlay: '叠加',
|
||||
compositeOverlayTip: '叠加:高光效果',
|
||||
compositeSoftLight: '柔光',
|
||||
compositeSoftLightTip: '柔光:混合效果',
|
||||
compositeHardLight: '强光',
|
||||
compositeHardLightTip: '强光:高光效果',
|
||||
compositeDifference: '差值',
|
||||
compositeDifferenceTip: '差值:取两图像颜色差',
|
||||
compositeExclusion: '排除',
|
||||
compositeExclusionTip: '排除:取两图像颜色差的绝对值',
|
||||
compositeHue: '色相',
|
||||
compositeHueTip: '色相:保留原图像颜色,改变新图像色相',
|
||||
compositeSaturation: '饱和度',
|
||||
compositeSaturationTip: '饱和度:保留原图像色相,改变新图像饱和度',
|
||||
compositeColor: '颜色',
|
||||
compositeColorTip: '颜色:保留原图像饱和度,改变新图像颜色',
|
||||
compositeLuminosity: '亮度',
|
||||
compositeLuminosityTip: '亮度:保留原图像颜色,改变新图像亮度',
|
||||
},
|
||||
clipDialog: {
|
||||
title: '上传您的个人资料照片',
|
||||
|
||||
@@ -11,12 +11,16 @@
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
const url =
|
||||
'https://minio-api.aida.com.hk/fida-public-bucket/furniture/sketches/e3082a38-55d2-4313-ad53-55aad715cf67.png'
|
||||
'https://www.minio-api.aida.com.hk/fida-user/2/d8512e53-f016-4ad6-8245-2f304d89e7b2.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20260331%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260331T032733Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=25e5ec227a0ca22942e71eff3a4f07a23f8812ff3db5522e1466b3a77288be70'
|
||||
const openCanvas = () => {
|
||||
myEvent.emit('openFlowCanvas', { url })
|
||||
}
|
||||
const openDepthCanvas = () => {
|
||||
myEvent.emit('openDepthCanvas', { url, canvasId: '' })
|
||||
myEvent.emit('openDepthCanvas', {
|
||||
url,
|
||||
canvasId: '69cb3f244a1dd46c0bdbb432',
|
||||
sketchId: '69c63417cb064e32ff6826a3'
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
if (route.query.depth) {
|
||||
|
||||
Reference in New Issue
Block a user