{"id":695,"date":"2025-03-10T02:09:30","date_gmt":"2025-03-10T02:09:30","guid":{"rendered":"https:\/\/divbydev.com\/?page_id=695"},"modified":"2025-03-12T21:47:09","modified_gmt":"2025-03-12T21:47:09","slug":"text-image-masker","status":"publish","type":"page","link":"https:\/\/divbydev.com\/index.php\/projects-2\/text-image-masker\/","title":{"rendered":"Text Image Masker"},"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>Text-Driven Image Masker<\/title>\n  <style>\n    body {\n      font-family: Arial, sans-serif;\n      margin: 20px;\n    }\n    #controls {\n      margin-bottom: 20px;\n      max-width: 400px;\n    }\n    canvas {\n      border: 1px solid #ccc;\n      margin-top: 10px;\n      max-width: 100%;\n      height: auto;\n    }\n    input[type=\"file\"],\n    input[type=\"text\"],\n    input[type=\"color\"],\n    input[type=\"number\"],\n    input[type=\"range\"],\n    select {\n      display: block;\n      margin-bottom: 10px;\n      width: 100%;\n      max-width: 400px;\n    }\n    label {\n      display: block;\n      margin-bottom: 10px;\n    }\n    button {\n      margin-right: 10px;\n    }\n    span {\n      margin-left: 10px;\n      font-weight: bold;\n    }\n  <\/style>\n<\/head>\n<body>\n  <div id=\"controls\">\n    <!-- File selector -->\n    <input type=\"file\" id=\"fileInput\" accept=\"image\/*\">\n    <!-- Text input for mask text -->\n    <input type=\"text\" id=\"maskText\" placeholder=\"Enter mask text\" value=\"MASK\">\n    <!-- Checkbox to maintain image aspect ratio -->\n    <label>\n      <input type=\"checkbox\" id=\"maintainAspect\" checked>\n      Maintain Image Aspect Ratio\n    <\/label>\n    <!-- Background color picker -->\n    <label>\n      Background Color:\n      <input type=\"color\" id=\"bgColor\" value=\"#ffffff\">\n    <\/label>\n    <!-- Font style drop-down -->\n    <label>\n      Font Style:\n      <select id=\"fontStyle\">\n        <option value=\"Arial\">Arial<\/option>\n        <option value=\"Helvetica\">Helvetica<\/option>\n        <option value=\"Verdana\">Verdana<\/option>\n        <option value=\"Tahoma\">Tahoma<\/option>\n        <option value=\"Trebuchet MS\">Trebuchet MS<\/option>\n        <option value=\"Georgia\">Georgia<\/option>\n        <option value=\"Times New Roman\">Times New Roman<\/option>\n        <option value=\"Courier New\">Courier New<\/option>\n        <option value=\"Lucida Sans\">Lucida Sans<\/option>\n        <option value=\"Segoe UI\">Segoe UI<\/option>\n      <\/select>\n    <\/label>\n    <!-- Text alignment drop-down -->\n    <label>\n      Text Alignment:\n      <select id=\"textAlignSelect\">\n        <option value=\"custom\">Custom<\/option>\n        <option value=\"top left\">Top Left<\/option>\n        <option value=\"top centre\">Top Centre<\/option>\n        <option value=\"top right\">Top Right<\/option>\n        <option value=\"centre left\">Centre Left<\/option>\n        <option value=\"centre\">Centre<\/option>\n        <option value=\"centre right\">Centre Right<\/option>\n        <option value=\"bottom left\">Bottom Left<\/option>\n        <option value=\"bottom centre\">Bottom Centre<\/option>\n        <option value=\"bottom right\">Bottom Right<\/option>\n      <\/select>\n    <\/label>\n    <!-- Text direction drop-down -->\n    <label>\n      Text Direction:\n      <select id=\"textDirection\">\n        <option value=\"horizontal\">Horizontal<\/option>\n        <option value=\"vertical\">Vertical<\/option>\n        <option value=\"diagonalTLBR\">Diagonal TL\u2013BR<\/option>\n        <option value=\"diagonalBLTR\">Diagonal BL\u2013TR<\/option>\n      <\/select>\n    <\/label>\n    <!-- Text size numerical input -->\n    <label>\n      Text Size (% of canvas height):\n      <input type=\"number\" id=\"textSize\" value=\"80\" min=\"10\" max=\"200\">%\n      <span id=\"textSizeVal\">80%<\/span>\n    <\/label>\n    <!-- Text X position slider -->\n    <label>\n      Text X Position (%):\n      <input type=\"range\" id=\"textX\" min=\"0\" max=\"100\" value=\"50\">\n      <span id=\"textXVal\">50%<\/span>\n    <\/label>\n    <!-- Text Y position slider -->\n    <label>\n      Text Y Position (%):\n      <input type=\"range\" id=\"textY\" min=\"0\" max=\"100\" value=\"50\">\n      <span id=\"textYVal\">50%<\/span>\n    <\/label>\n    <!-- Buttons -->\n    <button id=\"saveBtn\">Save Masked Image<\/button>\n    <button id=\"clearBtn\">Clear<\/button>\n  <\/div>\n  \n  <!-- Canvas to display the masked image -->\n  <canvas id=\"canvas\"><\/canvas>\n  \n  <script>\n    const fileInput = document.getElementById('fileInput');\n    const maskTextInput = document.getElementById('maskText');\n    const maintainAspectCheckbox = document.getElementById('maintainAspect');\n    const bgColorInput = document.getElementById('bgColor');\n    const fontStyleSelect = document.getElementById('fontStyle');\n    const textAlignSelect = document.getElementById('textAlignSelect');\n    const textDirectionSelect = document.getElementById('textDirection');\n    const textSizeInput = document.getElementById('textSize');\n    const textXInput = document.getElementById('textX');\n    const textYInput = document.getElementById('textY');\n    const textSizeVal = document.getElementById('textSizeVal');\n    const textXVal = document.getElementById('textXVal');\n    const textYVal = document.getElementById('textYVal');\n    const saveBtn = document.getElementById('saveBtn');\n    const clearBtn = document.getElementById('clearBtn');\n    const canvas = document.getElementById('canvas');\n    const ctx = canvas.getContext('2d');\n    let originalImage = null;\n    \n    \/\/ Update numerical indicators.\n    function updateSliderIndicators() {\n      textSizeVal.innerText = textSizeInput.value + '%';\n      textXVal.innerText = textXInput.value + '%';\n      textYVal.innerText = textYInput.value + '%';\n    }\n    \n    \/\/ When the text alignment drop-down changes, update slider values if not \"custom\".\n    function updateAlignmentPreset() {\n      const preset = textAlignSelect.value;\n      let newX, newY;\n      switch (preset) {\n        case \"top left\":\n          newX = 0; newY = 0; break;\n        case \"top centre\":\n          newX = 50; newY = 0; break;\n        case \"top right\":\n          newX = 100; newY = 0; break;\n        case \"centre left\":\n          newX = 0; newY = 50; break;\n        case \"centre\":\n          newX = 50; newY = 50; break;\n        case \"centre right\":\n          newX = 100; newY = 50; break;\n        case \"bottom left\":\n          newX = 0; newY = 100; break;\n        case \"bottom centre\":\n          newX = 50; newY = 100; break;\n        case \"bottom right\":\n          newX = 100; newY = 100; break;\n        default:\n          return; \/\/ \"custom\": do nothing.\n      }\n      textXInput.value = newX;\n      textYInput.value = newY;\n      updateSliderIndicators();\n      updateCanvas();\n    }\n    textAlignSelect.addEventListener('change', updateAlignmentPreset);\n    \n    function updateCanvas() {\n      if (!originalImage) return;\n      \n      \/\/ Determine canvas dimensions.\n      let canvasWidth, canvasHeight;\n      if (maintainAspectCheckbox.checked) {\n        canvasWidth = originalImage.width;\n        canvasHeight = originalImage.height;\n      } else {\n        canvasWidth = 800;\n        canvasHeight = 600;\n      }\n      canvas.width = canvasWidth;\n      canvas.height = canvasHeight;\n      \n      \/\/ Fill canvas with background color.\n      ctx.fillStyle = bgColorInput.value;\n      ctx.fillRect(0, 0, canvasWidth, canvasHeight);\n      \n      \/\/ Create offscreen canvas.\n      const offCanvas = document.createElement('canvas');\n      offCanvas.width = canvasWidth;\n      offCanvas.height = canvasHeight;\n      const offCtx = offCanvas.getContext('2d');\n      \n      \/\/ Draw the base image on the offscreen canvas.\n      offCtx.drawImage(originalImage, 0, 0, canvasWidth, canvasHeight);\n      \n      \/\/ Set composite operation for masking.\n      offCtx.globalCompositeOperation = 'destination-in';\n      \n      const maskText = maskTextInput.value || \"MASK\";\n      \n      \/\/ Get text size from the number input.\n      const textSizePct = parseFloat(textSizeInput.value) \/ 100;\n      const fontSize = canvasHeight * textSizePct;\n      \n      \/\/ Set the font style from the drop-down.\n      const fontStyle = fontStyleSelect.value;\n      offCtx.font = `bold ${fontSize}px ${fontStyle}`;\n      \n      \/\/ Set text alignment and baseline based on drop-down.\n      const alignOption = textAlignSelect.value;\n      let align, baseline;\n      switch (alignOption) {\n        case \"top left\": align = \"left\"; baseline = \"top\"; break;\n        case \"top centre\": align = \"center\"; baseline = \"top\"; break;\n        case \"top right\": align = \"right\"; baseline = \"top\"; break;\n        case \"centre left\": align = \"left\"; baseline = \"middle\"; break;\n        case \"centre\": align = \"center\"; baseline = \"middle\"; break;\n        case \"centre right\": align = \"right\"; baseline = \"middle\"; break;\n        case \"bottom left\": align = \"left\"; baseline = \"bottom\"; break;\n        case \"bottom centre\": align = \"center\"; baseline = \"bottom\"; break;\n        case \"bottom right\": align = \"right\"; baseline = \"bottom\"; break;\n        default:\n          align = \"center\";\n          baseline = \"middle\";\n          break;\n      }\n      offCtx.textAlign = align;\n      offCtx.textBaseline = baseline;\n      \n      \/\/ Determine text position from sliders.\n      const xPos = canvasWidth * (parseFloat(textXInput.value) \/ 100);\n      const yPos = canvasHeight * (parseFloat(textYInput.value) \/ 100);\n      \n      offCtx.fillStyle = '#000';\n      \n      offCtx.save();\n      \/\/ Apply text direction transformation.\n      const direction = textDirectionSelect.value;\n      if (direction === \"vertical\") {\n        offCtx.translate(xPos, yPos);\n        offCtx.rotate(-Math.PI \/ 2);\n        offCtx.translate(-xPos, -yPos);\n      } else if (direction === \"diagonalTLBR\") {\n        offCtx.translate(xPos, yPos);\n        offCtx.rotate(Math.PI \/ 4);\n        offCtx.translate(-xPos, -yPos);\n      } else if (direction === \"diagonalBLTR\") {\n        offCtx.translate(xPos, yPos);\n        offCtx.rotate(-Math.PI \/ 4);\n        offCtx.translate(-xPos, -yPos);\n      }\n      \n      offCtx.fillText(maskText, xPos, yPos);\n      offCtx.restore();\n      \n      offCtx.globalCompositeOperation = 'source-over';\n      \n      \/\/ Draw the offscreen canvas (masked image) onto the main canvas.\n      ctx.drawImage(offCanvas, 0, 0);\n      \n      updateSliderIndicators();\n    }\n    \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 = updateCanvas;\n          originalImage.src = event.target.result;\n        };\n        reader.readAsDataURL(fileInput.files[0]);\n      }\n    });\n    \n    maskTextInput.addEventListener('input', updateCanvas);\n    maintainAspectCheckbox.addEventListener('change', updateCanvas);\n    bgColorInput.addEventListener('input', updateCanvas);\n    fontStyleSelect.addEventListener('change', updateCanvas);\n    textSizeInput.addEventListener('input', () => { updateSliderIndicators(); updateCanvas(); });\n    textXInput.addEventListener('input', () => { updateSliderIndicators(); updateCanvas(); });\n    textYInput.addEventListener('input', () => { updateSliderIndicators(); updateCanvas(); });\n    textAlignSelect.addEventListener('change', updateAlignmentPreset);\n    textDirectionSelect.addEventListener('change', updateCanvas);\n    \n    saveBtn.addEventListener('click', () => {\n      if (canvas.width === 0 || canvas.height === 0) {\n        alert(\"Please load an image first.\");\n        return;\n      }\n      const link = document.createElement('a');\n      link.download = 'masked_image.png';\n      link.href = canvas.toDataURL('image\/png');\n      link.click();\n    });\n    \n    clearBtn.addEventListener('click', () => {\n      fileInput.value = '';\n      maskTextInput.value = '';\n      maintainAspectCheckbox.checked = true;\n      bgColorInput.value = \"#ffffff\";\n      fontStyleSelect.selectedIndex = 0;\n      textAlignSelect.value = \"custom\";\n      textDirectionSelect.value = \"horizontal\";\n      textSizeInput.value = 80;\n      textXInput.value = 50;\n      textYInput.value = 50;\n      updateSliderIndicators();\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n      canvas.width = canvas.height = 0;\n      originalImage = null;\n    });\n  <\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Stuff Text-Driven Image Masker Maintain Image Aspect Ratio Background Color: Font Style: ArialHelveticaVerdanaTahomaTrebuchet MSGeorgiaTimes New RomanCourier NewLucida SansSegoe UI Text Alignment: CustomTop LeftTop CentreTop RightCentre LeftCentreCentre RightBottom LeftBottom CentreBottom Right Text Direction: HorizontalVerticalDiagonal TL\u2013BRDiagonal BL\u2013TR Text Size (% of canvas height): % 80% Text X Position (%): 50% Text Y Position (%): 50% Save Masked &#8230; <a title=\"Text Image Masker\" class=\"read-more\" href=\"https:\/\/divbydev.com\/index.php\/projects-2\/text-image-masker\/\" aria-label=\"Read more about Text Image Masker\">Read more<\/a><\/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-695","page","type-page","status-publish"],"_links":{"self":[{"href":"https:\/\/divbydev.com\/index.php\/wp-json\/wp\/v2\/pages\/695","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=695"}],"version-history":[{"count":1,"href":"https:\/\/divbydev.com\/index.php\/wp-json\/wp\/v2\/pages\/695\/revisions"}],"predecessor-version":[{"id":696,"href":"https:\/\/divbydev.com\/index.php\/wp-json\/wp\/v2\/pages\/695\/revisions\/696"}],"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=695"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}