{"id":702,"date":"2025-03-12T05:19:46","date_gmt":"2025-03-12T05:19:46","guid":{"rendered":"https:\/\/divbydev.com\/?page_id=702"},"modified":"2025-03-12T21:54:10","modified_gmt":"2025-03-12T21:54:10","slug":"image-stippling-tool","status":"publish","type":"page","link":"https:\/\/divbydev.com\/index.php\/projects-2\/image-stippling-tool\/","title":{"rendered":"Image Stippling Tool"},"content":{"rendered":"\n<p>Stuff<\/p>\n\n\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Image Stippling Tool<\/title>\n  <style>\n    body {\n      font-family: Arial, sans-serif;\n      margin: 20px;\n    }\n    \n    #controls {\n      margin-bottom: 20px;\n      max-width: 400px;\n    }\n    \n    canvas {\n      border: 1px solid #ccc;\n      margin-top: 10px;\n      max-width: 100%;\n      height: auto;\n    }\n    \n    input[type=\"file\"],\n    input[type=\"range\"],\n    input[type=\"color\"],\n    select {\n      display: block;\n      margin-bottom: 10px;\n      width: 100%;\n      max-width: 400px;\n    }\n    \n    label {\n      display: block;\n      margin-bottom: 10px;\n    }\n    \n    button {\n      margin-right: 10px;\n    }\n    \n    span {\n      margin-left: 10px;\n      font-weight: bold;\n    }\n  <\/style>\n<\/head>\n<body>\n  <div id=\"controls\">\n    <!-- Image file selector -->\n    <input type=\"file\" id=\"fileInput\" accept=\"image\/*\">\n    \n    <!-- Sampling step control -->\n    <label>\n      Sample Step (px):\n      <input type=\"range\" id=\"sampleStep\" min=\"2\" max=\"50\" value=\"10\">\n      <span id=\"sampleStepVal\">10<\/span>\n    <\/label>\n    \n    <!-- Dot size factor control -->\n    <label>\n      Dot Size Factor:\n      <input type=\"range\" id=\"dotSize\" min=\"1\" max=\"50\" value=\"10\">\n      <span id=\"dotSizeVal\">10<\/span>\n    <\/label>\n    \n    <!-- Dot Transparency control -->\n    <label>\n      Dot Transparency (%):\n      <input type=\"range\" id=\"dotTransparency\" min=\"0\" max=\"100\" value=\"100\">\n      <span id=\"dotTransparencyVal\">100<\/span>\n    <\/label>\n    \n    <!-- Dot Colour picker -->\n    <label>\n      Dot Colour:\n      <input type=\"color\" id=\"dotColor\" value=\"#000000\">\n    <\/label>\n    \n    <!-- Dot Shape selector -->\n    <label>\n      Dot Shape:\n      <select id=\"dotShape\">\n        <option value=\"circle\">Circle<\/option>\n        <option value=\"square\">Square<\/option>\n        <option value=\"triangle\">Triangle<\/option>\n      <\/select>\n    <\/label>\n    \n    <!-- Adaptive Density toggle -->\n    <label>\n      <input type=\"checkbox\" id=\"adaptiveDensity\">\n      Adaptive Density\n    <\/label>\n    \n    <!-- Pixel Color Variation toggle -->\n    <label>\n      <input type=\"checkbox\" id=\"pixelColorVariation\">\n      Use Pixel Color Variation\n    <\/label>\n    \n    <button id=\"generateBtn\">Generate Stipple<\/button>\n    <button id=\"savePngBtn\">Save as PNG<\/button>\n    <button id=\"exportSvgBtn\">Save as SVG<\/button>\n    <button id=\"clearBtn\">Clear<\/button>\n  <\/div>\n  \n  <canvas id=\"canvas\"><\/canvas>\n  \n  <script>\n    \/\/ Get DOM elements\n    const fileInput = document.getElementById('fileInput');\n    const sampleStep = document.getElementById('sampleStep');\n    const sampleStepVal = document.getElementById('sampleStepVal');\n    const dotSize = document.getElementById('dotSize');\n    const dotSizeVal = document.getElementById('dotSizeVal');\n    const dotTransparency = document.getElementById('dotTransparency');\n    const dotTransparencyVal = document.getElementById('dotTransparencyVal');\n    const dotColorInput = document.getElementById('dotColor');\n    const dotShapeSelect = document.getElementById('dotShape');\n    const adaptiveDensityCheckbox = document.getElementById('adaptiveDensity');\n    const pixelColorVariationCheckbox = document.getElementById('pixelColorVariation');\n    \n    const generateBtn = document.getElementById('generateBtn');\n    const savePngBtn = document.getElementById('savePngBtn');\n    const exportSvgBtn = document.getElementById('exportSvgBtn');\n    const clearBtn = document.getElementById('clearBtn');\n    const canvas = document.getElementById('canvas');\n    const ctx = canvas.getContext('2d');\n    \n    let originalImage = null;\n    let drawnDots = []; \/\/ Store drawn dot info for SVG export\n    \n    \/\/ Update indicator values\n    function updateIndicators() {\n      sampleStepVal.innerText = sampleStep.value;\n      dotSizeVal.innerText = dotSize.value;\n      dotTransparencyVal.innerText = dotTransparency.value;\n    }\n    \n    sampleStep.addEventListener('input', updateIndicators);\n    dotSize.addEventListener('input', updateIndicators);\n    dotTransparency.addEventListener('input', updateIndicators);\n    \n    \/\/ Load the image from file input\n    fileInput.addEventListener('change', () => {\n      if (fileInput.files && fileInput.files[0]) {\n        const reader = new FileReader();\n        reader.onload = function(event) {\n          originalImage = new Image();\n          originalImage.onload = () => {\n            canvas.width = originalImage.width;\n            canvas.height = originalImage.height;\n          };\n          originalImage.src = event.target.result;\n        };\n        reader.readAsDataURL(fileInput.files[0]);\n      }\n    });\n    \n    \/\/ Draw a dot of specified shape at (x,y)\n    function drawDot(x, y, radius, fillStyle, shape) {\n      ctx.fillStyle = fillStyle;\n      if (shape === \"circle\") {\n        ctx.beginPath();\n        ctx.arc(x, y, radius, 0, Math.PI * 2);\n        ctx.fill();\n      } else if (shape === \"square\") {\n        ctx.fillRect(x - radius, y - radius, radius * 2, radius * 2);\n      } else if (shape === \"triangle\") {\n        ctx.beginPath();\n        ctx.moveTo(x, y - radius);\n        ctx.lineTo(x - radius, y + radius);\n        ctx.lineTo(x + radius, y + radius);\n        ctx.closePath();\n        ctx.fill();\n      }\n    }\n    \n    \/\/ Generate stippled art using iterative row rendering\n    function generateStipple() {\n      if (!originalImage) {\n        alert(\"Please load an image first.\");\n        return;\n      }\n      \n      drawnDots = [];\n      \n      canvas.width = originalImage.width;\n      canvas.height = originalImage.height;\n      \n      ctx.fillStyle = \"#FFFFFF\";\n      ctx.fillRect(0, 0, canvas.width, canvas.height);\n      \n      const offCanvas = document.createElement('canvas');\n      offCanvas.width = originalImage.width;\n      offCanvas.height = originalImage.height;\n      const offCtx = offCanvas.getContext('2d');\n      offCtx.drawImage(originalImage, 0, 0);\n      const imageData = offCtx.getImageData(0, 0, offCanvas.width, offCanvas.height).data;\n      \n      const step = parseInt(sampleStep.value);\n      const dotFactor = parseInt(dotSize.value);\n      const transparency = parseInt(dotTransparency.value) \/ 100;\n      const fixedColor = dotColorInput.value;\n      const shape = dotShapeSelect.value;\n      const adaptive = adaptiveDensityCheckbox.checked;\n      const usePixelColor = pixelColorVariationCheckbox.checked;\n      \n      ctx.globalAlpha = transparency;\n      \n      let y = 0;\n      function drawRow() {\n        for (let x = 0; x < offCanvas.width; x += step) {\n          const index = (y * offCanvas.width + x) * 4;\n          const r = imageData[index];\n          const g = imageData[index + 1];\n          const b = imageData[index + 2];\n          const brightness = (0.299 * r + 0.587 * g + 0.114 * b) \/ 255;\n          const radius = (1 - brightness) * dotFactor;\n          \n          if (adaptive) {\n            if (Math.random() > (1 - brightness)) continue;\n          }\n          \n          if (radius > 0.5) {\n            let fillColor;\n            if (usePixelColor) {\n              fillColor = `rgba(${r},${g},${b},${transparency})`;\n            } else {\n              fillColor = fixedColor;\n            }\n            drawDot(x, y, radius, fillColor, shape);\n            drawnDots.push({ x, y, radius, fillColor, shape });\n          }\n        }\n        y += step;\n        if (y < offCanvas.height) {\n          requestAnimationFrame(drawRow);\n        }\n      }\n      drawRow();\n    }\n    \n    \/\/ Export the drawn stipple as an SVG file\n    function exportSVG() {\n      if (drawnDots.length === 0) {\n        alert(\"No stipple generated to export.\");\n        return;\n      }\n      const width = canvas.width;\n      const height = canvas.height;\n      let svgContent = `<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"${width}\" height=\"${height}\">`;\n      svgContent += `<rect width=\"100%\" height=\"100%\" fill=\"#FFFFFF\" \/>`;\n      \n      drawnDots.forEach(dot => {\n        if (dot.shape === \"circle\") {\n          svgContent += `<circle cx=\"${dot.x}\" cy=\"${dot.y}\" r=\"${dot.radius}\" fill=\"${dot.fillColor}\" \/>`;\n        } else if (dot.shape === \"square\") {\n          const x = dot.x - dot.radius;\n          const y = dot.y - dot.radius;\n          svgContent += `<rect x=\"${x}\" y=\"${y}\" width=\"${dot.radius * 2}\" height=\"${dot.radius * 2}\" fill=\"${dot.fillColor}\" \/>`;\n        } else if (dot.shape === \"triangle\") {\n          const p1 = `${dot.x},${dot.y - dot.radius}`;\n          const p2 = `${dot.x - dot.radius},${dot.y + dot.radius}`;\n          const p3 = `${dot.x + dot.radius},${dot.y + dot.radius}`;\n          svgContent += `<polygon points=\"${p1} ${p2} ${p3}\" fill=\"${dot.fillColor}\" \/>`;\n        }\n      });\n      \n      svgContent += `<\/svg>`;\n      \n      const blob = new Blob([svgContent], { type: \"image\/svg+xml;charset=utf-8\" });\n      const url = URL.createObjectURL(blob);\n      const link = document.createElement('a');\n      link.download = 'stippled_image.svg';\n      link.href = url;\n      link.click();\n      URL.revokeObjectURL(url);\n    }\n    \n    \/\/ Save the canvas as a PNG image\n    function savePNG() {\n      if (canvas.width === 0 || canvas.height === 0) {\n        alert(\"No image to save. Please generate stipple first.\");\n        return;\n      }\n      const link = document.createElement('a');\n      link.download = 'stippled_image.png';\n      link.href = canvas.toDataURL('image\/png');\n      link.click();\n    }\n    \n    \/\/ Clear the canvas and reset controls\n    clearBtn.addEventListener('click', () => {\n      fileInput.value = \"\";\n      sampleStep.value = 10;\n      dotSize.value = 10;\n      dotTransparency.value = 100;\n      dotColorInput.value = \"#000000\";\n      dotShapeSelect.value = \"circle\";\n      adaptiveDensityCheckbox.checked = false;\n      pixelColorVariationCheckbox.checked = false;\n      updateIndicators();\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n      canvas.width = canvas.height = 0;\n      originalImage = null;\n      drawnDots = [];\n    });\n    \n    generateBtn.addEventListener('click', generateStipple);\n    exportSvgBtn.addEventListener('click', exportSVG);\n    savePngBtn.addEventListener('click', savePNG);\n  <\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Stuff Image Stippling Tool Sample Step (px): 10 Dot Size Factor: 10 Dot Transparency (%): 100 Dot Colour: Dot Shape: CircleSquareTriangle Adaptive Density Use Pixel Color Variation Generate Stipple Save as PNG Save as SVG Clear<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":230,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-702","page","type-page","status-publish"],"_links":{"self":[{"href":"https:\/\/divbydev.com\/index.php\/wp-json\/wp\/v2\/pages\/702","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/divbydev.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/divbydev.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/divbydev.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/divbydev.com\/index.php\/wp-json\/wp\/v2\/comments?post=702"}],"version-history":[{"count":1,"href":"https:\/\/divbydev.com\/index.php\/wp-json\/wp\/v2\/pages\/702\/revisions"}],"predecessor-version":[{"id":703,"href":"https:\/\/divbydev.com\/index.php\/wp-json\/wp\/v2\/pages\/702\/revisions\/703"}],"up":[{"embeddable":true,"href":"https:\/\/divbydev.com\/index.php\/wp-json\/wp\/v2\/pages\/230"}],"wp:attachment":[{"href":"https:\/\/divbydev.com\/index.php\/wp-json\/wp\/v2\/media?parent=702"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}