You've already forked Nano-Banana-AI-Image-Editor
初始化提交
This commit is contained in:
@@ -29,18 +29,18 @@ export const ImageCanvas: React.FC = () => {
|
||||
const [isDrawing, setIsDrawing] = useState(false);
|
||||
const [currentStroke, setCurrentStroke] = useState<number[]>([]);
|
||||
|
||||
// Load image and auto-fit when canvasImage changes
|
||||
// 加载图像并在 canvasImage 变化时自动适应
|
||||
useEffect(() => {
|
||||
if (canvasImage) {
|
||||
const img = new window.Image();
|
||||
img.onload = () => {
|
||||
setImage(img);
|
||||
|
||||
// Only auto-fit if this is a new image (no existing zoom/pan state)
|
||||
// 仅在这是新图像时自动适应(没有现有的缩放/平移状态)
|
||||
if (canvasZoom === 1 && canvasPan.x === 0 && canvasPan.y === 0) {
|
||||
// Auto-fit image to canvas
|
||||
// 自动适应图像到画布
|
||||
const isMobile = window.innerWidth < 768;
|
||||
const padding = isMobile ? 0.9 : 0.8; // Use more of the screen on mobile
|
||||
const padding = isMobile ? 0.9 : 0.8; // 在移动设备上使用更多屏幕空间
|
||||
|
||||
const scaleX = (stageSize.width * padding) / img.width;
|
||||
const scaleY = (stageSize.height * padding) / img.height;
|
||||
@@ -50,7 +50,7 @@ export const ImageCanvas: React.FC = () => {
|
||||
|
||||
setCanvasZoom(optimalZoom);
|
||||
|
||||
// Center the image
|
||||
// 居中图像
|
||||
setCanvasPan({ x: 0, y: 0 });
|
||||
}
|
||||
};
|
||||
@@ -60,7 +60,7 @@ export const ImageCanvas: React.FC = () => {
|
||||
}
|
||||
}, [canvasImage, stageSize, setCanvasZoom, setCanvasPan, canvasZoom, canvasPan]);
|
||||
|
||||
// Handle stage resize
|
||||
// 处理舞台大小调整
|
||||
useEffect(() => {
|
||||
const updateSize = () => {
|
||||
const container = document.getElementById('canvas-container');
|
||||
@@ -84,18 +84,18 @@ export const ImageCanvas: React.FC = () => {
|
||||
const stage = e.target.getStage();
|
||||
const pos = stage.getPointerPosition();
|
||||
|
||||
// Use Konva's getRelativePointerPosition for accurate coordinates
|
||||
// 使用 Konva 的 getRelativePointerPosition 获取准确坐标
|
||||
const relativePos = stage.getRelativePointerPosition();
|
||||
|
||||
// Calculate image bounds on the stage
|
||||
// 计算图像在舞台上的边界
|
||||
const imageX = (stageSize.width / canvasZoom - image.width) / 2;
|
||||
const imageY = (stageSize.height / canvasZoom - image.height) / 2;
|
||||
|
||||
// Convert to image-relative coordinates
|
||||
// 转换为相对于图像的坐标
|
||||
const relativeX = relativePos.x - imageX;
|
||||
const relativeY = relativePos.y - imageY;
|
||||
|
||||
// Check if click is within image bounds
|
||||
// 检查点击是否在图像边界内
|
||||
if (relativeX >= 0 && relativeX <= image.width && relativeY >= 0 && relativeY <= image.height) {
|
||||
setCurrentStroke([relativeX, relativeY]);
|
||||
}
|
||||
@@ -107,18 +107,18 @@ export const ImageCanvas: React.FC = () => {
|
||||
const stage = e.target.getStage();
|
||||
const pos = stage.getPointerPosition();
|
||||
|
||||
// Use Konva's getRelativePointerPosition for accurate coordinates
|
||||
// 使用 Konva 的 getRelativePointerPosition 获取准确坐标
|
||||
const relativePos = stage.getRelativePointerPosition();
|
||||
|
||||
// Calculate image bounds on the stage
|
||||
// 计算图像在舞台上的边界
|
||||
const imageX = (stageSize.width / canvasZoom - image.width) / 2;
|
||||
const imageY = (stageSize.height / canvasZoom - image.height) / 2;
|
||||
|
||||
// Convert to image-relative coordinates
|
||||
// 转换为相对于图像的坐标
|
||||
const relativeX = relativePos.x - imageX;
|
||||
const relativeY = relativePos.y - imageY;
|
||||
|
||||
// Check if within image bounds
|
||||
// 检查是否在图像边界内
|
||||
if (relativeX >= 0 && relativeX <= image.width && relativeY >= 0 && relativeY <= image.height) {
|
||||
setCurrentStroke([...currentStroke, relativeX, relativeY]);
|
||||
}
|
||||
@@ -174,10 +174,10 @@ export const ImageCanvas: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Toolbar */}
|
||||
<div className="p-3 border-b border-gray-800 bg-gray-950">
|
||||
{/* 工具栏 */}
|
||||
<div className="p-3 border-b border-gray-200 bg-white">
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Left side - Zoom controls */}
|
||||
{/* 左侧 - 缩放控制 */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button variant="outline" size="sm" onClick={() => handleZoom(-0.1)}>
|
||||
<ZoomOut className="h-4 w-4" />
|
||||
@@ -193,12 +193,12 @@ export const ImageCanvas: React.FC = () => {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Right side - Tools and actions */}
|
||||
{/* 右侧 - 工具和操作 */}
|
||||
<div className="flex items-center space-x-2">
|
||||
{selectedTool === 'mask' && (
|
||||
<>
|
||||
<div className="flex items-center space-x-2 mr-2">
|
||||
<span className="text-xs text-gray-400">Brush:</span>
|
||||
<span className="text-xs text-gray-400">画笔:</span>
|
||||
<input
|
||||
type="range"
|
||||
min="5"
|
||||
@@ -227,35 +227,35 @@ export const ImageCanvas: React.FC = () => {
|
||||
className={cn(showMasks && 'bg-yellow-400/10 border-yellow-400/50')}
|
||||
>
|
||||
{showMasks ? <Eye className="h-4 w-4" /> : <EyeOff className="h-4 w-4" />}
|
||||
<span className="hidden sm:inline ml-2">Masks</span>
|
||||
<span className="hidden sm:inline ml-2">遮罩</span>
|
||||
</Button>
|
||||
|
||||
{canvasImage && (
|
||||
<Button variant="secondary" size="sm" onClick={handleDownload}>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
<span className="hidden sm:inline">Download</span>
|
||||
<span className="hidden sm:inline">下载</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Canvas Area */}
|
||||
{/* 画布区域 */}
|
||||
<div
|
||||
id="canvas-container"
|
||||
className="flex-1 relative overflow-hidden bg-gray-800"
|
||||
className="flex-1 relative overflow-hidden bg-gray-100"
|
||||
>
|
||||
{!image && !isGenerating && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="text-6xl mb-4">🍌</div>
|
||||
<h2 className="text-xl font-medium text-gray-300 mb-2">
|
||||
Welcome to Nano Banana Framework
|
||||
欢迎使用 Nano Banana 框架
|
||||
</h2>
|
||||
<p className="text-gray-500 max-w-md">
|
||||
{selectedTool === 'generate'
|
||||
? 'Start by describing what you want to create in the prompt box'
|
||||
: 'Upload an image to begin editing'
|
||||
? '首先在提示框中描述您想要创建的内容'
|
||||
: '上传图像开始编辑'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
@@ -266,7 +266,7 @@ export const ImageCanvas: React.FC = () => {
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-gray-900/50">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-yellow-400 mb-4" />
|
||||
<p className="text-gray-300">Creating your image...</p>
|
||||
<p className="text-gray-300">正在创建您的图像...</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -302,7 +302,7 @@ export const ImageCanvas: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Brush Strokes */}
|
||||
{/* 画笔描边 */}
|
||||
{showMasks && brushStrokes.map((stroke) => (
|
||||
<Line
|
||||
key={stroke.id}
|
||||
@@ -319,7 +319,7 @@ export const ImageCanvas: React.FC = () => {
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Current stroke being drawn */}
|
||||
{/* 正在绘制的当前描边 */}
|
||||
{isDrawing && currentStroke.length > 2 && (
|
||||
<Line
|
||||
points={currentStroke}
|
||||
@@ -338,12 +338,12 @@ export const ImageCanvas: React.FC = () => {
|
||||
</Stage>
|
||||
</div>
|
||||
|
||||
{/* Status Bar */}
|
||||
<div className="p-3 border-t border-gray-800 bg-gray-950">
|
||||
{/* 状态栏 */}
|
||||
<div className="p-3 border-t border-gray-200 bg-white">
|
||||
<div className="flex items-center justify-between text-xs text-gray-500">
|
||||
<div className="flex items-center space-x-4">
|
||||
{brushStrokes.length > 0 && (
|
||||
<span className="text-yellow-400">{brushStrokes.length} brush stroke{brushStrokes.length !== 1 ? 's' : ''}</span>
|
||||
<span className="text-yellow-400">{brushStrokes.length} 个画笔描边{brushStrokes.length !== 1 ? 's' : ''}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -361,7 +361,7 @@ export const ImageCanvas: React.FC = () => {
|
||||
</span>
|
||||
<span className="text-gray-600 hidden md:inline">•</span>
|
||||
<span className="text-yellow-400 hidden md:inline">⚡</span>
|
||||
<span className="hidden md:inline">Powered by Gemini 2.5 Flash Image</span>
|
||||
<span className="hidden md:inline">由 Gemini 2.5 Flash Image 提供支持</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user