pixelarts / index.html
Saad4web's picture
good job now improve to make it able Export Options PNG Image Schematic Blueprint Commands - Initial Deployment
db3dca3 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minecraft Pixel Art Converter</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.11.0/dist/tf.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
minecraft: {
green: '#5b9c64',
dark: '#3a3a3a',
light: '#f0f0f0',
accent: '#7aa3d9'
}
}
}
}
}
</script>
<style>
.block-item {
transition: all 0.2s ease;
}
.block-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.pixel-grid {
display: grid;
gap: 0;
margin: 0 auto;
}
.pixel {
border: 1px solid rgba(0,0,0,0.1);
}
.drop-area {
border: 2px dashed #cbd5e0;
transition: all 0.3s ease;
}
.drop-area.active {
border-color: #7aa3d9;
background-color: rgba(122, 163, 217, 0.1);
}
.canvas-container {
position: relative;
overflow: auto;
max-width: 100%;
}
canvas {
max-width: 100%;
height: auto;
display: block;
}
.category-title {
position: sticky;
top: 0;
z-index: 10;
}
.export-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(20px, 1fr));
gap: 1px;
}
.export-cell {
width: 100%;
padding-bottom: 100%;
position: relative;
}
.export-cell-inner {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
font-size: 8px;
display: flex;
align-items: center;
justify-content: center;
}
</style>
</head>
<body class="bg-minecraft-light min-h-screen font-sans">
<!-- Header -->
<header class="bg-minecraft-dark text-white py-4 shadow-md">
<div class="container mx-auto px-4 flex items-center">
<i class="fas fa-cube text-minecraft-green text-2xl mr-3"></i>
<h1 class="text-2xl font-bold">Minecraft Pixel Art Converter</h1>
</div>
</header>
<main class="container mx-auto px-4 py-8">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Left Column: Image Upload and Preview -->
<div class="lg:col-span-1">
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 class="text-xl font-bold text-gray-800 mb-4">Upload Image</h2>
<div class="drop-area rounded-lg p-8 text-center cursor-pointer mb-4" id="drop-area">
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-3"></i>
<p class="text-gray-600 mb-2">Drag & drop your image here</p>
<p class="text-gray-500 text-sm mb-4">or</p>
<label for="file-upload" class="bg-minecraft-green hover:bg-green-600 text-white font-medium py-2 px-4 rounded cursor-pointer transition">
<i class="fas fa-folder-open mr-2"></i>Choose File
</label>
<input type="file" id="file-upload" class="hidden" accept="image/jpeg,image/png">
</div>
<p class="text-gray-500 text-sm text-center">Supported formats: JPG, PNG</p>
</div>
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 class="text-xl font-bold text-gray-800 mb-4">Original Image</h2>
<div class="border-2 border-dashed border-gray-300 rounded-lg flex items-center justify-center min-h-[200px]">
<img id="original-preview" class="max-w-full max-h-80 object-contain hidden">
<div id="original-placeholder" class="text-gray-500 text-center p-4">
<i class="fas fa-image text-4xl mb-3"></i>
<p>Your image will appear here</p>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-bold text-gray-800 mb-4">Conversion Settings</h2>
<div class="mb-4">
<label class="block text-gray-700 mb-2">Pixel Art Size</label>
<div class="flex items-center">
<input type="range" id="resolution-slider" min="16" max="128" value="64" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<span id="resolution-value" class="ml-4 text-gray-700 font-medium">64x64</span>
</div>
</div>
<div class="flex space-x-4">
<button id="convert-btn" class="flex-1 bg-minecraft-green hover:bg-green-600 text-white font-medium py-2 px-4 rounded transition disabled:opacity-50 disabled:cursor-not-allowed" disabled>
<i class="fas fa-magic mr-2"></i>Convert to Pixel Art
</button>
<button id="reset-btn" class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-medium py-2 px-4 rounded transition">
<i class="fas fa-redo mr-2"></i>Reset
</button>
</div>
</div>
</div>
<!-- Middle Column: Pixel Art Preview -->
<div class="lg:col-span-1">
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-800">Minecraft Pixel Art</h2>
<div class="flex items-center">
<span class="text-gray-600 mr-2">Zoom:</span>
<div class="flex space-x-1">
<button id="zoom-out" class="w-8 h-8 bg-gray-200 hover:bg-gray-300 rounded flex items-center justify-center">
<i class="fas fa-search-minus"></i>
</button>
<button id="zoom-in" class="w-8 h-8 bg-gray-200 hover:bg-gray-300 rounded flex items-center justify-center">
<i class="fas fa-search-plus"></i>
</button>
</div>
</div>
</div>
<div class="canvas-container border-2 border-gray-300 rounded-lg min-h-[300px] flex items-center justify-center" id="canvas-container">
<canvas id="pixel-art-canvas" class="hidden"></canvas>
<div id="pixel-art-placeholder" class="text-gray-500 text-center p-4">
<i class="fas fa-cubes text-4xl mb-3"></i>
<p>Your Minecraft pixel art will appear here</p>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-bold text-gray-800 mb-4">Export Minecraft Art</h2>
<button id="export-btn" class="w-full bg-minecraft-accent hover:bg-blue-600 text-white font-medium py-3 px-4 rounded transition mb-4 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
<i class="fas fa-download mr-2"></i>Export Minecraft Blueprint
</button>
<div id="export-results" class="hidden">
<div class="mb-4">
<h3 class="font-bold text-gray-700 mb-2">Art Dimensions</h3>
<p id="dimensions" class="text-gray-800">64x64 blocks</p>
</div>
<div class="mb-4">
<h3 class="font-bold text-gray-700 mb-2">Blocks Required</h3>
<div id="block-counts" class="bg-gray-50 rounded-lg p-4 max-h-60 overflow-y-auto">
<!-- Block counts will be inserted here -->
</div>
</div>
<div>
<h3 class="font-bold text-gray-700 mb-2">Placement Guide</h3>
<div class="bg-gray-50 rounded-lg p-4">
<div class="export-grid mb-4" id="placement-grid">
<!-- Miniature grid will be inserted here -->
</div>
<div class="flex justify-between">
<button id="download-txt" class="text-minecraft-accent hover:text-blue-600 font-medium">
<i class="fas fa-file-alt mr-2"></i>Download Text Guide
</button>
<button id="download-grid" class="text-minecraft-accent hover:text-blue-600 font-medium">
<i class="fas fa-table mr-2"></i>Download Grid Guide
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Right Column: Block Palette -->
<div class="lg:col-span-1">
<div class="bg-white rounded-lg shadow-md p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-800">Minecraft Block Palette</h2>
<div class="flex space-x-2">
<button id="select-all" class="text-sm bg-gray-200 hover:bg-gray-300 text-gray-800 py-1 px-3 rounded transition">
<i class="fas fa-check-square mr-1"></i>Select All
</button>
<button id="deselect-all" class="text-sm bg-gray-200 hover:bg-gray-300 text-gray-800 py-1 px-3 rounded transition">
<i class="fas fa-square mr-1"></i>Deselect All
</button>
</div>
</div>
<div class="mb-4">
<input type="text" id="block-search" placeholder="Search blocks..." class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-minecraft-accent">
</div>
<div class="block-palette-container max-h-[calc(100vh-250px)] overflow-y-auto pr-2">
<!-- Block categories will be inserted here -->
</div>
</div>
</div>
</div>
</main>
<script>
// Minecraft block data with representative colors
const blockPalette = [
// Easily Findable Blocks
{ name: "Clay", category: "Easily Findable", color: [158, 164, 176] },
{ name: "Jungle log", category: "Easily Findable", color: [87, 68, 27] },
{ name: "Birch log", category: "Easily Findable", color: [207, 206, 201] },
{ name: "Oak log", category: "Easily Findable", color: [102, 81, 50] },
{ name: "Spruce log", category: "Easily Findable", color: [46, 29, 12] },
{ name: "Stone slab", category: "Easily Findable", color: [125, 125, 125] },
{ name: "Gravel", category: "Easily Findable", color: [132, 127, 127] },
{ name: "Sandstone", category: "Easily Findable", color: [219, 211, 160] },
{ name: "White wool", category: "Easily Findable", color: [233, 236, 236] },
{ name: "Blue wool", category: "Easily Findable", color: [53, 57, 157] },
{ name: "Yellow wool", category: "Easily Findable", color: [248, 198, 40] },
{ name: "Red wool", category: "Easily Findable", color: [161, 39, 35] },
{ name: "Green wool", category: "Easily Findable", color: [86, 110, 44] },
{ name: "Snow block", category: "Easily Findable", color: [240, 251, 251] },
{ name: "Cobblestone", category: "Easily Findable", color: [125, 125, 125] },
{ name: "Sand", category: "Easily Findable", color: [219, 211, 160] },
{ name: "Dirt", category: "Easily Findable", color: [134, 96, 67] },
// Manufactured Blocks
{ name: "Bookshelf", category: "Manufactured", color: [178, 141, 86] },
{ name: "Block of Iron", category: "Manufactured", color: [220, 220, 220] },
{ name: "Bricks", category: "Manufactured", color: [150, 99, 83] },
{ name: "Stone brick", category: "Manufactured", color: [122, 122, 122] },
{ name: "Blue terracotta", category: "Manufactured", color: [74, 60, 91] },
{ name: "White terracotta", category: "Manufactured", color: [210, 178, 162] },
{ name: "Black concrete", category: "Manufactured", color: [9, 11, 16] },
{ name: "Block of Copper", category: "Manufactured", color: [198, 112, 100] },
// Blocks on Transparent Background
{ name: "Glass", category: "Transparent", color: [155, 186, 178, 200] },
{ name: "Black stained glass", category: "Transparent", color: [25, 25, 25, 200] },
{ name: "Blue stained glass", category: "Transparent", color: [51, 76, 178, 200] },
{ name: "Green stained glass", category: "Transparent", color: [102, 127, 51, 200] },
{ name: "Oak leaves", category: "Transparent", color: [48, 112, 20, 200] },
// Rare Blocks
{ name: "Block of Diamond", category: "Rare", color: [99, 219, 213] },
{ name: "Block of Emerald", category: "Rare", color: [42, 204, 112] },
{ name: "Block of Gold", category: "Rare", color: [249, 236, 50] },
{ name: "Obsidian", category: "Rare", color: [20, 18, 30] },
{ name: "Prismarine bricks", category: "Rare", color: [100, 160, 144] },
{ name: "Slime Block", category: "Rare", color: [128, 189, 90] }
];
// App state
let state = {
originalImage: null,
pixelArtData: null,
selectedBlocks: new Set(blockPalette.map(block => block.name)),
resolution: 64,
zoomLevel: 5,
conversionInProgress: false
};
// DOM Elements
const dom = {
dropArea: document.getElementById('drop-area'),
fileUpload: document.getElementById('file-upload'),
originalPreview: document.getElementById('original-preview'),
originalPlaceholder: document.getElementById('original-placeholder'),
convertBtn: document.getElementById('convert-btn'),
resetBtn: document.getElementById('reset-btn'),
pixelArtCanvas: document.getElementById('pixel-art-canvas'),
pixelArtPlaceholder: document.getElementById('pixel-art-placeholder'),
canvasContainer: document.getElementById('canvas-container'),
blockPaletteContainer: document.querySelector('.block-palette-container'),
blockSearch: document.getElementById('block-search'),
selectAllBtn: document.getElementById('select-all'),
deselectAllBtn: document.getElementById('deselect-all'),
resolutionSlider: document.getElementById('resolution-slider'),
resolutionValue: document.getElementById('resolution-value'),
zoomInBtn: document.getElementById('zoom-in'),
zoomOutBtn: document.getElementById('zoom-out'),
exportBtn: document.getElementById('export-btn'),
exportResults: document.getElementById('export-results'),
dimensions: document.getElementById('dimensions'),
blockCounts: document.getElementById('block-counts'),
placementGrid: document.getElementById('placement-grid'),
downloadTxt: document.getElementById('download-txt'),
downloadGrid: document.getElementById('download-grid')
};
// Initialize the application
function init() {
renderBlockPalette();
setupEventListeners();
}
// Render block palette with categories
function renderBlockPalette() {
// Group blocks by category
const categories = {};
blockPalette.forEach(block => {
if (!categories[block.category]) {
categories[block.category] = [];
}
categories[block.category].push(block);
});
// Generate HTML for each category
let html = '';
for (const category in categories) {
html += `
<div class="mb-6">
<h3 class="category-title bg-gray-100 text-gray-800 font-bold py-2 px-3 rounded-t-lg">${category}</h3>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3 p-3 bg-gray-50 rounded-b-lg">
${categories[category].map(block => `
<div class="block-item bg-white rounded-lg overflow-hidden shadow-sm border border-gray-200 ${state.selectedBlocks.has(block.name) ? 'border-minecraft-green' : ''}">
<label class="flex items-center p-2 cursor-pointer">
<input type="checkbox" class="block-checkbox mr-2" data-block="${block.name}" ${state.selectedBlocks.has(block.name) ? 'checked' : ''}>
<div class="flex items-center">
<div class="w-4 h-4 rounded-sm mr-2" style="background-color: rgb(${block.color.join(',')})"></div>
<span class="text-sm text-gray-700 truncate">${block.name}</span>
</div>
</label>
</div>
`).join('')}
</div>
</div>
`;
}
dom.blockPaletteContainer.innerHTML = html;
}
// Set up event listeners
function setupEventListeners() {
// File upload handling
dom.dropArea.addEventListener('dragover', (e) => {
e.preventDefault();
dom.dropArea.classList.add('active');
});
dom.dropArea.addEventListener('dragleave', () => {
dom.dropArea.classList.remove('active');
});
dom.dropArea.addEventListener('drop', (e) => {
e.preventDefault();
dom.dropArea.classList.remove('active');
if (e.dataTransfer.files.length) {
handleFileUpload(e.dataTransfer.files[0]);
}
});
dom.fileUpload.addEventListener('change', (e) => {
if (e.target.files.length) {
handleFileUpload(e.target.files[0]);
}
});
// Block selection
dom.blockPaletteContainer.addEventListener('change', (e) => {
if (e.target.classList.contains('block-checkbox')) {
const blockName = e.target.dataset.block;
if (e.target.checked) {
state.selectedBlocks.add(blockName);
} else {
state.selectedBlocks.delete(blockName);
}
// Update UI
const blockItem = e.target.closest('.block-item');
if (e.target.checked) {
blockItem.classList.add('border-minecraft-green');
} else {
blockItem.classList.remove('border-minecraft-green');
}
}
});
// Block search
dom.blockSearch.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase();
document.querySelectorAll('.block-item').forEach(item => {
const blockName = item.querySelector('span').textContent.toLowerCase();
if (blockName.includes(searchTerm)) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
});
// Select all/deselect all
dom.selectAllBtn.addEventListener('click', () => {
state.selectedBlocks = new Set(blockPalette.map(block => block.name));
document.querySelectorAll('.block-checkbox').forEach(checkbox => {
checkbox.checked = true;
});
document.querySelectorAll('.block-item').forEach(item => {
item.classList.add('border-minecraft-green');
});
});
dom.deselectAllBtn.addEventListener('click', () => {
state.selectedBlocks.clear();
document.querySelectorAll('.block-checkbox').forEach(checkbox => {
checkbox.checked = false;
});
document.querySelectorAll('.block-item').forEach(item => {
item.classList.remove('border-minecraft-green');
});
});
// Resolution slider
dom.resolutionSlider.addEventListener('input', (e) => {
state.resolution = parseInt(e.target.value);
dom.resolutionValue.textContent = `${state.resolution}x${state.resolution}`;
});
// Zoom controls
dom.zoomInBtn.addEventListener('click', () => {
state.zoomLevel = Math.min(state.zoomLevel + 1, 10);
renderPixelArt();
});
dom.zoomOutBtn.addEventListener('click', () => {
state.zoomLevel = Math.max(state.zoomLevel - 1, 1);
renderPixelArt();
});
// Convert button
dom.convertBtn.addEventListener('click', convertImageToPixelArt);
// Reset button
dom.resetBtn.addEventListener('click', resetApp);
// Export button
dom.exportBtn.addEventListener('click', exportArt);
// Download buttons
dom.downloadTxt.addEventListener('click', downloadTextGuide);
dom.downloadGrid.addEventListener('click', downloadGridGuide);
}
// Handle file upload
function handleFileUpload(file) {
if (!file.type.match('image.*')) {
alert('Please upload an image file (JPG or PNG)');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
state.originalImage = new Image();
state.originalImage.onload = () => {
// Show preview
dom.originalPreview.src = e.target.result;
dom.originalPreview.classList.remove('hidden');
dom.originalPlaceholder.classList.add('hidden');
// Enable convert button
dom.convertBtn.disabled = false;
};
state.originalImage.src = e.target.result;
};
reader.readAsDataURL(file);
}
// Convert image to pixel art using TensorFlow.js
async function convertImageToPixelArt() {
if (!state.originalImage || state.conversionInProgress) return;
state.conversionInProgress = true;
dom.convertBtn.disabled = true;
dom.convertBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Processing...';
try {
// Get selected blocks
const selectedBlocks = blockPalette.filter(block =>
state.selectedBlocks.has(block.name)
);
if (selectedBlocks.length === 0) {
alert('Please select at least one block from the palette');
state.conversionInProgress = false;
dom.convertBtn.disabled = false;
dom.convertBtn.innerHTML = '<i class="fas fa-magic mr-2"></i>Convert to Pixel Art';
return;
}
// Prepare TensorFlow.js operations
const tensor = tf.browser.fromPixels(state.originalImage);
const resized = tf.image.resizeBilinear(tensor, [state.resolution, state.resolution]);
// Convert to pixel art by finding nearest block color
const blockColors = selectedBlocks.map(block => {
const color = block.color.length === 4 ? block.color.slice(0, 3) : block.color;
return tf.tensor1d(color);
});
const pixelArtData = [];
const pixels = resized.arraySync();
for (let y = 0; y < state.resolution; y++) {
const row = [];
for (let x = 0; x < state.resolution; x++) {
const pixel = pixels[y][x];
let minDistance = Infinity;
let closestBlockIndex = 0;
// Find closest block color
for (let i = 0; i < blockColors.length; i++) {
const color = blockColors[i];
const distance = Math.sqrt(
Math.pow(pixel[0] - color.arraySync()[0], 2) +
Math.pow(pixel[1] - color.arraySync()[1], 2) +
Math.pow(pixel[2] - color.arraySync()[2], 2)
);
if (distance < minDistance) {
minDistance = distance;
closestBlockIndex = i;
}
}
row.push({
block: selectedBlocks[closestBlockIndex],
index: closestBlockIndex
});
}
pixelArtData.push(row);
}
// Clean up tensors
tf.dispose([tensor, resized]);
blockColors.forEach(t => t.dispose());
// Update state
state.pixelArtData = pixelArtData;
// Render pixel art
renderPixelArt();
// Enable export button
dom.exportBtn.disabled = false;
} catch (error) {
console.error('Conversion error:', error);
alert('An error occurred during conversion. Please try again.');
} finally {
state.conversionInProgress = false;
dom.convertBtn.disabled = false;
dom.convertBtn.innerHTML = '<i class="fas fa-magic mr-2"></i>Convert to Pixel Art';
}
}
// Render pixel art on canvas
function renderPixelArt() {
if (!state.pixelArtData) return;
const canvas = dom.pixelArtCanvas;
const ctx = canvas.getContext('2d');
const resolution = state.resolution;
const pixelSize = state.zoomLevel;
canvas.width = resolution * pixelSize;
canvas.height = resolution * pixelSize;
canvas.classList.remove('hidden');
dom.pixelArtPlaceholder.classList.add('hidden');
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw each pixel
for (let y = 0; y < resolution; y++) {
for (let x = 0; x < resolution; x++) {
const pixel = state.pixelArtData[y][x];
const color = pixel.block.color;
ctx.fillStyle = `rgb(${color.slice(0, 3).join(',')})`;
ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
// Add grid for larger pixels
if (pixelSize > 3) {
ctx.strokeStyle = 'rgba(0,0,0,0.1)';
ctx.strokeRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
}
}
}
}
// Export art results
function exportArt() {
if (!state.pixelArtData) return;
// Show dimensions
dom.dimensions.textContent = `${state.resolution}x${state.resolution} blocks`;
// Calculate block counts
const blockCounts = {};
state.pixelArtData.flat().forEach(pixel => {
const blockName = pixel.block.name;
blockCounts[blockName] = (blockCounts[blockName] || 0) + 1;
});
// Render block counts
let countsHTML = '';
Object.entries(blockCounts).sort((a, b) => b[1] - a[1]).forEach(([block, count]) => {
countsHTML += `
<div class="flex justify-between py-2 border-b border-gray-200">
<span>${block}</span>
<span class="font-medium">${count}</span>
</div>
`;
});
dom.blockCounts.innerHTML = countsHTML;
// Render miniature placement grid
dom.placementGrid.innerHTML = '';
const gridSize = Math.min(20, state.resolution); // Max 20x20 for preview
for (let y = 0; y < gridSize; y++) {
for (let x = 0; x < gridSize; x++) {
const pixel = state.pixelArtData[y][x];
const color = pixel.block.color;
const cell = document.createElement('div');
cell.className = 'export-cell';
const inner = document.createElement('div');
inner.className = 'export-cell-inner';
inner.style.backgroundColor = `rgb(${color.slice(0, 3).join(',')})`;
inner.title = pixel.block.name;
cell.appendChild(inner);
dom.placementGrid.appendChild(cell);
}
}
// Show export results
dom.exportResults.classList.remove('hidden');
// Scroll to export results
dom.exportResults.scrollIntoView({ behavior: 'smooth' });
}
// Download text guide
function downloadTextGuide() {
if (!state.pixelArtData) return;
let content = `Minecraft Pixel Art Blueprint\n`;
content += `Dimensions: ${state.resolution}x${state.resolution} blocks\n\n`;
content += "Block Placement Guide:\n\n";
for (let y = 0; y < state.resolution; y++) {
for (let x = 0; x < state.resolution; x++) {
const pixel = state.pixelArtData[y][x];
content += `(${x},${y}): ${pixel.block.name}\n`;
}
}
content += "\nBlock Quantities:\n";
const blockCounts = {};
state.pixelArtData.flat().forEach(pixel => {
const blockName = pixel.block.name;
blockCounts[blockName] = (blockCounts[blockName] || 0) + 1;
});
Object.entries(blockCounts).sort((a, b) => b[1] - a[1]).forEach(([block, count]) => {
content += `${block}: ${count}\n`;
});
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'minecraft-pixel-art-blueprint.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// Download grid guide
function downloadGridGuide() {
if (!state.pixelArtData) return;
// Create a canvas for the grid guide
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const resolution = state.resolution;
const pixelSize = 20; // Fixed size for download
canvas.width = resolution * pixelSize;
canvas.height = resolution * pixelSize;
// Draw each pixel
for (let y = 0; y < resolution; y++) {
for (let x = 0; x < resolution; x++) {
const pixel = state.pixelArtData[y][x];
const color = pixel.block.color;
ctx.fillStyle = `rgb(${color.slice(0, 3).join(',')})`;
ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
// Add grid
ctx.strokeStyle = 'rgba(0,0,0,0.1)';
ctx.strokeRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
}
}
// Add title and dimensions
ctx.fillStyle = '#000';
ctx.font = '20px Arial';
ctx.textAlign = 'center';
ctx.fillText(`Minecraft Pixel Art Blueprint - ${resolution}x${resolution}`, canvas.width / 2, 30);
// Convert to data URL and download
const url = canvas.toDataURL('image/png');
const a = document.createElement('a');
a.href = url;
a.download = 'minecraft-pixel-art-grid.png';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
// Reset application
function resetApp() {
state.originalImage = null;
state.pixelArtData = null;
state.selectedBlocks = new Set(blockPalette.map(block => block.name));
// Reset UI
dom.originalPreview.classList.add('hidden');
dom.originalPlaceholder.classList.remove('hidden');
dom.pixelArtCanvas.classList.add('hidden');
dom.pixelArtPlaceholder.classList.remove('hidden');
dom.convertBtn.disabled = true;
dom.exportBtn.disabled = true;
dom.exportResults.classList.add('hidden');
// Reset file input
dom.fileUpload.value = '';
// Reset block selections
renderBlockPalette();
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', init);
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Saad4web/pixelarts" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>