CoolFace
Apppublic

ppprew/dotmatrix-g-code-wizard

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
index.html429 linesDownload Raw Back to root
1<!DOCTYPE html>2<html lang="en">3<head>4    <meta charset="UTF-8">5    <meta name="viewport" content="width=device-width, initial-scale=1.0">6    <title>DotMatrix G-Code Wizard</title>7    <link rel="icon" type="image/x-icon" href="/static/favicon.ico">8    <script src="https://cdn.tailwindcss.com"></script>9    <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>10    <script src="https://unpkg.com/feather-icons"></script>11    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>12    <script>13        tailwind.config = {14            theme: {15                extend: {16                    colors: {17                        undefined: {18                            500: '#6366f1',19                            600: '#4f46e5',20                        }21                    }22                }23            }24        }25    </script>26    <style>27        .dropzone {28            border: 2px dashed #6366f1;29            transition: all 0.3s ease;30        }31        .dropzone.active {32            border-color: #4f46e5;33            background-color: rgba(99, 102, 241, 0.1);34        }35        .slider-thumb::-webkit-slider-thumb {36            -webkit-appearance: none;37            appearance: none;38            width: 20px;39            height: 20px;40            background: #6366f1;41            cursor: pointer;42            border-radius: 50%;43        }44        .slider-thumb::-moz-range-thumb {45            width: 20px;46            height: 20px;47            background: #6366f1;48            cursor: pointer;49            border-radius: 50%;50        }51        #canvas-container {52            position: relative;53        }54        canvas {55            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);56        }57    </style>58</head>59<body class="bg-gray-50 min-h-screen">60    <div class="max-w-6xl mx-auto px-4 py-8">61        <!-- Header -->62        <header class="mb-12 text-center">63            <h1 class="text-4xl font-bold text-gray-800 mb-2">DotMatrix G-Code Wizard 🌀</h1>64            <p class="text-gray-600">Transform your images into halftone patterns and generate G-code for fabrication</p>65        </header>66 67        <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">68            <!-- Left Column - Controls -->69            <div class="lg:col-span-1 bg-white rounded-xl shadow-md p-6">70                <h2 class="text-xl font-semibold text-gray-800 mb-6">Image Upload</h2>71                72                <!-- Dropzone -->73                <div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer mb-6">74                    <div class="flex flex-col items-center justify-center">75                        <i data-feather="upload" class="w-12 h-12 text-undefined-500 mb-3"></i>76                        <p class="text-gray-600">Drag & drop your image here</p>77                        <p class="text-sm text-gray-500 mt-1">or click to browse</p>78                    </div>79                    <input type="file" id="fileInput" class="hidden" accept="image/*">80                </div>81 82                <h2 class="text-xl font-semibold text-gray-800 mb-6 mt-8">Halftone Settings</h2>83                84                <!-- Controls -->85                <div class="space-y-6">86                    <div>87                        <div class="flex justify-between items-center mb-2">88                            <label class="text-gray-700">Spot Radius</label>89                            <span id="radiusValue" class="text-gray-600">5px</span>90                        </div>91                        <input type="range" id="radiusSlider" min="1" max="20" value="5" class="w-full slider-thumb">92                        <p class="text-xs text-gray-500 mt-1">Adjust the size of halftone dots</p>93                    </div>94 95                    <div>96                        <div class="flex justify-between items-center mb-2">97                            <label class="text-gray-700">Contrast</label>98                            <span id="contrastValue" class="text-gray-600">50%</span>99                        </div>100                        <input type="range" id="contrastSlider" min="0" max="100" value="50" class="w-full slider-thumb">101                        <p class="text-xs text-gray-500 mt-1">Control the contrast of the effect</p>102                    </div>103 104                    <div>105                        <div class="flex justify-between items-center mb-2">106                            <label class="text-gray-700">Angle</label>107                            <span id="angleValue" class="text-gray-600">45°</span>108                        </div>109                        <input type="range" id="angleSlider" min="0" max="180" value="45" class="w-full slider-thumb">110                        <p class="text-xs text-gray-500 mt-1">Set the angle of the pattern</p>111                    </div>112 113                    <div>114                        <div class="flex justify-between items-center mb-2">115                            <label class="text-gray-700">Density</label>116                            <span id="densityValue" class="text-gray-600">50%</span>117                        </div>118                        <input type="range" id="densitySlider" min="10" max="90" value="50" class="w-full slider-thumb">119                        <p class="text-xs text-gray-500 mt-1">Adjust dot distribution density</p>120                    </div>121                </div>122 123                <!-- Action Buttons -->124                <div class="flex space-x-3 mt-8">125                    <button id="resetBtn" class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 px-4 rounded-lg transition flex items-center justify-center">126                        <i data-feather="refresh-cw" class="w-4 h-4 mr-2"></i> Reset127                    </button>128                    <button id="generateBtn" class="flex-1 bg-undefined-600 hover:bg-undefined-700 text-white py-2 px-4 rounded-lg transition flex items-center justify-center">129                        <i data-feather="code" class="w-4 h-4 mr-2"></i> Generate G-Code130                    </button>131                </div>132            </div>133 134            <!-- Right Column - Canvas and Output -->135            <div class="lg:col-span-2 space-y-6">136                <!-- Canvas Preview -->137                <div class="bg-white rounded-xl shadow-md p-6">138                    <h2 class="text-xl font-semibold text-gray-800 mb-4">Preview</h2>139                    <div id="canvas-container" class="relative border border-gray-200 rounded-lg overflow-hidden">140                        <canvas id="previewCanvas" class="w-full"></canvas>141                        <div id="noImagePlaceholder" class="absolute inset-0 flex items-center justify-center bg-gray-100">142                            <div class="text-center p-6">143                                <i data-feather="image" class="w-12 h-12 text-gray-400 mx-auto mb-3"></i>144                                <p class="text-gray-500">Upload an image to see the preview</p>145                            </div>146                        </div>147                    </div>148                </div>149 150                <!-- G-Code Output -->151                <div class="bg-white rounded-xl shadow-md p-6">152                    <div class="flex justify-between items-center mb-4">153                        <h2 class="text-xl font-semibold text-gray-800">G-Code Output</h2>154                        <button id="downloadBtn" class="bg-undefined-600 hover:bg-undefined-700 text-white py-1 px-3 rounded-lg text-sm transition flex items-center disabled:opacity-50" disabled>155                            <i data-feather="download" class="w-3 h-3 mr-2"></i> Download156                        </button>157                    </div>158                    <textarea id="gcodeOutput" class="w-full h-64 p-3 border border-gray-200 rounded-lg font-mono text-sm bg-gray-50" placeholder="G-code will appear here..." readonly></textarea>159                </div>160            </div>161        </div>162    </div>163 164    <script>165        document.addEventListener('DOMContentLoaded', function() {166            feather.replace();167            168            // Canvas setup169            const canvas = new fabric.Canvas('previewCanvas', {170                backgroundColor: '#ffffff',171                preserveObjectStacking: true172            });173            174            // UI Elements175            const dropzone = document.getElementById('dropzone');176            const fileInput = document.getElementById('fileInput');177            const noImagePlaceholder = document.getElementById('noImagePlaceholder');178            const radiusSlider = document.getElementById('radiusSlider');179            const contrastSlider = document.getElementById('contrastSlider');180            const angleSlider = document.getElementById('angleSlider');181            const densitySlider = document.getElementById('densitySlider');182            const radiusValue = document.getElementById('radiusValue');183            const contrastValue = document.getElementById('contrastValue');184            const angleValue = document.getElementById('angleValue');185            const densityValue = document.getElementById('densityValue');186            const resetBtn = document.getElementById('resetBtn');187            const generateBtn = document.getElementById('generateBtn');188            const downloadBtn = document.getElementById('downloadBtn');189            const gcodeOutput = document.getElementById('gcodeOutput');190            191            // Default values192            let currentImage = null;193            let settings = {194                radius: 5,195                contrast: 50,196                angle: 45,197                density: 50198            };199            200            // Event Listeners201            dropzone.addEventListener('click', () => fileInput.click());202            dropzone.addEventListener('dragover', (e) => {203                e.preventDefault();204                dropzone.classList.add('active');205            });206            dropzone.addEventListener('dragleave', () => dropzone.classList.remove('active'));207            dropzone.addEventListener('drop', (e) => {208                e.preventDefault();209                dropzone.classList.remove('active');210                if (e.dataTransfer.files.length) {211                    handleImageUpload(e.dataTransfer.files[0]);212                }213            });214            215            fileInput.addEventListener('change', (e) => {216                if (e.target.files.length) {217                    handleImageUpload(e.target.files[0]);218                }219            });220            221            radiusSlider.addEventListener('input', updateSettings);222            contrastSlider.addEventListener('input', updateSettings);223            angleSlider.addEventListener('input', updateSettings);224            densitySlider.addEventListener('input', updateSettings);225            226            resetBtn.addEventListener('click', resetSettings);227            generateBtn.addEventListener('click', generateGCode);228            downloadBtn.addEventListener('click', downloadGCode);229            230            // Functions231            function handleImageUpload(file) {232                if (!file.type.match('image.*')) {233                    alert('Please select an image file (JPEG, PNG, etc.)');234                    return;235                }236                237                const reader = new FileReader();238                reader.onload = function(e) {239                    fabric.Image.fromURL(e.target.result, function(img) {240                        // Clear canvas241                        canvas.clear();242                        243                        // Scale image to fit canvas244                        const scale = Math.min(1, canvas.width / img.width, canvas.height / img.height);245                        img.scale(scale);246                        247                        // Center image248                        canvas.setWidth(img.width * scale);249                        canvas.setHeight(img.height * scale);250                        canvas.centerObject(img);251                        canvas.add(img);252                        canvas.renderAll();253                        254                        currentImage = img;255                        noImagePlaceholder.style.display = 'none';256                        257                        // Apply initial halftone effect258                        applyHalftoneEffect();259                    });260                };261                reader.readAsDataURL(file);262            }263            264            function updateSettings() {265                settings.radius = parseInt(radiusSlider.value);266                settings.contrast = parseInt(contrastSlider.value);267                settings.angle = parseInt(angleSlider.value);268                settings.density = parseInt(densitySlider.value);269                270                // Update display values271                radiusValue.textContent = `${settings.radius}px`;272                contrastValue.textContent = `${settings.contrast}%`;273                angleValue.textContent = `${settings.angle}°`;274                densityValue.textContent = `${settings.density}%`;275                276                if (currentImage) {277                    applyHalftoneEffect();278                }279            }280            function applyHalftoneEffect() {281                if (!currentImage) return;282                283                // Create offscreen canvas for processing284                const offscreenCanvas = document.createElement('canvas');285                const offscreenCtx = offscreenCanvas.getContext('2d');286                offscreenCanvas.width = currentImage.width * currentImage.scaleX;287                offscreenCanvas.height = currentImage.height * currentImage.scaleY;288                289                // Draw current image to offscreen canvas290                offscreenCtx.drawImage(currentImage._element, 0, 0, offscreenCanvas.width, offscreenCanvas.height);291                292                // Get image data293                const imageData = offscreenCtx.getImageData(0, 0, offscreenCanvas.width, offscreenCanvas.height);294                const data = imageData.data;295                296                // Halftone effect parameters297                const radius = settings.radius;298                const contrast = settings.contrast / 100;299                const angle = (settings.angle * Math.PI) / 180;300                const density = settings.density / 100;301                302                // Clear and prepare canvas303                canvas.clear();304                const ctx = canvas.getContext('2d');305                ctx.fillStyle = 'white';306                ctx.fillRect(0, 0, canvas.width, canvas.height);307                308                // Halftone algorithm309                const dotSpacing = radius * 2;310                const cosAngle = Math.cos(angle);311                const sinAngle = Math.sin(angle);312                313                // Convert to grayscale first for better performance314                const grayscaleData = new Array(offscreenCanvas.width * offscreenCanvas.height);315                for (let i = 0; i < data.length; i += 4) {316                    const idx = i / 4;317                    const r = data[i];318                    const g = data[i + 1];319                    const b = data[i + 2];320                    grayscaleData[idx] = 0.299 * r + 0.587 * g + 0.114 * b;321                }322                323                // Draw halftone pattern324                for (let y = 0; y < offscreenCanvas.height; y += dotSpacing) {325                    for (let x = 0; x < offscreenCanvas.width; x += dotSpacing) {326                        // Rotated grid position327                        const rx = Math.round(x * cosAngle - y * sinAngle);328                        const ry = Math.round(x * sinAngle + y * cosAngle);329                        330                        if (rx >= 0 && rx < offscreenCanvas.width && ry >= 0 && ry < offscreenCanvas.height) {331                            // Get brightness at this position (inverted for dark dots)332                            const idx = ry * offscreenCanvas.width + rx;333                            const brightness = 1 - grayscaleData[idx] / 255;334                            335                            // Apply contrast336                            let contrastBrightness = ((brightness - 0.5) * contrast) + 0.5;337                            contrastBrightness = Math.max(0, Math.min(1, contrastBrightness));338                            339                            // Scale by density340                            const scaledBrightness = contrastBrightness * density;341                            342                            // Draw dot (only if visible enough)343                            if (scaledBrightness > 0.05) {344                                const dotRadius = radius * scaledBrightness;345                                ctx.beginPath();346                                ctx.arc(x, y, dotRadius, 0, Math.PI * 2);347                                ctx.fillStyle = 'black';348                                ctx.fill();349                            }350                        }351                    }352                }353                354                canvas.renderAll();355            }356function resetSettings() {357                radiusSlider.value = 5;358                contrastSlider.value = 50;359                angleSlider.value = 45;360                densitySlider.value = 50;361                updateSettings();362            }363            function generateGCode() {364                if (!currentImage) {365                    alert('Please upload an image first');366                    return;367                }368                369                // First apply the current halftone effect370                applyHalftoneEffect();371// In a real implementation, this would generate the actual G-code372                // For this demo, we'll create a sample G-code373                const sampleGCode = `; DotMatrix G-Code374; Generated from image with halftone settings:375; Radius: ${settings.radius}px376; Contrast: ${settings.contrast}%377; Angle: ${settings.angle}°378; Density: ${settings.density}%379 380G21 ; Set units to millimeters381G90 ; Absolute positioning382G28 ; Home all axes383 384; Start drawing halftone pattern385G1 F3000 ; Set feedrate386G1 Z5 ; Raise Z-axis387G1 X0 Y0 ; Move to start position388G1 Z0 ; Lower Z-axis389 390; Halftone pattern would be here391M2 ; End program`;392 393                gcodeOutput.value = sampleGCode;394                downloadBtn.disabled = false;395            }396            397            function downloadGCode() {398                const gcode = gcodeOutput.value;399                if (!gcode) return;400                401                const blob = new Blob([gcode], { type: 'text/plain' });402                const url = URL.createObjectURL(blob);403                const a = document.createElement('a');404                a.href = url;405                a.download = 'halftone.gcode';406                document.body.appendChild(a);407                a.click();408                document.body.removeChild(a);409                URL.revokeObjectURL(url);410            }411            412            // Initialize canvas size413            function resizeCanvas() {414                const container = document.getElementById('canvas-container');415                canvas.setWidth(container.clientWidth);416                canvas.setHeight(container.clientWidth * 0.66); // 3:2 aspect ratio417                if (currentImage) {418                    canvas.renderAll();419                }420            }421            422            window.addEventListener('resize', resizeCanvas);423            resizeCanvas();424        });425    </script>426    <script>feather.replace();</script>427</body>428</html>429