{"id":193,"date":"2025-04-19T08:14:46","date_gmt":"2025-04-19T08:14:46","guid":{"rendered":"https:\/\/majhinaukri.in\/tools\/?page_id=193"},"modified":"2025-05-25T07:29:14","modified_gmt":"2025-05-25T07:29:14","slug":"photo-signature-joiner","status":"publish","type":"page","link":"https:\/\/majhinaukri.in\/tools\/photo-signature-joiner\/","title":{"rendered":"Photo Signature Joiner:  Photo &#038; Signature Joiner"},"content":{"rendered":"<div class='code-block code-block-7' style='margin: 8px 0; clear: both;'>\n<title> Photo & Signature Jointer<\/title>\n  <link href=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@5.3.0\/dist\/css\/bootstrap.min.css\" rel=\"stylesheet\">\n  <link rel=\"stylesheet\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/6.0.0\/css\/all.min.css\">\n  <link rel=\"stylesheet\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/cropperjs\/1.5.13\/cropper.min.css\">\n  <style>\n    .mn-photo-sign-tool {\n      font-family: 'Segoe UI', Arial, sans-serif;\n      background: #f7f7f9;\n      min-height: 100vh;\n      padding: 20px;\n    }\n    .mn-photo-sign-tool .container {\n      max-width: 800px;\n      margin: auto;\n      background: white;\n      padding: 30px;\n      border-radius: 20px;\n      box-shadow: 0 10px 30px rgba(0,0,0,0.1);\n    }\n    .mn-photo-sign-tool .tool-header {\n      text-align: center;\n      margin-bottom: 30px;\n      color: #2c3e50;\n      position: relative;\n    }\n    .mn-photo-sign-tool .tool-header:after {\n      content: '';\n      display: block;\n      width: 50px;\n      height: 3px;\n      background: #3498db;\n      margin: 10px auto;\n    }\n    .mn-photo-sign-tool .preview-container {\n      display: flex;\n      gap: 20px;\n      margin-bottom: 20px;\n    }\n    .mn-photo-sign-tool .preview-box {\n      flex: 1;\n      padding: 15px;\n      border: 2px dashed #e0e0e0;\n      border-radius: 10px;\n      text-align: center;\n      transition: all 0.3s ease;\n      position: relative;\n    }\n    .mn-photo-sign-tool .preview-box:hover {\n      border-color: #3498db;\n    }\n    .mn-photo-sign-tool .preview-box img {\n      max-width: 100%;\n      max-height: 200px;\n      object-fit: contain;\n    }\n    .mn-photo-sign-tool .preview {\n      max-width: 100%;\n      margin-bottom: 10px;\n      border-radius: 8px;\n    }\n    .mn-photo-sign-tool .settings-panel {\n      background: #f8f9fa;\n      padding: 20px;\n      border-radius: 10px;\n      margin: 20px 0;\n    }\n    .mn-photo-sign-tool .form-control, .mn-photo-sign-tool .form-select {\n      border-radius: 8px;\n      border: 1px solid #dee2e6;\n      padding: 10px;\n      margin-bottom: 15px;\n    }\n    .mn-photo-sign-tool .form-control:focus {\n      box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);\n    }\n    .mn-photo-sign-tool .btn-group {\n      display: flex;\n      gap: 15px;\n      margin: 20px 0;\n    }\n    .mn-photo-sign-tool .btn {\n      padding: 12px 25px;\n      border-radius: 8px;\n      font-weight: 600;\n      transition: all 0.3s ease;\n    }\n    .mn-photo-sign-tool .btn-success {\n      background: #2ecc71;\n      border: none;\n    }\n    .mn-photo-sign-tool .btn-success:hover {\n      background: #27ae60;\n      transform: translateY(-2px);\n    }\n    .mn-photo-sign-tool .btn-primary {\n      background: #3498db;\n      border: none;\n    }\n    .mn-photo-sign-tool .btn-primary:hover {\n      background: #2980b9;\n      transform: translateY(-2px);\n    }\n    .mn-photo-sign-tool #outputCanvas {\n      max-width: 100%;\n      border-radius: 10px;\n      box-shadow: 0 5px 15px rgba(0,0,0,0.1);\n    }\n    .mn-photo-sign-tool .drag-text {\n      color: #777;\n      margin: 20px 0;\n    }\n    .mn-photo-sign-tool .image-controls {\n      display: flex;\n      gap: 10px;\n      margin-top: 10px;\n    }\n    .mn-photo-sign-tool .control-btn {\n      padding: 5px 10px;\n      font-size: 14px;\n      border-radius: 5px;\n      background: #f0f0f0;\n      border: none;\n      cursor: pointer;\n    }\n    .mn-photo-sign-tool .control-btn:hover {\n      background: #e0e0e0;\n    }\n    .mn-photo-sign-tool .error-message {\n      color: #e74c3c;\n      font-size: 14px;\n      margin-top: 5px;\n      display: none;\n    }\n    .mn-photo-sign-tool .loading-spinner {\n      display: none;\n      margin: 20px auto;\n    }\n    .mn-photo-sign-tool .tooltip-custom {\n      position: relative;\n      display: inline-block;\n    }\n    .mn-photo-sign-tool .tooltip-custom:hover::after {\n      content: attr(data-tooltip);\n      position: absolute;\n      bottom: 100%;\n      left: 50%;\n      transform: translateX(-50%);\n      padding: 5px 10px;\n      background: rgba(0,0,0,0.8);\n      color: white;\n      border-radius: 5px;\n      font-size: 12px;\n      white-space: nowrap;\n    }\n    .mn-photo-sign-tool .cropper-container {\n      position: relative;\n      max-width: 100%;\n      margin: 10px 0;\n    }\n    .mn-photo-sign-tool .crop-controls {\n      margin: 10px 0;\n      display: none;\n    }\n    .mn-photo-sign-tool .aspect-ratio-button {\n      padding: 5px 10px;\n      margin: 0 5px;\n      border: 1px solid #ddd;\n      border-radius: 4px;\n      background: #fff;\n      cursor: pointer;\n      transition: all 0.3s;\n    }\n    .mn-photo-sign-tool .aspect-ratio-button.active {\n      background: #3498db;\n      color: white;\n      border-color: #3498db;\n    }\n    .mn-photo-sign-tool .modal-body {\n      padding: 20px;\n      max-height: 80vh;\n      overflow: auto;\n    }\n    .mn-photo-sign-tool .crop-preview {\n      max-width: 100%;\n      max-height: 500px;\n    }\n    @media (max-width: 600px) {\n      .mn-photo-sign-tool {\n        padding: 5px;\n      }\n      .mn-photo-sign-tool .container {\n        padding: 10px;\n        border-radius: 10px;\n      }\n      .mn-photo-sign-tool .tool-header {\n        font-size: 1.1em;\n        margin-bottom: 15px;\n      }\n      .mn-photo-sign-tool .preview-container {\n        flex-direction: column;\n        gap: 10px;\n      }\n      .mn-photo-sign-tool .preview-box {\n        padding: 8px;\n        min-width: 0;\n      }\n      .mn-photo-sign-tool .settings-panel {\n        padding: 10px;\n        margin: 10px 0;\n      }\n      .mn-photo-sign-tool .btn-group {\n        flex-direction: column;\n        gap: 8px;\n        margin: 10px 0;\n      }\n      .mn-photo-sign-tool .btn {\n        width: 100%;\n        padding: 10px 0;\n        font-size: 1em;\n      }\n      .mn-photo-sign-tool #outputCanvas {\n        max-width: 100vw;\n        height: auto !important;\n      }\n      .mn-photo-sign-tool .modal-dialog {\n        max-width: 98vw;\n        margin: 0 auto;\n      }\n      .mn-photo-sign-tool .modal-content {\n        border-radius: 8px;\n      }\n      .mn-photo-sign-tool .crop-preview {\n        max-width: 100vw;\n        max-height: 50vh;\n      }\n      .mn-photo-sign-tool .form-control, .mn-photo-sign-tool .form-select {\n        font-size: 1em;\n        padding: 8px;\n      }\n      .mn-photo-sign-tool label {\n        font-size: 0.95em;\n      }\n    }\n  <\/style>\n<div class=\"mn-photo-sign-tool\">\n  <div class=\"container\">\n    <div class=\"tool-header\">\n      <h3> Photo & Signature Jointer<\/h3>\n      <p class=\"text-muted\">Create professional document attachments with ease<\/p>\n    <\/div>\n\n    <div class=\"preview-container\">\n      <div class=\"preview-box\" id=\"photoBox\">\n        <i class=\"fas fa-image fa-3x mb-3 text-muted\"><\/i>\n        <p class=\"drag-text\">Drag & Drop Photo or Click to Upload<\/p>\n        <input type=\"file\" id=\"photoInput\" accept=\"image\/*\" class=\"form-control\">\n        <img id=\"photoPreview\" class=\"preview\" style=\"display:none;\">\n        <div class=\"image-controls\" id=\"photoControls\" style=\"display:none;\">\n          <button class=\"control-btn\" onclick=\"rotateImage('photo', -90)\"><i class=\"fas fa-undo\"><\/i><\/button>\n          <button class=\"control-btn\" onclick=\"rotateImage('photo', 90)\"><i class=\"fas fa-redo\"><\/i><\/button>\n          <button class=\"control-btn\" onclick=\"flipImage('photo', 'horizontal')\"><i class=\"fas fa-arrows-alt-h\"><\/i><\/button>\n          <button class=\"control-btn\" onclick=\"flipImage('photo', 'vertical')\"><i class=\"fas fa-arrows-alt-v\"><\/i><\/button>\n          <button class=\"control-btn\" onclick=\"openCropModal('photo')\"><i class=\"fas fa-crop-alt\"><\/i><\/button>\n        <\/div>\n      <\/div>\n\n      <div class=\"preview-box\" id=\"signBox\">\n        <i class=\"fas fa-signature fa-3x mb-3 text-muted\"><\/i>\n        <p class=\"drag-text\">Drag & Drop Signature or Click to Upload<\/p>\n        <input type=\"file\" id=\"signInput\" accept=\"image\/*\" class=\"form-control\">\n        <img id=\"signPreview\" class=\"preview\" style=\"display:none;\">\n        <div class=\"image-controls\" id=\"signControls\" style=\"display:none;\">\n          <button class=\"control-btn\" onclick=\"rotateImage('sign', -90)\"><i class=\"fas fa-undo\"><\/i><\/button>\n          <button class=\"control-btn\" onclick=\"rotateImage('sign', 90)\"><i class=\"fas fa-redo\"><\/i><\/button>\n          <button class=\"control-btn\" onclick=\"flipImage('sign', 'horizontal')\"><i class=\"fas fa-arrows-alt-h\"><\/i><\/button>\n          <button class=\"control-btn\" onclick=\"flipImage('sign', 'vertical')\"><i class=\"fas fa-arrows-alt-v\"><\/i><\/button>\n          <button class=\"control-btn\" onclick=\"openCropModal('sign')\"><i class=\"fas fa-crop-alt\"><\/i><\/button>\n        <\/div>\n      <\/div>\n    <\/div>\n\n    <div class=\"settings-panel\">\n      <div class=\"row\">\n        <div class=\"col-md-6\">\n          <label class=\"tooltip-custom\" data-tooltip=\"Set the dimensions for the photo\">Photo Size (width x height):<\/label>\n          <input type=\"text\" id=\"photoSize\" class=\"form-control\" placeholder=\"e.g. 300x400\">\n          \n          <label class=\"tooltip-custom\" data-tooltip=\"Set the dimensions for the signature\">Signature Size:<\/label>\n          <input type=\"text\" id=\"signSize\" class=\"form-control\" placeholder=\"e.g. 300x100\">\n          \n          <label class=\"tooltip-custom\" data-tooltip=\"Space between elements\">Padding (px):<\/label>\n          <input type=\"number\" id=\"paddingInput\" class=\"form-control\" value=\"10\">\n        <\/div>\n        \n        <div class=\"col-md-6\">\n          <label class=\"tooltip-custom\" data-tooltip=\"Border thickness\">Border Width (px):<\/label>\n          <input type=\"number\" id=\"borderInput\" class=\"form-control\" value=\"0\">\n          \n          <label class=\"tooltip-custom\" data-tooltip=\"Choose background color\">Background Color:<\/label>\n          <input type=\"color\" id=\"bgColor\" class=\"form-control\" value=\"#ffffff\">\n          \n          <label class=\"tooltip-custom\" data-tooltip=\"Set image quality (lower value = smaller file size)\">Quality (0.1 to 1):<\/label>\n          <input type=\"range\" id=\"compressionInput\" class=\"form-range\" value=\"0.9\" min=\"0.1\" max=\"1\" step=\"0.1\">\n          <span id=\"qualityValue\">0.9<\/span>\n        <\/div>\n      <\/div>\n\n      <div class=\"row mt-3\">\n        <div class=\"col-md-6\">\n          <label class=\"tooltip-custom\" data-tooltip=\"Choose border style\">Border Style:<\/label>\n          <select class=\"form-select\" id=\"borderStyle\">\n            <option value=\"solid\">Solid<\/option>\n            <option value=\"dashed\">Dashed<\/option>\n            <option value=\"dotted\">Dotted<\/option>\n          <\/select>\n        <\/div>\n        <div class=\"col-md-6\">\n          <label class=\"tooltip-custom\" data-tooltip=\"Choose border color\">Border Color:<\/label>\n          <input type=\"color\" id=\"borderColor\" class=\"form-control\" value=\"#000000\">\n        <\/div>\n      <\/div>\n    <\/div>\n\n    <div class=\"btn-group\">\n      <button class=\"btn btn-success\" id=\"generateBtn\" onclick=\"generateOutput()\" disabled>\n        <i class=\"fas fa-magic\"><\/i> Generate\n      <\/button>\n      <button class=\"btn btn-primary\" onclick=\"downloadOutput()\">\n        <i class=\"fas fa-download\"><\/i> Download\n      <\/button>\n      <button class=\"btn btn-secondary\" onclick=\"resetAll()\">\n        <i class=\"fas fa-redo\"><\/i> Reset\n      <\/button>\n    <\/div>\n\n    <div class=\"loading-spinner text-center\">\n      <div class=\"spinner-border text-primary\" role=\"status\">\n        <span class=\"visually-hidden\">Loading...<\/span>\n      <\/div>\n    <\/div>\n\n    <canvas id=\"outputCanvas\"><\/canvas>\n    <div id=\"errorMessage\" class=\"error-message\"><\/div>\n  <\/div>\n\n  <!-- Crop Modal -->\n  <div class=\"modal fade\" id=\"cropModal\" tabindex=\"-1\">\n    <div class=\"modal-dialog modal-lg\">\n      <div class=\"modal-content\">\n        <div class=\"modal-header\">\n          <h5 class=\"modal-title\">Crop Image<\/h5>\n          <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\"><\/button>\n        <\/div>\n        <div class=\"modal-body\">\n          <div class=\"cropper-container\">\n            <img id=\"cropImage\" class=\"crop-preview\">\n          <\/div>\n          <div class=\"crop-controls mt-3\">\n            <div class=\"d-flex justify-content-between align-items-center mb-3\">\n              <div class=\"aspect-ratios\">\n                <button class=\"aspect-ratio-button\" data-ratio=\"free\">Free<\/button>\n                <button class=\"aspect-ratio-button\" data-ratio=\"1\">1:1<\/button>\n                <button class=\"aspect-ratio-button\" data-ratio=\"4\/3\">4:3<\/button>\n                <button class=\"aspect-ratio-button\" data-ratio=\"3\/4\">3:4<\/button>\n                <button class=\"aspect-ratio-button\" data-ratio=\"16\/9\">16:9<\/button>\n              <\/div>\n              <div class=\"crop-actions\">\n                <button class=\"btn btn-outline-secondary btn-sm\" onclick=\"rotateCropper(-90)\">\n                  <i class=\"fas fa-undo\"><\/i>\n                <\/button>\n                <button class=\"btn btn-outline-secondary btn-sm\" onclick=\"rotateCropper(90)\">\n                  <i class=\"fas fa-redo\"><\/i>\n                <\/button>\n                <button class=\"btn btn-outline-secondary btn-sm\" onclick=\"flipCropper('horizontal')\">\n                  <i class=\"fas fa-arrows-alt-h\"><\/i>\n                <\/button>\n                <button class=\"btn btn-outline-secondary btn-sm\" onclick=\"flipCropper('vertical')\">\n                  <i class=\"fas fa-arrows-alt-v\"><\/i>\n                <\/button>\n              <\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n        <div class=\"modal-footer\">\n          <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel<\/button>\n          <button type=\"button\" class=\"btn btn-primary\" onclick=\"applyCrop()\">Apply Crop<\/button>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <div id=\"mn-attribution\" style=\"text-align:center; margin-top:30px; color:#888; font-size:15px;\">\n    Powered by <a href=\"https:\/\/majhinaukri.in\" target=\"_blank\" style=\"color:#3d85c6; text-decoration:none;\">Majhi Naukri<\/a>\n  <\/div>\n<\/div>\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@5.3.0\/dist\/js\/bootstrap.bundle.min.js\"><\/script>\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/cropperjs\/1.5.13\/cropper.min.js\"><\/script>\n\n<script>\nlet photo, signature;\nlet photoDataUrl = '', signatureDataUrl = '';\nconst canvas = document.getElementById('outputCanvas');\nconst ctx = canvas.getContext('2d');\n\nlet cropper = null;\nlet currentCropType = null;\nconst cropModal = new bootstrap.Modal(document.getElementById('cropModal'));\n\n\/\/ Initialize drag and drop\n['photo', 'sign'].forEach(type => {\n  const box = document.getElementById(`${type}Box`);\n  const input = document.getElementById(`${type}Input`);\n  \n  box.addEventListener('dragover', (e) => {\n    e.preventDefault();\n    box.style.borderColor = '#3498db';\n  });\n  \n  box.addEventListener('dragleave', () => {\n    box.style.borderColor = '#e0e0e0';\n  });\n  \n  box.addEventListener('drop', (e) => {\n    e.preventDefault();\n    box.style.borderColor = '#e0e0e0';\n    const file = e.dataTransfer.files[0];\n    if (file && file.type.startsWith('image\/')) {\n      input.files = e.dataTransfer.files;\n      handleImageUpload(type, file);\n    }\n  });\n});\n\ndocument.getElementById('photoInput').addEventListener('change', function(e) {\n  handleImageUpload('photo', e.target.files[0]);\n});\n\ndocument.getElementById('signInput').addEventListener('change', function(e) {\n  handleImageUpload('sign', e.target.files[0]);\n});\n\ndocument.getElementById('compressionInput').addEventListener('input', function(e) {\n  document.getElementById('qualityValue').textContent = e.target.value;\n});\n\nfunction updateGenerateButtonState() {\n  const btn = document.getElementById('generateBtn');\n  const ready = (photo && photo.src && photoDataUrl) && (signature && signature.src && signatureDataUrl);\n  console.log('Update button state:', {photo, photoSrc: photo && photo.src, photoDataUrl, signature, signatureSrc: signature && signature.src, signatureDataUrl, ready});\n  btn.disabled = !ready;\n}\n\nfunction handleImageUpload(type, file) {\n  if (!file) return;\n  const reader = new FileReader();\n  reader.onload = function(event) {\n    const img = new Image();\n    img.onload = () => {\n      if (type === 'photo') {\n        photo = img;\n        photoDataUrl = event.target.result;\n      } else {\n        signature = img;\n        signatureDataUrl = event.target.result;\n      }\n      document.getElementById(`${type}Preview`).src = event.target.result;\n      document.getElementById(`${type}Preview`).style.display = 'block';\n      document.getElementById(`${type}Controls`).style.display = 'flex';\n      console.log('Image loaded:', type, img, 'src:', img.src);\n      updateGenerateButtonState();\n      setTimeout(updateGenerateButtonState, 200); \/\/ re-check after short delay\n    };\n    img.src = event.target.result;\n  };\n  reader.readAsDataURL(file);\n}\n\nfunction rotateImage(type, degrees) {\n  const img = type === 'photo' ? photo : signature;\n  if (!img) return;\n\n  const tempCanvas = document.createElement('canvas');\n  const tempCtx = tempCanvas.getContext('2d');\n  \n  if (Math.abs(degrees) === 90) {\n    tempCanvas.width = img.height;\n    tempCanvas.height = img.width;\n  } else {\n    tempCanvas.width = img.width;\n    tempCanvas.height = img.height;\n  }\n\n  tempCtx.translate(tempCanvas.width\/2, tempCanvas.height\/2);\n  tempCtx.rotate(degrees * Math.PI\/180);\n  tempCtx.drawImage(img, -img.width\/2, -img.height\/2);\n\n  const rotatedImg = new Image();\n  rotatedImg.onload = () => {\n    if (type === 'photo') {\n      photo = rotatedImg;\n      photoDataUrl = rotatedImg.src;\n    } else {\n      signature = rotatedImg;\n      signatureDataUrl = rotatedImg.src;\n    }\n    document.getElementById(`${type}Preview`).src = rotatedImg.src;\n    console.log('Rotated image:', type, rotatedImg, 'src:', rotatedImg.src);\n    updateGenerateButtonState();\n    setTimeout(updateGenerateButtonState, 200);\n  };\n  rotatedImg.src = tempCanvas.toDataURL();\n}\n\nfunction flipImage(type, direction) {\n  const img = type === 'photo' ? photo : signature;\n  if (!img) return;\n\n  const tempCanvas = document.createElement('canvas');\n  const tempCtx = tempCanvas.getContext('2d');\n  \n  tempCanvas.width = img.width;\n  tempCanvas.height = img.height;\n\n  if (direction === 'horizontal') {\n    tempCtx.scale(-1, 1);\n    tempCtx.drawImage(img, -img.width, 0);\n  } else {\n    tempCtx.scale(1, -1);\n    tempCtx.drawImage(img, 0, -img.height);\n  }\n\n  const flippedImg = new Image();\n  flippedImg.onload = () => {\n    if (type === 'photo') {\n      photo = flippedImg;\n      photoDataUrl = flippedImg.src;\n    } else {\n      signature = flippedImg;\n      signatureDataUrl = flippedImg.src;\n    }\n    document.getElementById(`${type}Preview`).src = flippedImg.src;\n    console.log('Flipped image:', type, flippedImg, 'src:', flippedImg.src);\n    updateGenerateButtonState();\n    setTimeout(updateGenerateButtonState, 200);\n  };\n  flippedImg.src = tempCanvas.toDataURL();\n}\n\nfunction showError(message) {\n  const errorDiv = document.getElementById('errorMessage');\n  errorDiv.textContent = message;\n  errorDiv.style.display = 'block';\n  setTimeout(() => {\n    errorDiv.style.display = 'none';\n  }, 3000);\n}\n\nfunction showLoading(show) {\n  document.querySelector('.loading-spinner').style.display = show ? 'block' : 'none';\n}\n\nfunction resetAll() {\n  photo = null;\n  signature = null;\n  photoDataUrl = '';\n  signatureDataUrl = '';\n  document.getElementById('photoPreview').style.display = 'none';\n  document.getElementById('signPreview').style.display = 'none';\n  document.getElementById('photoControls').style.display = 'none';\n  document.getElementById('signControls').style.display = 'none';\n  document.getElementById('photoInput').value = '';\n  document.getElementById('signInput').value = '';\n  ctx.clearRect(0, 0, canvas.width, canvas.height);\n  canvas.width = 0;\n  canvas.height = 0;\n  updateGenerateButtonState();\n  setTimeout(updateGenerateButtonState, 200);\n}\n\nfunction generateOutput() {\n  if (!photo || !signature) {\n    showError(\"Please upload both Photo and Signature\");\n    return;\n  }\n\n  showLoading(true);\n\n  const padding = parseInt(document.getElementById('paddingInput').value) || 0;\n  const border = parseInt(document.getElementById('borderInput').value) || 0;\n  const bgColor = document.getElementById('bgColor').value || '#ffffff';\n  const borderStyle = document.getElementById('borderStyle').value;\n  const borderColor = document.getElementById('borderColor').value;\n\n  const photoSize = document.getElementById('photoSize').value;\n  const signSize = document.getElementById('signSize').value;\n\n  resizeImage(photo, photoSize, (pImg, pw, ph) => {\n    resizeImage(signature, signSize, (sImg, sw, sh) => {\n      const width = Math.max(pw, sw) + padding * 2 + border * 2;\n      const height = ph + sh + padding * 3 + border * 2;\n\n      canvas.width = width;\n      canvas.height = height;\n\n      \/\/ Draw background\n      ctx.fillStyle = bgColor;\n      ctx.fillRect(0, 0, width, height);\n\n      \/\/ Draw photo\n      ctx.drawImage(pImg, (width - pw) \/ 2, padding, pw, ph);\n      if (border > 0) {\n        ctx.strokeStyle = borderColor;\n        ctx.lineWidth = border;\n        if (borderStyle === 'dashed') {\n          ctx.setLineDash([5, 5]);\n        } else if (borderStyle === 'dotted') {\n          ctx.setLineDash([2, 2]);\n        } else {\n          ctx.setLineDash([]);\n        }\n        ctx.strokeRect((width - pw) \/ 2, padding, pw, ph);\n      }\n\n      \/\/ Draw signature\n      const sigY = padding * 2 + ph;\n      ctx.drawImage(sImg, (width - sw) \/ 2, sigY, sw, sh);\n      if (border > 0) {\n        ctx.strokeRect((width - sw) \/ 2, sigY, sw, sh);\n      }\n\n      showLoading(false);\n    });\n  });\n}\n\nfunction resizeImage(img, size, onReady) {\n  if (!size || !\/\\d+x\\d+\/.test(size)) return onReady(img, img.width, img.height);\n  \n  const [w, h] = size.split('x').map(Number);\n  const tempCanvas = document.createElement('canvas');\n  const tempCtx = tempCanvas.getContext('2d');\n  tempCanvas.width = w;\n  tempCanvas.height = h;\n  tempCtx.drawImage(img, 0, 0, w, h);\n  \n  const resizedImg = new Image();\n  resizedImg.onload = () => onReady(resizedImg, w, h);\n  resizedImg.src = tempCanvas.toDataURL();\n}\n\nfunction downloadOutput() {\n  const compression = parseFloat(document.getElementById('compressionInput').value) || 0.9;\n  const link = document.createElement('a');\n  const timestamp = new Date().toISOString().replace(\/[:.]\/g, '-');\n  link.download = `photo_signature_${timestamp}.jpg`;\n  link.href = canvas.toDataURL('image\/jpeg', compression);\n  link.click();\n}\n\nfunction openCropModal(type) {\n  currentCropType = type;\n  const img = type === 'photo' ? photo : signature;\n  if (!img) return;\n\n  const cropImage = document.getElementById('cropImage');\n  cropImage.src = img.src;\n  \n  if (cropper) {\n    cropper.destroy();\n  }\n  \n  cropModal.show();\n  \n  \/\/ Initialize cropper after modal is shown\n  setTimeout(() => {\n    cropper = new Cropper(cropImage, {\n      viewMode: 1,\n      dragMode: 'move',\n      aspectRatio: NaN,\n      autoCropArea: 1,\n      restore: false,\n      guides: true,\n      center: true,\n      highlight: true,\n      cropBoxMovable: true,\n      cropBoxResizable: true,\n      toggleDragModeOnDblclick: true,\n    });\n    document.querySelector('.crop-controls').style.display = 'block';\n  }, 500);\n}\n\nfunction applyCrop() {\n  if (!cropper) return;\n  const croppedCanvas = cropper.getCroppedCanvas();\n  const croppedImage = new Image();\n  croppedImage.onload = function() {\n    if (currentCropType === 'photo') {\n      photo = croppedImage;\n      photoDataUrl = croppedImage.src;\n      document.getElementById('photoPreview').src = croppedImage.src;\n    } else {\n      signature = croppedImage;\n      signatureDataUrl = croppedImage.src;\n      document.getElementById('signPreview').src = croppedImage.src;\n    }\n    cropModal.hide();\n    cropper.destroy();\n    cropper = null;\n    console.log('Cropped image:', currentCropType, croppedImage, 'src:', croppedImage.src);\n    updateGenerateButtonState();\n    setTimeout(updateGenerateButtonState, 200);\n  };\n  croppedImage.src = croppedCanvas.toDataURL();\n}\n\nfunction rotateCropper(degree) {\n  if (cropper) {\n    cropper.rotate(degree);\n  }\n}\n\nfunction flipCropper(direction) {\n  if (cropper) {\n    if (direction === 'horizontal') {\n      cropper.scaleX(cropper.getData().scaleX * -1);\n    } else {\n      cropper.scaleY(cropper.getData().scaleY * -1);\n    }\n  }\n}\n\n\/\/ Add event listeners for aspect ratio buttons\ndocument.querySelectorAll('.aspect-ratio-button').forEach(button => {\n  button.addEventListener('click', function() {\n    const ratio = this.dataset.ratio;\n    \n    \/\/ Remove active class from all buttons\n    document.querySelectorAll('.aspect-ratio-button').forEach(btn => {\n      btn.classList.remove('active');\n    });\n    \n    \/\/ Add active class to clicked button\n    this.classList.add('active');\n    \n    if (cropper) {\n      if (ratio === 'free') {\n        cropper.setAspectRatio(NaN);\n      } else {\n        cropper.setAspectRatio(eval(ratio));\n      }\n    }\n  });\n});\n\n\/\/ Cleanup when modal is hidden\ndocument.getElementById('cropModal').addEventListener('hidden.bs.modal', function () {\n  if (cropper) {\n    cropper.destroy();\n    cropper = null;\n  }\n  document.querySelector('.crop-controls').style.display = 'none';\n});\n\n\/\/ Attribution protection logic\nfunction checkAttribution() {\n  const attr = document.getElementById('mn-attribution');\n  if (!attr) {\n    disableTool();\n    return;\n  }\n  const link = attr.querySelector('a');\n  const textOk = attr.textContent && attr.textContent.includes('Powered by Majhi Naukri');\n  const linkOk = link && link.href && link.href.startsWith('https:\/\/majhinaukri.in');\n  if (!textOk || !linkOk) {\n    disableTool();\n  }\n}\n\nfunction disableTool() {\n  \/\/ Disable all inputs and buttons\n  document.querySelectorAll('input, button, select').forEach(el => {\n    el.disabled = true;\n    el.style.pointerEvents = 'none';\n    el.style.opacity = 0.5;\n  });\n  \/\/ Show alert\n  alert('Majhi Naukri attribution is required for this tool to work.');\n}\n\n\/\/ Check on load and periodically\nwindow.addEventListener('DOMContentLoaded', () => {\n  checkAttribution();\n  setInterval(checkAttribution, 2000);\n  updateGenerateButtonState();\n});\n<\/script>\n\n<\/div>\n\n<div class='code-block code-block-2' style='margin: 8px 0; clear: both;'>\n<div class='code-block code-block-43' style='margin: 8px 0; clear: both;'>\n<style>\n\/* ===============================\n   COMPACT OTHER TOOLS PANEL\n   =============================== *\/\n\n.mn-other-tools {\n  margin-top: 24px;\n}\n\n.mn-other-tools h3 {\n  font-size: 16px;\n  font-weight: 600;\n  color: #111827;\n  margin-bottom: 10px;\n}\n\n\/* Compact Grid *\/\n.mn-tools-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));\n  gap: 10px;\n}\n\n\/* Compact Card *\/\n.mn-tool-item {\n  background: #ffffff;\n  border-radius: 10px;\n  padding: 10px 12px;\n  text-decoration: none !important;\n  border: 1px solid #e5e7eb;\n  transition: all 0.2s ease;\n  display: flex;\n  align-items: center;\n  gap: 10px;\n}\n\n.mn-tool-item:hover {\n  background: #f8fafc;\n  border-color: #3d85c6;\n}\n\n\/* Smaller Icon *\/\n.mn-tool-icon {\n  width: 34px;\n  height: 34px;\n  border-radius: 8px;\n  background: #eef5ff;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 16px;\n}\n\n\/* Text *\/\n.mn-tool-text span {\n  font-size: 13px;\n  font-weight: 500;\n  color: #111827;\n  line-height: 1.2;\n}\n\n.mn-tool-text small {\n  display: none; \/* remove subtitle for compact UI *\/\n}\n\n\/* Mobile *\/\n@media (max-width: 600px) {\n  .mn-tools-grid {\n    grid-template-columns: 1fr 1fr;\n  }\n}\n<\/style>\n\n<div class=\"mn-other-tools\">\n  <h3>\ud83d\udd27 Other Tools<\/h3>\n\n  <div class=\"mn-tools-grid\" id=\"mnToolsGrid\">\n\n    <a href=\"\/tools\/image-resizer\/\" class=\"mn-tool-item\">\n      <div class=\"mn-tool-icon\">\ud83d\uddbc\ufe0f<\/div>\n      <div class=\"mn-tool-text\"><span>Image Resizer<\/span><\/div>\n    <\/a>\n\n    <a href=\"\/tools\/photo-signature-joiner\/\" class=\"mn-tool-item\">\n      <div class=\"mn-tool-icon\">\u270d\ufe0f<\/div>\n      <div class=\"mn-tool-text\"><span>Photo & Sign Joiner<\/span><\/div>\n    <\/a>\n\n    <a href=\"\/tools\/image-to-pdf\/\" class=\"mn-tool-item\">\n      <div class=\"mn-tool-icon\">\ud83d\udcc4<\/div>\n      <div class=\"mn-tool-text\"><span>Image to PDF<\/span><\/div>\n    <\/a>\n\n    <a href=\"\/tools\/marathi-typing-test\/\" class=\"mn-tool-item\">\n      <div class=\"mn-tool-icon\">\u2328\ufe0f<\/div>\n      <div class=\"mn-tool-text\"><span>Marathi Typing Test<\/span><\/div>\n    <\/a>\n\n    <a href=\"\/tools\/english-typing-test\/\" class=\"mn-tool-item\">\n      <div class=\"mn-tool-icon\">\u2328\ufe0f<\/div>\n      <div class=\"mn-tool-text\"><span>English Typing Test<\/span><\/div>\n    <\/a>\n\n    <a href=\"\/tools\/pdf-merger\/\" class=\"mn-tool-item\">\n      <div class=\"mn-tool-icon\">\ud83d\udcd1<\/div>\n      <div class=\"mn-tool-text\"><span>PDF Merger<\/span><\/div>\n    <\/a>\n\n    <a href=\"\/tools\/pdf-splitter\/\" class=\"mn-tool-item\">\n      <div class=\"mn-tool-icon\">\u2702\ufe0f<\/div>\n      <div class=\"mn-tool-text\"><span>PDF Splitter<\/span><\/div>\n    <\/a>\n\n    <a href=\"\/tools\/pdf-arranger\/\" class=\"mn-tool-item\">\n      <div class=\"mn-tool-icon\">\ud83d\uddc2\ufe0f<\/div>\n      <div class=\"mn-tool-text\"><span>PDF Arranger<\/span><\/div>\n    <\/a>\n\n    <a href=\"\/tools\/age-calculator\/\" class=\"mn-tool-item\">\n      <div class=\"mn-tool-icon\">\ud83d\udcc5<\/div>\n      <div class=\"mn-tool-text\"><span>Age Calculator<\/span><\/div>\n    <\/a>\n\n    <a href=\"\/tools\/emi-calculator\/\" class=\"mn-tool-item\">\n      <div class=\"mn-tool-icon\">\ud83d\udcb0<\/div>\n      <div class=\"mn-tool-text\"><span>EMI Calculator<\/span><\/div>\n    <\/a>\n\n    <a href=\"\/tools\/bmi-calculator\/\" class=\"mn-tool-item\">\n      <div class=\"mn-tool-icon\">\u2696\ufe0f<\/div>\n      <div class=\"mn-tool-text\"><span>BMI Calculator<\/span><\/div>\n    <\/a>\n\n    <a href=\"\/tools\/gst-calculator\/\" class=\"mn-tool-item\">\n      <div class=\"mn-tool-icon\">\ud83e\uddfe<\/div>\n      <div class=\"mn-tool-text\"><span>GST Calculator<\/span><\/div>\n    <\/a>\n\n    <a href=\"\/tools\/percentage-calculator\/\" class=\"mn-tool-item\">\n      <div class=\"mn-tool-icon\">\ud83d\udcca<\/div>\n      <div class=\"mn-tool-text\"><span>Percentage Calculator<\/span><\/div>\n    <\/a>\n\n  <\/div>\n<\/div>\n\n<script>\n\/* ===============================\n   AUTO-HIDE CURRENT TOOL\n   =============================== *\/\n(function () {\n  const currentPath = window.location.pathname.replace(\/\\\/$\/, \"\");\n  const tools = document.querySelectorAll(\"#mnToolsGrid a\");\n\n  tools.forEach(tool => {\n    const toolPath = tool.getAttribute(\"href\").replace(\/\\\/$\/, \"\");\n    if (currentPath === toolPath) {\n      tool.style.display = \"none\";\n    }\n  });\n})();\n<\/script>\n<\/div>\n<\/div>\n\n<h2 class=\"\" data-start=\"167\" data-end=\"246\">\ud83d\uddbc\ufe0f Photo Signature Joiner \u2013 Merge Passport Photo &amp; Signature in One Image<\/h2>\n<p class=\"\" data-start=\"248\" data-end=\"555\">When you&#8217;re applying for competitive exams, government jobs, or filling out online forms, you\u2019re often asked to upload a <strong data-start=\"369\" data-end=\"410\">photo + signature in a single image<\/strong>. It sounds simple \u2014 but not everyone has Photoshop or editing skills. That\u2019s why we\u2019ve built this <strong data-start=\"509\" data-end=\"554\">easy-to-use Photo &amp; Signature Joiner Tool<\/strong>.<\/p>\n<h3 class=\"\" data-start=\"557\" data-end=\"584\">\ud83d\udccb What This Tool Does:<\/h3>\n<p class=\"\" data-start=\"586\" data-end=\"743\">This tool merges your <strong data-start=\"608\" data-end=\"631\">passport-size photo<\/strong> and <strong data-start=\"636\" data-end=\"657\">digital signature<\/strong> into one file, side-by-side or one above the other \u2014 ready to upload wherever needed.<\/p>\n<h3 class=\"\" data-start=\"745\" data-end=\"764\">\ud83d\udee0\ufe0f How to Use:<\/h3>\n<ol data-start=\"766\" data-end=\"1046\">\n<li class=\"\" data-start=\"766\" data-end=\"815\">\n<p class=\"\" data-start=\"769\" data-end=\"815\"><strong data-start=\"769\" data-end=\"790\">Upload your photo<\/strong> (usually in JPG or PNG).<\/p>\n<\/li>\n<li class=\"\" data-start=\"816\" data-end=\"866\">\n<p class=\"\" data-start=\"819\" data-end=\"866\"><strong data-start=\"819\" data-end=\"844\">Upload your signature<\/strong> (scanned or digital).<\/p>\n<\/li>\n<li class=\"\" data-start=\"867\" data-end=\"921\">\n<p class=\"\" data-start=\"870\" data-end=\"921\">Choose your <strong data-start=\"882\" data-end=\"892\">layout<\/strong>: side-by-side or top-bottom.<\/p>\n<\/li>\n<li class=\"\" data-start=\"922\" data-end=\"972\">\n<p class=\"\" data-start=\"925\" data-end=\"972\">Adjust spacing, size, and background if needed.<\/p>\n<\/li>\n<li class=\"\" data-start=\"973\" data-end=\"1046\">\n<p class=\"\" data-start=\"976\" data-end=\"1046\"><strong data-start=\"976\" data-end=\"1003\">Click &#8220;Join &amp; Download&#8221;<\/strong> \u2013 your combined image is ready in seconds.<\/p>\n<\/li>\n<\/ol>\n<h3 class=\"\" data-start=\"1048\" data-end=\"1064\">\u2714\ufe0f Features:<\/h3>\n<ul data-start=\"1066\" data-end=\"1234\">\n<li class=\"\" data-start=\"1066\" data-end=\"1097\">\n<p class=\"\" data-start=\"1068\" data-end=\"1097\">No watermark, completely free<\/p>\n<\/li>\n<li class=\"\" data-start=\"1098\" data-end=\"1127\">\n<p class=\"\" data-start=\"1100\" data-end=\"1127\">Works on mobile and desktop<\/p>\n<\/li>\n<li class=\"\" data-start=\"1128\" data-end=\"1157\">\n<p class=\"\" data-start=\"1130\" data-end=\"1157\">Supports JPG, PNG, and WebP<\/p>\n<\/li>\n<li class=\"\" data-start=\"1158\" data-end=\"1203\">\n<p class=\"\" data-start=\"1160\" data-end=\"1203\">Ideal dimensions for most application forms<\/p>\n<\/li>\n<li class=\"\" data-start=\"1204\" data-end=\"1234\">\n<p class=\"\" data-start=\"1206\" data-end=\"1234\">No technical skills required<\/p>\n<\/li>\n<\/ul>\n<h3 class=\"\" data-start=\"1236\" data-end=\"1254\">\ud83d\udccc Useful For:<\/h3>\n<ul data-start=\"1256\" data-end=\"1381\">\n<li class=\"\" data-start=\"1256\" data-end=\"1310\">\n<p class=\"\" data-start=\"1258\" data-end=\"1310\">MPSC, UPSC, SSC, IBPS &amp; other competitive exam forms<\/p>\n<\/li>\n<li class=\"\" data-start=\"1311\" data-end=\"1337\">\n<p class=\"\" data-start=\"1313\" data-end=\"1337\">Scholarship applications<\/p>\n<\/li>\n<li class=\"\" data-start=\"1338\" data-end=\"1381\">\n<p class=\"\" data-start=\"1340\" data-end=\"1381\">Online job portals and government schemes<\/p>\n<\/li>\n<\/ul>\n<hr class=\"\" data-start=\"1383\" data-end=\"1386\" \/>\n<p class=\"\" data-start=\"1388\" data-end=\"1514\">\ud83d\udca1 <strong data-start=\"1391\" data-end=\"1403\">Pro Tip:<\/strong> Before uploading, double-check file size and dimensions required by the portal. Our tool helps you resize too!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\uddbc\ufe0f Photo Signature Joiner \u2013 Merge Passport Photo &amp; Signature in One Image When you&#8217;re applying for competitive exams, government jobs, or filling out online forms, you\u2019re often asked to upload a photo + signature in a single image. It sounds simple \u2014 but not everyone has Photoshop or editing skills. That\u2019s why we\u2019ve built [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":452,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-193","page","type-page","status-publish","has-post-thumbnail"],"_links":{"self":[{"href":"https:\/\/majhinaukri.in\/tools\/wp-json\/wp\/v2\/pages\/193","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/majhinaukri.in\/tools\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/majhinaukri.in\/tools\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/majhinaukri.in\/tools\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/majhinaukri.in\/tools\/wp-json\/wp\/v2\/comments?post=193"}],"version-history":[{"count":1,"href":"https:\/\/majhinaukri.in\/tools\/wp-json\/wp\/v2\/pages\/193\/revisions"}],"predecessor-version":[{"id":514,"href":"https:\/\/majhinaukri.in\/tools\/wp-json\/wp\/v2\/pages\/193\/revisions\/514"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/majhinaukri.in\/tools\/wp-json\/wp\/v2\/media\/452"}],"wp:attachment":[{"href":"https:\/\/majhinaukri.in\/tools\/wp-json\/wp\/v2\/media?parent=193"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}