{"id":1044,"date":"2026-09-06T15:03:48","date_gmt":"2026-09-06T15:03:48","guid":{"rendered":"https:\/\/freewebtools.co.in\/?page_id=1044"},"modified":"2026-09-06T16:26:14","modified_gmt":"2026-09-06T16:26:14","slug":"1044-2","status":"publish","type":"page","link":"https:\/\/freewebtools.co.in\/index.php\/1044-2\/","title":{"rendered":"Auto Cropper New"},"content":{"rendered":"\n<!-- IMAGE EDITOR CONTAINER -->\n<div id=\"wp-auto-cropper-wrapper\">\n<div id=\"wp-img-editor\" class=\"wp-img-editor-root\">\n  <!-- Top Toolbar -->\n  <div class=\"editor-toolbar\">\n    <label class=\"btn btn-primary\">\n      Open Image\n      <input type=\"file\" id=\"fileInput\" accept=\"image\/*\" style=\"display: none;\" \/>\n    <\/label>\n    <button id=\"btnAutoCrop\" class=\"btn\" disabled>Auto-Crop<\/button>\n    <button id=\"btnCrop\" class=\"btn\" disabled>Apply Crop<\/button>\n    <button id=\"btnRotateLeft\" class=\"btn\" disabled>\u21ba 90\u00b0<\/button>\n    <button id=\"btnRotateRight\" class=\"btn\" disabled>\u21bb 90\u00b0<\/button>\n    <button id=\"btnFlipH\" class=\"btn\" disabled>\u21c4 Flip H<\/button>\n    <button id=\"btnFlipV\" class=\"btn\" disabled>\u21c5 Flip V<\/button>\n    <button id=\"btnZoomIn\" class=\"btn\" disabled>\ud83d\udd0d+<\/button>\n    <button id=\"btnZoomOut\" class=\"btn\" disabled>\ud83d\udd0d-<\/button>\n    <button id=\"btnZoomReset\" class=\"btn\" disabled>Zoom 100%<\/button>\n    <button id=\"btnUndo\" class=\"btn\" disabled>Undo<\/button>\n    <button id=\"btnRedo\" class=\"btn\" disabled>Redo<\/button>\n    <button id=\"btnReset\" class=\"btn btn-danger\" disabled>Reset<\/button>\n  <\/div>\n\n  <!-- Workspace Section -->\n  <div class=\"editor-workspace\">\n    <!-- Controls Sidebar -->\n    <div class=\"editor-sidebar\">\n      <div class=\"control-group\">\n        <label>Aspect Ratio<\/label>\n        <div class=\"aspect-grid\">\n          <button class=\"aspect-btn active\" data-ratio=\"free\">Free<\/button>\n          <button class=\"aspect-btn\" data-ratio=\"1:1\">1:1<\/button>\n          <button class=\"aspect-btn\" data-ratio=\"4:3\">4:3<\/button>\n          <button class=\"aspect-btn\" data-ratio=\"16:9\">16:9<\/button>\n        <\/div>\n      <\/div>\n\n      <div class=\"control-group\">\n        <label for=\"slideZoom\">Zoom (<span id=\"valZoom\">100<\/span>%)<\/label>\n        <input type=\"range\" id=\"slideZoom\" min=\"10\" max=\"300\" value=\"100\" \/>\n      <\/div>\n\n      <div class=\"control-group\">\n        <label for=\"slideBright\">Brightness (<span id=\"valBright\">0<\/span>)<\/label>\n        <input type=\"range\" id=\"slideBright\" min=\"-100\" max=\"100\" value=\"0\" \/>\n      <\/div>\n\n      <div class=\"control-group\">\n        <label for=\"slideContrast\">Contrast (<span id=\"valContrast\">0<\/span>)<\/label>\n        <input type=\"range\" id=\"slideContrast\" min=\"-100\" max=\"100\" value=\"0\" \/>\n      <\/div>\n\n      <div class=\"control-group\">\n        <label for=\"slideSat\">Saturation (<span id=\"valSat\">0<\/span>)<\/label>\n        <input type=\"range\" id=\"slideSat\" min=\"-100\" max=\"100\" value=\"0\" \/>\n      <\/div>\n\n      <div class=\"control-group\">\n        <label for=\"slideGray\">Grayscale (<span id=\"valGray\">0<\/span>%)<\/label>\n        <input type=\"range\" id=\"slideGray\" min=\"0\" max=\"100\" value=\"0\" \/>\n      <\/div>\n\n      <div class=\"control-group\">\n        <label for=\"exportFormat\">Export Format<\/label>\n        <select id=\"exportFormat\" class=\"select-input\">\n          <option value=\"image\/png\">PNG<\/option>\n          <option value=\"image\/jpeg\">JPEG<\/option>\n          <option value=\"image\/webp\">WebP<\/option>\n        <\/select>\n      <\/div>\n\n      <div class=\"control-group\">\n        <label for=\"slideQuality\">Quality (<span id=\"valQuality\">90<\/span>%)<\/label>\n        <input type=\"range\" id=\"slideQuality\" min=\"10\" max=\"100\" value=\"90\" \/>\n      <\/div>\n    <\/div>\n\n    <!-- Canvas Stage -->\n    <div class=\"editor-stage\" id=\"stageContainer\">\n      <div id=\"canvasWrapper\" class=\"canvas-wrapper\">\n        <canvas id=\"mainCanvas\"><\/canvas>\n        <div id=\"cropOverlay\" class=\"crop-overlay\" style=\"display: none;\">\n          <div id=\"cropBox\" class=\"crop-box\">\n            <div class=\"crop-handle handle-nw\" data-handle=\"nw\"><\/div>\n            <div class=\"crop-handle handle-ne\" data-handle=\"ne\"><\/div>\n            <div class=\"crop-handle handle-sw\" data-handle=\"sw\"><\/div>\n            <div class=\"crop-handle handle-se\" data-handle=\"se\"><\/div>\n            <div class=\"crop-handle handle-n\" data-handle=\"n\"><\/div>\n            <div class=\"crop-handle handle-s\" data-handle=\"s\"><\/div>\n            <div class=\"crop-handle handle-w\" data-handle=\"w\"><\/div>\n            <div class=\"crop-handle handle-e\" data-handle=\"e\"><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <!-- Export & Action Footer -->\n  <div class=\"editor-footer\">\n    <span id=\"statusText\" class=\"status-text\">Upload an image to start editing.<\/span>\n    <div class=\"action-buttons\">\n      <button id=\"btnCopy\" class=\"btn\" disabled>Copy Image<\/button>\n      <button id=\"btnPrint\" class=\"btn\" disabled>Print<\/button>\n      <button id=\"btnDownloadFull\" class=\"btn\" disabled>Save Full<\/button>\n      <button id=\"btnDownloadCropped\" class=\"btn btn-primary\" disabled>Save Cropped<\/button>\n    <\/div>\n  <\/div>\n<\/div>\n<\/div>\n\n<!-- STYLES -->\n<style>\n  #wp-auto-cropper-wrapper .wp-img-editor-root {\n    font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n    background: #1e1e1e;\n    color: #e0e0e0;\n    border-radius: 8px;\n    overflow: hidden;\n    display: flex;\n    flex-direction: column;\n    max-width: 100%;\n    margin: 20px 0;\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);\n  }\n\n  #wp-auto-cropper-wrapper .wp-img-editor-root * {\n    box-sizing: border-box;\n  }\n\n  \/* Toolbar *\/\n  #wp-auto-cropper-wrapper .editor-toolbar {\n    background: #2a2a2a;\n    padding: 10px 15px;\n    display: flex;\n    flex-wrap: wrap;\n    gap: 8px;\n    align-items: center;\n    border-bottom: 1px solid #3a3a3a;\n  }\n\n  #wp-auto-cropper-wrapper .btn {\n    background: #3a3a3a;\n    color: #fff;\n    border: none;\n    padding: 6px 12px;\n    border-radius: 4px;\n    cursor: pointer;\n    font-size: 13px;\n    transition: background 0.2s, opacity 0.2s;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    user-select: none;\n  }\n\n  #wp-auto-cropper-wrapper .btn:hover:not(:disabled) { background: #4a4a4a; }\n  #wp-auto-cropper-wrapper .btn:disabled { opacity: 0.5; cursor: not-allowed; }\n  #wp-auto-cropper-wrapper .btn-primary { background: #0073aa; }\n  #wp-auto-cropper-wrapper .btn-primary:hover:not(:disabled) { background: #005177; }\n  #wp-auto-cropper-wrapper .btn-danger { background: #d63638; }\n  #wp-auto-cropper-wrapper .btn-danger:hover:not(:disabled) { background: #b32d2e; }\n\n  \/* Workspace & Sidebar *\/\n  #wp-auto-cropper-wrapper .editor-workspace {\n    display: flex;\n    min-height: 500px;\n    position: relative;\n    overflow: hidden;\n  }\n\n  #wp-auto-cropper-wrapper .editor-sidebar {\n    width: 260px;\n    background: #252525;\n    padding: 15px;\n    border-right: 1px solid #3a3a3a;\n    display: flex;\n    flex-direction: column;\n    gap: 15px;\n    overflow-y: auto;\n    max-height: 600px;\n  }\n\n  #wp-auto-cropper-wrapper .control-group {\n    display: flex;\n    flex-direction: column;\n    gap: 6px;\n  }\n\n  #wp-auto-cropper-wrapper .control-group label {\n    font-size: 12px;\n    text-transform: uppercase;\n    letter-spacing: 0.5px;\n    color: #aaa;\n  }\n\n  #wp-auto-cropper-wrapper .control-group input[type=\"range\"] {\n    width: 100%;\n    accent-color: #0073aa;\n  }\n\n  #wp-auto-cropper-wrapper .select-input {\n    background: #3a3a3a;\n    color: #fff;\n    border: 1px solid #4a4a4a;\n    padding: 6px;\n    border-radius: 4px;\n    outline: none;\n  }\n\n  #wp-auto-cropper-wrapper .aspect-grid {\n    display: grid;\n    grid-template-columns: repeat(2, 1fr);\n    gap: 6px;\n  }\n\n  #wp-auto-cropper-wrapper .aspect-btn {\n    background: #3a3a3a;\n    border: 1px solid #4a4a4a;\n    color: #ccc;\n    padding: 6px;\n    border-radius: 4px;\n    cursor: pointer;\n    font-size: 12px;\n  }\n\n  #wp-auto-cropper-wrapper .aspect-btn.active {\n    background: #0073aa;\n    color: #fff;\n    border-color: #005177;\n  }\n\n  \/* Stage & Canvas *\/\n  #wp-auto-cropper-wrapper .editor-stage {\n    flex: 1;\n    background: #121212;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    padding: 20px;\n    position: relative;\n    overflow: auto;\n  }\n\n  #wp-auto-cropper-wrapper .canvas-wrapper {\n    position: relative;\n    box-shadow: 0 0 15px rgba(0, 0, 0, 0.6);\n    line-height: 0;\n    display: inline-block;\n  }\n\n  #wp-auto-cropper-wrapper #mainCanvas {\n    max-width: 100%;\n    max-height: 500px;\n    display: block;\n    background-image: linear-gradient(45deg, #222 25%, transparent 25%), linear-gradient(-45deg, #222 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #222 75%), linear-gradient(-45deg, transparent 75%, #222 75%);\n    background-size: 20px 20px;\n    background-position: 0 0, 0 10px, 10px -10px, -10px 0px;\n  }\n\n  \/* Crop Box & Handles *\/\n  #wp-auto-cropper-wrapper .crop-overlay {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    pointer-events: none;\n  }\n\n  #wp-auto-cropper-wrapper .crop-box {\n    position: absolute;\n    border: 1px dashed #fff;\n    box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);\n    pointer-events: auto;\n    cursor: move;\n    touch-action: none;\n  }\n\n  #wp-auto-cropper-wrapper .crop-handle {\n    position: absolute;\n    width: 12px;\n    height: 12px;\n    background: #0073aa;\n    border: 1px solid #fff;\n    border-radius: 50%;\n    touch-action: none;\n  }\n\n  #wp-auto-cropper-wrapper .handle-nw { top: -6px; left: -6px; cursor: nwse-resize; }\n  #wp-auto-cropper-wrapper .handle-ne { top: -6px; right: -6px; cursor: nesw-resize; }\n  #wp-auto-cropper-wrapper .handle-sw { bottom: -6px; left: -6px; cursor: nesw-resize; }\n  #wp-auto-cropper-wrapper .handle-se { bottom: -6px; right: -6px; cursor: nwse-resize; }\n  #wp-auto-cropper-wrapper .handle-n  { top: -6px; left: calc(50% - 6px); cursor: ns-resize; }\n  #wp-auto-cropper-wrapper .handle-s  { bottom: -6px; left: calc(50% - 6px); cursor: ns-resize; }\n  #wp-auto-cropper-wrapper .handle-w  { top: calc(50% - 6px); left: -6px; cursor: ew-resize; }\n  #wp-auto-cropper-wrapper .handle-e  { top: calc(50% - 6px); right: -6px; cursor: ew-resize; }\n\n  \/* Footer *\/\n  #wp-auto-cropper-wrapper .editor-footer {\n    background: #2a2a2a;\n    padding: 10px 15px;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    border-top: 1px solid #3a3a3a;\n    flex-wrap: wrap;\n    gap: 10px;\n  }\n\n  #wp-auto-cropper-wrapper .status-text {\n    font-size: 12px;\n    color: #aaa;\n  }\n\n  #wp-auto-cropper-wrapper .action-buttons {\n    display: flex;\n    gap: 8px;\n  }\n\n  @media (max-width: 768px) {\n    #wp-auto-cropper-wrapper .editor-workspace { flex-direction: column; }\n    #wp-auto-cropper-wrapper .editor-sidebar { width: 100%; max-height: none; }\n  }\n<\/style>\n\n<!-- JAVASCRIPT ENGINE -->\n<script>\n(function() {\n  const root = document.getElementById('wp-auto-cropper-wrapper');\n  if (!root) return;\n\n  \/\/ Scoped DOM Elements\n  const fileInput = root.querySelector('#fileInput');\n  const stageContainer = root.querySelector('#stageContainer');\n  const mainCanvas = root.querySelector('#mainCanvas');\n  const ctx = mainCanvas ? mainCanvas.getContext('2d') : null;\n  const cropOverlay = root.querySelector('#cropOverlay');\n  const cropBox = root.querySelector('#cropBox');\n  const statusText = root.querySelector('#statusText');\n\n  \/\/ Sliders & Controls\n  const slideZoom = root.querySelector('#slideZoom');\n  const slideBright = root.querySelector('#slideBright');\n  const slideContrast = root.querySelector('#slideContrast');\n  const slideSat = root.querySelector('#slideSat');\n  const slideGray = root.querySelector('#slideGray');\n  const slideQuality = root.querySelector('#slideQuality');\n  const exportFormat = root.querySelector('#exportFormat');\n\n  const valZoom = root.querySelector('#valZoom');\n  const valBright = root.querySelector('#valBright');\n  const valContrast = root.querySelector('#valContrast');\n  const valSat = root.querySelector('#valSat');\n  const valGray = root.querySelector('#valGray');\n  const valQuality = root.querySelector('#valQuality');\n\n  \/\/ Buttons\n  const btnAutoCrop = root.querySelector('#btnAutoCrop');\n  const btnCrop = root.querySelector('#btnCrop');\n  const btnRotateLeft = root.querySelector('#btnRotateLeft');\n  const btnRotateRight = root.querySelector('#btnRotateRight');\n  const btnFlipH = root.querySelector('#btnFlipH');\n  const btnFlipV = root.querySelector('#btnFlipV');\n  const btnZoomIn = root.querySelector('#btnZoomIn');\n  const btnZoomOut = root.querySelector('#btnZoomOut');\n  const btnZoomReset = root.querySelector('#btnZoomReset');\n  const btnUndo = root.querySelector('#btnUndo');\n  const btnRedo = root.querySelector('#btnRedo');\n  const btnReset = root.querySelector('#btnReset');\n  const btnCopy = root.querySelector('#btnCopy');\n  const btnPrint = root.querySelector('#btnPrint');\n  const btnDownloadFull = root.querySelector('#btnDownloadFull');\n  const btnDownloadCropped = root.querySelector('#btnDownloadCropped');\n  const aspectBtns = root.querySelectorAll('.aspect-btn');\n\n  \/\/ Application State\n  let rawImage = null;\n  let rawFileName = 'photo';\n  let historyStack = [];\n  let historyIndex = -1;\n  let isExporting = false;\n\n  const defaultState = {\n    rotation: 0,\n    flipH: false,\n    flipV: false,\n    zoom: 100,\n    brightness: 0,\n    contrast: 0,\n    saturation: 0,\n    grayscale: 0,\n    aspectRatio: 'free'\n  };\n\n  let currentState = JSON.parse(JSON.stringify(defaultState));\n  \/\/ Normalized crop dimensions defined in full unzoomed rotated space\n  let cropNorm = { x: 0, y: 0, w: 0, h: 0 };\n\n  function setStatus(msg, isError = false) {\n    if (statusText) {\n      statusText.textContent = msg;\n      statusText.style.color = isError ? '#ff6b6b' : '#aaa';\n    }\n  }\n\n  function setControlsDisabled(disabled) {\n    const actionBtns = [\n      btnAutoCrop, btnCrop, btnRotateLeft, btnRotateRight, btnFlipH, btnFlipV,\n      btnZoomIn, btnZoomOut, btnZoomReset, btnReset, btnCopy, btnPrint,\n      btnDownloadFull, btnDownloadCropped\n    ];\n    actionBtns.forEach(btn => { if (btn) btn.disabled = disabled; });\n    updateUndoRedoUI();\n  }\n\n  function updateUndoRedoUI() {\n    if (btnUndo) btnUndo.disabled = !rawImage || historyIndex <= 0;\n    if (btnRedo) btnRedo.disabled = !rawImage || historyIndex >= historyStack.length - 1;\n  }\n\n  \/\/ File Upload Handling\n  function loadFile(file) {\n    if (!file || !file.type.startsWith('image\/')) {\n      setStatus('Please select a valid image file.', true);\n      return;\n    }\n\n    const namePart = file.name.substring(0, file.name.lastIndexOf('.')) || file.name;\n    rawFileName = namePart.replace(\/[^a-zA-Z0-9_-]\/g, '_');\n\n    const reader = new FileReader();\n    reader.onload = (event) => {\n      const img = new Image();\n      img.onload = () => {\n        rawImage = img;\n        currentState = JSON.parse(JSON.stringify(defaultState));\n        historyStack = [];\n        historyIndex = -1;\n        \n        if (slideZoom) slideZoom.value = 100;\n        if (slideBright) slideBright.value = 0;\n        if (slideContrast) slideContrast.value = 0;\n        if (slideSat) slideSat.value = 0;\n        if (slideGray) slideGray.value = 0;\n        aspectBtns.forEach(b => b.classList.toggle('active', b.dataset.ratio === 'free'));\n\n        setControlsDisabled(false);\n        initCropForCurrentDimensions();\n        renderEditorCanvas();\n        saveHistoryState();\n        setStatus('Image loaded successfully.');\n      };\n      img.onerror = () => setStatus('Failed to load image file.', true);\n      img.src = event.target.result;\n    };\n    reader.onerror = () => setStatus('Failed to read file.', true);\n    reader.readAsDataURL(file);\n  }\n\n  if (fileInput) {\n    fileInput.addEventListener('change', (e) => {\n      const file = e.target.files && e.target.files[0];\n      if (file) loadFile(file);\n    });\n  }\n\n  \/\/ Drag & Drop Support\n  if (stageContainer) {\n    ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {\n      stageContainer.addEventListener(eventName, (e) => {\n        e.preventDefault();\n        e.stopPropagation();\n      }, false);\n    });\n\n    stageContainer.addEventListener('drop', (e) => {\n      const dt = e.dataTransfer;\n      const file = dt && dt.files && dt.files[0];\n      if (file) loadFile(file);\n    });\n  }\n\n  function getBaseDimensions() {\n    if (!rawImage) return { w: 0, h: 0 };\n    const isSwapped = (currentState.rotation % 180 !== 0);\n    return {\n      w: isSwapped ? rawImage.height : rawImage.width,\n      h: isSwapped ? rawImage.width : rawImage.height\n    };\n  }\n\n  function initCropForCurrentDimensions() {\n    const dim = getBaseDimensions();\n    cropNorm = { x: 0, y: 0, w: dim.w, h: dim.h };\n    enforceAspectRatio();\n    clampCropNorm();\n  }\n\n  function saveHistoryState() {\n    if (!rawImage) return;\n    if (historyIndex < historyStack.length - 1) {\n      historyStack = historyStack.slice(0, historyIndex + 1);\n    }\n    historyStack.push({\n      state: JSON.parse(JSON.stringify(currentState)),\n      cropNorm: { ...cropNorm }\n    });\n    historyIndex = historyStack.length - 1;\n    updateUndoRedoUI();\n  }\n\n  function renderEditorCanvas() {\n    if (!rawImage || !mainCanvas || !ctx) return;\n\n    updateUIValues();\n\n    const dim = getBaseDimensions();\n    const zoomFactor = currentState.zoom \/ 100;\n\n    mainCanvas.width = Math.round(dim.w * zoomFactor);\n    mainCanvas.height = Math.round(dim.h * zoomFactor);\n\n    ctx.clearRect(0, 0, mainCanvas.width, mainCanvas.height);\n    ctx.save();\n\n    \/\/ Setup Transforms\n    ctx.translate(mainCanvas.width \/ 2, mainCanvas.height \/ 2);\n    ctx.rotate((currentState.rotation * Math.PI) \/ 180);\n    ctx.scale(\n      (currentState.flipH ? -1 : 1) * zoomFactor,\n      (currentState.flipV ? -1 : 1) * zoomFactor\n    );\n\n    \/\/ Filter adjustments\n    const b = 100 + currentState.brightness;\n    const c = 100 + currentState.contrast;\n    const s = 100 + currentState.saturation;\n    const g = currentState.grayscale;\n    ctx.filter = `brightness(${b}%) contrast(${c}%) saturate(${s}%) grayscale(${g}%)`;\n\n    ctx.drawImage(\n      rawImage,\n      -rawImage.width \/ 2,\n      -rawImage.height \/ 2,\n      rawImage.width,\n      rawImage.height\n    );\n\n    ctx.restore();\n\n    clampCropNorm();\n    updateCropOverlayUI();\n  }\n\n  function updateUIValues() {\n    if (valZoom &#038;&#038; slideZoom) valZoom.textContent = slideZoom.value;\n    if (valBright &#038;&#038; slideBright) valBright.textContent = slideBright.value;\n    if (valContrast &#038;&#038; slideContrast) valContrast.textContent = slideContrast.value;\n    if (valSat &#038;&#038; slideSat) valSat.textContent = slideSat.value;\n    if (valGray &#038;&#038; slideGray) valGray.textContent = slideGray.value;\n    if (valQuality &#038;&#038; slideQuality) valQuality.textContent = slideQuality.value;\n  }\n\n  function clampCropNorm() {\n    const dim = getBaseDimensions();\n    cropNorm.x = Math.max(0, Math.min(cropNorm.x, dim.w));\n    cropNorm.y = Math.max(0, Math.min(cropNorm.y, dim.h));\n    cropNorm.w = Math.max(10, Math.min(cropNorm.w, dim.w - cropNorm.x));\n    cropNorm.h = Math.max(10, Math.min(cropNorm.h, dim.h - cropNorm.y));\n  }\n\n  function enforceAspectRatio() {\n    if (!currentState.aspectRatio || currentState.aspectRatio === 'free' || cropNorm.w <= 0 || cropNorm.h <= 0) return;\n\n    const parts = currentState.aspectRatio.split(':');\n    if (parts.length !== 2) return;\n    const rw = parseFloat(parts[0]);\n    const rh = parseFloat(parts[1]);\n    if (!rw || !rh) return;\n\n    const targetRatio = rw \/ rh;\n    const dim = getBaseDimensions();\n\n    cropNorm.h = cropNorm.w \/ targetRatio;\n    if (cropNorm.y + cropNorm.h > dim.h) {\n      cropNorm.h = dim.h - cropNorm.y;\n      cropNorm.w = cropNorm.h * targetRatio;\n    }\n  }\n\n  function updateCropOverlayUI() {\n    if (!rawImage || !cropOverlay || !cropBox) {\n      if (cropOverlay) cropOverlay.style.display = 'none';\n      return;\n    }\n    cropOverlay.style.display = 'block';\n\n    const zoomFactor = currentState.zoom \/ 100;\n    const displayW = mainCanvas.clientWidth;\n    const displayH = mainCanvas.clientHeight;\n\n    const scaleX = displayW \/ (mainCanvas.width || 1);\n    const scaleY = displayH \/ (mainCanvas.height || 1);\n\n    const left = cropNorm.x * zoomFactor * scaleX;\n    const top = cropNorm.y * zoomFactor * scaleY;\n    const width = cropNorm.w * zoomFactor * scaleX;\n    const height = cropNorm.h * zoomFactor * scaleY;\n\n    cropBox.style.left = `${left}px`;\n    cropBox.style.top = `${top}px`;\n    cropBox.style.width = `${width}px`;\n    cropBox.style.height = `${height}px`;\n  }\n\n  \/\/ Rotations & Flips\n  if (btnRotateLeft) {\n    btnRotateLeft.addEventListener('click', () => {\n      if (!rawImage) return;\n      currentState.rotation = (currentState.rotation - 90 + 360) % 360;\n      initCropForCurrentDimensions();\n      renderEditorCanvas();\n      saveHistoryState();\n    });\n  }\n\n  if (btnRotateRight) {\n    btnRotateRight.addEventListener('click', () => {\n      if (!rawImage) return;\n      currentState.rotation = (currentState.rotation + 90) % 360;\n      initCropForCurrentDimensions();\n      renderEditorCanvas();\n      saveHistoryState();\n    });\n  }\n\n  if (btnFlipH) {\n    btnFlipH.addEventListener('click', () => {\n      if (!rawImage) return;\n      currentState.flipH = !currentState.flipH;\n      renderEditorCanvas();\n      saveHistoryState();\n    });\n  }\n\n  if (btnFlipV) {\n    btnFlipV.addEventListener('click', () => {\n      if (!rawImage) return;\n      currentState.flipV = !currentState.flipV;\n      renderEditorCanvas();\n      saveHistoryState();\n    });\n  }\n\n  \/\/ Zoom Button Controls\n  if (btnZoomIn) {\n    btnZoomIn.addEventListener('click', () => {\n      if (!rawImage) return;\n      currentState.zoom = Math.min(300, currentState.zoom + 10);\n      if (slideZoom) slideZoom.value = currentState.zoom;\n      renderEditorCanvas();\n      saveHistoryState();\n    });\n  }\n\n  if (btnZoomOut) {\n    btnZoomOut.addEventListener('click', () => {\n      if (!rawImage) return;\n      currentState.zoom = Math.max(10, currentState.zoom - 10);\n      if (slideZoom) slideZoom.value = currentState.zoom;\n      renderEditorCanvas();\n      saveHistoryState();\n    });\n  }\n\n  if (btnZoomReset) {\n    btnZoomReset.addEventListener('click', () => {\n      if (!rawImage) return;\n      currentState.zoom = 100;\n      if (slideZoom) slideZoom.value = 100;\n      renderEditorCanvas();\n      saveHistoryState();\n    });\n  }\n\n  \/\/ Auto Crop\n  if (btnAutoCrop) {\n    btnAutoCrop.addEventListener('click', () => {\n      if (!rawImage) return;\n      setStatus('Analyzing image content bounds...');\n\n      setTimeout(() => {\n        const dim = getBaseDimensions();\n        const off = document.createElement('canvas');\n        off.width = dim.w;\n        off.height = dim.h;\n        const oCtx = off.getContext('2d');\n        if (!oCtx) return;\n\n        oCtx.translate(dim.w \/ 2, dim.h \/ 2);\n        oCtx.rotate((currentState.rotation * Math.PI) \/ 180);\n        oCtx.scale(currentState.flipH ? -1 : 1, currentState.flipV ? -1 : 1);\n        oCtx.drawImage(rawImage, -rawImage.width \/ 2, -rawImage.height \/ 2);\n\n        const imgData = oCtx.getImageData(0, 0, dim.w, dim.h);\n        const data = imgData.data;\n\n        let minX = dim.w, minY = dim.h, maxX = 0, maxY = 0;\n        let found = false;\n\n        for (let y = 0; y < dim.h; y++) {\n          for (let x = 0; x < dim.w; x++) {\n            const idx = (y * dim.w + x) * 4;\n            const r = data[idx], g = data[idx + 1], b = data[idx + 2], a = data[idx + 3];\n\n            const isNotTransparent = a > 20;\n            const isNotWhite = !(r > 240 && g > 240 && b > 240);\n            const isNotBlack = !(r < 15 &#038;&#038; g < 15 &#038;&#038; b < 15);\n\n            if (isNotTransparent &#038;&#038; (isNotWhite || isNotBlack)) {\n              if (x < minX) minX = x;\n              if (x > maxX) maxX = x;\n              if (y < minY) minY = y;\n              if (y > maxY) maxY = y;\n              found = true;\n            }\n          }\n        }\n\n        if (found && maxX > minX && maxY > minY) {\n          const pad = 10;\n          cropNorm = {\n            x: Math.max(0, minX - pad),\n            y: Math.max(0, minY - pad),\n            w: Math.min(dim.w - Math.max(0, minX - pad), (maxX - minX) + (pad * 2)),\n            h: Math.min(dim.h - Math.max(0, minY - pad), (maxY - minY) + (pad * 2))\n          };\n          enforceAspectRatio();\n          clampCropNorm();\n          renderEditorCanvas();\n          saveHistoryState();\n          setStatus('Auto-crop area applied.');\n        } else {\n          setStatus('Could not automatically determine unique bounds.', true);\n        }\n      }, 50);\n    });\n  }\n\n  \/\/ Apply Crop (Non-Destructive)\n  if (btnCrop) {\n    btnCrop.addEventListener('click', () => {\n      if (!rawImage) return;\n      enforceAspectRatio();\n      clampCropNorm();\n      renderEditorCanvas();\n      saveHistoryState();\n      setStatus('Crop bounds confirmed.');\n    });\n  }\n\n  \/\/ Sliders listeners\n  if (slideZoom) {\n    slideZoom.addEventListener('input', (e) => {\n      if (!rawImage) return;\n      currentState.zoom = parseInt(e.target.value, 10);\n      renderEditorCanvas();\n    });\n    slideZoom.addEventListener('change', () => saveHistoryState());\n  }\n\n  if (slideBright) {\n    slideBright.addEventListener('input', (e) => {\n      if (!rawImage) return;\n      currentState.brightness = parseInt(e.target.value, 10);\n      renderEditorCanvas();\n    });\n    slideBright.addEventListener('change', () => saveHistoryState());\n  }\n\n  if (slideContrast) {\n    slideContrast.addEventListener('input', (e) => {\n      if (!rawImage) return;\n      currentState.contrast = parseInt(e.target.value, 10);\n      renderEditorCanvas();\n    });\n    slideContrast.addEventListener('change', () => saveHistoryState());\n  }\n\n  if (slideSat) {\n    slideSat.addEventListener('input', (e) => {\n      if (!rawImage) return;\n      currentState.saturation = parseInt(e.target.value, 10);\n      renderEditorCanvas();\n    });\n    slideSat.addEventListener('change', () => saveHistoryState());\n  }\n\n  if (slideGray) {\n    slideGray.addEventListener('input', (e) => {\n      if (!rawImage) return;\n      currentState.grayscale = parseInt(e.target.value, 10);\n      renderEditorCanvas();\n    });\n    slideGray.addEventListener('change', () => saveHistoryState());\n  }\n\n  if (slideQuality) {\n    slideQuality.addEventListener('input', () => updateUIValues());\n  }\n\n  \/\/ Aspect Ratios\n  aspectBtns.forEach(btn => {\n    btn.addEventListener('click', () => {\n      if (!rawImage) return;\n      aspectBtns.forEach(b => b.classList.remove('active'));\n      btn.classList.add('active');\n      currentState.aspectRatio = btn.dataset.ratio;\n      enforceAspectRatio();\n      clampCropNorm();\n      renderEditorCanvas();\n      saveHistoryState();\n    });\n  });\n\n  \/\/ Undo \/ Redo \/ Reset\n  if (btnUndo) {\n    btnUndo.addEventListener('click', () => {\n      if (historyIndex > 0) {\n        historyIndex--;\n        const record = historyStack[historyIndex];\n        currentState = JSON.parse(JSON.stringify(record.state));\n        cropNorm = { ...record.cropNorm };\n\n        if (slideZoom) slideZoom.value = currentState.zoom;\n        if (slideBright) slideBright.value = currentState.brightness;\n        if (slideContrast) slideContrast.value = currentState.contrast;\n        if (slideSat) slideSat.value = currentState.saturation;\n        if (slideGray) slideGray.value = currentState.grayscale;\n\n        aspectBtns.forEach(b => b.classList.toggle('active', b.dataset.ratio === currentState.aspectRatio));\n\n        renderEditorCanvas();\n        updateUndoRedoUI();\n        setStatus('Action undone.');\n      }\n    });\n  }\n\n  if (btnRedo) {\n    btnRedo.addEventListener('click', () => {\n      if (historyIndex < historyStack.length - 1) {\n        historyIndex++;\n        const record = historyStack[historyIndex];\n        currentState = JSON.parse(JSON.stringify(record.state));\n        cropNorm = { ...record.cropNorm };\n\n        if (slideZoom) slideZoom.value = currentState.zoom;\n        if (slideBright) slideBright.value = currentState.brightness;\n        if (slideContrast) slideContrast.value = currentState.contrast;\n        if (slideSat) slideSat.value = currentState.saturation;\n        if (slideGray) slideGray.value = currentState.grayscale;\n\n        aspectBtns.forEach(b => b.classList.toggle('active', b.dataset.ratio === currentState.aspectRatio));\n\n        renderEditorCanvas();\n        updateUndoRedoUI();\n        setStatus('Action redone.');\n      }\n    });\n  }\n\n  if (btnReset) {\n    btnReset.addEventListener('click', () => {\n      if (!rawImage) return;\n      currentState = JSON.parse(JSON.stringify(defaultState));\n      if (slideZoom) slideZoom.value = 100;\n      if (slideBright) slideBright.value = 0;\n      if (slideContrast) slideContrast.value = 0;\n      if (slideSat) slideSat.value = 0;\n      if (slideGray) slideGray.value = 0;\n      aspectBtns.forEach(b => b.classList.toggle('active', b.dataset.ratio === 'free'));\n\n      initCropForCurrentDimensions();\n      renderEditorCanvas();\n      saveHistoryState();\n      setStatus('Editor reset to default state.');\n    });\n  }\n\n  \/\/ Pointer Handling for Touch\/Mouse Dragging & Resizing\n  let isDragging = false;\n  let activeHandle = null;\n  let startX = 0, startY = 0;\n  let startCrop = { x: 0, y: 0, w: 0, h: 0 };\n\n  function getClientCoords(e) {\n    if (e.touches && e.touches.length > 0) {\n      return { x: e.touches[0].clientX, y: e.touches[0].clientY };\n    }\n    return { x: e.clientX, y: e.clientY };\n  }\n\n  function onPointerDown(e) {\n    if (!rawImage) return;\n    e.preventDefault();\n    isDragging = true;\n    activeHandle = e.target.dataset.handle || 'move';\n    const coords = getClientCoords(e);\n    startX = coords.x;\n    startY = coords.y;\n    startCrop = { ...cropNorm };\n\n    window.addEventListener('mousemove', onPointerMove, { passive: false });\n    window.addEventListener('mouseup', onPointerUp);\n    window.addEventListener('touchmove', onPointerMove, { passive: false });\n    window.addEventListener('touchend', onPointerUp);\n  }\n\n  function onPointerMove(e) {\n    if (!isDragging || !rawImage) return;\n    if (e.cancelable) e.preventDefault();\n\n    const zoomFactor = currentState.zoom \/ 100;\n    const displayW = mainCanvas.clientWidth;\n    const displayH = mainCanvas.clientHeight;\n\n    const scaleX = (mainCanvas.width \/ (displayW || 1)) \/ zoomFactor;\n    const scaleY = (mainCanvas.height \/ (displayH || 1)) \/ zoomFactor;\n\n    const coords = getClientCoords(e);\n    const dx = (coords.x - startX) * scaleX;\n    const dy = (coords.y - startY) * scaleY;\n\n    if (activeHandle === 'move') {\n      cropNorm.x = startCrop.x + dx;\n      cropNorm.y = startCrop.y + dy;\n    } else {\n      if (activeHandle.includes('e')) cropNorm.w = startCrop.w + dx;\n      if (activeHandle.includes('s')) cropNorm.h = startCrop.h + dy;\n      if (activeHandle.includes('w')) {\n        cropNorm.x = startCrop.x + dx;\n        cropNorm.w = startCrop.w - dx;\n      }\n      if (activeHandle.includes('n')) {\n        cropNorm.y = startCrop.y + dy;\n        cropNorm.h = startCrop.h - dy;\n      }\n    }\n\n    enforceAspectRatio();\n    clampCropNorm();\n    renderEditorCanvas();\n  }\n\n  function onPointerUp() {\n    if (isDragging) {\n      isDragging = false;\n      window.removeEventListener('mousemove', onPointerMove);\n      window.removeEventListener('mouseup', onPointerUp);\n      window.removeEventListener('touchmove', onPointerMove);\n      window.removeEventListener('touchend', onPointerUp);\n      saveHistoryState();\n    }\n  }\n\n  if (cropBox) {\n    cropBox.addEventListener('mousedown', onPointerDown);\n    cropBox.addEventListener('touchstart', onPointerDown, { passive: false });\n  }\n\n  \/\/ Full High-Resolution Processing Pipeline\n  function generateFinalOutput(isCropped = true) {\n    if (!rawImage) return null;\n\n    const dim = getBaseDimensions();\n\n    \/\/ Step 1: Render full resolution transformed image\n    const fullCanvas = document.createElement('canvas');\n    fullCanvas.width = dim.w;\n    fullCanvas.height = dim.h;\n    const fCtx = fullCanvas.getContext('2d');\n    if (!fCtx) return null;\n\n    fCtx.translate(dim.w \/ 2, dim.h \/ 2);\n    fCtx.rotate((currentState.rotation * Math.PI) \/ 180);\n    fCtx.scale(currentState.flipH ? -1 : 1, currentState.flipV ? -1 : 1);\n\n    const b = 100 + currentState.brightness;\n    const c = 100 + currentState.contrast;\n    const s = 100 + currentState.saturation;\n    const g = currentState.grayscale;\n    fCtx.filter = `brightness(${b}%) contrast(${c}%) saturate(${s}%) grayscale(${g}%)`;\n\n    fCtx.drawImage(rawImage, -rawImage.width \/ 2, -rawImage.height \/ 2, rawImage.width, rawImage.height);\n\n    if (!isCropped) {\n      return fullCanvas;\n    }\n\n    \/\/ Step 2: Extract Crop\n    clampCropNorm();\n    const targetW = Math.max(1, Math.round(cropNorm.w));\n    const targetH = Math.max(1, Math.round(cropNorm.h));\n\n    const cropCanvas = document.createElement('canvas');\n    cropCanvas.width = targetW;\n    cropCanvas.height = targetH;\n    const cCtx = cropCanvas.getContext('2d');\n    if (!cCtx) return null;\n\n    cCtx.drawImage(\n      fullCanvas,\n      Math.round(cropNorm.x), Math.round(cropNorm.y), targetW, targetH,\n      0, 0, targetW, targetH\n    );\n\n    return cropCanvas;\n  }\n\n  function canvasToBlobPromise(canvas, mimeType, quality) {\n    return new Promise((resolve) => {\n      let processCanvas = canvas;\n      if (mimeType === 'image\/jpeg') {\n        const opaqueCanvas = document.createElement('canvas');\n        opaqueCanvas.width = canvas.width;\n        opaqueCanvas.height = canvas.height;\n        const oCtx = opaqueCanvas.getContext('2d');\n        if (oCtx) {\n          oCtx.fillStyle = '#FFFFFF';\n          oCtx.fillRect(0, 0, canvas.width, canvas.height);\n          oCtx.drawImage(canvas, 0, 0);\n          processCanvas = opaqueCanvas;\n        }\n      }\n\n      processCanvas.toBlob((blob) => {\n        resolve(blob);\n      }, mimeType, quality);\n    });\n  }\n\n  async function performDownload(isCropped = true) {\n    if (!rawImage || isExporting) return;\n    isExporting = true;\n    setStatus('Generating export file...');\n\n    try {\n      const canvas = generateFinalOutput(isCropped);\n      if (!canvas) throw new Error('Canvas generation failed');\n\n      const mimeType = exportFormat ? exportFormat.value : 'image\/png';\n      const qualityVal = slideQuality ? parseFloat(slideQuality.value) \/ 100 : 0.9;\n      const blob = await canvasToBlobPromise(canvas, mimeType, qualityVal);\n\n      if (!blob) throw new Error('Blob creation failed');\n\n      let ext = 'png';\n      if (mimeType === 'image\/jpeg') ext = 'jpg';\n      if (mimeType === 'image\/webp') ext = 'webp';\n\n      const fileName = `${rawFileName}-${isCropped ? 'cropped' : 'full'}.${ext}`;\n      const url = URL.createObjectURL(blob);\n\n      const a = document.createElement('a');\n      a.href = url;\n      a.download = fileName;\n      document.body.appendChild(a);\n      a.click();\n      document.body.removeChild(a);\n\n      setTimeout(() => URL.revokeObjectURL(url), 1000);\n      setStatus(`Downloaded: ${fileName}`);\n    } catch (err) {\n      console.error(err);\n      setStatus('Failed to export image.', true);\n    } finally {\n      isExporting = false;\n    }\n  }\n\n  \/\/ Export Events\n  if (btnDownloadCropped) {\n    btnDownloadCropped.addEventListener('click', () => performDownload(true));\n  }\n\n  if (btnDownloadFull) {\n    btnDownloadFull.addEventListener('click', () => performDownload(false));\n  }\n\n  if (btnCopy) {\n    btnCopy.addEventListener('click', async () => {\n      if (!rawImage || isExporting) return;\n      isExporting = true;\n      setStatus('Copying image to clipboard...');\n\n      try {\n        if (!navigator.clipboard || !window.ClipboardItem) {\n          throw new Error('Clipboard API not fully supported by browser\/context');\n        }\n\n        const canvas = generateFinalOutput(true);\n        if (!canvas) throw new Error('Canvas generation failed');\n\n        const blob = await canvasToBlobPromise(canvas, 'image\/png', 1.0);\n        if (!blob) throw new Error('PNG Blob generation failed');\n\n        await navigator.clipboard.write([\n          new ClipboardItem({ 'image\/png': blob })\n        ]);\n\n        setStatus('Cropped image successfully copied to clipboard!');\n      } catch (err) {\n        console.error(err);\n        setStatus('Copy failed. Ensure HTTPS or modern browser support.', true);\n      } finally {\n        isExporting = false;\n      }\n    });\n  }\n\n  if (btnPrint) {\n    btnPrint.addEventListener('click', async () => {\n      if (!rawImage || isExporting) return;\n      isExporting = true;\n      setStatus('Preparing image for printing...');\n\n      try {\n        const canvas = generateFinalOutput(true);\n        if (!canvas) throw new Error('Canvas generation failed');\n\n        const blob = await canvasToBlobPromise(canvas, 'image\/png', 1.0);\n        if (!blob) throw new Error('Blob generation failed');\n\n        const url = URL.createObjectURL(blob);\n        const printWin = window.open('', '_blank');\n\n        if (!printWin) {\n          URL.revokeObjectURL(url);\n          throw new Error('Popup blocked by browser.');\n        }\n\n        printWin.document.write(`\n          <!DOCTYPE html>\n          <html>\n            <head>\n              <title>Print Image<\/title>\n              <style>\n                body { margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #fff; }\n                img { max-width: 100%; max-height: 100vh; object-fit: contain; }\n              <\/style>\n            <\/head>\n            <body>\n              <img decoding=\"async\" id=\"printImg\" src=\"${url}\" \/>\n            <\/body>\n          <\/html>\n        `);\n        printWin.document.close();\n\n        const imgEl = printWin.document.getElementById('printImg');\n        const triggerPrint = () => {\n          printWin.focus();\n          printWin.print();\n          printWin.close();\n          URL.revokeObjectURL(url);\n        };\n\n        if (imgEl) {\n          if (imgEl.complete) {\n            triggerPrint();\n          } else {\n            imgEl.addEventListener('load', triggerPrint);\n          }\n        }\n        setStatus('Print initiated.');\n      } catch (err) {\n        console.error(err);\n        setStatus('Print failed or popup blocked.', true);\n      } finally {\n        isExporting = false;\n      }\n    });\n  }\n})();\n<\/script>\n","protected":false},"excerpt":{"rendered":"<p>Open Image Auto-Crop Apply Crop \u21ba 90\u00b0 \u21bb 90\u00b0 \u21c4 Flip H \u21c5 Flip V \ud83d\udd0d+ \ud83d\udd0d- Zoom 100% Undo Redo Reset Aspect Ratio Free 1:1 4:3 16:9 Zoom (100%) Brightness (0) Contrast (0) Saturation (0) Grayscale (0%) Export Format PNGJPEGWebP Quality (90%) Upload an image to start editing. Copy Image Print Save Full Save [&hellip;]<\/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-1044","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/freewebtools.co.in\/index.php\/wp-json\/wp\/v2\/pages\/1044","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/freewebtools.co.in\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/freewebtools.co.in\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/freewebtools.co.in\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/freewebtools.co.in\/index.php\/wp-json\/wp\/v2\/comments?post=1044"}],"version-history":[{"count":9,"href":"https:\/\/freewebtools.co.in\/index.php\/wp-json\/wp\/v2\/pages\/1044\/revisions"}],"predecessor-version":[{"id":1056,"href":"https:\/\/freewebtools.co.in\/index.php\/wp-json\/wp\/v2\/pages\/1044\/revisions\/1056"}],"wp:attachment":[{"href":"https:\/\/freewebtools.co.in\/index.php\/wp-json\/wp\/v2\/media?parent=1044"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}