Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a99cb57a2f |
10
app.py
10
app.py
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
图片编辑器 - Image Editor v1.2.5
|
图片编辑器 - Image Editor v1.2.6
|
||||||
前端图片处理工具:合并、分割、挖孔填充、圆形切图、文字图片等
|
前端图片处理工具:合并、分割、挖孔填充、圆形切图、文字图片等
|
||||||
v1.2.5: 修复操作后面板判断问题
|
v1.2.6: 操作后显示预览效果,保留原始图片
|
||||||
|
|
||||||
端口: 19018
|
端口: 19018
|
||||||
"""
|
"""
|
||||||
@@ -25,7 +25,7 @@ def index():
|
|||||||
|
|
||||||
@app.route('/api/health')
|
@app.route('/api/health')
|
||||||
def health():
|
def health():
|
||||||
return jsonify({'status': 'ok', 'version': '1.2.5', 'time': datetime.now().isoformat()})
|
return jsonify({'status': 'ok', 'version': '1.2.6', 'time': datetime.now().isoformat()})
|
||||||
|
|
||||||
@app.route('/api/save', methods=['POST'])
|
@app.route('/api/save', methods=['POST'])
|
||||||
def save_image():
|
def save_image():
|
||||||
@@ -64,9 +64,9 @@ def list_images():
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("=" * 50)
|
print("=" * 50)
|
||||||
print("图片编辑器 - Image Editor v1.2.5")
|
print("图片编辑器 - Image Editor v1.2.6")
|
||||||
print("=" * 50)
|
print("=" * 50)
|
||||||
print("修复操作后面板判断问题")
|
print("操作后显示预览效果,保留原始图片")
|
||||||
print(f"访问地址: http://localhost:19018")
|
print(f"访问地址: http://localhost:19018")
|
||||||
print("=" * 50)
|
print("=" * 50)
|
||||||
app.run(host='0.0.0.0', port=19018, debug=True)
|
app.run(host='0.0.0.0', port=19018, debug=True)
|
||||||
BIN
outputs/edited_1776761087118.png
Normal file
BIN
outputs/edited_1776761087118.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 763 KiB |
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>图片编辑器 - Image Editor v1.2.5</title>
|
<title>图片编辑器 - Image Editor v1.2.6</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
@@ -183,7 +183,10 @@
|
|||||||
selection: null,
|
selection: null,
|
||||||
textStyles: { bold: false, italic: false },
|
textStyles: { bold: false, italic: false },
|
||||||
mergeImageOrder: [],
|
mergeImageOrder: [],
|
||||||
mergeImageSizes: {}
|
mergeImageSizes: {},
|
||||||
|
previewMode: false, // 预览模式
|
||||||
|
previewData: null, // 预览图片数据
|
||||||
|
originalImages: null // 预览前保存的原始图片
|
||||||
};
|
};
|
||||||
|
|
||||||
const canvas = document.getElementById('mainCanvas');
|
const canvas = document.getElementById('mainCanvas');
|
||||||
@@ -194,10 +197,129 @@
|
|||||||
function hasImage() {
|
function hasImage() {
|
||||||
return state.images.length > 0 || document.getElementById('emptyHint').classList.contains('hidden');
|
return state.images.length > 0 || document.getElementById('emptyHint').classList.contains('hidden');
|
||||||
}
|
}
|
||||||
function goHome() {
|
// 显示预览效果(操作后显示结果,但保留原始图片)
|
||||||
document.querySelectorAll('.tool-btn').forEach(btn => btn.classList.remove('active'));
|
function showPreviewResult(dataUrl, name) {
|
||||||
state.currentTool = null;
|
// 进入预览模式
|
||||||
showPanel('home');
|
state.previewMode = true;
|
||||||
|
state.previewData = dataUrl;
|
||||||
|
|
||||||
|
// 保存原始图片状态
|
||||||
|
state.originalImages = state.images.map(img => ({
|
||||||
|
...img,
|
||||||
|
// 不复制img对象,只保存数据
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 显示预览效果(追加结果图片,不清除原始)
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
// 调整画布大小以适应结果图片
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
|
||||||
|
// 绘制结果图片
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
|
||||||
|
hideEmptyHint();
|
||||||
|
|
||||||
|
// 显示预览面板
|
||||||
|
showPreviewPanel(name);
|
||||||
|
};
|
||||||
|
img.src = dataUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示预览面板
|
||||||
|
function showPreviewPanel(operationName) {
|
||||||
|
document.getElementById('panelTitle').textContent = '预览效果';
|
||||||
|
document.getElementById('panelContent').innerHTML = `
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="p-3 bg-green-50 rounded-lg">
|
||||||
|
<p class="text-sm text-green-700">
|
||||||
|
<i class="ri-check-line"></i> ${operationName} 完成!
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-green-600 mt-1">预览效果已显示,原图片仍保留</p>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500">选择"应用结果"替换原图,或"取消"恢复</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.getElementById('panelActions').innerHTML = `
|
||||||
|
<button onclick="applyPreviewResult()" class="px-4 py-2 bg-green-500 text-white rounded-lg w-full mb-2">
|
||||||
|
<i class="ri-check-double-line mr-1"></i>应用结果
|
||||||
|
</button>
|
||||||
|
<button onclick="cancelPreview()" class="px-4 py-2 bg-gray-200 rounded-lg w-full mb-2">
|
||||||
|
<i class="ri-close-line mr-1"></i>取消预览
|
||||||
|
</button>
|
||||||
|
<button onclick="goHome()" class="px-4 py-2 bg-blue-100 text-blue-600 rounded-lg w-full">
|
||||||
|
<i class="ri-home-line mr-1"></i>返回首页
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用预览结果
|
||||||
|
function applyPreviewResult() {
|
||||||
|
if (!state.previewData) return;
|
||||||
|
|
||||||
|
// 清空原始图片,只保留结果
|
||||||
|
state.images = [];
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
state.images.push({
|
||||||
|
img: img,
|
||||||
|
name: 'result_' + Date.now(),
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: img.width,
|
||||||
|
height: img.height,
|
||||||
|
dataUrl: state.previewData
|
||||||
|
});
|
||||||
|
|
||||||
|
drawCanvas();
|
||||||
|
updateImageList();
|
||||||
|
saveState('应用结果');
|
||||||
|
|
||||||
|
// 清除预览状态
|
||||||
|
state.previewMode = false;
|
||||||
|
state.previewData = null;
|
||||||
|
state.originalImages = null;
|
||||||
|
|
||||||
|
// 返回首页
|
||||||
|
goHome();
|
||||||
|
};
|
||||||
|
img.src = state.previewData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消预览
|
||||||
|
function cancelPreview() {
|
||||||
|
if (!state.originalImages) {
|
||||||
|
goHome();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复原始图片
|
||||||
|
state.images = [];
|
||||||
|
state.originalImages.forEach((item, idx) => {
|
||||||
|
if (state.images[idx] && state.images[idx].img) {
|
||||||
|
state.images.push(state.images[idx]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果没有恢复成功,重新从画布历史恢复
|
||||||
|
if (state.images.length === 0 && state.historyStack.length > 0) {
|
||||||
|
const lastState = state.historyStack[state.historyStack.length - 1];
|
||||||
|
restoreState(lastState);
|
||||||
|
} else {
|
||||||
|
drawCanvas();
|
||||||
|
updateImageList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除预览状态
|
||||||
|
state.previewMode = false;
|
||||||
|
state.previewData = null;
|
||||||
|
state.originalImages = null;
|
||||||
|
|
||||||
|
// 返回首页
|
||||||
|
goHome();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新当前工具面板(执行操作后保持当前配置)
|
// 刷新当前工具面板(执行操作后保持当前配置)
|
||||||
@@ -863,6 +985,11 @@
|
|||||||
img.src = dataUrl;
|
img.src = dataUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载操作结果(不清空原始图片)
|
||||||
|
function loadOperationResult(dataUrl, name, operationName) {
|
||||||
|
showPreviewResult(dataUrl, operationName);
|
||||||
|
}
|
||||||
|
|
||||||
function drawCanvas() {
|
function drawCanvas() {
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
ctx.fillStyle = '#ffffff';
|
ctx.fillStyle = '#ffffff';
|
||||||
@@ -964,6 +1091,7 @@
|
|||||||
canvas.width = cols * maxWidth + (cols - 1) * gap;
|
canvas.width = cols * maxWidth + (cols - 1) * gap;
|
||||||
canvas.height = rows * maxHeight + (rows - 1) * gap;
|
canvas.height = rows * maxHeight + (rows - 1) * gap;
|
||||||
|
|
||||||
|
// 绘制合并结果到画布(预览)
|
||||||
ctx.fillStyle = bgColor;
|
ctx.fillStyle = bgColor;
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
@@ -983,10 +1111,9 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 显示预览效果(不清除原始图片)
|
||||||
const mergedData = canvas.toDataURL('image/png');
|
const mergedData = canvas.toDataURL('image/png');
|
||||||
hideEmptyHint(); state.images = [];
|
showPreviewResult(mergedData, '合并图片');
|
||||||
loadImage(mergedData, 'merged');
|
|
||||||
refreshCurrentPanel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applySplit() {
|
function applySplit() {
|
||||||
@@ -1162,9 +1289,7 @@
|
|||||||
ctx.drawImage(circleCanvas, 0, 0);
|
ctx.drawImage(circleCanvas, 0, 0);
|
||||||
|
|
||||||
const circleData = canvas.toDataURL('image/png');
|
const circleData = canvas.toDataURL('image/png');
|
||||||
hideEmptyHint(); state.images = [];
|
showPreviewResult(circleData, '圆形切图');
|
||||||
loadImage(circleData, 'circle');
|
|
||||||
refreshCurrentPanel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTextImage() {
|
function createTextImage() {
|
||||||
@@ -1213,9 +1338,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const textData = canvas.toDataURL('image/png');
|
const textData = canvas.toDataURL('image/png');
|
||||||
hideEmptyHint(); state.images = [];
|
showPreviewResult(textData, '文字图片');
|
||||||
loadImage(textData, 'text');
|
|
||||||
refreshCurrentPanel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyResize() {
|
function applyResize() {
|
||||||
@@ -1231,9 +1354,7 @@
|
|||||||
ctx.drawImage(img.img, 0, 0, newWidth, newHeight);
|
ctx.drawImage(img.img, 0, 0, newWidth, newHeight);
|
||||||
|
|
||||||
const resizedData = canvas.toDataURL('image/png');
|
const resizedData = canvas.toDataURL('image/png');
|
||||||
hideEmptyHint(); state.images = [];
|
showPreviewResult(resizedData, '调整大小');
|
||||||
loadImage(resizedData, 'resized');
|
|
||||||
refreshCurrentPanel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyCrop() {
|
function applyCrop() {
|
||||||
@@ -1251,9 +1372,7 @@
|
|||||||
ctx.drawImage(img.img, x, y, w, h, 0, 0, w, h);
|
ctx.drawImage(img.img, x, y, w, h, 0, 0, w, h);
|
||||||
|
|
||||||
const croppedData = canvas.toDataURL('image/png');
|
const croppedData = canvas.toDataURL('image/png');
|
||||||
hideEmptyHint(); state.images = [];
|
showPreviewResult(croppedData, '裁剪');
|
||||||
loadImage(croppedData, 'cropped');
|
|
||||||
refreshCurrentPanel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveImage() {
|
function saveImage() {
|
||||||
|
|||||||
Reference in New Issue
Block a user