ucalyptus
Claude
commited on
Commit
·
4516471
1
Parent(s):
73b2a25
Set up watermark remover React application with build output
Browse filesBuilt a complete watermark removal tool using React, Tailwind CSS, jsPDF, and PDF.js. Includes source code, configuration files, and pre-built dist/ folder for HuggingFace Spaces deployment.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- .gitignore +33 -0
- README.md +0 -10
- dist/README.md +18 -0
- dist/assets/html2canvas.esm-QH1iLAAe.js +0 -0
- dist/assets/index-CXi72gcy.js +0 -0
- dist/assets/index-DDQzBmv1.css +1 -0
- dist/assets/index.es-Dcp_LghZ.js +0 -0
- dist/assets/pdf.worker.min-Cpi8b8z3.mjs +0 -0
- dist/assets/purify.es-B6FQ9oRL.js +2 -0
- dist/index.html +13 -0
- index.html +11 -18
- package-lock.json +0 -0
- package.json +25 -0
- postcss.config.js +6 -0
- src/WatermarkRemover.jsx +746 -0
- src/index.css +3 -0
- src/main.jsx +10 -0
- style.css +0 -28
- tailwind.config.js +11 -0
- vite.config.js +6 -0
.gitignore
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dependencies
|
| 2 |
+
node_modules/
|
| 3 |
+
|
| 4 |
+
# Environment variables
|
| 5 |
+
.env
|
| 6 |
+
.env.local
|
| 7 |
+
.env.production.local
|
| 8 |
+
.env.development.local
|
| 9 |
+
|
| 10 |
+
# Editor directories and files
|
| 11 |
+
.vscode/
|
| 12 |
+
.idea/
|
| 13 |
+
*.swp
|
| 14 |
+
*.swo
|
| 15 |
+
*~
|
| 16 |
+
|
| 17 |
+
# OS files
|
| 18 |
+
.DS_Store
|
| 19 |
+
Thumbs.db
|
| 20 |
+
|
| 21 |
+
# Logs
|
| 22 |
+
logs
|
| 23 |
+
*.log
|
| 24 |
+
npm-debug.log*
|
| 25 |
+
yarn-debug.log*
|
| 26 |
+
yarn-error.log*
|
| 27 |
+
pnpm-debug.log*
|
| 28 |
+
|
| 29 |
+
# Testing
|
| 30 |
+
coverage/
|
| 31 |
+
|
| 32 |
+
# Misc
|
| 33 |
+
.cache/
|
README.md
DELETED
|
@@ -1,10 +0,0 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: Watermark Remover App
|
| 3 |
-
emoji: 💻
|
| 4 |
-
colorFrom: indigo
|
| 5 |
-
colorTo: gray
|
| 6 |
-
sdk: static
|
| 7 |
-
pinned: false
|
| 8 |
-
---
|
| 9 |
-
|
| 10 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dist/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Watermark Remover
|
| 3 |
+
emoji: 🖼️
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: static
|
| 7 |
+
pinned: false
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# Watermark Remover
|
| 11 |
+
|
| 12 |
+
A privacy-first, local-processing watermark remover app powered by React, pdf.js, and Canvas API.
|
| 13 |
+
|
| 14 |
+
## Features
|
| 15 |
+
- 🚀 **100% Local Processing**: No images/PDFs are sent to any server.
|
| 16 |
+
- 📄 **PDF Support**: Handles multi-page PDFs automatically.
|
| 17 |
+
- 🎨 **Smart Inpainting**: Uses Telea and Navier-Stokes algorithms.
|
| 18 |
+
- 🛠️ **Adjustable**: Customize the watermark region, blend radius, and more.
|
dist/assets/html2canvas.esm-QH1iLAAe.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
dist/assets/index-CXi72gcy.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
dist/assets/index-DDQzBmv1.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-auto{height:auto}.min-h-screen{min-height:100vh}.w-full{width:100%}.max-w-6xl{max-width:72rem}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-gray-600{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-purple-600{--tw-bg-opacity: 1;background-color:rgb(147 51 234 / var(--tw-bg-opacity, 1))}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.hover\:bg-blue-700:hover{--tw-bg-opacity: 1;background-color:rgb(29 78 216 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-600:hover{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.hover\:bg-green-700:hover{--tw-bg-opacity: 1;background-color:rgb(21 128 61 / var(--tw-bg-opacity, 1))}.hover\:bg-red-700:hover{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity, 1))}.disabled\:bg-gray-600:disabled{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.disabled\:bg-gray-800:disabled{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.disabled\:text-gray-600:disabled{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}@media(min-width:768px){.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}
|
dist/assets/index.es-Dcp_LghZ.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
dist/assets/pdf.worker.min-Cpi8b8z3.mjs
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
dist/assets/purify.es-B6FQ9oRL.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*! @license DOMPurify 3.3.0 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.0/LICENSE */const{entries:_t,setPrototypeOf:ct,isFrozen:Yt,getPrototypeOf:Xt,getOwnPropertyDescriptor:jt}=Object;let{freeze:S,seal:y,create:Pe}=Object,{apply:ve,construct:ke}=typeof Reflect<"u"&&Reflect;S||(S=function(o){return o});y||(y=function(o){return o});ve||(ve=function(o,l){for(var a=arguments.length,c=new Array(a>2?a-2:0),O=2;O<a;O++)c[O-2]=arguments[O];return o.apply(l,c)});ke||(ke=function(o){for(var l=arguments.length,a=new Array(l>1?l-1:0),c=1;c<l;c++)a[c-1]=arguments[c];return new o(...a)});const ce=R(Array.prototype.forEach),Vt=R(Array.prototype.lastIndexOf),ft=R(Array.prototype.pop),q=R(Array.prototype.push),$t=R(Array.prototype.splice),ue=R(String.prototype.toLowerCase),Ne=R(String.prototype.toString),Ie=R(String.prototype.match),K=R(String.prototype.replace),qt=R(String.prototype.indexOf),Kt=R(String.prototype.trim),b=R(Object.prototype.hasOwnProperty),A=R(RegExp.prototype.test),Z=Zt(TypeError);function R(s){return function(o){o instanceof RegExp&&(o.lastIndex=0);for(var l=arguments.length,a=new Array(l>1?l-1:0),c=1;c<l;c++)a[c-1]=arguments[c];return ve(s,o,a)}}function Zt(s){return function(){for(var o=arguments.length,l=new Array(o),a=0;a<o;a++)l[a]=arguments[a];return ke(s,l)}}function r(s,o){let l=arguments.length>2&&arguments[2]!==void 0?arguments[2]:ue;ct&&ct(s,null);let a=o.length;for(;a--;){let c=o[a];if(typeof c=="string"){const O=l(c);O!==c&&(Yt(o)||(o[a]=O),c=O)}s[c]=!0}return s}function Jt(s){for(let o=0;o<s.length;o++)b(s,o)||(s[o]=null);return s}function M(s){const o=Pe(null);for(const[l,a]of _t(s))b(s,l)&&(Array.isArray(a)?o[l]=Jt(a):a&&typeof a=="object"&&a.constructor===Object?o[l]=M(a):o[l]=a);return o}function J(s,o){for(;s!==null;){const a=jt(s,o);if(a){if(a.get)return R(a.get);if(typeof a.value=="function")return R(a.value)}s=Xt(s)}function l(){return null}return l}const ut=S(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","search","section","select","shadow","slot","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),Ce=S(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","enterkeyhint","exportparts","filter","font","g","glyph","glyphref","hkern","image","inputmode","line","lineargradient","marker","mask","metadata","mpath","part","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),Me=S(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),Qt=S(["animate","color-profile","cursor","discard","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor","unknown","use"]),we=S(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover","mprescripts"]),en=S(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),mt=S(["#text"]),pt=S(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","controls","controlslist","coords","crossorigin","datetime","decoding","default","dir","disabled","disablepictureinpicture","disableremoteplayback","download","draggable","enctype","enterkeyhint","exportparts","face","for","headers","height","hidden","high","href","hreflang","id","inert","inputmode","integrity","ismap","kind","label","lang","list","loading","loop","low","max","maxlength","media","method","min","minlength","multiple","muted","name","nonce","noshade","novalidate","nowrap","open","optimum","part","pattern","placeholder","playsinline","popover","popovertarget","popovertargetaction","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","slot","span","srclang","start","src","srcset","step","style","summary","tabindex","title","translate","type","usemap","valign","value","width","wrap","xmlns","slot"]),xe=S(["accent-height","accumulate","additive","alignment-baseline","amplitude","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clippathunits","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dur","edgemode","elevation","end","exponent","fill","fill-opacity","fill-rule","filter","filterunits","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","intercept","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","mask-type","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","preserveaspectratio","primitiveunits","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","slope","specularconstant","specularexponent","spreadmethod","startoffset","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","systemlanguage","tabindex","tablevalues","targetx","targety","transform","transform-origin","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","version","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"]),dt=S(["accent","accentunder","align","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","dir","display","displaystyle","encoding","fence","frame","height","href","id","largeop","length","linethickness","lspace","lquote","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"]),fe=S(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),tn=y(/\{\{[\w\W]*|[\w\W]*\}\}/gm),nn=y(/<%[\w\W]*|[\w\W]*%>/gm),on=y(/\$\{[\w\W]*/gm),an=y(/^data-[\-\w.\u00B7-\uFFFF]+$/),rn=y(/^aria-[\-\w]+$/),gt=y(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),sn=y(/^(?:\w+script|data):/i),ln=y(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),ht=y(/^html$/i),cn=y(/^[a-z][.\w]*(-[.\w]+)+$/i);var Tt=Object.freeze({__proto__:null,ARIA_ATTR:rn,ATTR_WHITESPACE:ln,CUSTOM_ELEMENT:cn,DATA_ATTR:an,DOCTYPE_NAME:ht,ERB_EXPR:nn,IS_ALLOWED_URI:gt,IS_SCRIPT_OR_DATA:sn,MUSTACHE_EXPR:tn,TMPLIT_EXPR:on});const Q={element:1,text:3,progressingInstruction:7,comment:8,document:9},fn=function(){return typeof window>"u"?null:window},un=function(o,l){if(typeof o!="object"||typeof o.createPolicy!="function")return null;let a=null;const c="data-tt-policy-suffix";l&&l.hasAttribute(c)&&(a=l.getAttribute(c));const O="dompurify"+(a?"#"+a:"");try{return o.createPolicy(O,{createHTML(P){return P},createScriptURL(P){return P}})}catch{return console.warn("TrustedTypes policy "+O+" could not be created."),null}},Et=function(){return{afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}};function At(){let s=arguments.length>0&&arguments[0]!==void 0?arguments[0]:fn();const o=i=>At(i);if(o.version="3.3.0",o.removed=[],!s||!s.document||s.document.nodeType!==Q.document||!s.Element)return o.isSupported=!1,o;let{document:l}=s;const a=l,c=a.currentScript,{DocumentFragment:O,HTMLTemplateElement:P,Node:me,Element:Ue,NodeFilter:B,NamedNodeMap:St=s.NamedNodeMap||s.MozNamedAttrMap,HTMLFormElement:Rt,DOMParser:Ot,trustedTypes:ee}=s,Y=Ue.prototype,Lt=J(Y,"cloneNode"),yt=J(Y,"remove"),bt=J(Y,"nextSibling"),Dt=J(Y,"childNodes"),te=J(Y,"parentNode");if(typeof P=="function"){const i=l.createElement("template");i.content&&i.content.ownerDocument&&(l=i.content.ownerDocument)}let g,X="";const{implementation:pe,createNodeIterator:Nt,createDocumentFragment:It,getElementsByTagName:Ct}=l,{importNode:Mt}=a;let h=Et();o.isSupported=typeof _t=="function"&&typeof te=="function"&&pe&&pe.createHTMLDocument!==void 0;const{MUSTACHE_EXPR:de,ERB_EXPR:Te,TMPLIT_EXPR:Ee,DATA_ATTR:wt,ARIA_ATTR:xt,IS_SCRIPT_OR_DATA:Pt,ATTR_WHITESPACE:Fe,CUSTOM_ELEMENT:vt}=Tt;let{IS_ALLOWED_URI:He}=Tt,p=null;const ze=r({},[...ut,...Ce,...Me,...we,...mt]);let T=null;const Ge=r({},[...pt,...xe,...dt,...fe]);let u=Object.seal(Pe(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),j=null,_e=null;const v=Object.seal(Pe(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let We=!0,ge=!0,Be=!1,Ye=!0,k=!1,ne=!0,w=!1,he=!1,Ae=!1,U=!1,oe=!1,ie=!1,Xe=!0,je=!1;const kt="user-content-";let Se=!0,V=!1,F={},H=null;const Ve=r({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let $e=null;const qe=r({},["audio","video","img","source","image","track"]);let Re=null;const Ke=r({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),ae="http://www.w3.org/1998/Math/MathML",re="http://www.w3.org/2000/svg",N="http://www.w3.org/1999/xhtml";let z=N,Oe=!1,Le=null;const Ut=r({},[ae,re,N],Ne);let se=r({},["mi","mo","mn","ms","mtext"]),le=r({},["annotation-xml"]);const Ft=r({},["title","style","font","a","script"]);let $=null;const Ht=["application/xhtml+xml","text/html"],zt="text/html";let d=null,G=null;const Gt=l.createElement("form"),Ze=function(e){return e instanceof RegExp||e instanceof Function},ye=function(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(!(G&&G===e)){if((!e||typeof e!="object")&&(e={}),e=M(e),$=Ht.indexOf(e.PARSER_MEDIA_TYPE)===-1?zt:e.PARSER_MEDIA_TYPE,d=$==="application/xhtml+xml"?Ne:ue,p=b(e,"ALLOWED_TAGS")?r({},e.ALLOWED_TAGS,d):ze,T=b(e,"ALLOWED_ATTR")?r({},e.ALLOWED_ATTR,d):Ge,Le=b(e,"ALLOWED_NAMESPACES")?r({},e.ALLOWED_NAMESPACES,Ne):Ut,Re=b(e,"ADD_URI_SAFE_ATTR")?r(M(Ke),e.ADD_URI_SAFE_ATTR,d):Ke,$e=b(e,"ADD_DATA_URI_TAGS")?r(M(qe),e.ADD_DATA_URI_TAGS,d):qe,H=b(e,"FORBID_CONTENTS")?r({},e.FORBID_CONTENTS,d):Ve,j=b(e,"FORBID_TAGS")?r({},e.FORBID_TAGS,d):M({}),_e=b(e,"FORBID_ATTR")?r({},e.FORBID_ATTR,d):M({}),F=b(e,"USE_PROFILES")?e.USE_PROFILES:!1,We=e.ALLOW_ARIA_ATTR!==!1,ge=e.ALLOW_DATA_ATTR!==!1,Be=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Ye=e.ALLOW_SELF_CLOSE_IN_ATTR!==!1,k=e.SAFE_FOR_TEMPLATES||!1,ne=e.SAFE_FOR_XML!==!1,w=e.WHOLE_DOCUMENT||!1,U=e.RETURN_DOM||!1,oe=e.RETURN_DOM_FRAGMENT||!1,ie=e.RETURN_TRUSTED_TYPE||!1,Ae=e.FORCE_BODY||!1,Xe=e.SANITIZE_DOM!==!1,je=e.SANITIZE_NAMED_PROPS||!1,Se=e.KEEP_CONTENT!==!1,V=e.IN_PLACE||!1,He=e.ALLOWED_URI_REGEXP||gt,z=e.NAMESPACE||N,se=e.MATHML_TEXT_INTEGRATION_POINTS||se,le=e.HTML_INTEGRATION_POINTS||le,u=e.CUSTOM_ELEMENT_HANDLING||{},e.CUSTOM_ELEMENT_HANDLING&&Ze(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(u.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&Ze(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(u.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements=="boolean"&&(u.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),k&&(ge=!1),oe&&(U=!0),F&&(p=r({},mt),T=[],F.html===!0&&(r(p,ut),r(T,pt)),F.svg===!0&&(r(p,Ce),r(T,xe),r(T,fe)),F.svgFilters===!0&&(r(p,Me),r(T,xe),r(T,fe)),F.mathMl===!0&&(r(p,we),r(T,dt),r(T,fe))),e.ADD_TAGS&&(typeof e.ADD_TAGS=="function"?v.tagCheck=e.ADD_TAGS:(p===ze&&(p=M(p)),r(p,e.ADD_TAGS,d))),e.ADD_ATTR&&(typeof e.ADD_ATTR=="function"?v.attributeCheck=e.ADD_ATTR:(T===Ge&&(T=M(T)),r(T,e.ADD_ATTR,d))),e.ADD_URI_SAFE_ATTR&&r(Re,e.ADD_URI_SAFE_ATTR,d),e.FORBID_CONTENTS&&(H===Ve&&(H=M(H)),r(H,e.FORBID_CONTENTS,d)),Se&&(p["#text"]=!0),w&&r(p,["html","head","body"]),p.table&&(r(p,["tbody"]),delete j.tbody),e.TRUSTED_TYPES_POLICY){if(typeof e.TRUSTED_TYPES_POLICY.createHTML!="function")throw Z('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof e.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw Z('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');g=e.TRUSTED_TYPES_POLICY,X=g.createHTML("")}else g===void 0&&(g=un(ee,c)),g!==null&&typeof X=="string"&&(X=g.createHTML(""));S&&S(e),G=e}},Je=r({},[...Ce,...Me,...Qt]),Qe=r({},[...we,...en]),Wt=function(e){let t=te(e);(!t||!t.tagName)&&(t={namespaceURI:z,tagName:"template"});const n=ue(e.tagName),f=ue(t.tagName);return Le[e.namespaceURI]?e.namespaceURI===re?t.namespaceURI===N?n==="svg":t.namespaceURI===ae?n==="svg"&&(f==="annotation-xml"||se[f]):!!Je[n]:e.namespaceURI===ae?t.namespaceURI===N?n==="math":t.namespaceURI===re?n==="math"&&le[f]:!!Qe[n]:e.namespaceURI===N?t.namespaceURI===re&&!le[f]||t.namespaceURI===ae&&!se[f]?!1:!Qe[n]&&(Ft[n]||!Je[n]):!!($==="application/xhtml+xml"&&Le[e.namespaceURI]):!1},D=function(e){q(o.removed,{element:e});try{te(e).removeChild(e)}catch{yt(e)}},x=function(e,t){try{q(o.removed,{attribute:t.getAttributeNode(e),from:t})}catch{q(o.removed,{attribute:null,from:t})}if(t.removeAttribute(e),e==="is")if(U||oe)try{D(t)}catch{}else try{t.setAttribute(e,"")}catch{}},et=function(e){let t=null,n=null;if(Ae)e="<remove></remove>"+e;else{const m=Ie(e,/^[\r\n\t ]+/);n=m&&m[0]}$==="application/xhtml+xml"&&z===N&&(e='<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>'+e+"</body></html>");const f=g?g.createHTML(e):e;if(z===N)try{t=new Ot().parseFromString(f,$)}catch{}if(!t||!t.documentElement){t=pe.createDocument(z,"template",null);try{t.documentElement.innerHTML=Oe?X:f}catch{}}const _=t.body||t.documentElement;return e&&n&&_.insertBefore(l.createTextNode(n),_.childNodes[0]||null),z===N?Ct.call(t,w?"html":"body")[0]:w?t.documentElement:_},tt=function(e){return Nt.call(e.ownerDocument||e,e,B.SHOW_ELEMENT|B.SHOW_COMMENT|B.SHOW_TEXT|B.SHOW_PROCESSING_INSTRUCTION|B.SHOW_CDATA_SECTION,null)},be=function(e){return e instanceof Rt&&(typeof e.nodeName!="string"||typeof e.textContent!="string"||typeof e.removeChild!="function"||!(e.attributes instanceof St)||typeof e.removeAttribute!="function"||typeof e.setAttribute!="function"||typeof e.namespaceURI!="string"||typeof e.insertBefore!="function"||typeof e.hasChildNodes!="function")},nt=function(e){return typeof me=="function"&&e instanceof me};function I(i,e,t){ce(i,n=>{n.call(o,e,t,G)})}const ot=function(e){let t=null;if(I(h.beforeSanitizeElements,e,null),be(e))return D(e),!0;const n=d(e.nodeName);if(I(h.uponSanitizeElement,e,{tagName:n,allowedTags:p}),ne&&e.hasChildNodes()&&!nt(e.firstElementChild)&&A(/<[/\w!]/g,e.innerHTML)&&A(/<[/\w!]/g,e.textContent)||e.nodeType===Q.progressingInstruction||ne&&e.nodeType===Q.comment&&A(/<[/\w]/g,e.data))return D(e),!0;if(!(v.tagCheck instanceof Function&&v.tagCheck(n))&&(!p[n]||j[n])){if(!j[n]&&at(n)&&(u.tagNameCheck instanceof RegExp&&A(u.tagNameCheck,n)||u.tagNameCheck instanceof Function&&u.tagNameCheck(n)))return!1;if(Se&&!H[n]){const f=te(e)||e.parentNode,_=Dt(e)||e.childNodes;if(_&&f){const m=_.length;for(let L=m-1;L>=0;--L){const C=Lt(_[L],!0);C.__removalCount=(e.__removalCount||0)+1,f.insertBefore(C,bt(e))}}}return D(e),!0}return e instanceof Ue&&!Wt(e)||(n==="noscript"||n==="noembed"||n==="noframes")&&A(/<\/no(script|embed|frames)/i,e.innerHTML)?(D(e),!0):(k&&e.nodeType===Q.text&&(t=e.textContent,ce([de,Te,Ee],f=>{t=K(t,f," ")}),e.textContent!==t&&(q(o.removed,{element:e.cloneNode()}),e.textContent=t)),I(h.afterSanitizeElements,e,null),!1)},it=function(e,t,n){if(Xe&&(t==="id"||t==="name")&&(n in l||n in Gt))return!1;if(!(ge&&!_e[t]&&A(wt,t))){if(!(We&&A(xt,t))){if(!(v.attributeCheck instanceof Function&&v.attributeCheck(t,e))){if(!T[t]||_e[t]){if(!(at(e)&&(u.tagNameCheck instanceof RegExp&&A(u.tagNameCheck,e)||u.tagNameCheck instanceof Function&&u.tagNameCheck(e))&&(u.attributeNameCheck instanceof RegExp&&A(u.attributeNameCheck,t)||u.attributeNameCheck instanceof Function&&u.attributeNameCheck(t,e))||t==="is"&&u.allowCustomizedBuiltInElements&&(u.tagNameCheck instanceof RegExp&&A(u.tagNameCheck,n)||u.tagNameCheck instanceof Function&&u.tagNameCheck(n))))return!1}else if(!Re[t]){if(!A(He,K(n,Fe,""))){if(!((t==="src"||t==="xlink:href"||t==="href")&&e!=="script"&&qt(n,"data:")===0&&$e[e])){if(!(Be&&!A(Pt,K(n,Fe,"")))){if(n)return!1}}}}}}}return!0},at=function(e){return e!=="annotation-xml"&&Ie(e,vt)},rt=function(e){I(h.beforeSanitizeAttributes,e,null);const{attributes:t}=e;if(!t||be(e))return;const n={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:T,forceKeepAttr:void 0};let f=t.length;for(;f--;){const _=t[f],{name:m,namespaceURI:L,value:C}=_,W=d(m),De=C;let E=m==="value"?De:Kt(De);if(n.attrName=W,n.attrValue=E,n.keepAttr=!0,n.forceKeepAttr=void 0,I(h.uponSanitizeAttribute,e,n),E=n.attrValue,je&&(W==="id"||W==="name")&&(x(m,e),E=kt+E),ne&&A(/((--!?|])>)|<\/(style|title|textarea)/i,E)){x(m,e);continue}if(W==="attributename"&&Ie(E,"href")){x(m,e);continue}if(n.forceKeepAttr)continue;if(!n.keepAttr){x(m,e);continue}if(!Ye&&A(/\/>/i,E)){x(m,e);continue}k&&ce([de,Te,Ee],lt=>{E=K(E,lt," ")});const st=d(e.nodeName);if(!it(st,W,E)){x(m,e);continue}if(g&&typeof ee=="object"&&typeof ee.getAttributeType=="function"&&!L)switch(ee.getAttributeType(st,W)){case"TrustedHTML":{E=g.createHTML(E);break}case"TrustedScriptURL":{E=g.createScriptURL(E);break}}if(E!==De)try{L?e.setAttributeNS(L,m,E):e.setAttribute(m,E),be(e)?D(e):ft(o.removed)}catch{x(m,e)}}I(h.afterSanitizeAttributes,e,null)},Bt=function i(e){let t=null;const n=tt(e);for(I(h.beforeSanitizeShadowDOM,e,null);t=n.nextNode();)I(h.uponSanitizeShadowNode,t,null),ot(t),rt(t),t.content instanceof O&&i(t.content);I(h.afterSanitizeShadowDOM,e,null)};return o.sanitize=function(i){let e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},t=null,n=null,f=null,_=null;if(Oe=!i,Oe&&(i="<!-->"),typeof i!="string"&&!nt(i))if(typeof i.toString=="function"){if(i=i.toString(),typeof i!="string")throw Z("dirty is not a string, aborting")}else throw Z("toString is not a function");if(!o.isSupported)return i;if(he||ye(e),o.removed=[],typeof i=="string"&&(V=!1),V){if(i.nodeName){const C=d(i.nodeName);if(!p[C]||j[C])throw Z("root node is forbidden and cannot be sanitized in-place")}}else if(i instanceof me)t=et("<!---->"),n=t.ownerDocument.importNode(i,!0),n.nodeType===Q.element&&n.nodeName==="BODY"||n.nodeName==="HTML"?t=n:t.appendChild(n);else{if(!U&&!k&&!w&&i.indexOf("<")===-1)return g&&ie?g.createHTML(i):i;if(t=et(i),!t)return U?null:ie?X:""}t&&Ae&&D(t.firstChild);const m=tt(V?i:t);for(;f=m.nextNode();)ot(f),rt(f),f.content instanceof O&&Bt(f.content);if(V)return i;if(U){if(oe)for(_=It.call(t.ownerDocument);t.firstChild;)_.appendChild(t.firstChild);else _=t;return(T.shadowroot||T.shadowrootmode)&&(_=Mt.call(a,_,!0)),_}let L=w?t.outerHTML:t.innerHTML;return w&&p["!doctype"]&&t.ownerDocument&&t.ownerDocument.doctype&&t.ownerDocument.doctype.name&&A(ht,t.ownerDocument.doctype.name)&&(L="<!DOCTYPE "+t.ownerDocument.doctype.name+`>
|
| 2 |
+
`+L),k&&ce([de,Te,Ee],C=>{L=K(L,C," ")}),g&&ie?g.createHTML(L):L},o.setConfig=function(){let i=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};ye(i),he=!0},o.clearConfig=function(){G=null,he=!1},o.isValidAttribute=function(i,e,t){G||ye({});const n=d(i),f=d(e);return it(n,f,t)},o.addHook=function(i,e){typeof e=="function"&&q(h[i],e)},o.removeHook=function(i,e){if(e!==void 0){const t=Vt(h[i],e);return t===-1?void 0:$t(h[i],t,1)[0]}return ft(h[i])},o.removeHooks=function(i){h[i]=[]},o.removeAllHooks=function(){h=Et()},o}var mn=At();export{mn as default};
|
dist/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>Watermark Remover</title>
|
| 7 |
+
<script type="module" crossorigin src="/assets/index-CXi72gcy.js"></script>
|
| 8 |
+
<link rel="stylesheet" crossorigin href="/assets/index-DDQzBmv1.css">
|
| 9 |
+
</head>
|
| 10 |
+
<body>
|
| 11 |
+
<div id="root"></div>
|
| 12 |
+
</body>
|
| 13 |
+
</html>
|
index.html
CHANGED
|
@@ -1,19 +1,12 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
<p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
|
| 13 |
-
<p>
|
| 14 |
-
Also don't forget to check the
|
| 15 |
-
<a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
|
| 16 |
-
</p>
|
| 17 |
-
</div>
|
| 18 |
-
</body>
|
| 19 |
</html>
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>Watermark Remover</title>
|
| 7 |
+
</head>
|
| 8 |
+
<body>
|
| 9 |
+
<div id="root"></div>
|
| 10 |
+
<script type="module" src="/src/main.jsx"></script>
|
| 11 |
+
</body>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
</html>
|
package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "watermark-remover",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "1.0.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"preview": "vite preview"
|
| 10 |
+
},
|
| 11 |
+
"dependencies": {
|
| 12 |
+
"jspdf": "^3.0.4",
|
| 13 |
+
"lucide-react": "^0.468.0",
|
| 14 |
+
"pdfjs-dist": "^5.4.394",
|
| 15 |
+
"react": "^18.3.1",
|
| 16 |
+
"react-dom": "^18.3.1"
|
| 17 |
+
},
|
| 18 |
+
"devDependencies": {
|
| 19 |
+
"@vitejs/plugin-react": "^4.3.4",
|
| 20 |
+
"autoprefixer": "^10.4.20",
|
| 21 |
+
"postcss": "^8.4.49",
|
| 22 |
+
"tailwindcss": "^3.4.17",
|
| 23 |
+
"vite": "^6.0.5"
|
| 24 |
+
}
|
| 25 |
+
}
|
postcss.config.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export default {
|
| 2 |
+
plugins: {
|
| 3 |
+
tailwindcss: {},
|
| 4 |
+
autoprefixer: {},
|
| 5 |
+
},
|
| 6 |
+
}
|
src/WatermarkRemover.jsx
ADDED
|
@@ -0,0 +1,746 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useRef, useCallback } from 'react';
|
| 2 |
+
import { Upload, Download, Settings, Play, Eye, EyeOff, RotateCcw, ChevronLeft, ChevronRight } from 'lucide-react';
|
| 3 |
+
import * as pdfjsLib from 'pdfjs-dist';
|
| 4 |
+
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url';
|
| 5 |
+
import { jsPDF } from 'jspdf';
|
| 6 |
+
|
| 7 |
+
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;
|
| 8 |
+
|
| 9 |
+
export default function WatermarkRemover() {
|
| 10 |
+
const [images, setImages] = useState([]);
|
| 11 |
+
const [originalFileName, setOriginalFileName] = useState('');
|
| 12 |
+
const [currentPage, setCurrentPage] = useState(0);
|
| 13 |
+
const [processing, setProcessing] = useState(false);
|
| 14 |
+
const [showOriginal, setShowOriginal] = useState(false);
|
| 15 |
+
const [settings, setSettings] = useState({
|
| 16 |
+
width: 220,
|
| 17 |
+
height: 80,
|
| 18 |
+
offsetX: 0,
|
| 19 |
+
offsetY: 0,
|
| 20 |
+
blendRadius: 10,
|
| 21 |
+
method: 'telea' // 'telea' or 'ns' (Navier-Stokes)
|
| 22 |
+
});
|
| 23 |
+
const [showSettings, setShowSettings] = useState(false);
|
| 24 |
+
const canvasRef = useRef(null);
|
| 25 |
+
const fileInputRef = useRef(null);
|
| 26 |
+
|
| 27 |
+
// Convert PDF pages to images using PDF.js approach (simplified - uses canvas rendering)
|
| 28 |
+
const handleFileUpload = async (e) => {
|
| 29 |
+
const file = e.target.files[0];
|
| 30 |
+
if (!file) return;
|
| 31 |
+
|
| 32 |
+
setProcessing(true);
|
| 33 |
+
const loadedImages = [];
|
| 34 |
+
|
| 35 |
+
if (file.type === 'application/pdf') {
|
| 36 |
+
try {
|
| 37 |
+
setOriginalFileName(file.name.replace(/\.pdf$/i, ''));
|
| 38 |
+
const arrayBuffer = await file.arrayBuffer();
|
| 39 |
+
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
| 40 |
+
const numPages = pdf.numPages;
|
| 41 |
+
|
| 42 |
+
for (let pageNum = 1; pageNum <= numPages; pageNum++) {
|
| 43 |
+
const page = await pdf.getPage(pageNum);
|
| 44 |
+
const scale = 1.5;
|
| 45 |
+
const viewport = page.getViewport({ scale });
|
| 46 |
+
|
| 47 |
+
const canvas = document.createElement('canvas');
|
| 48 |
+
const context = canvas.getContext('2d');
|
| 49 |
+
canvas.width = viewport.width;
|
| 50 |
+
canvas.height = viewport.height;
|
| 51 |
+
|
| 52 |
+
await page.render({
|
| 53 |
+
canvasContext: context,
|
| 54 |
+
viewport: viewport
|
| 55 |
+
}).promise;
|
| 56 |
+
|
| 57 |
+
const img = new Image();
|
| 58 |
+
await new Promise((resolve) => {
|
| 59 |
+
img.onload = resolve;
|
| 60 |
+
img.src = canvas.toDataURL('image/jpeg', 0.85);
|
| 61 |
+
});
|
| 62 |
+
|
| 63 |
+
loadedImages.push({
|
| 64 |
+
original: img,
|
| 65 |
+
processed: null,
|
| 66 |
+
name: `${file.name}_page_${pageNum}.png`
|
| 67 |
+
});
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
setImages(loadedImages);
|
| 71 |
+
setCurrentPage(0);
|
| 72 |
+
setProcessing(false);
|
| 73 |
+
return;
|
| 74 |
+
} catch (err) {
|
| 75 |
+
console.error('PDF loading error:', err);
|
| 76 |
+
alert('Error loading PDF: ' + err.message);
|
| 77 |
+
setProcessing(false);
|
| 78 |
+
return;
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
// Handle image files
|
| 83 |
+
if (file.type.startsWith('image/')) {
|
| 84 |
+
const img = new Image();
|
| 85 |
+
img.onload = () => {
|
| 86 |
+
loadedImages.push({
|
| 87 |
+
original: img,
|
| 88 |
+
processed: null,
|
| 89 |
+
name: file.name
|
| 90 |
+
});
|
| 91 |
+
setImages(loadedImages);
|
| 92 |
+
setCurrentPage(0);
|
| 93 |
+
setProcessing(false);
|
| 94 |
+
};
|
| 95 |
+
img.src = URL.createObjectURL(file);
|
| 96 |
+
}
|
| 97 |
+
};
|
| 98 |
+
|
| 99 |
+
const handleMultipleImages = async (e) => {
|
| 100 |
+
const files = Array.from(e.target.files);
|
| 101 |
+
if (files.length === 0) return;
|
| 102 |
+
|
| 103 |
+
setProcessing(true);
|
| 104 |
+
const loadedImages = [];
|
| 105 |
+
|
| 106 |
+
for (const file of files) {
|
| 107 |
+
if (file.type.startsWith('image/')) {
|
| 108 |
+
await new Promise((resolve) => {
|
| 109 |
+
const img = new Image();
|
| 110 |
+
img.onload = () => {
|
| 111 |
+
loadedImages.push({
|
| 112 |
+
original: img,
|
| 113 |
+
processed: null,
|
| 114 |
+
name: file.name
|
| 115 |
+
});
|
| 116 |
+
resolve();
|
| 117 |
+
};
|
| 118 |
+
img.src = URL.createObjectURL(file);
|
| 119 |
+
});
|
| 120 |
+
}
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
// Sort by filename
|
| 124 |
+
loadedImages.sort((a, b) => a.name.localeCompare(b.name));
|
| 125 |
+
setImages(loadedImages);
|
| 126 |
+
setCurrentPage(0);
|
| 127 |
+
setProcessing(false);
|
| 128 |
+
};
|
| 129 |
+
|
| 130 |
+
// Telea inpainting algorithm (Fast Marching Method)
|
| 131 |
+
const inpaintTelea = (imageData, mask, radius) => {
|
| 132 |
+
const { width, height, data } = imageData;
|
| 133 |
+
const result = new Uint8ClampedArray(data);
|
| 134 |
+
|
| 135 |
+
// Distance transform for prioritizing pixels
|
| 136 |
+
const distance = new Float32Array(width * height).fill(Infinity);
|
| 137 |
+
const known = new Uint8Array(width * height);
|
| 138 |
+
|
| 139 |
+
// Initialize known pixels and boundary
|
| 140 |
+
const boundary = [];
|
| 141 |
+
for (let y = 0; y < height; y++) {
|
| 142 |
+
for (let x = 0; x < width; x++) {
|
| 143 |
+
const idx = y * width + x;
|
| 144 |
+
if (!mask[idx]) {
|
| 145 |
+
known[idx] = 1;
|
| 146 |
+
distance[idx] = 0;
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
// Find initial boundary (unknown pixels adjacent to known)
|
| 152 |
+
for (let y = 0; y < height; y++) {
|
| 153 |
+
for (let x = 0; x < width; x++) {
|
| 154 |
+
const idx = y * width + x;
|
| 155 |
+
if (mask[idx]) {
|
| 156 |
+
// Check 4-neighbors
|
| 157 |
+
for (const [dx, dy] of [[0, 1], [0, -1], [1, 0], [-1, 0]]) {
|
| 158 |
+
const nx = x + dx, ny = y + dy;
|
| 159 |
+
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
| 160 |
+
const nidx = ny * width + nx;
|
| 161 |
+
if (!mask[nidx]) {
|
| 162 |
+
distance[idx] = 1;
|
| 163 |
+
boundary.push({ x, y, dist: 1 });
|
| 164 |
+
break;
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
// Sort boundary by distance (priority queue simulation)
|
| 173 |
+
boundary.sort((a, b) => a.dist - b.dist);
|
| 174 |
+
|
| 175 |
+
// Process pixels in order of distance
|
| 176 |
+
while (boundary.length > 0) {
|
| 177 |
+
const { x, y } = boundary.shift();
|
| 178 |
+
const idx = y * width + x;
|
| 179 |
+
|
| 180 |
+
if (known[idx]) continue;
|
| 181 |
+
|
| 182 |
+
// Compute color using weighted average of known neighbors
|
| 183 |
+
let totalWeight = 0;
|
| 184 |
+
let r = 0, g = 0, b = 0;
|
| 185 |
+
|
| 186 |
+
for (let dy = -radius; dy <= radius; dy++) {
|
| 187 |
+
for (let dx = -radius; dx <= radius; dx++) {
|
| 188 |
+
const nx = x + dx, ny = y + dy;
|
| 189 |
+
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
| 190 |
+
const nidx = ny * width + nx;
|
| 191 |
+
if (known[nidx]) {
|
| 192 |
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
| 193 |
+
if (dist <= radius && dist > 0) {
|
| 194 |
+
// Weight based on distance and direction
|
| 195 |
+
const weight = 1 / (dist * dist);
|
| 196 |
+
const pixelIdx = nidx * 4;
|
| 197 |
+
r += result[pixelIdx] * weight;
|
| 198 |
+
g += result[pixelIdx + 1] * weight;
|
| 199 |
+
b += result[pixelIdx + 2] * weight;
|
| 200 |
+
totalWeight += weight;
|
| 201 |
+
}
|
| 202 |
+
}
|
| 203 |
+
}
|
| 204 |
+
}
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
if (totalWeight > 0) {
|
| 208 |
+
const pixelIdx = idx * 4;
|
| 209 |
+
result[pixelIdx] = r / totalWeight;
|
| 210 |
+
result[pixelIdx + 1] = g / totalWeight;
|
| 211 |
+
result[pixelIdx + 2] = b / totalWeight;
|
| 212 |
+
result[pixelIdx + 3] = 255;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
known[idx] = 1;
|
| 216 |
+
|
| 217 |
+
// Add new boundary pixels
|
| 218 |
+
for (const [dx, dy] of [[0, 1], [0, -1], [1, 0], [-1, 0]]) {
|
| 219 |
+
const nx = x + dx, ny = y + dy;
|
| 220 |
+
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
| 221 |
+
const nidx = ny * width + nx;
|
| 222 |
+
if (mask[nidx] && !known[nidx] && distance[nidx] === Infinity) {
|
| 223 |
+
distance[nidx] = distance[idx] + 1;
|
| 224 |
+
boundary.push({ x: nx, y: ny, dist: distance[nidx] });
|
| 225 |
+
// Keep sorted (simple insertion)
|
| 226 |
+
boundary.sort((a, b) => a.dist - b.dist);
|
| 227 |
+
}
|
| 228 |
+
}
|
| 229 |
+
}
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
return new ImageData(result, width, height);
|
| 233 |
+
};
|
| 234 |
+
|
| 235 |
+
// Navier-Stokes based inpainting (diffusion-based)
|
| 236 |
+
const inpaintNavierStokes = (imageData, mask, radius, iterations = 100) => {
|
| 237 |
+
const { width, height, data } = imageData;
|
| 238 |
+
const result = new Float32Array(data.length);
|
| 239 |
+
for (let i = 0; i < data.length; i++) result[i] = data[i];
|
| 240 |
+
|
| 241 |
+
// Iterative diffusion
|
| 242 |
+
for (let iter = 0; iter < iterations; iter++) {
|
| 243 |
+
const temp = new Float32Array(result);
|
| 244 |
+
|
| 245 |
+
for (let y = 1; y < height - 1; y++) {
|
| 246 |
+
for (let x = 1; x < width - 1; x++) {
|
| 247 |
+
const idx = y * width + x;
|
| 248 |
+
if (mask[idx]) {
|
| 249 |
+
const pixelIdx = idx * 4;
|
| 250 |
+
|
| 251 |
+
// Laplacian diffusion
|
| 252 |
+
for (let c = 0; c < 3; c++) {
|
| 253 |
+
const north = ((y - 1) * width + x) * 4 + c;
|
| 254 |
+
const south = ((y + 1) * width + x) * 4 + c;
|
| 255 |
+
const east = (y * width + (x + 1)) * 4 + c;
|
| 256 |
+
const west = (y * width + (x - 1)) * 4 + c;
|
| 257 |
+
|
| 258 |
+
result[pixelIdx + c] = (temp[north] + temp[south] + temp[east] + temp[west]) / 4;
|
| 259 |
+
}
|
| 260 |
+
result[pixelIdx + 3] = 255;
|
| 261 |
+
}
|
| 262 |
+
}
|
| 263 |
+
}
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
const output = new Uint8ClampedArray(result.length);
|
| 267 |
+
for (let i = 0; i < result.length; i++) {
|
| 268 |
+
output[i] = Math.max(0, Math.min(255, Math.round(result[i])));
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
return new ImageData(output, width, height);
|
| 272 |
+
};
|
| 273 |
+
|
| 274 |
+
// Create feathered mask for smooth blending
|
| 275 |
+
const createFeatheredMask = (width, height, region, featherRadius) => {
|
| 276 |
+
const mask = new Uint8Array(width * height);
|
| 277 |
+
const featherWeight = new Float32Array(width * height);
|
| 278 |
+
|
| 279 |
+
const { x: rx, y: ry, w: rw, h: rh } = region;
|
| 280 |
+
|
| 281 |
+
for (let y = 0; y < height; y++) {
|
| 282 |
+
for (let x = 0; x < width; x++) {
|
| 283 |
+
const idx = y * width + x;
|
| 284 |
+
|
| 285 |
+
// Check if inside the watermark region
|
| 286 |
+
if (x >= rx && x < rx + rw && y >= ry && y < ry + rh) {
|
| 287 |
+
mask[idx] = 1;
|
| 288 |
+
|
| 289 |
+
// Calculate feather weight (distance from edge)
|
| 290 |
+
const distFromLeft = x - rx;
|
| 291 |
+
const distFromRight = (rx + rw) - x;
|
| 292 |
+
const distFromTop = y - ry;
|
| 293 |
+
const distFromBottom = (ry + rh) - y;
|
| 294 |
+
const minDist = Math.min(distFromLeft, distFromRight, distFromTop, distFromBottom);
|
| 295 |
+
|
| 296 |
+
if (minDist < featherRadius) {
|
| 297 |
+
featherWeight[idx] = minDist / featherRadius;
|
| 298 |
+
} else {
|
| 299 |
+
featherWeight[idx] = 1;
|
| 300 |
+
}
|
| 301 |
+
}
|
| 302 |
+
}
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
return { mask, featherWeight };
|
| 306 |
+
};
|
| 307 |
+
|
| 308 |
+
// Process single image
|
| 309 |
+
const processImage = useCallback((imgData, ctx, imgWidth, imgHeight) => {
|
| 310 |
+
const { width: wmWidth, height: wmHeight, offsetX, offsetY, blendRadius, method } = settings;
|
| 311 |
+
|
| 312 |
+
// Watermark region (bottom-right corner)
|
| 313 |
+
const region = {
|
| 314 |
+
x: imgWidth - wmWidth - offsetX,
|
| 315 |
+
y: imgHeight - wmHeight - offsetY,
|
| 316 |
+
w: wmWidth,
|
| 317 |
+
h: wmHeight
|
| 318 |
+
};
|
| 319 |
+
|
| 320 |
+
// Get image data
|
| 321 |
+
const imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
|
| 322 |
+
|
| 323 |
+
// Create feathered mask
|
| 324 |
+
const { mask, featherWeight } = createFeatheredMask(imgWidth, imgHeight, region, blendRadius);
|
| 325 |
+
|
| 326 |
+
// Apply inpainting
|
| 327 |
+
let result;
|
| 328 |
+
if (method === 'telea') {
|
| 329 |
+
result = inpaintTelea(imageData, mask, blendRadius);
|
| 330 |
+
} else {
|
| 331 |
+
result = inpaintNavierStokes(imageData, mask, blendRadius, 150);
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
// Blend with feathering for smooth edges
|
| 335 |
+
const blended = new Uint8ClampedArray(imageData.data);
|
| 336 |
+
for (let y = 0; y < imgHeight; y++) {
|
| 337 |
+
for (let x = 0; x < imgWidth; x++) {
|
| 338 |
+
const idx = y * imgWidth + x;
|
| 339 |
+
if (mask[idx]) {
|
| 340 |
+
const pixelIdx = idx * 4;
|
| 341 |
+
const weight = featherWeight[idx];
|
| 342 |
+
for (let c = 0; c < 3; c++) {
|
| 343 |
+
blended[pixelIdx + c] = Math.round(
|
| 344 |
+
result.data[pixelIdx + c] * weight +
|
| 345 |
+
imageData.data[pixelIdx + c] * (1 - weight)
|
| 346 |
+
);
|
| 347 |
+
}
|
| 348 |
+
}
|
| 349 |
+
}
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
return new ImageData(blended, imgWidth, imgHeight);
|
| 353 |
+
}, [settings]);
|
| 354 |
+
|
| 355 |
+
// Remove watermark from current page
|
| 356 |
+
const removeWatermark = useCallback(() => {
|
| 357 |
+
if (images.length === 0) return;
|
| 358 |
+
|
| 359 |
+
setProcessing(true);
|
| 360 |
+
|
| 361 |
+
setTimeout(() => {
|
| 362 |
+
const canvas = canvasRef.current;
|
| 363 |
+
const ctx = canvas.getContext('2d');
|
| 364 |
+
const img = images[currentPage].original;
|
| 365 |
+
|
| 366 |
+
canvas.width = img.width;
|
| 367 |
+
canvas.height = img.height;
|
| 368 |
+
ctx.drawImage(img, 0, 0);
|
| 369 |
+
|
| 370 |
+
const result = processImage(null, ctx, img.width, img.height);
|
| 371 |
+
ctx.putImageData(result, 0, 0);
|
| 372 |
+
|
| 373 |
+
// Store processed result
|
| 374 |
+
const processedImg = new Image();
|
| 375 |
+
processedImg.src = canvas.toDataURL('image/png');
|
| 376 |
+
|
| 377 |
+
setImages(prev => {
|
| 378 |
+
const updated = [...prev];
|
| 379 |
+
updated[currentPage] = {
|
| 380 |
+
...updated[currentPage],
|
| 381 |
+
processed: processedImg,
|
| 382 |
+
processedDataUrl: canvas.toDataURL('image/jpeg', 0.85)
|
| 383 |
+
};
|
| 384 |
+
return updated;
|
| 385 |
+
});
|
| 386 |
+
|
| 387 |
+
setProcessing(false);
|
| 388 |
+
}, 50);
|
| 389 |
+
}, [images, currentPage, processImage]);
|
| 390 |
+
|
| 391 |
+
// Process all pages
|
| 392 |
+
const processAllPages = useCallback(async () => {
|
| 393 |
+
if (images.length === 0) return;
|
| 394 |
+
|
| 395 |
+
setProcessing(true);
|
| 396 |
+
|
| 397 |
+
const canvas = canvasRef.current;
|
| 398 |
+
const ctx = canvas.getContext('2d');
|
| 399 |
+
|
| 400 |
+
for (let i = 0; i < images.length; i++) {
|
| 401 |
+
await new Promise(resolve => {
|
| 402 |
+
setTimeout(() => {
|
| 403 |
+
const img = images[i].original;
|
| 404 |
+
canvas.width = img.width;
|
| 405 |
+
canvas.height = img.height;
|
| 406 |
+
ctx.drawImage(img, 0, 0);
|
| 407 |
+
|
| 408 |
+
const result = processImage(null, ctx, img.width, img.height);
|
| 409 |
+
ctx.putImageData(result, 0, 0);
|
| 410 |
+
|
| 411 |
+
const processedImg = new Image();
|
| 412 |
+
processedImg.src = canvas.toDataURL('image/png');
|
| 413 |
+
|
| 414 |
+
setImages(prev => {
|
| 415 |
+
const updated = [...prev];
|
| 416 |
+
updated[i] = {
|
| 417 |
+
...updated[i],
|
| 418 |
+
processed: processedImg,
|
| 419 |
+
processedDataUrl: canvas.toDataURL('image/jpeg', 0.85)
|
| 420 |
+
};
|
| 421 |
+
return updated;
|
| 422 |
+
});
|
| 423 |
+
|
| 424 |
+
resolve();
|
| 425 |
+
}, 50);
|
| 426 |
+
});
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
setProcessing(false);
|
| 430 |
+
}, [images, processImage]);
|
| 431 |
+
|
| 432 |
+
// Download current processed image
|
| 433 |
+
const downloadCurrent = () => {
|
| 434 |
+
if (!images[currentPage]?.processedDataUrl) return;
|
| 435 |
+
|
| 436 |
+
const link = document.createElement('a');
|
| 437 |
+
link.download = `processed_${images[currentPage].name}`;
|
| 438 |
+
link.href = images[currentPage].processedDataUrl;
|
| 439 |
+
link.click();
|
| 440 |
+
};
|
| 441 |
+
|
| 442 |
+
// Download all as PDF
|
| 443 |
+
const downloadAll = () => {
|
| 444 |
+
const processedImages = images.filter(img => img.processedDataUrl);
|
| 445 |
+
if (processedImages.length === 0) return;
|
| 446 |
+
|
| 447 |
+
const firstImg = processedImages[0].processed;
|
| 448 |
+
const pdf = new jsPDF({
|
| 449 |
+
orientation: firstImg.width > firstImg.height ? 'landscape' : 'portrait',
|
| 450 |
+
unit: 'px',
|
| 451 |
+
format: [firstImg.width, firstImg.height]
|
| 452 |
+
});
|
| 453 |
+
|
| 454 |
+
processedImages.forEach((img, idx) => {
|
| 455 |
+
if (idx > 0) {
|
| 456 |
+
pdf.addPage([img.processed.width, img.processed.height],
|
| 457 |
+
img.processed.width > img.processed.height ? 'landscape' : 'portrait');
|
| 458 |
+
}
|
| 459 |
+
pdf.addImage(img.processedDataUrl, 'JPEG', 0, 0, img.processed.width, img.processed.height, undefined, 'FAST');
|
| 460 |
+
});
|
| 461 |
+
|
| 462 |
+
pdf.save(`${originalFileName || 'processed'}_new.pdf`);
|
| 463 |
+
};
|
| 464 |
+
|
| 465 |
+
// Draw preview with watermark region indicator
|
| 466 |
+
const drawPreview = useCallback(() => {
|
| 467 |
+
if (images.length === 0 || !canvasRef.current) return;
|
| 468 |
+
|
| 469 |
+
const canvas = canvasRef.current;
|
| 470 |
+
const ctx = canvas.getContext('2d');
|
| 471 |
+
const currentImg = images[currentPage];
|
| 472 |
+
const img = showOriginal || !currentImg.processed ? currentImg.original : currentImg.processed;
|
| 473 |
+
|
| 474 |
+
canvas.width = img.width;
|
| 475 |
+
canvas.height = img.height;
|
| 476 |
+
ctx.drawImage(img, 0, 0);
|
| 477 |
+
|
| 478 |
+
// Draw watermark region indicator
|
| 479 |
+
if (showOriginal || !currentImg.processed) {
|
| 480 |
+
const { width: wmWidth, height: wmHeight, offsetX, offsetY } = settings;
|
| 481 |
+
ctx.strokeStyle = '#ff4444';
|
| 482 |
+
ctx.lineWidth = 2;
|
| 483 |
+
ctx.setLineDash([5, 5]);
|
| 484 |
+
ctx.strokeRect(
|
| 485 |
+
img.width - wmWidth - offsetX,
|
| 486 |
+
img.height - wmHeight - offsetY,
|
| 487 |
+
wmWidth,
|
| 488 |
+
wmHeight
|
| 489 |
+
);
|
| 490 |
+
ctx.setLineDash([]);
|
| 491 |
+
}
|
| 492 |
+
}, [images, currentPage, showOriginal, settings]);
|
| 493 |
+
|
| 494 |
+
React.useEffect(() => {
|
| 495 |
+
drawPreview();
|
| 496 |
+
}, [drawPreview]);
|
| 497 |
+
|
| 498 |
+
return (
|
| 499 |
+
<div className="min-h-screen bg-gray-900 text-white p-6">
|
| 500 |
+
<div className="max-w-6xl mx-auto">
|
| 501 |
+
<h1 className="text-3xl font-bold mb-2 text-center">Watermark Remover</h1>
|
| 502 |
+
<p className="text-gray-400 text-center mb-6">
|
| 503 |
+
Classic CV inpainting for bottom-right watermarks
|
| 504 |
+
</p>
|
| 505 |
+
|
| 506 |
+
{/* Upload Section */}
|
| 507 |
+
<div className="bg-gray-800 rounded-lg p-6 mb-6">
|
| 508 |
+
<div className="flex flex-wrap gap-4 justify-center">
|
| 509 |
+
<label className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg cursor-pointer transition">
|
| 510 |
+
<Upload size={20} />
|
| 511 |
+
Upload PDF
|
| 512 |
+
<input
|
| 513 |
+
type="file"
|
| 514 |
+
accept="application/pdf"
|
| 515 |
+
onChange={handleFileUpload}
|
| 516 |
+
className="hidden"
|
| 517 |
+
/>
|
| 518 |
+
</label>
|
| 519 |
+
|
| 520 |
+
<label className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg cursor-pointer transition">
|
| 521 |
+
<Upload size={20} />
|
| 522 |
+
Upload Images
|
| 523 |
+
<input
|
| 524 |
+
type="file"
|
| 525 |
+
accept="image/*"
|
| 526 |
+
multiple
|
| 527 |
+
onChange={handleMultipleImages}
|
| 528 |
+
className="hidden"
|
| 529 |
+
ref={fileInputRef}
|
| 530 |
+
/>
|
| 531 |
+
</label>
|
| 532 |
+
|
| 533 |
+
<button
|
| 534 |
+
onClick={() => setShowSettings(!showSettings)}
|
| 535 |
+
className={`flex items-center gap-2 px-4 py-2 rounded-lg transition ${
|
| 536 |
+
showSettings ? 'bg-purple-600' : 'bg-gray-700 hover:bg-gray-600'
|
| 537 |
+
}`}
|
| 538 |
+
>
|
| 539 |
+
<Settings size={20} />
|
| 540 |
+
Settings
|
| 541 |
+
</button>
|
| 542 |
+
</div>
|
| 543 |
+
|
| 544 |
+
{/* Settings Panel */}
|
| 545 |
+
{showSettings && (
|
| 546 |
+
<div className="mt-4 p-4 bg-gray-700 rounded-lg">
|
| 547 |
+
<h3 className="font-semibold mb-3">Watermark Region (Bottom-Right Corner)</h3>
|
| 548 |
+
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
| 549 |
+
<div>
|
| 550 |
+
<label className="block text-sm text-gray-400 mb-1">Width (px)</label>
|
| 551 |
+
<input
|
| 552 |
+
type="number"
|
| 553 |
+
value={settings.width}
|
| 554 |
+
onChange={(e) => setSettings(s => ({ ...s, width: parseInt(e.target.value) || 0 }))}
|
| 555 |
+
className="w-full px-3 py-2 bg-gray-600 rounded"
|
| 556 |
+
/>
|
| 557 |
+
</div>
|
| 558 |
+
<div>
|
| 559 |
+
<label className="block text-sm text-gray-400 mb-1">Height (px)</label>
|
| 560 |
+
<input
|
| 561 |
+
type="number"
|
| 562 |
+
value={settings.height}
|
| 563 |
+
onChange={(e) => setSettings(s => ({ ...s, height: parseInt(e.target.value) || 0 }))}
|
| 564 |
+
className="w-full px-3 py-2 bg-gray-600 rounded"
|
| 565 |
+
/>
|
| 566 |
+
</div>
|
| 567 |
+
<div>
|
| 568 |
+
<label className="block text-sm text-gray-400 mb-1">Offset X (px)</label>
|
| 569 |
+
<input
|
| 570 |
+
type="number"
|
| 571 |
+
value={settings.offsetX}
|
| 572 |
+
onChange={(e) => setSettings(s => ({ ...s, offsetX: parseInt(e.target.value) || 0 }))}
|
| 573 |
+
className="w-full px-3 py-2 bg-gray-600 rounded"
|
| 574 |
+
/>
|
| 575 |
+
</div>
|
| 576 |
+
<div>
|
| 577 |
+
<label className="block text-sm text-gray-400 mb-1">Offset Y (px)</label>
|
| 578 |
+
<input
|
| 579 |
+
type="number"
|
| 580 |
+
value={settings.offsetY}
|
| 581 |
+
onChange={(e) => setSettings(s => ({ ...s, offsetY: parseInt(e.target.value) || 0 }))}
|
| 582 |
+
className="w-full px-3 py-2 bg-gray-600 rounded"
|
| 583 |
+
/>
|
| 584 |
+
</div>
|
| 585 |
+
<div>
|
| 586 |
+
<label className="block text-sm text-gray-400 mb-1">Blend Radius</label>
|
| 587 |
+
<input
|
| 588 |
+
type="number"
|
| 589 |
+
value={settings.blendRadius}
|
| 590 |
+
onChange={(e) => setSettings(s => ({ ...s, blendRadius: parseInt(e.target.value) || 1 }))}
|
| 591 |
+
className="w-full px-3 py-2 bg-gray-600 rounded"
|
| 592 |
+
/>
|
| 593 |
+
</div>
|
| 594 |
+
<div>
|
| 595 |
+
<label className="block text-sm text-gray-400 mb-1">Method</label>
|
| 596 |
+
<select
|
| 597 |
+
value={settings.method}
|
| 598 |
+
onChange={(e) => setSettings(s => ({ ...s, method: e.target.value }))}
|
| 599 |
+
className="w-full px-3 py-2 bg-gray-600 rounded"
|
| 600 |
+
>
|
| 601 |
+
<option value="telea">Telea (Fast Marching)</option>
|
| 602 |
+
<option value="ns">Navier-Stokes (Diffusion)</option>
|
| 603 |
+
</select>
|
| 604 |
+
</div>
|
| 605 |
+
</div>
|
| 606 |
+
</div>
|
| 607 |
+
)}
|
| 608 |
+
</div>
|
| 609 |
+
|
| 610 |
+
{/* Main Content */}
|
| 611 |
+
{images.length > 0 && (
|
| 612 |
+
<div className="bg-gray-800 rounded-lg p-6">
|
| 613 |
+
{/* Controls */}
|
| 614 |
+
<div className="flex flex-wrap gap-3 mb-4 justify-center">
|
| 615 |
+
<button
|
| 616 |
+
onClick={removeWatermark}
|
| 617 |
+
disabled={processing}
|
| 618 |
+
className="flex items-center gap-2 px-4 py-2 bg-green-600 hover:bg-green-700 disabled:bg-gray-600 rounded-lg transition"
|
| 619 |
+
>
|
| 620 |
+
<Play size={20} />
|
| 621 |
+
{processing ? 'Processing...' : 'Remove Watermark'}
|
| 622 |
+
</button>
|
| 623 |
+
|
| 624 |
+
<button
|
| 625 |
+
onClick={processAllPages}
|
| 626 |
+
disabled={processing}
|
| 627 |
+
className="flex items-center gap-2 px-4 py-2 bg-green-600 hover:bg-green-700 disabled:bg-gray-600 rounded-lg transition"
|
| 628 |
+
>
|
| 629 |
+
<Play size={20} />
|
| 630 |
+
Process All ({images.length})
|
| 631 |
+
</button>
|
| 632 |
+
|
| 633 |
+
<button
|
| 634 |
+
onClick={() => setShowOriginal(!showOriginal)}
|
| 635 |
+
className="flex items-center gap-2 px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition"
|
| 636 |
+
>
|
| 637 |
+
{showOriginal ? <Eye size={20} /> : <EyeOff size={20} />}
|
| 638 |
+
{showOriginal ? 'Show Processed' : 'Show Original'}
|
| 639 |
+
</button>
|
| 640 |
+
|
| 641 |
+
{images[currentPage]?.processed && (
|
| 642 |
+
<>
|
| 643 |
+
<button
|
| 644 |
+
onClick={downloadCurrent}
|
| 645 |
+
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition"
|
| 646 |
+
>
|
| 647 |
+
<Download size={20} />
|
| 648 |
+
Download
|
| 649 |
+
</button>
|
| 650 |
+
|
| 651 |
+
<button
|
| 652 |
+
onClick={downloadAll}
|
| 653 |
+
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition"
|
| 654 |
+
>
|
| 655 |
+
<Download size={20} />
|
| 656 |
+
Download full PDF
|
| 657 |
+
</button>
|
| 658 |
+
</>
|
| 659 |
+
)}
|
| 660 |
+
|
| 661 |
+
<button
|
| 662 |
+
onClick={() => {
|
| 663 |
+
setImages([]);
|
| 664 |
+
setCurrentPage(0);
|
| 665 |
+
}}
|
| 666 |
+
className="flex items-center gap-2 px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg transition"
|
| 667 |
+
>
|
| 668 |
+
<RotateCcw size={20} />
|
| 669 |
+
Reset
|
| 670 |
+
</button>
|
| 671 |
+
</div>
|
| 672 |
+
|
| 673 |
+
{/* Navigation */}
|
| 674 |
+
{images.length > 1 && (
|
| 675 |
+
<div className="flex items-center justify-center gap-4 mb-4">
|
| 676 |
+
<button
|
| 677 |
+
onClick={() => setCurrentPage(p => Math.max(0, p - 1))}
|
| 678 |
+
disabled={currentPage === 0}
|
| 679 |
+
className="p-2 bg-gray-700 hover:bg-gray-600 disabled:bg-gray-800 disabled:text-gray-600 rounded-lg transition"
|
| 680 |
+
>
|
| 681 |
+
<ChevronLeft size={24} />
|
| 682 |
+
</button>
|
| 683 |
+
<span className="text-lg">
|
| 684 |
+
Page {currentPage + 1} of {images.length}
|
| 685 |
+
</span>
|
| 686 |
+
<button
|
| 687 |
+
onClick={() => setCurrentPage(p => Math.min(images.length - 1, p + 1))}
|
| 688 |
+
disabled={currentPage === images.length - 1}
|
| 689 |
+
className="p-2 bg-gray-700 hover:bg-gray-600 disabled:bg-gray-800 disabled:text-gray-600 rounded-lg transition"
|
| 690 |
+
>
|
| 691 |
+
<ChevronRight size={24} />
|
| 692 |
+
</button>
|
| 693 |
+
</div>
|
| 694 |
+
)}
|
| 695 |
+
|
| 696 |
+
{/* Canvas Preview */}
|
| 697 |
+
<div className="flex justify-center overflow-auto bg-gray-900 rounded-lg p-4">
|
| 698 |
+
<canvas
|
| 699 |
+
ref={canvasRef}
|
| 700 |
+
className="max-w-full h-auto border border-gray-700"
|
| 701 |
+
style={{ maxHeight: '600px' }}
|
| 702 |
+
/>
|
| 703 |
+
</div>
|
| 704 |
+
|
| 705 |
+
{/* Status */}
|
| 706 |
+
<div className="mt-4 text-center text-sm text-gray-400">
|
| 707 |
+
{processing && <span className="text-yellow-400">Processing... Please wait.</span>}
|
| 708 |
+
{!processing && images[currentPage]?.processed && (
|
| 709 |
+
<span className="text-green-400">✓ Watermark removed</span>
|
| 710 |
+
)}
|
| 711 |
+
{!processing && !images[currentPage]?.processed && (
|
| 712 |
+
<span>Red dashed box shows watermark region to remove</span>
|
| 713 |
+
)}
|
| 714 |
+
</div>
|
| 715 |
+
</div>
|
| 716 |
+
)}
|
| 717 |
+
|
| 718 |
+
{/* Instructions */}
|
| 719 |
+
{images.length === 0 && (
|
| 720 |
+
<div className="bg-gray-800 rounded-lg p-6 text-center">
|
| 721 |
+
<h3 className="text-xl font-semibold mb-4">How to Use</h3>
|
| 722 |
+
<ol className="text-left max-w-md mx-auto space-y-2 text-gray-300">
|
| 723 |
+
<li>1. Convert your PDF pages to PNG/JPG images first</li>
|
| 724 |
+
<li>2. Upload all page images using the button above</li>
|
| 725 |
+
<li>3. Adjust the watermark region settings if needed</li>
|
| 726 |
+
<li>4. Click "Process All" to remove watermarks</li>
|
| 727 |
+
<li>5. Download the processed images</li>
|
| 728 |
+
</ol>
|
| 729 |
+
<p className="mt-4 text-sm text-gray-500">
|
| 730 |
+
Tip: Use tools like <code className="bg-gray-700 px-1 rounded">pdftoppm</code> or online converters to convert PDF to images
|
| 731 |
+
</p>
|
| 732 |
+
</div>
|
| 733 |
+
)}
|
| 734 |
+
|
| 735 |
+
{/* Algorithm Info */}
|
| 736 |
+
<div className="mt-6 text-sm text-gray-500 text-center">
|
| 737 |
+
<p>
|
| 738 |
+
Uses Telea (Fast Marching) or Navier-Stokes inpainting algorithms.
|
| 739 |
+
<br />
|
| 740 |
+
Feathered blending ensures smooth edges.
|
| 741 |
+
</p>
|
| 742 |
+
</div>
|
| 743 |
+
</div>
|
| 744 |
+
</div>
|
| 745 |
+
);
|
| 746 |
+
}
|
src/index.css
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@tailwind base;
|
| 2 |
+
@tailwind components;
|
| 3 |
+
@tailwind utilities;
|
src/main.jsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react'
|
| 2 |
+
import ReactDOM from 'react-dom/client'
|
| 3 |
+
import WatermarkRemover from './WatermarkRemover'
|
| 4 |
+
import './index.css'
|
| 5 |
+
|
| 6 |
+
ReactDOM.createRoot(document.getElementById('root')).render(
|
| 7 |
+
<React.StrictMode>
|
| 8 |
+
<WatermarkRemover />
|
| 9 |
+
</React.StrictMode>,
|
| 10 |
+
)
|
style.css
DELETED
|
@@ -1,28 +0,0 @@
|
|
| 1 |
-
body {
|
| 2 |
-
padding: 2rem;
|
| 3 |
-
font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
|
| 4 |
-
}
|
| 5 |
-
|
| 6 |
-
h1 {
|
| 7 |
-
font-size: 16px;
|
| 8 |
-
margin-top: 0;
|
| 9 |
-
}
|
| 10 |
-
|
| 11 |
-
p {
|
| 12 |
-
color: rgb(107, 114, 128);
|
| 13 |
-
font-size: 15px;
|
| 14 |
-
margin-bottom: 10px;
|
| 15 |
-
margin-top: 5px;
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
.card {
|
| 19 |
-
max-width: 620px;
|
| 20 |
-
margin: 0 auto;
|
| 21 |
-
padding: 16px;
|
| 22 |
-
border: 1px solid lightgray;
|
| 23 |
-
border-radius: 16px;
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
.card p:last-child {
|
| 27 |
-
margin-bottom: 0;
|
| 28 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tailwind.config.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/** @type {import('tailwindcss').Config} */
|
| 2 |
+
export default {
|
| 3 |
+
content: [
|
| 4 |
+
"./index.html",
|
| 5 |
+
"./src/**/*.{js,ts,jsx,tsx}",
|
| 6 |
+
],
|
| 7 |
+
theme: {
|
| 8 |
+
extend: {},
|
| 9 |
+
},
|
| 10 |
+
plugins: [],
|
| 11 |
+
}
|
vite.config.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig } from 'vite'
|
| 2 |
+
import react from '@vitejs/plugin-react'
|
| 3 |
+
|
| 4 |
+
export default defineConfig({
|
| 5 |
+
plugins: [react()],
|
| 6 |
+
})
|