694 lines
17 KiB
JavaScript
694 lines
17 KiB
JavaScript
import { reactive, readonly } from "vue";
|
||
// import texturePresetManager from "../managers/brushes/TexturePresetManager";
|
||
class texturePresetManager { }
|
||
|
||
export class BrushState {
|
||
constructor(options = {}) {
|
||
this.state = reactive({
|
||
// 笔刷基础属性
|
||
size: options.size || 5, // 笔刷大小
|
||
color: options.color || "#000000", // 笔刷颜色
|
||
opacity: options.opacity || 1, // 笔刷透明度
|
||
type: options.type || "pencil", // 当前笔刷类型
|
||
|
||
// 阴影相关属性
|
||
shadowEnabled: false, // 是否启用阴影
|
||
shadowColor: "#000000", // 阴影颜色(默认为笔刷颜色)
|
||
shadowWidth: 0, // 阴影宽度
|
||
shadowOffsetX: 0, // 阴影X偏移
|
||
shadowOffsetY: 0, // 阴影Y偏移
|
||
|
||
// 笔刷材质相关
|
||
textureScale: 1, // 材质缩放
|
||
textureEnabled: false, // 是否启用材质
|
||
texturePath: "", // 材质图片路径
|
||
textureOpacity: 1, // 材质透明度
|
||
textureRepeat: "repeat", // 材质重复模式
|
||
textureAngle: 0, // 材质旋转角度
|
||
selectedTextureId: null, // 当前选中的材质ID
|
||
|
||
// 可用笔刷类型列表 (由BrushManager初始化)
|
||
availableBrushes: [],
|
||
|
||
// 自定义笔刷列表
|
||
customBrushes: [],
|
||
|
||
// 笔刷预设
|
||
presets: [
|
||
// { name: "细线", size: 2, opacity: 1, color: "#000000", type: "pencil" },
|
||
// { name: "中粗", size: 5, opacity: 1, color: "#000000", type: "pencil" },
|
||
// { name: "粗线", size: 10, opacity: 1, color: "#000000", type: "pencil" },
|
||
// { name: "水彩", size: 15, opacity: 0.7, color: "#3366ff", type: "marker" },
|
||
// { name: "喷枪", size: 20, opacity: 0.5, color: "#ff6633", type: "spray" },
|
||
],
|
||
|
||
// 材质预设
|
||
texturePresets: [
|
||
{
|
||
name: "默认纹理",
|
||
textureId: "preset_texture_0",
|
||
scale: 1,
|
||
opacity: 1,
|
||
repeat: "repeat",
|
||
angle: 0,
|
||
},
|
||
{
|
||
name: "细纹理",
|
||
textureId: "preset_texture_1",
|
||
scale: 0.5,
|
||
opacity: 0.8,
|
||
repeat: "repeat",
|
||
angle: 0,
|
||
},
|
||
{
|
||
name: "粗纹理",
|
||
textureId: "preset_texture_2",
|
||
scale: 2,
|
||
opacity: 1,
|
||
repeat: "repeat",
|
||
angle: 45,
|
||
},
|
||
{
|
||
name: "水彩纹理",
|
||
textureId: "preset_texture_5",
|
||
scale: 1.5,
|
||
opacity: 0.6,
|
||
repeat: "no-repeat",
|
||
angle: 0,
|
||
}
|
||
],
|
||
|
||
// 上传的纹理缓存列表
|
||
uploadedTextures: [],
|
||
|
||
// 最近使用的颜色
|
||
recentColors: ["#000000", "#ffffff", "#ff0000", "#00ff00", "#0000ff"],
|
||
|
||
// 最近使用的材质
|
||
recentTextures: [],
|
||
|
||
// 当前笔刷可配置属性(由当前选中笔刷动态设置)
|
||
currentBrushProperties: [],
|
||
|
||
// 当前笔刷实例的引用
|
||
currentBrushInstance: null,
|
||
|
||
// 笔刷属性值的映射,存储可由UI修改的属性值
|
||
propertyValues: {},
|
||
});
|
||
}
|
||
setBrushSize(size) {
|
||
this.state.size = Math.max(0.5, Math.min(100, size));
|
||
}
|
||
|
||
setBrushColor(color) {
|
||
this.state.color = color;
|
||
// 添加到最近使用的颜色
|
||
if (!this.state.recentColors.includes(color)) {
|
||
this.state.recentColors.unshift(color);
|
||
if (this.state.recentColors.length > 10) {
|
||
this.state.recentColors.pop();
|
||
}
|
||
}
|
||
}
|
||
|
||
setBrushOpacity(opacity) {
|
||
this.state.opacity = Math.max(0.05, Math.min(1, opacity));
|
||
}
|
||
|
||
setBrushType(type) {
|
||
if (this.state.availableBrushes.some((brush) => brush.id === type)) {
|
||
this.state.type = type;
|
||
}
|
||
}
|
||
|
||
setTextureScale(scale) {
|
||
this.state.textureScale = Math.max(0.1, Math.min(10, scale));
|
||
}
|
||
|
||
setTextureEnabled(enabled) {
|
||
this.state.textureEnabled = enabled;
|
||
}
|
||
|
||
setTexturePath(path) {
|
||
this.state.texturePath = path;
|
||
}
|
||
|
||
// 阴影相关方法
|
||
setShadowEnabled(enabled) {
|
||
this.state.shadowEnabled = enabled;
|
||
}
|
||
|
||
setShadowColor(color) {
|
||
this.state.shadowColor = color;
|
||
}
|
||
|
||
setShadowWidth(width) {
|
||
this.state.shadowWidth = Math.max(0, Math.min(50, width));
|
||
}
|
||
|
||
setShadowOffsetX(offsetX) {
|
||
this.state.shadowOffsetX = Math.max(-50, Math.min(50, offsetX));
|
||
}
|
||
|
||
setShadowOffsetY(offsetY) {
|
||
this.state.shadowOffsetY = Math.max(-50, Math.min(50, offsetY));
|
||
}
|
||
|
||
setAvailableBrushes(brushes) {
|
||
this.state.availableBrushes = brushes;
|
||
}
|
||
|
||
addCustomBrush(brush) {
|
||
if (!brush.id) {
|
||
brush.id = `custom_${Date.now()}`;
|
||
}
|
||
|
||
this.state.customBrushes.push(brush);
|
||
return brush.id;
|
||
}
|
||
|
||
removeCustomBrush(brushId) {
|
||
const index = this.state.customBrushes.findIndex((b) => b.id === brushId);
|
||
if (index !== -1) {
|
||
this.state.customBrushes.splice(index, 1);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 应用预设
|
||
applyPreset(presetIndex) {
|
||
const preset = this.state.presets[presetIndex];
|
||
if (preset) {
|
||
this.state.size = preset.size;
|
||
this.state.opacity = preset.opacity;
|
||
this.state.color = preset.color;
|
||
this.state.type = preset.type;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 将当前设置保存为新预设
|
||
saveCurrentAsPreset(name) {
|
||
const newPreset = {
|
||
name: name || `预设 ${this.state.presets.length + 1}`,
|
||
size: this.state.size,
|
||
opacity: this.state.opacity,
|
||
color: this.state.color,
|
||
type: this.state.type,
|
||
textureEnabled: this.state.textureEnabled,
|
||
textureScale: this.state.textureScale,
|
||
texturePath: this.state.texturePath,
|
||
};
|
||
|
||
this.state.presets.push(newPreset);
|
||
return this.state.presets.length - 1; // 返回新预设的索引
|
||
}
|
||
|
||
/**
|
||
* 设置当前笔刷实例
|
||
* @param {Object} brushInstance BaseBrush实例
|
||
*/
|
||
setCurrentBrushInstance(brushInstance) {
|
||
this.state.currentBrushInstance = brushInstance;
|
||
|
||
// 获取并设置当前笔刷的可配置属性
|
||
if (brushInstance && brushInstance.getConfigurableProperties) {
|
||
const properties = brushInstance.getConfigurableProperties();
|
||
this.state.currentBrushProperties = properties;
|
||
|
||
// 初始化属性值
|
||
properties.forEach((prop) => {
|
||
// 如果是基础属性,使用已有值
|
||
if (prop.id === "size") {
|
||
this.state.propertyValues[prop.id] = this.state.size;
|
||
} else if (prop.id === "color") {
|
||
this.state.propertyValues[prop.id] = this.state.color;
|
||
} else if (prop.id === "opacity") {
|
||
this.state.propertyValues[prop.id] = this.state.opacity;
|
||
} else {
|
||
// 对于特殊属性,使用默认值
|
||
this.state.propertyValues[prop.id] = prop.defaultValue;
|
||
}
|
||
});
|
||
} else {
|
||
// 如果没有实例或方法,清空属性列表
|
||
this.state.currentBrushProperties = [];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新笔刷属性值
|
||
* @param {String} propId 属性ID
|
||
* @param {any} value 属性值
|
||
*/
|
||
updatePropertyValue(propId, value) {
|
||
// 更新Store中的值
|
||
this.state.propertyValues[propId] = value;
|
||
|
||
// 同步更新基础属性
|
||
if (propId === "size") {
|
||
this.state.size = value;
|
||
} else if (propId === "color") {
|
||
this.state.color = value;
|
||
} else if (propId === "opacity") {
|
||
this.state.opacity = value;
|
||
}
|
||
|
||
// 如果有当前笔刷实例且有更新方法,则调用
|
||
if (this.state.currentBrushInstance && this.state.currentBrushInstance.updateProperty) {
|
||
this.state.currentBrushInstance.updateProperty(propId, value);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取属性值
|
||
* @param {String} propId 属性ID
|
||
* @param {any} defaultValue 默认值
|
||
* @returns {any} 属性值
|
||
*/
|
||
getPropertyValue(propId, defaultValue) {
|
||
// 检查属性值是否存在
|
||
if (Object.prototype.hasOwnProperty.call(this.state.propertyValues, propId)) {
|
||
return this.state.propertyValues[propId];
|
||
}
|
||
|
||
// 对于基础属性,返回store中的值
|
||
if (propId === "size") {
|
||
return this.state.size;
|
||
} else if (propId === "color") {
|
||
return this.state.color;
|
||
} else if (propId === "opacity") {
|
||
return this.state.opacity;
|
||
}
|
||
|
||
// 否则返回默认值
|
||
return defaultValue;
|
||
}
|
||
|
||
/**
|
||
* 按分类获取当前笔刷可配置属性
|
||
* @returns {Object} 按分类分组的属性对象
|
||
*/
|
||
getPropertiesByCategory() {
|
||
const result = {};
|
||
|
||
this.state.currentBrushProperties.forEach((prop) => {
|
||
const category = prop.category || "默认";
|
||
if (!result[category]) {
|
||
result[category] = [];
|
||
}
|
||
result[category].push({
|
||
...prop,
|
||
value: this.getPropertyValue(prop.id, prop.defaultValue),
|
||
});
|
||
});
|
||
|
||
// 按order排序每个分类中的属性
|
||
Object.keys(result).forEach((category) => {
|
||
result[category].sort((a, b) => (a.order || 0) - (b.order || 0));
|
||
});
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 材质相关方法
|
||
*/
|
||
setTextureOpacity(opacity) {
|
||
this.state.textureOpacity = Math.max(0, Math.min(1, opacity));
|
||
}
|
||
|
||
setTextureRepeat(repeat) {
|
||
const validModes = ["repeat", "repeat-x", "repeat-y", "no-repeat"];
|
||
if (validModes.includes(repeat)) {
|
||
this.state.textureRepeat = repeat;
|
||
}
|
||
}
|
||
|
||
setTextureAngle(angle) {
|
||
this.state.textureAngle = angle % 360;
|
||
}
|
||
|
||
setSelectedTextureId(textureId) {
|
||
this.state.selectedTextureId = textureId;
|
||
|
||
// 添加到最近使用的材质
|
||
if (textureId && !this.state.recentTextures.includes(textureId)) {
|
||
this.state.recentTextures.unshift(textureId);
|
||
if (this.state.recentTextures.length > 8) {
|
||
this.state.recentTextures.pop();
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 应用材质预设
|
||
* @param {Number} presetIndex 预设索引
|
||
* @returns {Boolean} 是否应用成功
|
||
*/
|
||
applyTexturePreset(presetIndex) {
|
||
const preset = this.state.texturePresets[presetIndex];
|
||
if (preset) {
|
||
this.state.selectedTextureId = preset.textureId;
|
||
this.state.textureScale = preset.scale;
|
||
this.state.textureOpacity = preset.opacity;
|
||
this.state.textureRepeat = preset.repeat;
|
||
this.state.textureAngle = preset.angle;
|
||
|
||
// 添加到最近使用
|
||
this.setSelectedTextureId(preset.textureId);
|
||
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 将当前材质设置保存为新预设
|
||
* @param {String} name 预设名称
|
||
* @returns {Number} 新预设的索引
|
||
*/
|
||
saveCurrentTextureAsPreset(name) {
|
||
const newPreset = {
|
||
name: name || `材质预设 ${this.state.texturePresets.length + 1}`,
|
||
textureId: this.state.selectedTextureId,
|
||
scale: this.state.textureScale,
|
||
opacity: this.state.textureOpacity,
|
||
repeat: this.state.textureRepeat,
|
||
angle: this.state.textureAngle,
|
||
};
|
||
|
||
this.state.texturePresets.push(newPreset);
|
||
return this.state.texturePresets.length - 1;
|
||
}
|
||
|
||
/**
|
||
* 删除材质预设
|
||
* @param {Number} presetIndex 预设索引
|
||
* @returns {Boolean} 是否删除成功
|
||
*/
|
||
removeTexturePreset(presetIndex) {
|
||
if (presetIndex >= 0 && presetIndex < this.state.texturePresets.length) {
|
||
this.state.texturePresets.splice(presetIndex, 1);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 获取所有可用材质(预设+自定义)
|
||
* @returns {Array} 材质列表
|
||
*/
|
||
getAllTextures() {
|
||
return texturePresetManager.getAllTextures();
|
||
}
|
||
|
||
/**
|
||
* 根据ID获取材质信息
|
||
* @param {String} textureId 材质ID
|
||
* @returns {Object|null} 材质对象
|
||
*/
|
||
getTextureById(textureId) {
|
||
return texturePresetManager.getTextureById(textureId);
|
||
}
|
||
|
||
/**
|
||
* 按分类获取材质
|
||
* @param {String} category 分类名称
|
||
* @returns {Array} 材质列表
|
||
*/
|
||
getTexturesByCategory(category) {
|
||
return texturePresetManager.getTexturesByCategory(category);
|
||
}
|
||
|
||
/**
|
||
* 获取材质分类列表
|
||
* @returns {Array} 分类名称数组
|
||
*/
|
||
getTextureCategories() {
|
||
return texturePresetManager.getCategories();
|
||
}
|
||
|
||
/**
|
||
* 搜索材质
|
||
* @param {String} keyword 搜索关键词
|
||
* @returns {Array} 匹配的材质列表
|
||
*/
|
||
searchTextures(keyword) {
|
||
return texturePresetManager.searchTextures(keyword);
|
||
}
|
||
|
||
/**
|
||
* 添加自定义材质
|
||
* @param {Object} textureData 材质数据
|
||
* @returns {String} 材质ID
|
||
*/
|
||
addCustomTexture(textureData) {
|
||
const textureId = texturePresetManager.addCustomTexture(textureData);
|
||
|
||
// 保存到本地存储
|
||
texturePresetManager.saveCustomTexturesToStorage();
|
||
|
||
return textureId;
|
||
}
|
||
|
||
/**
|
||
* 删除自定义材质
|
||
* @param {String} textureId 材质ID
|
||
* @returns {Boolean} 是否删除成功
|
||
*/
|
||
removeCustomTexture(textureId) {
|
||
const success = texturePresetManager.removeCustomTexture(textureId);
|
||
|
||
if (success) {
|
||
// 如果删除的是当前选中的材质,清空选择
|
||
if (this.state.selectedTextureId === textureId) {
|
||
this.state.selectedTextureId = null;
|
||
}
|
||
|
||
// 从最近使用中移除
|
||
const recentIndex = this.state.recentTextures.indexOf(textureId);
|
||
if (recentIndex !== -1) {
|
||
this.state.recentTextures.splice(recentIndex, 1);
|
||
}
|
||
|
||
// 保存到本地存储
|
||
texturePresetManager.saveCustomTexturesToStorage();
|
||
}
|
||
|
||
return success;
|
||
}
|
||
|
||
/**
|
||
* 从文件上传自定义材质
|
||
* @param {File} file 图片文件
|
||
* @param {String} name 材质名称(可选)
|
||
* @returns {Promise<String>} 材质ID
|
||
*/
|
||
uploadCustomTexture(file, name) {
|
||
return new Promise((resolve, reject) => {
|
||
// 验证文件
|
||
if (!texturePresetManager.validateTextureFile(file)) {
|
||
reject(new Error("无效的材质文件"));
|
||
return;
|
||
}
|
||
|
||
// 读取文件
|
||
const reader = new FileReader();
|
||
reader.onload = (e) => {
|
||
try {
|
||
const textureData = {
|
||
name: name || file.name.replace(/\.[^/.]+$/, ""),
|
||
path: e.target.result,
|
||
preview: e.target.result,
|
||
description: `用户上传的材质: ${file.name}`,
|
||
};
|
||
|
||
const textureId = this.addCustomTexture(textureData);
|
||
resolve(textureId);
|
||
} catch (error) {
|
||
reject(error);
|
||
}
|
||
};
|
||
|
||
reader.onerror = () => {
|
||
reject(new Error("文件读取失败"));
|
||
};
|
||
|
||
reader.readAsDataURL(file);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 导出材质预设配置
|
||
* @returns {String} JSON格式的配置
|
||
*/
|
||
exportTexturePresets() {
|
||
const config = {
|
||
texturePresets: this.state.texturePresets,
|
||
customTextures: texturePresetManager.exportCustomTextures(),
|
||
};
|
||
return JSON.stringify(config, null, 2);
|
||
}
|
||
|
||
/**
|
||
* 导入材质预设配置
|
||
* @param {String} configJson JSON格式的配置
|
||
* @returns {Boolean} 是否导入成功
|
||
*/
|
||
importTexturePresets(configJson) {
|
||
try {
|
||
const config = JSON.parse(configJson);
|
||
|
||
// 导入材质预设
|
||
if (config.texturePresets && Array.isArray(config.texturePresets)) {
|
||
this.state.texturePresets = [...this.state.texturePresets, ...config.texturePresets];
|
||
}
|
||
|
||
// 导入自定义材质
|
||
if (config.customTextures) {
|
||
texturePresetManager.importCustomTextures(config.customTextures);
|
||
}
|
||
|
||
return true;
|
||
} catch (error) {
|
||
console.error("导入材质预设失败:", error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化材质预设管理器
|
||
*/
|
||
initializeTexturePresets() {
|
||
// 从本地存储加载自定义材质
|
||
texturePresetManager.loadCustomTexturesFromStorage();
|
||
|
||
// 确保预设材质引用的是有效的材质ID
|
||
this.state.texturePresets.forEach((preset, index) => {
|
||
const texture = texturePresetManager.getTextureById(preset.textureId);
|
||
if (!texture) {
|
||
console.warn(`材质预设 "${preset.name}" 引用的材质 ${preset.textureId} 不存在`);
|
||
// 可以选择使用默认材质替换或删除该预设
|
||
if (texturePresetManager.getAllTextures().length > 0) {
|
||
preset.textureId = texturePresetManager.getAllTextures()[0].id;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 清空上传的纹理缓存
|
||
*/
|
||
clearUploadedTextures() {
|
||
this.state.uploadedTextures = [];
|
||
}
|
||
|
||
/**
|
||
* 添加纹理到缓存
|
||
* @param {String} textureId 材质ID
|
||
*/
|
||
cacheUploadedTexture(textureId) {
|
||
const texture = texturePresetManager.getTextureById(textureId);
|
||
if (texture && !this.state.uploadedTextures.includes(textureId)) {
|
||
this.state.uploadedTextures.push(textureId);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从缓存中移除纹理
|
||
* @param {String} textureId 材质ID
|
||
*/
|
||
removeCachedTexture(textureId) {
|
||
const index = this.state.uploadedTextures.indexOf(textureId);
|
||
if (index !== -1) {
|
||
this.state.uploadedTextures.splice(index, 1);
|
||
}
|
||
}
|
||
|
||
// 辅助方法
|
||
getRGBAColor() {
|
||
// 解析十六进制颜色并添加透明度
|
||
const hex = this.state.color.replace("#", "");
|
||
const r = parseInt(hex.substring(0, 2), 16);
|
||
const g = parseInt(hex.substring(2, 4), 16);
|
||
const b = parseInt(hex.substring(4, 6), 16);
|
||
|
||
return `rgba(${r}, ${g}, ${b}, ${this.state.opacity})`;
|
||
}
|
||
|
||
/**
|
||
* 获取当前笔刷的实际绘制颜色(包含透明度)
|
||
* @returns {String} RGBA格式的颜色字符串
|
||
*/
|
||
getCurrentBrushColor() {
|
||
return this.getRGBAColor();
|
||
}
|
||
|
||
/**
|
||
* 从RGBA颜色字符串中提取RGB值
|
||
* @param {String} rgbaColor RGBA颜色字符串
|
||
* @returns {Object} {r, g, b, a} 颜色值对象
|
||
*/
|
||
parseRGBAColor(rgbaColor) {
|
||
const rgbaMatch = rgbaColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
||
if (rgbaMatch) {
|
||
return {
|
||
r: parseInt(rgbaMatch[1]),
|
||
g: parseInt(rgbaMatch[2]),
|
||
b: parseInt(rgbaMatch[3]),
|
||
a: rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1,
|
||
};
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 将RGB值转换为十六进制颜色
|
||
* @param {Number} r 红色值 (0-255)
|
||
* @param {Number} g 绿色值 (0-255)
|
||
* @param {Number} b 蓝色值 (0-255)
|
||
* @returns {String} 十六进制颜色字符串
|
||
*/
|
||
rgbToHex(r, g, b) {
|
||
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
|
||
}
|
||
|
||
/**
|
||
* 获取当前阴影配置对象
|
||
* @returns {Object|null} fabric.Shadow配置对象或null
|
||
*/
|
||
getShadowConfig() {
|
||
if (!this.state.shadowEnabled) {
|
||
return null;
|
||
}
|
||
|
||
return {
|
||
color: this.state.shadowColor,
|
||
blur: this.state.shadowWidth,
|
||
offsetX: this.state.shadowOffsetX,
|
||
offsetY: this.state.shadowOffsetY,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 创建fabric.Shadow实例
|
||
* @returns {fabric.Shadow|null} fabric.Shadow实例或null
|
||
*/
|
||
createFabricShadow() {
|
||
const config = this.getShadowConfig();
|
||
if (!config) {
|
||
return null;
|
||
}
|
||
|
||
// 确保fabric已加载
|
||
if (typeof fabric !== "undefined" && fabric.Shadow) {
|
||
return new fabric.Shadow(config);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
} |