이 간단한 썸네일 편집기를 사용하면 이미지를 업로드하고, 사용자 정의 텍스트를 추가하고, 다양한 스타일을 실시간으로 적용할 수 있습니다. 글꼴 크기, 텍스트 색상을 쉽게 조정하고 굵게, 기울임꼴, 밑줄 및 윤곽선 효과를 추가할 수 있습니다. 실시간 미리보기 기능을 통해 변경 사항을 즉시 확인할 수 있습니다. 만족스러우면 다운로드 또는 공유할 수 있는 고품질 썸네일 이미지를 생성하세요. 시각적 콘텐츠를 쉽게 향상시키려는 콘텐츠 제작자, 마케팅 담당자 및 디자이너에게 적합합니다.
이 썸네일 편집기는 아주 간단한 편집만 가능합니다.
텍스트를 입력하고, 크기, 색깔, 외곽선 등을 간편하게 편집할 수 있습니다.
고급 기능은 없고, 간단 - 대량 - 신속 생산에 적합합니다.
이번 편집기는 v1입니다. 고급 기능은 차츰 추가해 가면서 나만의 이미지 편집기로 만들어 갈 계획입니다.
그리고, 다음편에서는 이미지 생성을 AI에게 요청하는 형태로 업데이트 하는 것과
파일 저장 등의 기능 등을 반영할 계획입니다
자바스크립트로 만들었고, 파일 구성은 다음과 같습니다.
index.html - style.css - script.js
파이썬 fastapi로 만들다가 생각보다 좋은 결과물이 안나와서 급히 자바스크립트로 변경해 만들었습니다.
index는 부트스트랩 스타일로 간단하게 바꿨습니다.
아래는 코드 전문입니다.
Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Thumbnail Editor</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container mt-5">
<div class="row">
<div class="col text-center">
<h1 class="mb-4">Thumbnail Editor</h1>
</div>
</div>
<div class="row">
<div class="col text-center">
<input type="file" id="imageUpload" accept="image/*" class="form-control-file mb-3">
</div>
</div>
<div class="row mt-4">
<div class="col-md-6 text-center">
<h2>Original Image</h2>
<div class="image-preview">
<img id="originalImage" src="" alt="Original Image">
</div>
</div>
<div class="col-md-6 text-center">
<h2>Edited Image</h2>
<div class="image-preview">
<canvas id="imageCanvas"></canvas>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col text-center">
<h2>Text Settings</h2>
<div class="form-group">
<label for="textInput">Text:</label>
<input type="text" id="textInput" placeholder="Enter your text here" class="form-control mb-3">
</div>
<div class="form-group row">
<div class="col-md-4">
<label for="fontSizeInput">Font Size:</label>
<input type="number" id="fontSizeInput" value="30" min="10" max="100" class="form-control mb-3">
</div>
<div class="col-md-4">
<label for="fontFamilyInput">Font Family:</label>
<select id="fontFamilyInput" class="form-control mb-3">
<option value="Arial">Arial</option>
<option value="Verdana">Verdana</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Courier New">Courier New</option>
<option value="Georgia">Georgia</option>
</select>
</div>
<div class="col-md-4">
<label for="textColorInput">Text Color:</label>
<input type="color" id="textColorInput" value="#ffffff" class="form-control mb-3">
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<label for="outlineCheckbox">Outline:</label>
<div class="form-check">
<input type="checkbox" id="outlineCheckbox" class="form-check-input" checked>
<label for="outlineCheckbox" class="form-check-label">Enable</label>
</div>
</div>
<div class="col-md-3">
<label for="outlineColorInput">Outline Color:</label>
<input type="color" id="outlineColorInput" value="#000000" class="form-control mb-3">
</div>
<div class="col-md-2">
<label for="boldCheckbox">Bold:</label>
<div class="form-check">
<input type="checkbox" id="boldCheckbox" class="form-check-input">
<label for="boldCheckbox" class="form-check-label">Enable</label>
</div>
</div>
<div class="col-md-2">
<label for="italicCheckbox">Italic:</label>
<div class="form-check">
<input type="checkbox" id="italicCheckbox" class="form-check-input">
<label for="italicCheckbox" class="form-check-label">Enable</label>
</div>
</div>
<div class="col-md-2">
<label for="underlineCheckbox">Underline:</label>
<div class="form-check">
<input type="checkbox" id="underlineCheckbox" class="form-check-input">
<label for="underlineCheckbox" class="form-check-label">Enable</label>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-5">
<div class="col text-center">
<button id="generateButton" class="btn btn-primary mb-5">Generate Thumbnail</button>
</div>
</div>
</div>
<script src="script.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
</body>
</html>
style.css
.image-preview {
width: 100%;
padding-top: 56.25%; /* 16:9 Aspect Ratio */
position: relative;
background-color: #f0f0f0;
border: 1px solid #ddd;
margin-top: 20px;
}
.image-preview img, .image-preview canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
script.js
document.getElementById('imageUpload').addEventListener('change', handleImageUpload);
document.getElementById('textInput').addEventListener('input', updateCanvas);
document.getElementById('fontSizeInput').addEventListener('input', updateCanvas);
document.getElementById('fontFamilyInput').addEventListener('change', updateCanvas);
document.getElementById('textColorInput').addEventListener('input', updateCanvas);
document.getElementById('outlineCheckbox').addEventListener('change', updateCanvas);
document.getElementById('outlineColorInput').addEventListener('input', updateCanvas);
document.getElementById('boldCheckbox').addEventListener('change', updateCanvas);
document.getElementById('italicCheckbox').addEventListener('change', updateCanvas);
document.getElementById('underlineCheckbox').addEventListener('change', updateCanvas);
document.getElementById('generateButton').addEventListener('click', generateThumbnail);
let uploadedImage;
function handleImageUpload(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function (e) {
const img = new Image();
img.onload = function () {
const originalImage = document.getElementById('originalImage');
originalImage.src = img.src;
const canvas = document.getElementById('imageCanvas');
const ctx = canvas.getContext('2d');
canvas.width = 600;
canvas.height = 337.5; // 600 / (16/9)
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
uploadedImage = img;
updateCanvas();
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
}
function updateCanvas() {
if (!uploadedImage) return;
const canvas = document.getElementById('imageCanvas');
const ctx = canvas.getContext('2d');
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Redraw the uploaded image
ctx.drawImage(uploadedImage, 0, 0, canvas.width, canvas.height);
const text = document.getElementById('textInput').value;
const fontSize = document.getElementById('fontSizeInput').value;
const fontFamily = document.getElementById('fontFamilyInput').value;
const textColor = document.getElementById('textColorInput').value;
const outline = document.getElementById('outlineCheckbox').checked;
const outlineColor = document.getElementById('outlineColorInput').value;
const bold = document.getElementById('boldCheckbox').checked;
const italic = document.getElementById('italicCheckbox').checked;
const underline = document.getElementById('underlineCheckbox').checked;
// Construct the font style
let fontStyle = '';
if (bold) fontStyle += 'bold ';
if (italic) fontStyle += 'italic ';
fontStyle += `${fontSize}px ${fontFamily}`;
ctx.font = fontStyle;
ctx.fillStyle = textColor;
ctx.textAlign = 'center';
const maxWidth = canvas.width - 20; // 10px padding on each side
const lineHeight = parseInt(fontSize) + 10;
const x = canvas.width / 2;
const y = canvas.height / 2;
wrapText(ctx, text, x, y, maxWidth, lineHeight, outline, outlineColor, underline);
}
function generateThumbnail() {
if (!uploadedImage) return;
updateCanvas();
// Show the image in a pop-up
const canvas = document.getElementById('imageCanvas');
const imageUrl = canvas.toDataURL('image/png');
const popupWindow = window.open('', '_blank');
popupWindow.document.write('<img src="' + imageUrl + '" />');
}
function wrapText(ctx, text, x, y, maxWidth, lineHeight, outline, outlineColor, underline) {
const words = text.split(' ');
let line = '';
let testLine = '';
let testWidth = 0;
for (let n = 0; n < words.length; n++) {
testLine = line + words[n] + ' ';
testWidth = ctx.measureText(testLine).width;
if (testWidth > maxWidth && n > 0) {
if (outline) {
ctx.strokeStyle = outlineColor;
ctx.lineWidth = 2;
ctx.strokeText(line, x, y);
}
ctx.fillText(line, x, y);
if (underline) {
const textWidth = ctx.measureText(line).width;
ctx.beginPath();
ctx.moveTo(x - textWidth / 2, y + 5);
ctx.lineTo(x + textWidth / 2, y + 5);
ctx.stroke();
}
line = words[n] + ' ';
y += lineHeight;
} else {
line = testLine;
}
}
if (outline) {
ctx.strokeStyle = outlineColor;
ctx.lineWidth = 2;
ctx.strokeText(line, x, y);
}
ctx.fillText(line, x, y);
if (underline) {
const textWidth = ctx.measureText(line).width;
ctx.beginPath();
ctx.moveTo(x - textWidth / 2, y + 5);
ctx.lineTo(x + textWidth / 2, y + 5);
ctx.stroke();
}
}