Sketch2Render / script.js
DucLai's picture
Update script.js
075ef7d verified
class SketchToRenderApp {
constructor() {
this.canvas = document.getElementById('drawingCanvas');
this.ctx = this.canvas.getContext('2d');
this.isDrawing = false;
this.drawingHistory = [];
this.currentPath = [];
this.renderedImageUrl = null;
this.initializeCanvas();
this.setupEventListeners();
this.saveCanvasState();
}
initializeCanvas() {
this.ctx.lineCap = 'round';
this.ctx.lineJoin = 'round';
this.ctx.fillStyle = 'white';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
setupEventListeners() {
this.canvas.addEventListener('mousedown', (e) => this.startDrawing(e));
this.canvas.addEventListener('mousemove', (e) => this.draw(e));
this.canvas.addEventListener('mouseup', () => this.stopDrawing());
this.canvas.addEventListener('mouseout', () => this.stopDrawing());
this.canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const touch = e.touches[0];
const mouseEvent = new MouseEvent('mousedown', {
clientX: touch.clientX,
clientY: touch.clientY
});
this.canvas.dispatchEvent(mouseEvent);
});
this.canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
const touch = e.touches[0];
const mouseEvent = new MouseEvent('mousemove', {
clientX: touch.clientX,
clientY: touch.clientY
});
this.canvas.dispatchEvent(mouseEvent);
});
this.canvas.addEventListener('touchend', (e) => {
e.preventDefault();
const mouseEvent = new MouseEvent('mouseup', {});
this.canvas.dispatchEvent(mouseEvent);
});
document.getElementById('brushSize').addEventListener('input', (e) => {
document.getElementById('brushSizeValue').textContent = e.target.value + 'px';
});
document.getElementById('clearCanvas').addEventListener('click', () => this.clearCanvas());
document.getElementById('undoBtn').addEventListener('click', () => this.undo());
document.getElementById('generateBtn').addEventListener('click', () => this.generateRender());
document.getElementById('downloadBtn').addEventListener('click', () => this.downloadImage());
}
getMousePos(e) {
const rect = this.canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
startDrawing(e) {
this.isDrawing = true;
const pos = this.getMousePos(e);
this.currentPath = [pos];
this.ctx.strokeStyle = document.getElementById('brushColor').value;
this.ctx.lineWidth = document.getElementById('brushSize').value;
this.ctx.beginPath();
this.ctx.moveTo(pos.x, pos.y);
}
draw(e) {
if (!this.isDrawing) return;
const pos = this.getMousePos(e);
this.currentPath.push(pos);
this.ctx.lineTo(pos.x, pos.y);
this.ctx.stroke();
}
stopDrawing() {
if (this.isDrawing) {
this.isDrawing = false;
this.saveCanvasState();
}
}
saveCanvasState() {
this.drawingHistory.push(this.canvas.toDataURL());
if (this.drawingHistory.length > 20) {
this.drawingHistory.shift();
}
}
clearCanvas() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.fillStyle = 'white';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.saveCanvasState();
}
undo() {
if (this.drawingHistory.length > 1) {
this.drawingHistory.pop();
const previousState = this.drawingHistory[this.drawingHistory.length - 1];
const img = new Image();
img.onload = () => {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.drawImage(img, 0, 0);
};
img.src = previousState;
}
}
async generateRender() {
if (!this.isCanvasEmpty()) {
this.showLoading(true);
this.hideError();
try {
const analysisResult = await this.analyzeSketchWithGemini();
const imageUrl = await this.generateImageFromDescription(analysisResult.description);
this.displayResults(imageUrl, analysisResult.score, analysisResult.prediction);
} catch (error) {
console.error(error);
this.showError(`Lỗi: ${error.message}`);
} finally {
this.showLoading(false);
}
} else {
this.showError('Vui lòng vẽ gì đó trước khi tạo render');
}
}
isCanvasEmpty() {
const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
return imageData.data.every((pixel, index) => {
if ((index + 1) % 4 === 0) return true;
return pixel === 255;
});
}
async analyzeSketchWithGemini() {
const canvasDataURL = this.canvas.toDataURL('image/png');
const base64Data = canvasDataURL.split(',')[1];
const prompt = `Hãy phân tích bức vẽ sketch sau và cung cấp:
1. ĐIỂM SỐ: Đánh giá độ thẩm mỹ của sketch theo thang 6–10
2. DỰ ĐOÁN: Tên ngắn gọn của vật thể được vẽ (ví dụ: "Mèo", "Ngôi nhà", "Hoa")
3. MÔ TẢ: Viết mô tả ngắn gọn bằng tiếng Anh, mô tả nội dung sketch dùng cho AI vẽ lại. Bao gồm:
- Miêu tả đối tượng
- Phong cách: cute, line-art style, soft pastel colors, minimalist illustration
- Tránh chi tiết phức tạp, ưu tiên phong cách đơn giản, đáng yêu
Định dạng phản hồi:
ĐIỂM: [6–10]
DỰ ĐOÁN: [Tên đối tượng]
MÔ TẢ: ["A cute [object] in line-art style, with soft pastel colors, minimalist illustration..."]`;
const geminiApiKey = 'AIzaSyCinF1_Z7XXPzu_zvMzifKyPIy7i7eUBGc'; // dùng key bạn dán sẵn
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${geminiApiKey}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
contents: [{
parts: [
{ text: prompt },
{
inline_data: {
mime_type: "image/png",
data: base64Data
}
}
]
}]
})
});
const data = await response.json();
const text = data.candidates[0].content.parts[0].text;
return this.parseGeminiResponse(text);
}
parseGeminiResponse(text) {
const score = parseInt(text.match(/ĐIỂM[:\s]*(\d+)/i)?.[1] || 7);
const prediction = (text.match(/DỰ ĐOÁN[:\s]*(.*)/i)?.[1] || 'Không xác định').trim();
const description = (text.match(/MÔ TẢ[:\s]*([\s\S]*)/i)?.[1] || 'simple line drawing').trim();
return { score, prediction, description };
}
// async generateImageFromDescription(description) {
// const enhancedPrompt = `${description}`;
// const seed = Math.floor(Math.random() * 1e6);
// const engineId = 'stable-diffusion-xl-1024-v1-0';
// const url = `https://api.stability.ai/v1/generation/${engineId}/text-to-image`;
// const STABILITY_API_KEY = 'sk-JzOAcde6bqHRjfInzdrNmNjjrCs0LFAvB838xi14OhhEQ06T '; // Dán key của bạn
// const response = await fetch(url, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// 'Accept': 'application/json',
// 'Authorization': `Bearer ${STABILITY_API_KEY}`
// },
// body: JSON.stringify({
// text_prompts: [{ text: enhancedPrompt }],
// cfg_scale: 7,
// width: 1024,
// height: 1024,
// samples: 1,
// steps: 30,
// seed: seed
// })
// });
// const data = await response.json();
// const base64 = data.artifacts[0].base64;
// return `data:image/png;base64,${base64}`;
// }
async generateImageFromDescription(description) {
// Using Pollinations AI (free image generation API)
// This creates an image based on the description from Gemini
const enhancedPrompt = `${description}, high quality, detailed artwork, colored pencil, cute illustration`;
const encodedPrompt = encodeURIComponent(enhancedPrompt);
// Generate a unique seed to avoid caching
const seed = Math.floor(Math.random() * 1000000);
const imageUrl = `https://image.pollinations.ai/prompt/${encodedPrompt}?seed=${seed}&width=512&height=512&model=flux`;
// Verify the image loads
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(imageUrl);
img.onerror = () => reject(new Error('Không thể tạo ảnh render'));
img.src = imageUrl;
});
}
displayResults(imageUrl, score, prediction) {
const imageContainer = document.getElementById('imageContainer');
imageContainer.innerHTML = `<img src="${imageUrl}" alt="Rendered Image" class="rendered-image">`;
this.renderedImageUrl = imageUrl;
document.getElementById('downloadBtn').style.display = 'block';
document.getElementById('scoreDisplay').textContent = `${score}/10`;
document.getElementById('predictionText').textContent = prediction;
document.getElementById('resultContent').style.display = 'block';
}
downloadImage() {
if (this.renderedImageUrl) {
const link = document.createElement('a');
link.download = `sketch-render-${Date.now()}.png`;
link.href = this.renderedImageUrl;
link.click();
}
}
showLoading(show) {
document.getElementById('loading').style.display = show ? 'block' : 'none';
document.getElementById('resultContent').style.display = show ? 'none' : 'block';
}
showError(message) {
const errorElement = document.getElementById('errorMessage');
errorElement.textContent = message;
errorElement.style.display = 'block';
}
hideError() {
document.getElementById('errorMessage').style.display = 'none';
}
}
// Khởi tạo khi DOM sẵn sàng
document.addEventListener('DOMContentLoaded', () => {
new SketchToRenderApp();
});