{"id":3897,"date":"2025-12-22T14:38:44","date_gmt":"2025-12-22T14:38:44","guid":{"rendered":"https:\/\/bkupdate.in\/?page_id=3897"},"modified":"2025-12-22T17:14:15","modified_gmt":"2025-12-22T17:14:15","slug":"photo-signature-joiner","status":"publish","type":"page","link":"https:\/\/bkupdate.in\/?page_id=3897","title":{"rendered":"Photo &amp; Signature Joiner"},"content":{"rendered":"\n<!-- Photo & Signature Joiner Tool Start -->\n<div id=\"rtps_tool_wrapper_hq\">\n\n    <!-- External Libraries -->\n    <link href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/cropperjs\/1.5.12\/cropper.min.css\" rel=\"stylesheet\">\n    <link rel=\"stylesheet\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/6.4.0\/css\/all.min.css\">\n    <link href=\"https:\/\/fonts.googleapis.com\/css2?family=Space+Grotesk:wght@400;500;700&#038;display=swap\" rel=\"stylesheet\">\n    <link href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@300;400;500;600;700&#038;display=swap\" rel=\"stylesheet\">\n\n    <style>\n        \/* Scoped Styles for WordPress Compatibility *\/\n        #rtps_tool_wrapper_hq {\n            all: initial;\n            font-family: 'Inter', sans-serif;\n            --rtps-primary: #2563eb;\n            --rtps-dark: #1d4ed8;\n            --rtps-light: #dbeafe;\n            --rtps-text: #1e293b;\n            --rtps-bg: #f8fafc;\n            display: block;\n            width: 100%;\n            margin: 20px auto;\n        }\n\n        #rtps_tool_wrapper_hq * { box-sizing: border-box; }\n\n        #rtps_tool_wrapper_hq .rtps-main-box {\n            background-color: #ffffff; padding: 2rem; border-radius: 12px;\n            box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); width: 100%; max-width: 1000px;\n            margin: 0 auto; border: 1px solid #e2e8f0; position: relative;\n        }\n\n        #rtps_tool_wrapper_hq h1 {\n            color: var(--rtps-text); font-size: 1.8rem; font-weight: 800;\n            text-align: center; margin-bottom: 10px; line-height: 1.3; font-family: 'Inter', sans-serif;\n        }\n\n        #rtps_tool_wrapper_hq .rtps-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-bottom: 2rem; margin-top: 2rem; }\n        #rtps_tool_wrapper_hq .rtps-card { border: 1px solid #e2e8f0; border-radius: 8px; padding: 1.5rem; background: #fff; }\n        \n        #rtps_tool_wrapper_hq .rtps-upload-label {\n            display: flex; align-items: center; justify-content: center; width: 100%; padding: 12px;\n            border: 2px dashed var(--rtps-primary); background: #f0f9ff; color: var(--rtps-primary);\n            border-radius: 8px; cursor: pointer; font-weight: 600; margin-bottom: 1rem; transition: 0.3s;\n        }\n        #rtps_tool_wrapper_hq .rtps-upload-label:hover { background: var(--rtps-light); }\n        #rtps_tool_wrapper_hq input[type=\"file\"] { display: none; }\n\n        #rtps_tool_wrapper_hq .rtps-preview-area {\n            width: 100%; height: 300px; background: #f1f5f9; border-radius: 8px; overflow: hidden;\n            display: flex; justify-content: center; align-items: center; position: relative;\n        }\n        #rtps_tool_wrapper_hq img { max-width: 100%; display: block; }\n\n        #rtps_tool_wrapper_hq .rtps-controls { margin-top: 1rem; display: flex; flex-direction: column; gap: 10px; }\n        #rtps_tool_wrapper_hq .rtps-control-row { display: flex; align-items: center; gap: 10px; }\n        #rtps_tool_wrapper_hq label { font-size: 0.9rem; font-weight: 600; width: 70px; color: #475569; }\n        \n        #rtps_tool_wrapper_hq input[type=\"range\"] { flex-grow: 1; height: 6px; background: #cbd5e1; border-radius: 5px; appearance: none; }\n        #rtps_tool_wrapper_hq input[type=\"range\"]::-webkit-slider-thumb { appearance: none; width: 18px; height: 18px; background: var(--rtps-primary); border-radius: 50%; cursor: pointer; }\n\n        #rtps_tool_wrapper_hq .rtps-options-bar {\n            background:#f1f5f9; padding:15px; border-radius:8px; margin-bottom:20px; \n            display:flex; align-items:center; justify-content: space-between; flex-wrap: wrap; gap:15px;\n        }\n\n        #rtps_tool_wrapper_hq .rtps-btn {\n            width: 100%; padding: 16px; background: var(--rtps-primary); color: white; border: none;\n            border-radius: 8px; font-size: 1.1rem; font-weight: 700; cursor: pointer; transition: 0.3s;\n            box-shadow: 0 4px 6px -1px rgba(37, 99, 235, 0.2);\n        }\n        #rtps_tool_wrapper_hq .rtps-btn:hover { background: var(--rtps-dark); transform: translateY(-2px); }\n        #rtps_tool_wrapper_hq .rtps-btn:disabled { background: #94a3b8; cursor: not-allowed; transform: none; }\n\n        \/* Result Area *\/\n        #rtps_tool_wrapper_hq .rtps-result { margin-top: 2rem; border-top: 2px solid #f1f5f9; padding-top: 2rem; display: none; text-align: center; }\n        #rtps_tool_wrapper_hq .rtps-download-btn {\n            background: #16a34a; color: white; text-decoration: none; padding: 12px 25px;\n            border-radius: 8px; font-weight: 700; display: inline-flex; align-items: center; gap: 8px;\n            margin-top: 15px; font-family: 'Inter', sans-serif;\n        }\n        #rtps_tool_wrapper_hq .rtps-download-btn:hover { background: #15803d; }\n\n        \/* Checkbox Style *\/\n        #rtps_tool_wrapper_hq .checkbox-wrapper { display: flex; align-items: center; gap: 8px; cursor: pointer; }\n        #rtps_tool_wrapper_hq input[type=\"checkbox\"] { width: 18px; height: 18px; cursor: pointer; }\n\n        @media (max-width: 768px) { #rtps_tool_wrapper_hq .rtps-grid { grid-template-columns: 1fr; } }\n\n        \/* Loader *\/\n        #rtps_loader_hq {\n            position: fixed; top: 0; left: 0; width: 100%; height: 100%;\n            background: rgba(15, 23, 42, 0.95); z-index: 999999;\n            display: none; justify-content: center; align-items: center; flex-direction: column;\n            font-family: 'Space Grotesk', sans-serif;\n        }\n        .rtps-spinner { width: 70px; height: 70px; border: 4px solid rgba(255,255,255,0.1); border-top: 4px solid #00f0ff; border-radius: 50%; animation: rtpsSpin 1s linear infinite; }\n        .rtps-loading-text { color: #00f0ff; margin-top: 20px; font-size: 1.2rem; letter-spacing: 1px; }\n        @keyframes rtpsSpin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }\n    <\/style>\n\n    <div class=\"rtps-main-box\">\n        <h1>Photo &#038; Signature Joiner<\/h1>\n        <p style=\"text-align:center; color:#64748b;\">Perfectly resized, high-quality output for official forms.<\/p>\n\n        <div class=\"rtps-grid\">\n            <!-- PHOTO -->\n            <div class=\"rtps-card\">\n                <h2><i class=\"fas fa-user\"><\/i> Photo<\/h2>\n                <label for=\"rtps_hq_photo\" class=\"rtps-upload-label\"><i class=\"fas fa-upload\"><\/i> Upload Photo<\/label>\n                <input type=\"file\" id=\"rtps_hq_photo\" accept=\"image\/*\">\n                <div class=\"rtps-preview-area\"><img decoding=\"async\" id=\"prev_photo\" src=\"\"><\/div>\n                <div class=\"rtps-controls\">\n                    <div class=\"rtps-control-row\"><label>Rotate<\/label><input type=\"range\" id=\"rot_photo\" min=\"-180\" max=\"180\" value=\"0\"><\/div>\n                    <div class=\"rtps-control-row\"><label>Zoom<\/label><input type=\"range\" id=\"zoom_photo\" min=\"0.1\" max=\"3\" value=\"1\" step=\"0.1\"><\/div>\n                <\/div>\n            <\/div>\n\n            <!-- SIGNATURE -->\n            <div class=\"rtps-card\">\n                <h2><i class=\"fas fa-pen\"><\/i> Signature<\/h2>\n                <label for=\"rtps_hq_sign\" class=\"rtps-upload-label\"><i class=\"fas fa-upload\"><\/i> Upload Signature<\/label>\n                <input type=\"file\" id=\"rtps_hq_sign\" accept=\"image\/*\">\n                <div class=\"rtps-preview-area\"><img decoding=\"async\" id=\"prev_sign\" src=\"\"><\/div>\n                <div class=\"rtps-controls\">\n                    <div class=\"rtps-control-row\"><label>Rotate<\/label><input type=\"range\" id=\"rot_sign\" min=\"-180\" max=\"180\" value=\"0\"><\/div>\n                    <div class=\"rtps-control-row\"><label>Zoom<\/label><input type=\"range\" id=\"zoom_sign\" min=\"0.1\" max=\"3\" value=\"1\" step=\"0.1\"><\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <!-- Options Bar -->\n        <div class=\"rtps-options-bar\">\n            <div style=\"display:flex; align-items:center; gap:10px;\">\n                <label style=\"font-weight:bold; color:#334155; width:auto;\">Max Size (KB):<\/label>\n                <input type=\"number\" id=\"rtps_hq_size\" value=\"50\" min=\"10\" style=\"padding:8px; border-radius:4px; border:1px solid #cbd5e1; width:80px;\">\n            <\/div>\n            \n            <label class=\"checkbox-wrapper\" style=\"width:auto; color:#1e293b; font-weight:600;\">\n                <input type=\"checkbox\" id=\"rtps_add_border\">\n                Add Black Border\n            <\/label>\n        <\/div>\n\n        <button id=\"rtps_hq_btn\" class=\"rtps-btn\">Generate Photo<\/button>\n\n        <!-- RESULT -->\n        <div id=\"rtps_hq_result\" class=\"rtps-result\">\n            <h3 style=\"color:#166534; font-weight:700; font-size:1.5rem; margin-bottom:10px;\">Done! <i class=\"fas fa-check-circle\"><\/i><\/h3>\n            <p>Final Size: <span id=\"rtps_hq_final_size\" style=\"font-weight:bold; color:#2563eb;\"><\/span><\/p>\n            <img id=\"rtps_hq_img\" style=\"max-width:350px; box-shadow:0 10px 15px rgba(0,0,0,0.1); border-radius:4px; margin:20px auto;\">\n            <br>\n            <a href=\"#\" id=\"rtps_hq_link\" class=\"rtps-download-btn\" download=\"RTPS_Final.jpg\">\n                <i class=\"fas fa-download\"><\/i> Download HD Image\n            <\/a>\n        <\/div>\n\n        <canvas id=\"rtps_hq_canvas\" style=\"display:none;\"><\/canvas>\n    <\/div>\n\n    <!-- LOADER -->\n    <div id=\"rtps_loader_hq\">\n        <div class=\"rtps-spinner\"><\/div>\n        <div class=\"rtps-loading-text\" id=\"rtps_loading_txt\">Processing&#8230;<\/div>\n    <\/div>\n\n<\/div>\n\n<!-- Scripts -->\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/cropperjs\/1.5.12\/cropper.min.js\"><\/script>\n<script>\n(function() {\n    document.addEventListener('DOMContentLoaded', function() {\n        \n        const getEl = (id) => document.querySelector(`#rtps_tool_wrapper_hq #${id}`);\n        \n        const inputs = { photo: getEl('rtps_hq_photo'), sign: getEl('rtps_hq_sign') };\n        const previews = { photo: getEl('prev_photo'), sign: getEl('prev_sign') };\n        const controls = {\n            pRot: getEl('rot_photo'), pZoom: getEl('zoom_photo'),\n            sRot: getEl('rot_sign'), sZoom: getEl('zoom_sign')\n        };\n        const btn = getEl('rtps_hq_btn');\n        const loader = getEl('rtps_loader_hq');\n        const loadTxt = getEl('rtps_loading_txt');\n        const resultDiv = getEl('rtps_hq_result');\n        const resultImg = getEl('rtps_hq_img');\n        const resultLink = getEl('rtps_hq_link');\n        const resultSize = getEl('rtps_hq_final_size');\n        const canvas = getEl('rtps_hq_canvas');\n        const targetSizeInput = getEl('rtps_hq_size');\n        const borderCheckbox = getEl('rtps_add_border');\n\n        let croppers = { photo: null, sign: null };\n\n        function initCropper(type) {\n            inputs[type].addEventListener('change', (e) => {\n                if (e.target.files && e.target.files[0]) {\n                    const reader = new FileReader();\n                    reader.onload = (evt) => {\n                        previews[type].src = evt.target.result;\n                        if (croppers[type]) croppers[type].destroy();\n                        \n                        croppers[type] = new Cropper(previews[type], {\n                            aspectRatio: type === 'photo' ? (350\/350) : NaN, \n                            viewMode: 1,\n                            dragMode: 'move',\n                            autoCropArea: 1,\n                            background: false,\n                            imageSmoothingQuality: 'high'\n                        });\n                    };\n                    reader.readAsDataURL(e.target.files[0]);\n                }\n            });\n        }\n\n        initCropper('photo');\n        initCropper('sign');\n\n        \/\/ Controls\n        controls.pRot.addEventListener('input', () => croppers.photo?.rotateTo(controls.pRot.value));\n        controls.pZoom.addEventListener('input', () => croppers.photo?.zoomTo(controls.pZoom.value));\n        controls.sRot.addEventListener('input', () => croppers.sign?.rotateTo(controls.sRot.value));\n        controls.sZoom.addEventListener('input', () => croppers.sign?.zoomTo(controls.sZoom.value));\n\n        \/\/ Generate High Quality Image\n        btn.addEventListener('click', () => {\n            if (!croppers.photo || !croppers.sign) {\n                alert('Please upload both images!');\n                return;\n            }\n\n            loader.style.display = 'flex';\n            const logs = [\"Enhancing Resolution...\", \"Merging Layers...\", \"Optimizing Quality...\", \"Finalizing...\"];\n            let i = 0;\n            const logInt = setInterval(() => {\n                if (i < logs.length) loadTxt.innerText = \"> \" + logs[i++];\n            }, 800);\n\n            setTimeout(async () => {\n                clearInterval(logInt);\n\n                \/\/ High Resolution Canvas (Double scale for sharpness)\n                const W = 700; \n                const H_PHOTO = 720;\n                const H_SIGN = 180;\n                const TOTAL_H = 900;\n\n                canvas.width = W;\n                canvas.height = TOTAL_H;\n                const ctx = canvas.getContext('2d');\n\n                \/\/ High Quality Settings\n                ctx.imageSmoothingEnabled = true;\n                ctx.imageSmoothingQuality = 'high';\n\n                \/\/ White Background\n                ctx.fillStyle = '#ffffff';\n                ctx.fillRect(0, 0, W, TOTAL_H);\n\n                \/\/ Get Cropped Data (High Res)\n                const pImg = croppers.photo.getCroppedCanvas({ width: W, height: H_PHOTO, imageSmoothingQuality: 'high' });\n                const sImg = croppers.sign.getCroppedCanvas({ width: W, height: H_SIGN, imageSmoothingQuality: 'high' });\n\n                ctx.drawImage(pImg, 0, 0, W, H_PHOTO);\n                ctx.drawImage(sImg, 0, H_PHOTO, W, H_SIGN);\n\n                \/\/ --- ADD BORDER LOGIC ---\n                if (borderCheckbox.checked) {\n                    ctx.lineWidth = 10; \/\/ Proportional for high-res canvas (looks like 2-3px on screen)\n                    ctx.strokeStyle = '#000000';\n                    ctx.strokeRect(0, 0, W, TOTAL_H);\n                }\n\n                \/\/ Smart Compression Logic\n                const maxKB = parseInt(targetSizeInput.value);\n                let quality = 1.0; \n                let blob = await new Promise(r => canvas.toBlob(r, 'image\/jpeg', quality));\n\n                while (blob.size > maxKB * 1024 && quality > 0.1) {\n                    quality -= 0.02; \n                    blob = await new Promise(r => canvas.toBlob(r, 'image\/jpeg', quality));\n                }\n\n                const url = URL.createObjectURL(blob);\n                resultImg.src = url;\n                resultLink.href = url;\n                resultSize.innerText = (blob.size \/ 1024).toFixed(2) + \" KB\";\n\n                loader.style.display = 'none';\n                resultDiv.style.display = 'block';\n                resultDiv.scrollIntoView({ behavior: 'smooth' });\n\n            }, 4000);\n        });\n    });\n})();\n<\/script>\n<\/div>\n<!-- Photo & Signature Joiner Tool End -->\n","protected":false},"excerpt":{"rendered":"<p>Photo &#038; Signature Joiner Perfectly resized, high-quality output for official forms. Photo Upload Photo Rotate Zoom Signature Upload &#8230; <\/p>\n<p class=\"read-more-container\"><a title=\"Photo &amp; Signature Joiner\" class=\"read-more button\" href=\"https:\/\/bkupdate.in\/?page_id=3897#more-3897\" aria-label=\"Read more about Photo &amp; Signature Joiner\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-3897","page","type-page","status-publish"],"_links":{"self":[{"href":"https:\/\/bkupdate.in\/index.php?rest_route=\/wp\/v2\/pages\/3897","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bkupdate.in\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/bkupdate.in\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/bkupdate.in\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bkupdate.in\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3897"}],"version-history":[{"count":5,"href":"https:\/\/bkupdate.in\/index.php?rest_route=\/wp\/v2\/pages\/3897\/revisions"}],"predecessor-version":[{"id":3916,"href":"https:\/\/bkupdate.in\/index.php?rest_route=\/wp\/v2\/pages\/3897\/revisions\/3916"}],"wp:attachment":[{"href":"https:\/\/bkupdate.in\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3897"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}