| <!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 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"> |
| |
| <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> |
|
|
| |
| <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"> |
| |
| </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"> |
| |
| </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> |
|
|
| |
| <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"> |
| |
| </div> |
| </div> |
| </div> |
| </div> |
| </main> |
|
|
| <script> |
| |
| const blockPalette = [ |
| |
| { 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] }, |
| |
| |
| { 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] }, |
| |
| |
| { 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] }, |
| |
| |
| { 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] } |
| ]; |
| |
| |
| let state = { |
| originalImage: null, |
| pixelArtData: null, |
| selectedBlocks: new Set(blockPalette.map(block => block.name)), |
| resolution: 64, |
| zoomLevel: 5, |
| conversionInProgress: false |
| }; |
| |
| |
| 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') |
| }; |
| |
| |
| function init() { |
| renderBlockPalette(); |
| setupEventListeners(); |
| } |
| |
| |
| function renderBlockPalette() { |
| |
| const categories = {}; |
| blockPalette.forEach(block => { |
| if (!categories[block.category]) { |
| categories[block.category] = []; |
| } |
| categories[block.category].push(block); |
| }); |
| |
| |
| 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; |
| } |
| |
| |
| function setupEventListeners() { |
| |
| 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]); |
| } |
| }); |
| |
| |
| 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); |
| } |
| |
| const blockItem = e.target.closest('.block-item'); |
| if (e.target.checked) { |
| blockItem.classList.add('border-minecraft-green'); |
| } else { |
| blockItem.classList.remove('border-minecraft-green'); |
| } |
| } |
| }); |
| |
| |
| 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'; |
| } |
| }); |
| }); |
| |
| |
| 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'); |
| }); |
| }); |
| |
| |
| dom.resolutionSlider.addEventListener('input', (e) => { |
| state.resolution = parseInt(e.target.value); |
| dom.resolutionValue.textContent = `${state.resolution}x${state.resolution}`; |
| }); |
| |
| |
| 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(); |
| }); |
| |
| |
| dom.convertBtn.addEventListener('click', convertImageToPixelArt); |
| |
| |
| dom.resetBtn.addEventListener('click', resetApp); |
| |
| |
| dom.exportBtn.addEventListener('click', exportArt); |
| |
| |
| dom.downloadTxt.addEventListener('click', downloadTextGuide); |
| dom.downloadGrid.addEventListener('click', downloadGridGuide); |
| } |
| |
| |
| 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 = () => { |
| |
| dom.originalPreview.src = e.target.result; |
| dom.originalPreview.classList.remove('hidden'); |
| dom.originalPlaceholder.classList.add('hidden'); |
| |
| |
| dom.convertBtn.disabled = false; |
| }; |
| state.originalImage.src = e.target.result; |
| }; |
| reader.readAsDataURL(file); |
| } |
| |
| |
| 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 { |
| |
| 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; |
| } |
| |
| |
| const tensor = tf.browser.fromPixels(state.originalImage); |
| const resized = tf.image.resizeBilinear(tensor, [state.resolution, state.resolution]); |
| |
| |
| 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; |
| |
| |
| 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); |
| } |
| |
| |
| tf.dispose([tensor, resized]); |
| blockColors.forEach(t => t.dispose()); |
| |
| |
| state.pixelArtData = pixelArtData; |
| |
| |
| renderPixelArt(); |
| |
| |
| 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'; |
| } |
| } |
| |
| |
| 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'); |
| |
| |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| |
| |
| 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); |
| |
| |
| if (pixelSize > 3) { |
| ctx.strokeStyle = 'rgba(0,0,0,0.1)'; |
| ctx.strokeRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize); |
| } |
| } |
| } |
| } |
| |
| |
| function exportArt() { |
| if (!state.pixelArtData) return; |
| |
| |
| dom.dimensions.textContent = `${state.resolution}x${state.resolution} blocks`; |
| |
| |
| const blockCounts = {}; |
| state.pixelArtData.flat().forEach(pixel => { |
| const blockName = pixel.block.name; |
| blockCounts[blockName] = (blockCounts[blockName] || 0) + 1; |
| }); |
| |
| |
| 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; |
| |
| |
| dom.placementGrid.innerHTML = ''; |
| const gridSize = Math.min(20, state.resolution); |
| |
| 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); |
| } |
| } |
| |
| |
| dom.exportResults.classList.remove('hidden'); |
| |
| |
| dom.exportResults.scrollIntoView({ behavior: 'smooth' }); |
| } |
| |
| |
| 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); |
| } |
| |
| |
| function downloadGridGuide() { |
| if (!state.pixelArtData) return; |
| |
| |
| const canvas = document.createElement('canvas'); |
| const ctx = canvas.getContext('2d'); |
| const resolution = state.resolution; |
| const pixelSize = 20; |
| |
| canvas.width = resolution * pixelSize; |
| canvas.height = resolution * pixelSize; |
| |
| |
| 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); |
| |
| |
| ctx.strokeStyle = 'rgba(0,0,0,0.1)'; |
| ctx.strokeRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize); |
| } |
| } |
| |
| |
| ctx.fillStyle = '#000'; |
| ctx.font = '20px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText(`Minecraft Pixel Art Blueprint - ${resolution}x${resolution}`, canvas.width / 2, 30); |
| |
| |
| 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); |
| } |
| |
| |
| function resetApp() { |
| state.originalImage = null; |
| state.pixelArtData = null; |
| state.selectedBlocks = new Set(blockPalette.map(block => block.name)); |
| |
| |
| 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'); |
| |
| |
| dom.fileUpload.value = ''; |
| |
| |
| renderBlockPalette(); |
| } |
| |
| |
| 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> |