├── .env.example ├── resources ├── icon.icns ├── icon.ico ├── icon.png ├── splash.png ├── screenshot.png └── theme │ ├── theme │ ├── dark │ │ ├── up.png │ │ ├── card.png │ │ ├── down.png │ │ ├── empty.png │ │ ├── right.png │ │ ├── size.png │ │ ├── notebook.png │ │ ├── on-basic.png │ │ ├── box-accent.png │ │ ├── box-basic.png │ │ ├── box-hover.png │ │ ├── hor-accent.png │ │ ├── hor-basic.png │ │ ├── hor-hover.png │ │ ├── off-basic.png │ │ ├── on-accent.png │ │ ├── rect-basic.png │ │ ├── rect-hover.png │ │ ├── scale-hor.png │ │ ├── scale-vert.png │ │ ├── separator.png │ │ ├── tab-basic.png │ │ ├── tab-hover.png │ │ ├── tree-basic.png │ │ ├── up-accent.png │ │ ├── vert-basic.png │ │ ├── vert-hover.png │ │ ├── box-invalid.png │ │ ├── button-hover.png │ │ ├── check-accent.png │ │ ├── check-basic.png │ │ ├── check-hover.png │ │ ├── circle-accent.png │ │ ├── circle-basic.png │ │ ├── circle-hover.png │ │ ├── down-accent.png │ │ ├── outline-basic.png │ │ ├── outline-hover.png │ │ ├── radio-accent.png │ │ ├── radio-basic.png │ │ ├── radio-hover.png │ │ ├── rect-accent.png │ │ ├── tab-disabled.png │ │ ├── tree-pressed.png │ │ ├── vert-accent.png │ │ ├── check-tri-basic.png │ │ ├── check-tri-hover.png │ │ ├── radio-tri-basic.png │ │ ├── radio-tri-hover.png │ │ ├── tick-hor-accent.png │ │ ├── tick-hor-basic.png │ │ ├── tick-hor-hover.png │ │ ├── tick-vert-basic.png │ │ ├── tick-vert-hover.png │ │ ├── check-tri-accent.png │ │ ├── combo-button-basic.png │ │ ├── combo-button-focus.png │ │ ├── combo-button-hover.png │ │ ├── radio-tri-accent.png │ │ ├── rect-accent-hover.png │ │ └── tick-vert-accent.png │ ├── light │ │ ├── up.png │ │ ├── card.png │ │ ├── down.png │ │ ├── size.png │ │ ├── empty.png │ │ ├── right.png │ │ ├── box-basic.png │ │ ├── box-hover.png │ │ ├── hor-basic.png │ │ ├── hor-hover.png │ │ ├── notebook.png │ │ ├── off-basic.png │ │ ├── off-hover.png │ │ ├── on-accent.png │ │ ├── on-basic.png │ │ ├── on-hover.png │ │ ├── scale-hor.png │ │ ├── separator.png │ │ ├── tab-basic.png │ │ ├── tab-hover.png │ │ ├── up-accent.png │ │ ├── box-accent.png │ │ ├── box-invalid.png │ │ ├── button-hover.png │ │ ├── check-accent.png │ │ ├── check-basic.png │ │ ├── check-hover.png │ │ ├── circle-basic.png │ │ ├── circle-hover.png │ │ ├── down-accent.png │ │ ├── hor-accent.png │ │ ├── radio-accent.png │ │ ├── radio-basic.png │ │ ├── radio-hover.png │ │ ├── rect-accent.png │ │ ├── rect-basic.png │ │ ├── rect-hover.png │ │ ├── scale-vert.png │ │ ├── tab-disabled.png │ │ ├── tree-basic.png │ │ ├── tree-pressed.png │ │ ├── vert-accent.png │ │ ├── vert-basic.png │ │ ├── vert-hover.png │ │ ├── circle-accent.png │ │ ├── outline-basic.png │ │ ├── outline-hover.png │ │ ├── tick-hor-basic.png │ │ ├── tick-hor-hover.png │ │ ├── check-tri-accent.png │ │ ├── check-tri-basic.png │ │ ├── check-tri-hover.png │ │ ├── radio-tri-accent.png │ │ ├── radio-tri-basic.png │ │ ├── radio-tri-hover.png │ │ ├── rect-accent-hover.png │ │ ├── tick-hor-accent.png │ │ ├── tick-vert-accent.png │ │ ├── tick-vert-basic.png │ │ ├── tick-vert-hover.png │ │ ├── combo-button-basic.png │ │ ├── combo-button-focus.png │ │ └── combo-button-hover.png │ ├── dark.tcl │ └── light.tcl │ └── main.tcl ├── CHANGELOG.md ├── scripts ├── examples │ ├── linux_example.sh │ ├── macos_cli_example.sh │ ├── macos_app_example.sh │ └── windows_exe_example.sh ├── run_win.bat └── run_unix.sh ├── requirements.txt ├── README.md ├── test_build.local.sh ├── LICENSE ├── quality.py ├── .github └── workflows │ ├── debug_macos_app.yml │ ├── debug_windows.yml │ ├── debug_macos.yml │ ├── debug_linux.yml │ └── build_and_release.yml ├── .gitignore ├── CONTRIBUTING.md ├── installer.nsi ├── WatermarkRemover.py └── main.py /.env.example: -------------------------------------------------------------------------------- 1 | DEBUG=False 2 | VERSION=0.1.0 3 | SENTRY_DSN= -------------------------------------------------------------------------------- /resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/icon.icns -------------------------------------------------------------------------------- /resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/icon.ico -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/icon.png -------------------------------------------------------------------------------- /resources/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/splash.png -------------------------------------------------------------------------------- /resources/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/screenshot.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/up.png -------------------------------------------------------------------------------- /resources/theme/theme/light/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/up.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/card.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/down.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/empty.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/right.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/size.png -------------------------------------------------------------------------------- /resources/theme/theme/light/card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/card.png -------------------------------------------------------------------------------- /resources/theme/theme/light/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/down.png -------------------------------------------------------------------------------- /resources/theme/theme/light/size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/size.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/notebook.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/on-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/on-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/empty.png -------------------------------------------------------------------------------- /resources/theme/theme/light/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/right.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/box-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/box-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/box-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/box-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/box-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/box-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/hor-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/hor-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/hor-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/hor-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/hor-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/hor-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/off-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/off-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/on-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/on-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/rect-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/rect-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/rect-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/rect-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/scale-hor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/scale-hor.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/scale-vert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/scale-vert.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/separator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/separator.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/tab-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/tab-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/tab-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/tab-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/tree-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/tree-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/up-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/up-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/vert-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/vert-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/vert-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/vert-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/box-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/box-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/box-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/box-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/hor-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/hor-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/hor-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/hor-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/notebook.png -------------------------------------------------------------------------------- /resources/theme/theme/light/off-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/off-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/off-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/off-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/on-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/on-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/on-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/on-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/on-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/on-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/scale-hor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/scale-hor.png -------------------------------------------------------------------------------- /resources/theme/theme/light/separator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/separator.png -------------------------------------------------------------------------------- /resources/theme/theme/light/tab-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/tab-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/tab-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/tab-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/up-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/up-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/box-invalid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/box-invalid.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/button-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/button-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/check-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/check-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/check-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/check-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/check-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/check-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/circle-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/circle-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/circle-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/circle-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/circle-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/circle-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/down-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/down-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/outline-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/outline-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/outline-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/outline-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/radio-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/radio-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/radio-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/radio-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/radio-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/radio-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/rect-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/rect-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/tab-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/tab-disabled.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/tree-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/tree-pressed.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/vert-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/vert-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/box-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/box-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/box-invalid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/box-invalid.png -------------------------------------------------------------------------------- /resources/theme/theme/light/button-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/button-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/check-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/check-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/check-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/check-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/check-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/check-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/circle-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/circle-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/circle-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/circle-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/down-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/down-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/hor-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/hor-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/radio-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/radio-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/radio-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/radio-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/radio-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/radio-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/rect-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/rect-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/rect-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/rect-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/rect-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/rect-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/scale-vert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/scale-vert.png -------------------------------------------------------------------------------- /resources/theme/theme/light/tab-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/tab-disabled.png -------------------------------------------------------------------------------- /resources/theme/theme/light/tree-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/tree-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/tree-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/tree-pressed.png -------------------------------------------------------------------------------- /resources/theme/theme/light/vert-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/vert-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/vert-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/vert-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/vert-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/vert-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/check-tri-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/check-tri-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/check-tri-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/check-tri-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/radio-tri-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/radio-tri-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/radio-tri-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/radio-tri-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/tick-hor-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/tick-hor-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/tick-hor-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/tick-hor-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/tick-hor-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/tick-hor-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/tick-vert-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/tick-vert-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/tick-vert-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/tick-vert-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/circle-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/circle-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/outline-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/outline-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/outline-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/outline-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/tick-hor-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/tick-hor-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/tick-hor-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/tick-hor-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/check-tri-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/check-tri-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/combo-button-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/combo-button-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/combo-button-focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/combo-button-focus.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/combo-button-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/combo-button-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/radio-tri-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/radio-tri-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/rect-accent-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/rect-accent-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/dark/tick-vert-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/dark/tick-vert-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/check-tri-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/check-tri-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/check-tri-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/check-tri-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/check-tri-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/check-tri-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/radio-tri-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/radio-tri-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/radio-tri-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/radio-tri-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/radio-tri-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/radio-tri-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/rect-accent-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/rect-accent-hover.png -------------------------------------------------------------------------------- /resources/theme/theme/light/tick-hor-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/tick-hor-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/tick-vert-accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/tick-vert-accent.png -------------------------------------------------------------------------------- /resources/theme/theme/light/tick-vert-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/tick-vert-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/tick-vert-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/tick-vert-hover.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | - Add support for all GEOS applications 4 | - Add sentry for error reporting 5 | - Generating env file during build process 6 | -------------------------------------------------------------------------------- /resources/theme/theme/light/combo-button-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/combo-button-basic.png -------------------------------------------------------------------------------- /resources/theme/theme/light/combo-button-focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/combo-button-focus.png -------------------------------------------------------------------------------- /resources/theme/theme/light/combo-button-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naskio/watermark-remover/HEAD/resources/theme/theme/light/combo-button-hover.png -------------------------------------------------------------------------------- /scripts/examples/linux_example.sh: -------------------------------------------------------------------------------- 1 | #chmod +x ./scripts/run_unix.sh 2 | chmod +x ./dist/WatermarkRemover-linux 3 | ./scripts/run_unix.sh -e "./dist/WatermarkRemover-linux" -t 15; echo $? # 10 is enough time -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.21.5 2 | Pillow==9.0.1 3 | pikepdf==5.0.0 4 | opencv-python==4.5.5.62 5 | pyinstaller==4.9 6 | fire==0.4.0 7 | darkdetect==0.5.1 8 | environs==9.5.0 9 | marshmallow==3.14.1 10 | sentry-sdk==1.5.6 11 | # python-docx 12 | # docxtpl 13 | # PyPDF2 -------------------------------------------------------------------------------- /scripts/examples/macos_cli_example.sh: -------------------------------------------------------------------------------- 1 | #chmod +x ./scripts/run_unix.sh 2 | chmod +x ./dist/WatermarkRemover-macos-cmd 3 | ./scripts/run_unix.sh -e "./dist/WatermarkRemover-macos-cmd" -t 30; echo $? # 20 is enough time 4 | # not authorized => exit code 1 5 | # authorize from System Preferences > Security & Privacy 6 | # run again 7 | # pop up window => click "Open" 8 | # success => exit code 0 -------------------------------------------------------------------------------- /scripts/examples/macos_app_example.sh: -------------------------------------------------------------------------------- 1 | #chmod +x ./scripts/run_unix.sh 2 | # uncompress zip file 3 | # move to the directory 4 | ./scripts/run_unix.sh -a "./dist/WatermarkRemover.app/" -t 30; echo $? # 20 is enough time 5 | # not authorized => Failure with exit code 0 (we force to exit code 1) 6 | # authorize from System Preferences > Security & Privacy 7 | # run again 8 | # pop up window => click "Open" 9 | # success => exit code 0 -------------------------------------------------------------------------------- /scripts/examples/windows_exe_example.sh: -------------------------------------------------------------------------------- 1 | cd Desktop\watermark-remover\ 2 | .\scripts\run_win.bat .\dist\WatermarkRemover.exe 20 3 | 4 | # Rem cd Desktop\watermark-remover\ 5 | # Rem .\scripts\run_win.bat .\dist\WatermarkRemover.exe 20 6 | # Rem .\scripts\run_win.bat .\dist\tt.exe 2 7 | # Rem .\scripts\run_win.bat .\dist\5min.exe 5 8 | 9 | # @REM timeout /T %TIMEOUT% /NOBREAK > NUL 10 | # @REM ping 127.0.0.1 -n %TIMEOUT% > NUL 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # watermark-remover 2 | 3 | Python GUI to remove watermarks from images, docx and pdf files. 4 | 5 | ## Getting Started 6 | 7 | ### Install 8 | 9 | Download the latest release from [GitHub releases](https://github.com/naskio/watermark-remover/releases) and run it. 10 | 11 | ![screenshot](resources/screenshot.png) 12 | 13 | ## Contributing 14 | 15 | [Pull requests](https://github.com/naskio/watermark-remover/pulls) are welcome! For any bug reports, please create 16 | an [issue](https://github.com/naskio/watermark-remover/issues). 17 | 18 | Start by reading the [contributor guideline](CONTRIBUTING.md). 19 | 20 | ## License 21 | 22 | [License](LICENSE) -------------------------------------------------------------------------------- /test_build.local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Build & test locally (unix: bash) 3 | source ".env" 4 | timeout=${1:-45} # default timeout 60s 5 | appname=WatermarkRemover 6 | platform=macos # or: linux 7 | echo "Building for $platform version $VERSION:" 8 | echo "cleaning ..." 9 | python3 build.py --clean --debug=$DEBUG 10 | echo "building ..." 11 | python3 build.py --debug=$DEBUG --version=$VERSION --genenv --sentry_dsn=$SENTRY_DSN 12 | echo "Build complete." 13 | echo "----------------------------------------------------" 14 | echo "Running CLI tests ..." 15 | echo "renaming cli ..." 16 | mv ./dist/$appname ./dist/$appname-$platform 17 | echo "adding permission to cli ..." 18 | chmod +x ./dist/$appname-$platform 19 | echo "running cli ..." 20 | ./scripts/run_unix.sh -e "./dist/$appname-$platform" -t $timeout; echo $? 21 | # if macOS, run tests of .app 22 | if [ $platform = "macos" ]; then 23 | echo "----------------------------------------------------" 24 | echo "Running .app tests ..." 25 | echo "adding permission to .app ..." 26 | chmod +x ./dist/$appname.app/Contents/MacOS/$appname 27 | echo "running .app ..." 28 | ./scripts/run_unix.sh -a "./dist/$appname.app/" -t $timeout; echo $? 29 | fi 30 | -------------------------------------------------------------------------------- /scripts/run_win.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | SETLOCAL 3 | set EXECUTABLE=%1 4 | set FILENAME=%~nx1 5 | set TIMEOUT=%2 6 | 7 | echo executable is %EXECUTABLE%. 8 | echo filename is %FILENAME%. 9 | dir 10 | 11 | echo Starting %EXECUTABLE%... 12 | IF NOT EXIST %EXECUTABLE% ( 13 | echo %EXECUTABLE% is missing. 14 | exit 1 15 | ) 16 | START "" %EXECUTABLE% 17 | 18 | echo Waiting for %TIMEOUT% seconds... 19 | echo Started waiting at %TIME%. 20 | ping 127.0.0.1 -n %TIMEOUT% > NUL 21 | echo Wait finished at %TIME%. 22 | 23 | echo Checking if %FILENAME% is still running... 24 | tasklist /fi "ImageName eq %FILENAME%" /fo csv 2>NUL | find /I "%FILENAME%"> NUL 25 | echo ERROR LEVEL is %ERRORLEVEL%. 26 | 27 | IF %ERRORLEVEL% NEQ 0 ( 28 | echo Program %FILENAME% is not running. 29 | echo Re-running %FILENAME% to get logs... 30 | start /WAIT /B "" %EXECUTABLE% 31 | echo Trying to quit %FILENAME% even if not running... 32 | taskkill /f /im %FILENAME% 33 | echo Failed: EXE failed with code %ERRORLEVEL% . 34 | exit 1 35 | ) ELSE ( 36 | echo Program %FILENAME% is running. 37 | echo Quitting %FILENAME%... 38 | taskkill /f /im %FILENAME% 39 | echo Success: EXE succeeded with code %ERRORLEVEL%. 40 | exit 0 41 | ) 42 | ENDLOCAL 43 | 44 | echo END -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /quality.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | 4 | def improve_text_in_image(image: Image) -> Image: 5 | """ 6 | Improve the quality of the text in the image. 7 | :param image: 8 | :return: 9 | """ 10 | black = (0, 0, 0) 11 | white = (255, 255, 255) 12 | threshold = (160, 160, 160) 13 | image = image.convert("LA") 14 | pixels = image.getdata() 15 | new_pixels = [] 16 | 17 | for pixel in pixels: 18 | if pixel < threshold: 19 | new_pixels.append(black) 20 | # new_pixels.append(pixel) 21 | else: 22 | new_pixels.append(white) 23 | # return Image.new(image.mode, image.size) 24 | image = Image.new("RGB", image.size) 25 | image.putdata(new_pixels) 26 | return image 27 | 28 | 29 | """ 30 | A4 size 31 | 3508 x 2480 px 32 | 297 x 210 mm 33 | px: 595 x 842 794 x 1123 1240 x 1754 2480 x 3508 34 | px: 72 PPI 96 PPI 150 PPI 300 PPI 35 | 8.3 x 11.7 inches (8.27 x 11.69) 36 | """ 37 | if __name__ == '__main__': 38 | image_low_quality = Image.open("low_quality.png") 39 | s1 = image_low_quality.size 40 | print(image_low_quality.size, s1[0] / s1[1]) 41 | image_full_size = Image.open("full_size.png") 42 | s2 = image_full_size.size 43 | print(image_full_size.size, s2[0] / s2[1]) 44 | # resize the image to a new size 45 | new_width = 1240 46 | w_percent = (new_width / float(s1[0])) 47 | new_height = int((float(s1[1]) * float(w_percent))) 48 | new_sized_img = image_low_quality.resize((new_width, new_height), Image.ANTIALIAS) 49 | new_sized_img.show() 50 | new_sized_img = improve_text_in_image(new_sized_img) 51 | new_sized_img.show() 52 | -------------------------------------------------------------------------------- /.github/workflows/debug_macos_app.yml: -------------------------------------------------------------------------------- 1 | name: Debug macOS (.app bundle) 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'version' 8 | required: true 9 | default: '0.1.0' 10 | type: string 11 | timeout: 12 | description: 'timeout in seconds' 13 | required: true 14 | default: '30' 15 | type: string 16 | 17 | jobs: 18 | debug_job: 19 | name: Debug on macOS (.app bundle) 20 | runs-on: macos-latest 21 | timeout-minutes: 10 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v2 27 | with: 28 | python-version: '3.10' 29 | cache: 'pip' 30 | 31 | - run: | 32 | echo "version: ${{ github.event.inputs.version }}" 33 | 34 | - name: Upgrade pip 35 | run: | 36 | python -m pip install --upgrade pip 37 | 38 | - name: Install Dependencies 39 | run: | 40 | python -m pip install -r requirements.txt 41 | 42 | - name: Build 43 | run: | 44 | python build.py --version=${{ github.event.inputs.version }} --nodebug --dirmode 45 | 46 | - name: Install coreutils (timeout cmd needs this) 47 | if: runner.os == 'macOS' 48 | run: brew install coreutils 49 | 50 | - name: Rename, run and test (.app bundle) 51 | if: runner.os == 'macOS' 52 | run: | 53 | ls 54 | echo "----------------------------------------" 55 | ls ./dist 56 | echo "----------------------------------------" 57 | ls ./dist/WatermarkRemover 58 | echo "----------------------------------------" 59 | echo "adding permission ..." 60 | chmod +x ./dist/WatermarkRemover.app/Contents/MacOS/WatermarkRemover 61 | echo "running ./scripts/run_unix.sh ..." 62 | ./scripts/run_unix.sh -a "./dist/WatermarkRemover.app/" -t ${{ github.event.inputs.timeout }}; echo $? 63 | echo "done" -------------------------------------------------------------------------------- /.github/workflows/debug_windows.yml: -------------------------------------------------------------------------------- 1 | name: Debug Windows 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'version' 8 | required: true 9 | default: '0.1.0' 10 | type: string 11 | timeout: 12 | description: 'timeout in seconds' 13 | required: true 14 | default: '30' 15 | type: string 16 | 17 | jobs: 18 | debug_job: 19 | name: Debug on windows 20 | runs-on: windows-latest 21 | timeout-minutes: 10 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v2 27 | with: 28 | python-version: '3.10' 29 | cache: 'pip' 30 | 31 | - run: | 32 | echo "version: ${{ github.event.inputs.version }}" 33 | 34 | - name: Upgrade pip 35 | run: | 36 | python -m pip install --upgrade pip 37 | 38 | - name: Install Dependencies 39 | run: | 40 | python -m pip install -r requirements.txt 41 | 42 | - name: Build 43 | run: | 44 | echo Building version ${{ github.event.inputs.version }} 45 | python build.py --nodebug --dirmode --genenv --sentry_dsn=${{ secrets.SENTRY_DSN }} --version=${{ github.event.inputs.version }} 46 | 47 | - name: Rename, run and test .exe 48 | if: runner.os == 'Windows' 49 | run: | 50 | DIR 51 | DIR .\dist 52 | DIR .\dist\WatermarkRemover 53 | ECHO running .\scripts\run_win.bat ... 54 | .\scripts\run_win.bat .\dist\WatermarkRemover\WatermarkRemover.exe ${{ github.event.inputs.timeout }} 55 | ECHO %ERRORLEVEL% 56 | ECHO done 57 | shell: cmd 58 | 59 | - name: Create installer (using NSIS) 60 | uses: joncloud/makensis-action@v3.3 61 | with: 62 | script-file: installer.nsi 63 | 64 | - name: Logging 65 | run: | 66 | DIR 67 | DIR .\dist 68 | DIR .\dist\WatermarkRemover 69 | shell: cmd -------------------------------------------------------------------------------- /.github/workflows/debug_macos.yml: -------------------------------------------------------------------------------- 1 | name: Debug macOS 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'version' 8 | required: true 9 | default: '0.1.0' 10 | type: string 11 | timeout: 12 | description: 'timeout in seconds' 13 | required: true 14 | default: '30' 15 | type: string 16 | 17 | jobs: 18 | debug_job: 19 | name: Debug on macOS 20 | runs-on: macos-latest 21 | timeout-minutes: 10 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v2 27 | with: 28 | python-version: '3.10' 29 | cache: 'pip' 30 | 31 | - run: | 32 | echo "version: ${{ github.event.inputs.version }}" 33 | 34 | - name: Upgrade pip 35 | run: | 36 | python -m pip install --upgrade pip 37 | 38 | - name: Install Dependencies 39 | run: | 40 | python -m pip install -r requirements.txt 41 | 42 | - name: Build 43 | run: | 44 | python build.py --version=${{ github.event.inputs.version }} --nodebug 45 | 46 | - name: Install coreutils (timeout cmd needs this) 47 | if: runner.os == 'macOS' 48 | run: brew install coreutils 49 | 50 | - name: Rename, run and test 51 | if: runner.os == 'macOS' 52 | run: | 53 | ls 54 | echo "----------------------------------------" 55 | ls ./dist 56 | echo "----------------------------------------" 57 | # chmod +x ./scripts/run_unix.sh 58 | echo "renaming ..." 59 | mv ./dist/WatermarkRemover ./dist/WatermarkRemover-macos 60 | echo "----------------------------------------" 61 | ls 62 | echo "----------------------------------------" 63 | ls ./dist 64 | echo "----------------------------------------" 65 | echo "adding permission ..." 66 | chmod +x ./dist/WatermarkRemover-macos 67 | echo "running ./scripts/run_unix.sh ..." 68 | ./scripts/run_unix.sh -e "./dist/WatermarkRemover-macos" -t ${{ github.event.inputs.timeout }}; echo $? 69 | echo "done" -------------------------------------------------------------------------------- /scripts/run_unix.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # usage: 4 | # Executable: 5 | # ./scripts/run_unix.sh -e "./dist/WatermarkRemover-macos" -t 30; echo $? 6 | # app: 7 | # ./scripts/run_unix.sh -a "./dist/WatermarkRemover.app/" -t 30; echo $? 8 | 9 | helpFunction() 10 | { 11 | echo "" 12 | echo "Usage: $0 -a macOSAppPath -e executablePath -t timeout" 13 | echo -e "\t-a macOS app" 14 | echo -e "\t-e executable (macOS and linux)" 15 | echo -e "\t-t timeout for success (in seconds)" 16 | exit 1 # Exit script after printing help 17 | } 18 | 19 | while getopts "a:e:t:" opt 20 | do 21 | case "$opt" in 22 | a ) app="$OPTARG" ;; 23 | e ) executable="$OPTARG" ;; 24 | t ) timeout="$OPTARG" ;; 25 | ? ) helpFunction ;; # Print helpFunction in case parameter is non-existent 26 | esac 27 | done 28 | 29 | # set default timeout 30 | if [ -z "$timeout" ]; then 31 | timeout=60 32 | echo "No timeout specified, using default timeout of $timeout seconds." 33 | fi 34 | 35 | # Print helpFunction in case parameters both empty 36 | if [ -z "$app" ] && [ -z "$executable" ] 37 | then 38 | echo "Only one parameter should be set, but none are set." 39 | helpFunction 40 | fi 41 | 42 | # Print helpFunction in case parameters are empty 43 | if [ "$app" ] && [ "$executable" ] 44 | then 45 | echo "Only one parameter should be set, but both are set." 46 | helpFunction 47 | fi 48 | 49 | echo "Current directory: $(pwd)." 50 | # Begin script in case all parameters are correct 51 | if [ "$app" ] 52 | then 53 | echo "Opening app $app with timeout $timeout seconds..." 54 | timeout $timeout sh -c "open -n $app -W || exit 1" --preserve-status --kill-after 1 55 | elif [ "$executable" ] 56 | then 57 | echo "Running script $executable with timeout $timeout seconds..." 58 | timeout $timeout sh -c "$executable || exit 1" --preserve-status --kill-after 1 59 | fi 60 | 61 | status=$? 62 | # Check if script and app were run 63 | if [ $status -eq 124 ] || [ $status -eq 137 ] || [ $status -eq 9 ] 64 | then 65 | echo "Success: timeout of $timeout seconds has been reached with code $status." 66 | if [ "$app" ]; then 67 | appname="$(basename -- $app)" 68 | echo "Closing app $appname..." 69 | osascript -e "quit app \"$appname\"" 70 | fi 71 | exit 0 72 | else 73 | if [ "$app" ] 74 | then 75 | echo "Failed => The .app has crashed." 76 | exit 1 # force failure 77 | elif [ "$executable" ] 78 | then 79 | echo "Failed => The script has crashed with code $status." 80 | exit $status # preserve exit status 81 | else 82 | exit 1 # force failure 83 | fi 84 | fi -------------------------------------------------------------------------------- /.github/workflows/debug_linux.yml: -------------------------------------------------------------------------------- 1 | name: Debug Linux (ubuntu) 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'version' 8 | required: true 9 | default: '0.1.0' 10 | type: string 11 | timeout: 12 | description: 'timeout in seconds' 13 | required: true 14 | default: '30' 15 | type: string 16 | 17 | jobs: 18 | debug_job: 19 | name: Debug on Linux 20 | runs-on: ubuntu-latest 21 | timeout-minutes: 10 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v2 27 | with: 28 | python-version: '3.10' 29 | cache: 'pip' 30 | 31 | - run: | 32 | echo "version: ${{ github.event.inputs.version }}" 33 | 34 | - name: Upgrade pip 35 | run: | 36 | python -m pip install --upgrade pip 37 | 38 | - name: Install Dependencies 39 | run: | 40 | python -m pip install -r requirements.txt 41 | 42 | - name: Build 43 | run: | 44 | python build.py --version=${{ github.event.inputs.version }} --nodebug 45 | 46 | - name: Install and launch xvfb (GUI on Linux) 47 | if: runner.os == 'Linux' 48 | run: | 49 | sudo apt-get install -y xvfb libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 50 | sudo /usr/bin/Xvfb $DISPLAY -screen 0 1280x1024x24 & # start xvfb on (default) display :0 51 | 52 | - name: Rename, run and test 53 | if: runner.os == 'Linux' 54 | run: | 55 | ls 56 | echo "----------------------------------------" 57 | ls ./dist 58 | echo "----------------------------------------" 59 | # chmod +x ./scripts/run_unix.sh 60 | echo "renaming ..." 61 | mv ./dist/WatermarkRemover ./dist/WatermarkRemover-linux 62 | echo "----------------------------------------" 63 | ls 64 | echo "----------------------------------------" 65 | ls ./dist 66 | echo "----------------------------------------" 67 | echo "adding permission ..." 68 | chmod +x ./dist/WatermarkRemover-linux 69 | echo "running ./scripts/run_unix.sh ..." 70 | ./scripts/run_unix.sh -e "./dist/WatermarkRemover-linux" -t ${{ github.event.inputs.timeout }}; echo $? 71 | echo "done" 72 | env: 73 | DISPLAY: ":0" # set DISPLAY to :0 to run on (default) display 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | 132 | ### IDEA 133 | .idea/ 134 | cmake-build-*/ 135 | .idea_modules/ 136 | # File-based project format 137 | *.iws 138 | # IntelliJ 139 | out/ 140 | ### IDEA 141 | 142 | ### VS Code 143 | .vscode/ 144 | .history/ 145 | *.code-workspace 146 | ### VS Code 147 | 148 | ### MAC OS 149 | # General 150 | .DS_Store 151 | .AppleDouble 152 | .LSOverride 153 | 154 | # Icon must end with two \r 155 | Icon 156 | 157 | # Thumbnails 158 | ._* 159 | 160 | # Files that might appear in the root of a volume 161 | .DocumentRevisions-V100 162 | .fseventsd 163 | .Spotlight-V100 164 | .TemporaryItems 165 | .Trashes 166 | .VolumeIcon.icns 167 | .com.apple.timemachine.donotpresent 168 | 169 | # Directories potentially created on remote AFP share 170 | .AppleDB 171 | .AppleDesktop 172 | Network Trash Folder 173 | Temporary Items 174 | .apdisk 175 | ### MAC OS 176 | 177 | 178 | # Others 179 | *.tmp.* 180 | *.tmp 181 | /tmp 182 | *.exe 183 | vars.txt -------------------------------------------------------------------------------- /resources/theme/main.tcl: -------------------------------------------------------------------------------- 1 | # Copyright © 2021 rdbende 2 | 3 | source [file join [file dirname [info script]] theme light.tcl] 4 | source [file join [file dirname [info script]] theme dark.tcl] 5 | 6 | option add *tearOff 0 7 | 8 | proc set_theme {mode} { 9 | if {$mode == "dark"} { 10 | ttk::style theme use "azure-dark" 11 | 12 | array set colors { 13 | -fg "#ffffff" 14 | -bg "#333333" 15 | -disabledfg "#ffffff" 16 | -disabledbg "#737373" 17 | -selectfg "#ffffff" 18 | -selectbg "#007fff" 19 | } 20 | 21 | ttk::style configure . \ 22 | -background $colors(-bg) \ 23 | -foreground $colors(-fg) \ 24 | -troughcolor $colors(-bg) \ 25 | -focuscolor $colors(-selectbg) \ 26 | -selectbackground $colors(-selectbg) \ 27 | -selectforeground $colors(-selectfg) \ 28 | -insertcolor $colors(-fg) \ 29 | -insertwidth 1 \ 30 | -fieldbackground $colors(-selectbg) \ 31 | -font {"Segoe Ui" 10} \ 32 | -borderwidth 1 \ 33 | -relief flat 34 | 35 | tk_setPalette background [ttk::style lookup . -background] \ 36 | foreground [ttk::style lookup . -foreground] \ 37 | highlightColor [ttk::style lookup . -focuscolor] \ 38 | selectBackground [ttk::style lookup . -selectbackground] \ 39 | selectForeground [ttk::style lookup . -selectforeground] \ 40 | activeBackground [ttk::style lookup . -selectbackground] \ 41 | activeForeground [ttk::style lookup . -selectforeground] 42 | 43 | ttk::style map . -foreground [list disabled $colors(-disabledfg)] 44 | 45 | option add *font [ttk::style lookup . -font] 46 | option add *Menu.selectcolor $colors(-fg) 47 | 48 | } elseif {$mode == "light"} { 49 | ttk::style theme use "azure-light" 50 | 51 | array set colors { 52 | -fg "#000000" 53 | -bg "#ffffff" 54 | -disabledfg "#737373" 55 | -disabledbg "#ffffff" 56 | -selectfg "#ffffff" 57 | -selectbg "#007fff" 58 | } 59 | 60 | ttk::style configure . \ 61 | -background $colors(-bg) \ 62 | -foreground $colors(-fg) \ 63 | -troughcolor $colors(-bg) \ 64 | -focuscolor $colors(-selectbg) \ 65 | -selectbackground $colors(-selectbg) \ 66 | -selectforeground $colors(-selectfg) \ 67 | -insertcolor $colors(-fg) \ 68 | -insertwidth 1 \ 69 | -fieldbackground $colors(-selectbg) \ 70 | -font {"Segoe Ui" 10} \ 71 | -borderwidth 1 \ 72 | -relief flat 73 | 74 | tk_setPalette background [ttk::style lookup . -background] \ 75 | foreground [ttk::style lookup . -foreground] \ 76 | highlightColor [ttk::style lookup . -focuscolor] \ 77 | selectBackground [ttk::style lookup . -selectbackground] \ 78 | selectForeground [ttk::style lookup . -selectforeground] \ 79 | activeBackground [ttk::style lookup . -selectbackground] \ 80 | activeForeground [ttk::style lookup . -selectforeground] 81 | 82 | ttk::style map . -foreground [list disabled $colors(-disabledfg)] 83 | 84 | option add *font [ttk::style lookup . -font] 85 | option add *Menu.selectcolor $colors(-fg) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome! 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | # Environment setup 8 | 9 | 1. Install python 3.7 or later 10 | 2. Install requirements 11 | ```shell 12 | python -m pip install --upgrade pip wheel setuptools 13 | python -m pip install -r requirements.txt 14 | ``` 15 | 16 | 3. Install os dependencies of [pikepdf](https://pikepdf.readthedocs.io/en/latest/) 17 | , [Pillow](https://pillow.readthedocs.io/en/stable/), etc. (check `requirements.txt`) 18 | 19 | -------------------------------------------------------------------------------- 20 | 21 | # Build the application 22 | 23 | Generate builds: 24 | 25 | 1. Create and configure env variables (`.env` file) 26 | ```shell 27 | cp .env.example .env 28 | ``` 29 | `.env` is used during development and at build-time (but not included in builds, we generate and include `vars.txt`). 30 | 31 | 2. Load env variables 32 | ```shell 33 | source .env 34 | ECHO $VERSION 35 | ECHO $DEBUG 36 | ``` 37 | 38 | 3. using `build.py` script 39 | ```shell 40 | source .env; python3 build.py --debug=$DEBUG --version=$VERSION --genenv --sentry_dsn=$SENTRY_DSN 41 | ``` 42 | 43 | 4. or Using [pyinstaller](https://pyinstaller.readthedocs.io/en/stable/index.html) 44 | ```shell 45 | pyinstaller --onefile --windowed --icon=.ico --add-data ";" 46 | pyinstaller --onefile --windowed WaterMarkRemover.py 47 | ``` 48 | 49 | You will find a directory named `dist` with the executable file `WaterMarkRemover.exe`, `WaterMarkRemover` 50 | or `WaterMarkRemover.app`. 51 | 52 | to run the app on macOS: 53 | 54 | ```shell 55 | open -n WatermarkRemover.app 56 | ``` 57 | 58 | ## Build and Test the application locally 59 | 60 | ```shell 61 | ./test_build.local.sh 35 62 | ``` 63 | 64 | ## Build installer (Windows only) 65 | 66 | Using nsis: 67 | 68 | ``` 69 | makensis config.nsi 70 | ``` 71 | 72 | ## GitHub Actions 73 | 74 | Optionally, we can create an `.env` file in the workflow with the following variables: 75 | 76 | - VERSION: the version of the application 77 | - DEBUG: the debug mode of the application 78 | 79 | -------------------------------------------------------------------------------- 80 | 81 | # Release 82 | 83 | To release a new version of the project you need to: 84 | 85 | - Change version in the env file `.env`. 86 | - Commit the changes and push. 87 | - Create a new tag locally. 88 | - Push the new tag to the remote. 89 | 90 | ## Create release 91 | 92 | You can create a release using the following command: 93 | 94 | ```shell 95 | source .env; git push; git tag $VERSION -a -m "v$VERSION"; git push origin $VERSION 96 | # or 97 | git push; VERSION="0.1.0"; MESSAGE="v$VERSION"; git tag $VERSION -a -m $MESSAGE; git push origin $VERSION 98 | ``` 99 | 100 | ## Cancel release 101 | 102 | - Remove the tag from local and remote 103 | ```shell 104 | source .env; git tag --delete $VERSION; git push --delete origin $VERSION 105 | # or 106 | VERSION="0.1.0"; git tag --delete $VERSION; git push --delete origin $VERSION 107 | ``` 108 | 109 | - Delete release from [GitHub](https://github.com/naskio/watermark-remover/releases/). 110 | 111 | -------------------------------------------------------------------------------- 112 | 113 | # Acknowledgments 114 | 115 | - [pikepdf](https://pikepdf.readthedocs.io/en/latest/topics/page.html) 116 | - [PyPDF2](https://stackoverflow.com/questions/41769120/search-and-replace-for-text-within-a-pdf-in-python) 117 | -------------------------------------------------------------------------------- /installer.nsi: -------------------------------------------------------------------------------- 1 | # NSIS Script File (.nsi) is config to build the .exe installer (Windows only) 2 | ; install files, creates desktop shortcuts and an entry in the start menu, adding Uninstall.exe. 3 | 4 | ;-------------------------------- 5 | ;Include Modern UI 6 | 7 | !include "MUI2.nsh" 8 | 9 | ;-------------------------------- 10 | ;General 11 | 12 | ;Name and file 13 | Name "WatermarkRemover" 14 | OutFile "WatermarkRemover-installer.exe" 15 | Unicode True 16 | !define NAME "WatermarkRemover" 17 | 18 | ;Default installation folder 19 | InstallDir "$PROGRAMFILES\${NAME}" 20 | 21 | ;Get installation folder from registry if available 22 | InstallDirRegKey HKCU "Software\${NAME}" "" 23 | 24 | ;Request root privileges 25 | ;RequestExecutionLevel admin 26 | 27 | ;Others 28 | ;ShowInstDetails "nevershow" 29 | ;ShowUninstDetails "nevershow" 30 | SetCompressor "lzma" 31 | # zlib|bzip2|lzma 32 | 33 | ;-------------------------------- 34 | ;Interface Settings 35 | 36 | ; icon 37 | !define MUI_ICON "resources\icon.ico" 38 | !define MUI_UNICON "resources\icon.ico" 39 | 40 | ;-------------------------------- 41 | ;Pages 42 | 43 | !define MUI_PAGE_CUSTOMFUNCTION_SHOW MyWelcomeShowCallback 44 | !insertmacro MUI_PAGE_WELCOME 45 | !insertmacro MUI_PAGE_LICENSE "LICENSE" 46 | !insertmacro MUI_PAGE_DIRECTORY 47 | !insertmacro MUI_PAGE_INSTFILES 48 | !insertmacro MUI_UNPAGE_CONFIRM 49 | !insertmacro MUI_UNPAGE_INSTFILES 50 | 51 | ;-------------------------------- 52 | ;Languages 53 | 54 | !insertmacro MUI_LANGUAGE "English" 55 | 56 | ;-------------------------------- 57 | ;Installer Sections 58 | 59 | Section "Main Section" SecMain 60 | 61 | SetOutPath "$INSTDIR" 62 | 63 | # File "LICENSE" 64 | # File "dist\${NAME}.exe" 65 | # File /r "resources\*" 66 | 67 | ;Adding files 68 | File "LICENSE" 69 | File /r "dist\${NAME}\*" 70 | 71 | 72 | ;create desktop shortcut 73 | CreateShortCut "$DESKTOP\${NAME}.lnk" "$INSTDIR\${NAME}.exe" "" 74 | 75 | ;create start-menu items 76 | CreateDirectory "$SMPROGRAMS\${NAME}" 77 | CreateShortCut "$SMPROGRAMS\${NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0 78 | CreateShortCut "$SMPROGRAMS\${NAME}\${NAME}.lnk" "$INSTDIR\${NAME}.exe" "" "$INSTDIR\${NAME}.exe" 0 79 | 80 | ;write uninstall information to the registry 81 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${NAME}" "DisplayName" "${NAME} (remove only)" 82 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${NAME}" "UninstallString" "$INSTDIR\Uninstall.exe" 83 | 84 | ;Store installation folder 85 | WriteRegStr HKCU "Software\${NAME}" "" $INSTDIR 86 | 87 | ;Create uninstaller 88 | WriteUninstaller "$INSTDIR\Uninstall.exe" 89 | 90 | SectionEnd 91 | 92 | ;-------------------------------- 93 | ;Uninstaller Section 94 | 95 | Section "Uninstall" 96 | 97 | ;ADD YOUR OWN FILES HERE... 98 | ;SetOutPath "$INSTDIR" 99 | 100 | ;Delete Files 101 | Delete "$INSTDIR\Uninstall.exe" 102 | RMDir /r "$INSTDIR\*" 103 | 104 | ;Remove the installation directory 105 | RMDir "$INSTDIR" 106 | 107 | ;Delete Start Menu Shortcuts 108 | Delete "$DESKTOP\${NAME}.lnk" 109 | Delete "$SMPROGRAMS\${NAME}\*" 110 | RmDir "$SMPROGRAMS\${NAME}" 111 | 112 | ;Delete Uninstaller And Uninstall Registry Entries 113 | DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\${NAME}" 114 | DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${NAME}" 115 | 116 | DeleteRegKey /ifempty HKCU "Software\${NAME}" 117 | 118 | SectionEnd 119 | 120 | 121 | ;-------------------------------- 122 | ; Custom functions 123 | 124 | Function MyWelcomeShowCallback 125 | ${NSD_CreateLabel} 120u 150u 50% 12u "Developed by: GitHub@naskio" 126 | Pop $0 127 | SetCtlColors $0 "" "${MUI_BGCOLOR}" 128 | FunctionEnd 129 | 130 | ;-------------------------------- 131 | ;MessageBox Section 132 | 133 | ;Function that calls a messagebox when installation finished correctly 134 | 135 | #Function .onInstSuccess 136 | # MessageBox MB_OK "You have successfully installed ${NAME}. Use the desktop icon to start the program." 137 | #FunctionEnd 138 | 139 | #Function un.onUninstSuccess 140 | # MessageBox MB_OK "You have successfully uninstalled ${NAME}." 141 | #FunctionEnd -------------------------------------------------------------------------------- /.github/workflows/build_and_release.yml: -------------------------------------------------------------------------------- 1 | ########## ENV VARS ########## 2 | # - SENTRY_DSN 3 | ########## ENV VARS ########## 4 | 5 | 6 | name: Build, Test and Release 7 | 8 | on: 9 | push: 10 | tags: 11 | - "*.*.*" 12 | 13 | jobs: 14 | build_and_test: 15 | name: Build and Test 16 | runs-on: ${{ matrix.os }} 17 | timeout-minutes: 20 18 | strategy: 19 | matrix: 20 | os: [ ubuntu-latest, windows-latest, macos-latest ] 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - name: Get the version # get the version from the tag 25 | id: get_version 26 | run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} 27 | 28 | - name: Set the version to env var 29 | run: | 30 | echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 31 | shell: bash 32 | 33 | - name: show version 34 | run: | 35 | echo RELEASE_VERSION: $RELEASE_VERSION 36 | echo RELEASE_VERSION_env: ${{ env.RELEASE_VERSION }} 37 | echo RELEASE_VERSION_set_output: ${{ steps.get_version.outputs.VERSION }} 38 | shell: bash 39 | 40 | - name: Set up Python ${{ matrix.python-version }} 41 | uses: actions/setup-python@v2 42 | with: 43 | python-version: '3.10' # python version used for build 44 | cache: 'pip' 45 | 46 | - name: Upgrade pip 47 | run: | 48 | python -m pip install --upgrade pip 49 | 50 | - name: Install Dependencies 51 | run: | 52 | python -m pip install -r requirements.txt 53 | 54 | - name: Build app 55 | run: | 56 | echo "Building version $RELEASE_VERSION" 57 | python build.py --nodebug --genenv --sentry_dsn=${{ secrets.SENTRY_DSN }} --version=$RELEASE_VERSION 58 | shell: bash 59 | 60 | - name: Build app (dirmode) 61 | run: | 62 | echo "Building version $RELEASE_VERSION" 63 | python build.py --nodebug --dirmode --genenv --sentry_dsn=${{ secrets.SENTRY_DSN }} --version=$RELEASE_VERSION 64 | if: runner.os == 'Windows' 65 | shell: bash 66 | 67 | - name: Linux - install and launch xvfb (GUI on Linux) 68 | if: runner.os == 'Linux' 69 | run: | 70 | sudo apt-get install -y xvfb libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 71 | sudo /usr/bin/Xvfb $DISPLAY -screen 0 1280x1024x24 & # start xvfb on (default) display :0 72 | 73 | - name: Linux - rename, repair and test run 74 | if: runner.os == 'Linux' 75 | run: | 76 | ls 77 | echo "..." 78 | ls ./dist 79 | echo "renaming ..." 80 | mv ./dist/WatermarkRemover ./dist/WatermarkRemover-linux 81 | ls 82 | echo "..." 83 | ls ./dist 84 | echo "adding permission ..." 85 | chmod +x ./dist/WatermarkRemover-linux 86 | echo "running ./scripts/run_unix.sh ..." 87 | ./scripts/run_unix.sh -e "./dist/WatermarkRemover-linux" -t 30; echo $? 88 | echo "done" 89 | env: 90 | DISPLAY: ":0" # set DISPLAY to :0 to run on (default) display 91 | 92 | - name: macOS - Install coreutils (for timeout cmd) 93 | if: runner.os == 'macOS' 94 | run: brew install coreutils 95 | 96 | - name: macOS - Rename, repair and test run (CLI) 97 | if: runner.os == 'macOS' 98 | run: | 99 | ls 100 | echo "..." 101 | ls ./dist 102 | echo "renaming ..." 103 | mv ./dist/WatermarkRemover ./dist/WatermarkRemover-macos 104 | ls 105 | echo "..." 106 | ls ./dist 107 | echo "adding permission ..." 108 | chmod +x ./dist/WatermarkRemover-macos 109 | echo "running ./scripts/run_unix.sh ..." 110 | ./scripts/run_unix.sh -e "./dist/WatermarkRemover-macos" -t 30; echo $? 111 | echo "done" 112 | 113 | - name: macOS - Rename, repair and test run (.app bundle) 114 | if: runner.os == 'macOS' 115 | run: | 116 | ls 117 | echo "..." 118 | ls ./dist 119 | echo "adding permission ..." 120 | chmod +x ./dist/WatermarkRemover.app/Contents/MacOS/WatermarkRemover 121 | echo "running ./scripts/run_unix.sh ..." 122 | ./scripts/run_unix.sh -a "./dist/WatermarkRemover.app/" -t 30; echo $? 123 | echo "done" 124 | 125 | - name: Windows - Rename, repair and test run (.exe) 126 | if: runner.os == 'Windows' 127 | run: | 128 | DIR 129 | DIR .\dist 130 | ECHO running .\scripts\run_win.bat ... 131 | .\scripts\run_win.bat .\dist\WatermarkRemover.exe 30 132 | ECHO %ERRORLEVEL% 133 | ECHO done 134 | shell: cmd 135 | 136 | - name: Create installer (using NSIS) 137 | uses: joncloud/makensis-action@v3.3 138 | with: 139 | script-file: installer.nsi 140 | if: runner.os == 'Windows' 141 | 142 | - name: Move installer to dist folder 143 | run: mv WatermarkRemover-installer.exe dist/WatermarkRemover-installer.exe 144 | if: runner.os == 'Windows' 145 | 146 | - name: Upload Artifacts 147 | uses: actions/upload-artifact@v2 148 | with: 149 | name: ${{ matrix.os }} 150 | path: dist/ 151 | 152 | 153 | release: 154 | name: Release 155 | needs: [ build_and_test ] 156 | runs-on: ubuntu-latest 157 | steps: 158 | - uses: actions/checkout@v2 # get CHANGELOG.md 159 | 160 | - uses: actions/download-artifact@v2 161 | with: 162 | name: ubuntu-latest 163 | path: ubuntu-latest 164 | 165 | - uses: actions/download-artifact@v2 166 | with: 167 | name: windows-latest 168 | path: windows-latest 169 | 170 | - uses: actions/download-artifact@v2 171 | with: 172 | name: macos-latest 173 | path: macos-latest 174 | 175 | - name: Repair (add permissions) 176 | run: | 177 | chmod +x ./ubuntu-latest/WatermarkRemover-linux 178 | chmod +x ./macos-latest/WatermarkRemover-macos 179 | ls ./macos-latest/WatermarkRemover.app/Contents/MacOS/ 180 | chmod +x ./macos-latest/WatermarkRemover.app/Contents/MacOS/WatermarkRemover 181 | 182 | - name: Install zip 183 | uses: montudor/action-zip@v1 184 | 185 | - name: Zip MAC OS app 186 | run: zip -qq -r WatermarkRemover-macos-app.zip WatermarkRemover.app 187 | working-directory: macos-latest/ 188 | 189 | - name: List the files 190 | run: ls -l && ls -l ubuntu-latest/ && ls -l windows-latest/ && ls -l macos-latest/ 191 | 192 | - name: create Release 193 | uses: softprops/action-gh-release@v1 194 | env: 195 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 196 | with: 197 | body_path: CHANGELOG.md 198 | files: | 199 | ubuntu-latest/* 200 | windows-latest/* 201 | macos-latest/* 202 | LICENSE 203 | -------------------------------------------------------------------------------- /WatermarkRemover.py: -------------------------------------------------------------------------------- 1 | from tkinter import Tk, StringVar, END, RIGHT, X, LEFT, Text, DISABLED, NORMAL, filedialog 2 | from tkinter.ttk import Button, Radiobutton, Label 3 | from pathlib import Path 4 | from main import main, generate_output_path, MethodChoice, w_sentry 5 | from typing import Optional 6 | import darkdetect 7 | from environs import Env 8 | from marshmallow.validate import Regexp 9 | import sys 10 | from logging import getLogger 11 | import sentry_sdk 12 | from sentry_sdk import capture_exception, capture_message 13 | 14 | logger = getLogger(__name__) 15 | 16 | BASE_DIR = Path(__file__).parent 17 | DEFAULT_DIR = Path.home() / 'Desktop' 18 | ENV_FILE = BASE_DIR / 'vars.txt' # for builds 19 | if not ENV_FILE.exists(): # for development mode (running python script directly) 20 | ENV_FILE = BASE_DIR / '.env' 21 | THEME_DIR = BASE_DIR / 'resources' / 'theme' 22 | 23 | # VARS 24 | env = Env() 25 | env.read_env(str(ENV_FILE)) # read .env file, if it exists 26 | DEBUG = env.bool("DEBUG", False) 27 | VERSION = env.str( 28 | "VERSION", 29 | validate=Regexp(r"^\d+\.\d+\.\d+$"), 30 | default="0.0.0", 31 | ) 32 | SENTRY_DSN = env.str("SENTRY_DSN", default=None) 33 | TITLE = f"Watermark Remover - by [www.NasK.io]" 34 | RAISE_THEME_EXCEPTION = True 35 | if VERSION != "0.0.0": 36 | TITLE = f"Watermark Remover v{VERSION} - by [www.NasK.io]" 37 | 38 | # Widgets 39 | LOG_TXT_AREA_WIDGET = None 40 | METHOD_CHOICE_STRING_VAR = None 41 | # OTHERS 42 | SENTRY_STATUS = False 43 | 44 | 45 | def log_clear(): 46 | LOG_TXT_AREA_WIDGET.config(state=NORMAL) 47 | LOG_TXT_AREA_WIDGET.delete(1.0, END) 48 | LOG_TXT_AREA_WIDGET.config(state=DISABLED) 49 | 50 | 51 | def log_write(text): 52 | LOG_TXT_AREA_WIDGET.config(state=NORMAL) 53 | LOG_TXT_AREA_WIDGET.insert(END, text) 54 | LOG_TXT_AREA_WIDGET.insert(END, '\n') 55 | LOG_TXT_AREA_WIDGET.config(state=DISABLED) 56 | 57 | 58 | def open_files(): 59 | input_files = filedialog.askopenfilenames( 60 | initialdir=str(DEFAULT_DIR), 61 | title="Open Word or PDF file", 62 | filetypes=[("Word Documents", ".docx"), ("PDF Files", ".pdf"), ("Image Files", ".png"), 63 | ("Image Files", ".jpg"), ("Image Files", ".jpeg"), ], 64 | ) 65 | log_clear() 66 | log_write(f"Sentry status: {'Enabled' if SENTRY_STATUS else 'Disabled'}") 67 | if input_files: # file selected 68 | output_dir = get_output_dir(DEFAULT_DIR) 69 | if output_dir: 70 | output_dir_path = Path(output_dir) 71 | if not output_dir_path.exists(): 72 | output_dir_path.mkdir(parents=True) 73 | for input_file in input_files: 74 | input_path = Path(input_file) 75 | if not input_path.exists(): 76 | log_write(f"File {input_path} does not exist") 77 | elif str(input_path.suffix).lower() not in ('.docx', '.pdf', '.png', '.jpg', '.jpeg'): 78 | log_write(f"File {input_path} is not a Word, PDF or Image file") 79 | else: # processing 80 | log_write(f'Input: {input_file}') 81 | try: 82 | output_file = output_dir_path / (input_path.stem + f"_generated{input_path.suffix}") 83 | log_write(f'Output: {output_file}') 84 | log_write("Processing...") 85 | p_method_choice = MethodChoice.from_str(METHOD_CHOICE_STRING_VAR.get()) 86 | log_write(f"method: {p_method_choice.value}") 87 | output_file = main(input_file, str(output_file), p_method_choice) 88 | log_write(f'File has been saved successfully to: {output_file}') 89 | except Exception as e: 90 | log_write(str(e)) 91 | else: 92 | log_write(f'Operation cancelled: you should select a folder') 93 | 94 | 95 | def get_output_file(input_path: Path) -> Optional[str]: 96 | initial_path = generate_output_path(input_path) 97 | default_extension = str(input_path.suffix).lower() 98 | filetypes = [] 99 | if default_extension == '.pdf': 100 | filetypes = [("PDF Files", ".pdf")] 101 | elif default_extension == '.docx': 102 | filetypes = [("Word Documents", ".docx")] 103 | elif default_extension == '.png': 104 | filetypes = [("Image Files", ".png")] 105 | elif default_extension == '.jpg': 106 | filetypes = [("Image Files", ".jpg")] 107 | elif default_extension == '.jpeg': 108 | filetypes = [("Image Files", ".jpeg")] 109 | tf = filedialog.asksaveasfile( 110 | mode='w', 111 | title="Save file to", 112 | initialdir=str(input_path.parent), 113 | initialfile=f"{initial_path.stem}{input_path.suffix}", 114 | defaultextension=default_extension, 115 | filetypes=filetypes, 116 | ) 117 | if tf is None: 118 | return None 119 | else: 120 | name = tf.name 121 | tf.close() 122 | # print(name) # full path 123 | return name 124 | 125 | 126 | def get_output_dir(base_dir: Path) -> Optional[str]: 127 | _dir = filedialog.askdirectory( 128 | title="Select output folder", 129 | initialdir=str(base_dir), 130 | mustexist=False, 131 | ) 132 | if _dir is None: 133 | return None 134 | else: 135 | # print(_dir) 136 | return str(_dir) 137 | 138 | 139 | def main_ui(): 140 | ws = Tk() 141 | # setting theme 142 | try: 143 | ws.tk.call("source", THEME_DIR / "main.tcl") 144 | if darkdetect and darkdetect.isDark(): 145 | ws.tk.call("set_theme", "dark") 146 | else: 147 | ws.tk.call("set_theme", "light") 148 | except Exception as e: 149 | logger.error("Theme not loaded", exc_info=e) 150 | w_sentry(capture_message, "Theme not loaded") 151 | w_sentry(capture_exception, e) 152 | if RAISE_THEME_EXCEPTION: 153 | raise e 154 | 155 | ws.title(TITLE) 156 | # ws.resizable(False, False) 157 | # center window 158 | window_height = 664 159 | window_width = 720 160 | screen_width = ws.winfo_screenwidth() 161 | screen_height = ws.winfo_screenheight() 162 | x_coordinate = int((screen_width / 2) - (window_width / 2)) 163 | y_coordinate = int((screen_height / 2) - (window_height / 2)) 164 | ws.geometry(f"{window_width}x{window_height}+{x_coordinate}+{y_coordinate}") 165 | # ws.geometry("694x600") 166 | # ws['bg'] = 'gray' 167 | global LOG_TXT_AREA_WIDGET 168 | LOG_TXT_AREA_WIDGET = Text( 169 | ws, width=60, height=30, state=DISABLED, 170 | # bg='white', fg='black' 171 | ) 172 | LOG_TXT_AREA_WIDGET.pack(pady=30) 173 | 174 | Button( 175 | ws, 176 | text="Choose Files", 177 | command=open_files, 178 | # bg='gray', 179 | # fg='black' 180 | ).pack(side=RIGHT, expand=True, fill=X, padx=30) 181 | 182 | # display title 183 | Label(ws, text="Method").pack(side=LEFT, expand=True, fill=X, padx=30) 184 | global METHOD_CHOICE_STRING_VAR 185 | METHOD_CHOICE_STRING_VAR = StringVar(value=MethodChoice.geos.name) 186 | for method in MethodChoice: 187 | Radiobutton( 188 | ws, 189 | text=method.value, 190 | variable=METHOD_CHOICE_STRING_VAR, 191 | value=method.name, 192 | # bg='gray', 193 | # fg='black' 194 | ).pack(side=LEFT, expand=True, fill=X, padx=5) 195 | 196 | ws.mainloop() 197 | 198 | 199 | try: 200 | SENTRY_STATUS = w_sentry(sentry_sdk.init, 201 | SENTRY_DSN, 202 | traces_sample_rate=0.10, ) # capture x% of transactions for performance monitoring 203 | main_ui() 204 | except Exception as ee: 205 | logger.error("Unhandled exception", exc_info=ee) 206 | w_sentry(capture_message, ee) 207 | sys.exit(1) 208 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Union, Callable 2 | from PIL import Image 3 | from pathlib import Path 4 | import zlib 5 | import numpy 6 | from pikepdf import Pdf, PdfImage, Name, parse_content_stream, unparse_content_stream, ContentStreamInstruction 7 | from decimal import Decimal 8 | import cv2 9 | import zipfile 10 | import io 11 | import numpy as np 12 | # from quality import improve_text_in_image 13 | import fire 14 | from enum import Enum 15 | from logging import getLogger 16 | from sentry_sdk import capture_message 17 | 18 | logger = getLogger(__name__) 19 | 20 | 21 | def w_sentry(fn: Callable, *args, **kwargs) -> bool: 22 | try: 23 | fn(*args, **kwargs) 24 | return True 25 | except Exception as e: 26 | logger.info("Sentry setup issue", exc_info=e) 27 | return False 28 | 29 | 30 | class MethodChoice(Enum): 31 | geos = "GEOS" 32 | colors_replacement = "Replace colors" 33 | openCV2 = "OpenCV2" 34 | 35 | @staticmethod 36 | def from_str(label): 37 | if label in ('geos',): 38 | return MethodChoice.geos 39 | elif label in ('colors_replacement',): 40 | return MethodChoice.colors_replacement 41 | elif label in ('openCV2',): 42 | return MethodChoice.openCV2 43 | else: 44 | raise NotImplementedError 45 | 46 | 47 | def hex_to_rbg(hex_color: str) -> tuple: 48 | """ 49 | Convert hex color to rgb 50 | example: #FFFFFF to (255, 255, 255) 51 | :param hex_color: 52 | :return: 53 | """ 54 | r = int(hex_color[1:3], 16) 55 | g = int(hex_color[3:5], 16) 56 | b = int(hex_color[5:7], 16) 57 | return r, g, b 58 | 59 | 60 | def rgb_to_hex(rgb_color: tuple) -> str: 61 | """ 62 | Convert rgb color to hex 63 | example: (255, 255, 255) to #FFFFFF 64 | :param rgb_color: 65 | :return: 66 | """ 67 | r, g, b = rgb_color 68 | return '#%02x%02x%02x' % (r, g, b) 69 | 70 | 71 | def hex_to_rgba(hex_color: str, alpha: int = 255): 72 | """ 73 | Convert hex color to rgba 74 | example: #FFFFFF to (255, 255, 255, 255) 75 | :param hex_color: 76 | :param alpha: 77 | :return: 78 | """ 79 | r, g, b = hex_to_rbg(hex_color) 80 | return r, g, b, alpha 81 | 82 | 83 | def rgba_to_hex(rgba_color: tuple): 84 | """ 85 | Convert rgba color to hex 86 | example: (255, 255, 255, 128) to #FFFFFF 87 | :param rgba_color: 88 | :return: 89 | """ 90 | r, g, b, a = rgba_color 91 | return '#%02x%02x%02x' % (r, g, b) 92 | 93 | 94 | def replace_images_in_zip(in_zip: Path, out_zip: Path, replacements: Dict[str, io.BytesIO]) -> Path: 95 | """ 96 | Replace images in zip 97 | :param in_zip: 98 | :param out_zip: 99 | :param replacements: 100 | :return: 101 | """ 102 | 103 | with zipfile.ZipFile(in_zip, 'r') as zin: 104 | with zipfile.ZipFile(out_zip, 'w') as zout: 105 | zout.comment = zin.comment # preserve the comment 106 | for item in zin.infolist(): 107 | # copy all files except the images to replace 108 | if item.filename not in replacements.keys(): 109 | with zout.open(item.filename, 'w') as f: 110 | f.write(zin.read(item.filename)) 111 | else: # replace the image 112 | with zout.open(item.filename, 'w') as f: 113 | f.write(replacements[item.filename].getvalue()) 114 | return out_zip 115 | 116 | 117 | def remove_watermark_from_cv_image(img: numpy.ndarray) -> numpy.ndarray: 118 | """ 119 | Remove watermark from open cv image 120 | :param img: 121 | :return: image without watermark 122 | """ 123 | # convert image to hsv colorspace 124 | hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) 125 | h, s, v = cv2.split(hsv) 126 | # threshold saturation image 127 | thresh1 = cv2.threshold(s, 92, 255, cv2.THRESH_BINARY)[1] 128 | # threshold value image and invert 129 | thresh2 = cv2.threshold(v, 128, 255, cv2.THRESH_BINARY)[1] 130 | thresh2 = 255 - thresh2 131 | # combine the two threshold images as a mask 132 | mask = cv2.add(thresh1, thresh2) 133 | # use mask to remove lines in background of input 134 | result = img.copy() 135 | result[mask == 0] = (255, 255, 255) 136 | return result 137 | 138 | 139 | def replace_colors_in_image(image: Image, replacements: Dict[str, str]) -> Image: 140 | """ 141 | Replace colors in image with new colors from replacements {to_replace_hex: new_hex} 142 | example = { 143 | '#f0f0f0': '#FFFFFF', 144 | '#c0c0c0': '#FFFFFF', 145 | } 146 | :param image: 147 | :param replacements: 148 | :return: 149 | """ 150 | image = image.convert('RGBA') 151 | data = np.array(image) # "data" is a height x width x 4 numpy array 152 | red, green, blue, alpha = data.T # Temporarily unpack the bands for readability 153 | for old_c, new_c in replacements.items(): 154 | old_color = hex_to_rbg(old_c) 155 | new_color = hex_to_rbg(new_c) 156 | white_areas = (red == old_color[0]) & (blue == old_color[1]) & (green == old_color[2]) 157 | data[..., :-1][white_areas.T] = new_color 158 | return Image.fromarray(data) 159 | 160 | 161 | def remove_watermark_from_pil_image(image: Image, method_choice: MethodChoice, 162 | replacements: Dict[str, str] = None) -> Image: 163 | """ 164 | Remove watermark from pil image 165 | :param image: 166 | :param method_choice: 167 | :param replacements: 168 | :return: 169 | """ 170 | if method_choice == MethodChoice.openCV2: 171 | # noinspection PyTypeChecker 172 | opencv_image = cv2.cvtColor(numpy.array(image), cv2.COLOR_RGB2BGR) 173 | opencv_image = remove_watermark_from_cv_image(opencv_image) 174 | image = Image.fromarray(cv2.cvtColor(opencv_image, cv2.COLOR_BGR2RGB)) 175 | return image 176 | else: 177 | return replace_colors_in_image(image, replacements or { 178 | '#f0f0f0': '#FFFFFF', 179 | '#c0c0c0': '#FFFFFF', 180 | '#b4b4fe': '#FFFFFF', 181 | }) 182 | 183 | 184 | def generate_output_path(input_path: Path) -> Path: 185 | """ 186 | Generate output_path from input_path 187 | :param input_path: 188 | :return: 189 | """ 190 | return input_path.parent / (input_path.stem + f"_generated{input_path.suffix}") 191 | 192 | 193 | def remove_tj_maj(instructions: List[ContentStreamInstruction], tj_start_with: Union[List[bytes], str]) -> \ 194 | List[ContentStreamInstruction]: 195 | """Remove all "TJ" operators if it contains tj_start_with from PDF.""" 196 | dest = tj_start_with 197 | if isinstance(tj_start_with, str): 198 | dest = list(map(lambda c: bytes(c, 'utf-8'), tj_start_with)) 199 | for i in range(len(instructions)): 200 | instruction = instructions[i] 201 | op = instruction.operator.__str__() 202 | if op == 'TJ': 203 | source1 = [] 204 | source2 = [] 205 | for j in range(len(instruction.operands[0])): 206 | if type(instruction.operands[0][j]) not in [int, float, Decimal]: 207 | operand_str = instruction.operands[0][j].__str__() 208 | source2.append(bytes(operand_str, 'utf-8')) 209 | for k in range(len(operand_str)): 210 | source1.append(bytes(operand_str[k], 'utf-8')) 211 | for j in range(len(dest)): 212 | if dest[j] != source1[j]: 213 | break 214 | if j == len(dest) - 1: 215 | instructions[i] = None 216 | for j in range(len(dest)): 217 | if dest[j] != source2[j]: 218 | break 219 | if j == len(dest) - 1: 220 | instructions[i] = None 221 | return [x for x in instructions if x is not None] 222 | 223 | 224 | def remove_tjs_min(instructions: List[ContentStreamInstruction], text: Union[List[bytes], str]) -> \ 225 | List[ContentStreamInstruction]: 226 | """Remove all successive "Tj" if it combined equals to text from PDF.""" 227 | indexes = [] 228 | found = "" 229 | for i in range(len(instructions)): 230 | instruction = instructions[i] 231 | op = instruction.operator.__str__() 232 | if op == 'Tj': 233 | indexes.append(i) 234 | found += instruction.operands[0].__str__() 235 | else: 236 | if found: 237 | if not text.startswith(found): 238 | indexes = [] 239 | found = "" 240 | elif text == found: 241 | for index in indexes: 242 | instructions[index] = None 243 | indexes = [] 244 | found = "" 245 | else: 246 | indexes = [] 247 | found = "" 248 | return [x for x in instructions if x is not None] 249 | 250 | 251 | def remove_by_reversed_orders(instructions: List[ContentStreamInstruction], orders: Dict[str, List[int]]) -> \ 252 | List[ContentStreamInstruction]: 253 | """Remove all instructions by reversed orders of operators from PDF.""" 254 | indexes = {} 255 | for i in reversed(range(len(instructions))): 256 | instruction = instructions[i] 257 | op = instruction.operator.__str__() 258 | if indexes.get(op, 0) in orders.get(op, []): 259 | instructions[i] = None 260 | indexes[op] = indexes.get(op, 0) + 1 261 | return [x for x in instructions if x is not None] 262 | 263 | 264 | def remove_watermark_from_geos_pdf(input_file: Path, output_file: Path) -> str: 265 | """ 266 | Remove watermark from pdf (exported from any GEOS app) and save to output_file. 267 | :param input_file: 268 | :param output_file: 269 | :return: 270 | """ 271 | pdf = Pdf.open(input_file) 272 | for page_number, page in enumerate(pdf.pages): 273 | page_instructions = parse_content_stream(page) 274 | previous_len = len(page_instructions) 275 | # VERSION EVALUATION 276 | page_instructions = remove_tjs_min(page_instructions, "VERSION EVALUATION") 277 | # alt: VERSION EVALUATION 278 | new_len = len(page_instructions) 279 | if new_len == previous_len: 280 | page_instructions = remove_by_reversed_orders(page_instructions, { 281 | 'f': [0, ], 282 | }) 283 | message = f"{input_file} at page {page_number + 1} => we used 'f' operator to remove watermark." 284 | logger.warning(message) 285 | w_sentry(capture_message, message) 286 | 287 | # Trial - XXX 288 | page_instructions = remove_tj_maj(page_instructions, "Trial - ") 289 | page_instructions = remove_tj_maj(page_instructions, 290 | [b'\x007', b'\x00U', b'\x00L', b'\x00D', b'\x00O', b'\x00\x03', b'\x00\x10', 291 | b'\x00\x03']) 292 | 293 | # UnRegistered 294 | page_instructions = remove_tj_maj(page_instructions, "UnRegistered") 295 | 296 | # save page 297 | new_content_stream = unparse_content_stream(page_instructions) 298 | page.Contents = pdf.make_stream(new_content_stream) # override page contents 299 | pdf.save(output_file) 300 | return str(output_file) 301 | 302 | 303 | def remove_watermark_from_pdf(input_file: Path, output_file: Path, method_choice: MethodChoice = None) -> str: 304 | """ 305 | Remove watermark from pdf and save to output_file 306 | :param input_file: 307 | :param output_file: 308 | :param method_choice: 309 | :return: 310 | """ 311 | if method_choice == MethodChoice.geos: 312 | return remove_watermark_from_geos_pdf(input_file, output_file) 313 | pdf = Pdf.open(input_file) 314 | for page in pdf.pages: 315 | for image_key in page.images.keys(): 316 | raw_image = page.images[image_key] 317 | pdf_image = PdfImage(raw_image) 318 | pil_image = pdf_image.as_pil_image() 319 | pil_image = remove_watermark_from_pil_image(pil_image, method_choice) 320 | raw_image.write(zlib.compress(pil_image.tobytes()), filter=Name("/FlateDecode")) 321 | pdf.save(output_file) 322 | return str(output_file) 323 | 324 | 325 | def remove_watermark_from_docx(input_file: Path, output_file: Path, method_choice: MethodChoice = None) -> str: 326 | """ 327 | Remove watermark from docx and save to output_file 328 | :param input_file: 329 | :param output_file: 330 | :param method_choice: 331 | :return: 332 | """ 333 | z = zipfile.ZipFile(input_file) 334 | all_files = z.namelist() 335 | # get all files in word/media/ directory 336 | images = list(filter(lambda x: x.startswith('word/media/'), all_files)) 337 | replacements = {} 338 | for image in images: 339 | with z.open(image) as f: 340 | pil_image = Image.open(f) 341 | # pil_image = improve_text_in_image(pil_image) 342 | pil_image = remove_watermark_from_pil_image(pil_image, method_choice) 343 | image_file_tmp = io.BytesIO() 344 | pil_image.save(image_file_tmp, format="PNG") 345 | replacements[image] = image_file_tmp 346 | z.close() 347 | return str(replace_images_in_zip(input_file, output_file, replacements)) 348 | 349 | 350 | def remove_watermark_from_image(input_file: Path, output_file: Path, method_choice: MethodChoice) -> str: 351 | """ 352 | Remove watermark from image 353 | :param input_file: 354 | :param output_file: 355 | :param method_choice: 356 | :return: 357 | """ 358 | pil_image = Image.open(input_file) 359 | pil_image = remove_watermark_from_pil_image(pil_image, method_choice) 360 | pil_image.save(output_file) 361 | return str(output_file) 362 | 363 | 364 | def main(input_file: Union[str, Path], output_file: Union[str, Path] = None, method_choice: MethodChoice = None) -> str: 365 | """ 366 | Entry point 367 | :param input_file: 368 | :param output_file: 369 | :param method_choice: 370 | :return: 371 | """ 372 | input_path = Path(input_file) 373 | if not input_path or not input_path.exists(): 374 | raise FileNotFoundError(f"File {input_path} does not exist") 375 | # generate output path 376 | if not output_file: 377 | output_path = generate_output_path(input_path) 378 | else: 379 | output_path = Path(output_file) 380 | # remove output file if it exists 381 | if output_path.exists(): 382 | output_path.unlink() 383 | # remove watermark 384 | if str(input_path.suffix).lower() == ".pdf": 385 | return remove_watermark_from_pdf(input_path, output_path, method_choice) 386 | elif str(input_path.suffix).lower() == ".docx": 387 | return remove_watermark_from_docx(input_path, output_path, method_choice) 388 | elif str(input_path.suffix).lower() in [".png", ".jpg", ".jpeg"]: 389 | return remove_watermark_from_image(input_path, output_path, method_choice) 390 | else: 391 | raise Exception(f"Unsupported file type: {input_path.suffix}") 392 | 393 | 394 | def mmain(input_files: List[Union[str, Path]], output_dir: Union[str, Path] = None, 395 | method_choice: MethodChoice = None) -> List[str]: 396 | """ 397 | Entry point 398 | :param input_files: 399 | :param output_dir: 400 | :param method_choice: 401 | :return: 402 | """ 403 | output_files = [] 404 | for input_file in input_files: 405 | input_path = Path(input_file) 406 | if not output_dir: 407 | output_dir = input_path.parent 408 | output_file = output_dir / (input_path.stem + f"_generated{input_path.suffix}") 409 | output_file = main(input_file, output_file, method_choice) 410 | output_files.append(output_file) 411 | return output_files 412 | 413 | 414 | if __name__ == "__main__": 415 | fire.Fire(main) 416 | -------------------------------------------------------------------------------- /resources/theme/theme/dark.tcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 rdbende 2 | 3 | # The Azure theme is a beautiful modern ttk theme inspired by Microsoft's fluent design. 4 | 5 | package require Tk 8.6 6 | 7 | namespace eval ttk::theme::azure-dark { 8 | variable version 2.0 9 | package provide ttk::theme::azure-dark $version 10 | 11 | ttk::style theme create azure-dark -parent clam -settings { 12 | proc load_images {imgdir} { 13 | variable I 14 | foreach file [glob -directory $imgdir *.png] { 15 | set img [file tail [file rootname $file]] 16 | set I($img) [image create photo -file $file -format png] 17 | } 18 | } 19 | 20 | load_images [file join [file dirname [info script]] dark] 21 | 22 | array set colors { 23 | -fg "#ffffff" 24 | -bg "#333333" 25 | -disabledfg "#ffffff" 26 | -disabledbg "#737373" 27 | -selectfg "#ffffff" 28 | -selectbg "#007fff" 29 | } 30 | 31 | ttk::style layout TButton { 32 | Button.button -children { 33 | Button.padding -children { 34 | Button.label -side left -expand true 35 | } 36 | } 37 | } 38 | 39 | ttk::style layout Toolbutton { 40 | Toolbutton.button -children { 41 | Toolbutton.padding -children { 42 | Toolbutton.label -side left -expand true 43 | } 44 | } 45 | } 46 | 47 | ttk::style layout TMenubutton { 48 | Menubutton.button -children { 49 | Menubutton.padding -children { 50 | Menubutton.indicator -side right 51 | Menubutton.label -side right -expand true 52 | } 53 | } 54 | } 55 | 56 | ttk::style layout TOptionMenu { 57 | OptionMenu.button -children { 58 | OptionMenu.padding -children { 59 | OptionMenu.indicator -side right 60 | OptionMenu.label -side right -expand true 61 | } 62 | } 63 | } 64 | 65 | ttk::style layout Accent.TButton { 66 | AccentButton.button -children { 67 | AccentButton.padding -children { 68 | AccentButton.label -side left -expand true 69 | } 70 | } 71 | } 72 | 73 | ttk::style layout TCheckbutton { 74 | Checkbutton.button -children { 75 | Checkbutton.padding -children { 76 | Checkbutton.indicator -side left 77 | Checkbutton.label -side right -expand true 78 | } 79 | } 80 | } 81 | 82 | ttk::style layout Switch.TCheckbutton { 83 | Switch.button -children { 84 | Switch.padding -children { 85 | Switch.indicator -side left 86 | Switch.label -side right -expand true 87 | } 88 | } 89 | } 90 | 91 | ttk::style layout Toggle.TButton { 92 | ToggleButton.button -children { 93 | ToggleButton.padding -children { 94 | ToggleButton.label -side left -expand true 95 | } 96 | } 97 | } 98 | 99 | ttk::style layout TRadiobutton { 100 | Radiobutton.button -children { 101 | Radiobutton.padding -children { 102 | Radiobutton.indicator -side left 103 | Radiobutton.label -side right -expand true 104 | } 105 | } 106 | } 107 | 108 | ttk::style layout Vertical.TScrollbar { 109 | Vertical.Scrollbar.trough -sticky ns -children { 110 | Vertical.Scrollbar.thumb -expand true 111 | } 112 | } 113 | 114 | ttk::style layout Horizontal.TScrollbar { 115 | Horizontal.Scrollbar.trough -sticky ew -children { 116 | Horizontal.Scrollbar.thumb -expand true 117 | } 118 | } 119 | 120 | ttk::style layout TCombobox { 121 | Combobox.field -sticky nswe -children { 122 | Combobox.padding -expand true -sticky nswe -children { 123 | Combobox.textarea -sticky nswe 124 | } 125 | } 126 | Combobox.button -side right -sticky ns -children { 127 | Combobox.arrow -sticky nsew 128 | } 129 | } 130 | 131 | ttk::style layout TSpinbox { 132 | Spinbox.field -sticky nsew -children { 133 | Spinbox.padding -expand true -sticky nswe -children { 134 | Spinbox.textarea -sticky nswe 135 | } 136 | 137 | } 138 | Spinbox.button -side right -sticky ns -children { 139 | null -side right -children { 140 | Spinbox.uparrow -side top 141 | Spinbox.downarrow -side bottom 142 | } 143 | } 144 | } 145 | 146 | ttk::style layout Horizontal.TSeparator { 147 | Horizontal.separator -sticky nswe 148 | } 149 | 150 | ttk::style layout Vertical.TSeparator { 151 | Vertical.separator -sticky nswe 152 | } 153 | 154 | ttk::style layout Horizontal.Tick.TScale { 155 | Horizontal.TickScale.trough -sticky ew -children { 156 | Horizontal.TickScale.slider -sticky w 157 | } 158 | } 159 | 160 | ttk::style layout Vertical.Tick.TScale { 161 | Vertical.TickScale.trough -sticky ns -children { 162 | Vertical.TickScale.slider -sticky n 163 | } 164 | } 165 | 166 | ttk::style layout Card.TFrame { 167 | Card.field { 168 | Card.padding -expand 1 169 | } 170 | } 171 | 172 | ttk::style layout TLabelframe { 173 | Labelframe.border { 174 | Labelframe.padding -expand 1 -children { 175 | Labelframe.label -side right 176 | } 177 | } 178 | } 179 | 180 | ttk::style layout TNotebook.Tab { 181 | Notebook.tab -children { 182 | Notebook.padding -side top -children { 183 | Notebook.label -side top -sticky {} 184 | } 185 | } 186 | } 187 | 188 | ttk::style layout Treeview.Item { 189 | Treeitem.padding -sticky nswe -children { 190 | Treeitem.indicator -side left -sticky {} 191 | Treeitem.image -side left -sticky {} 192 | Treeitem.text -side left -sticky {} 193 | } 194 | } 195 | 196 | 197 | # Elements 198 | 199 | # Button 200 | ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center 201 | 202 | ttk::style element create Button.button image \ 203 | [list $I(rect-basic) \ 204 | {selected disabled} $I(rect-basic) \ 205 | disabled $I(rect-basic) \ 206 | pressed $I(rect-basic) \ 207 | selected $I(rect-basic) \ 208 | active $I(button-hover) \ 209 | ] -border 4 -sticky ewns 210 | 211 | # Toolbutton 212 | ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center 213 | 214 | ttk::style element create Toolbutton.button image \ 215 | [list $I(empty) \ 216 | {selected disabled} $I(empty) \ 217 | disabled $I(empty) \ 218 | pressed $I(rect-basic) \ 219 | selected $I(rect-basic) \ 220 | active $I(rect-basic) \ 221 | ] -border 4 -sticky ewns 222 | 223 | # Menubutton 224 | ttk::style configure TMenubutton -padding {8 4 4 4} 225 | 226 | ttk::style element create Menubutton.button \ 227 | image [list $I(rect-basic) \ 228 | disabled $I(rect-basic) \ 229 | pressed $I(rect-basic) \ 230 | active $I(button-hover) \ 231 | ] -border 4 -sticky ewns 232 | 233 | ttk::style element create Menubutton.indicator \ 234 | image [list $I(down) \ 235 | active $I(down) \ 236 | pressed $I(down) \ 237 | disabled $I(down) \ 238 | ] -width 15 -sticky e 239 | 240 | # OptionMenu 241 | ttk::style configure TOptionMenu -padding {8 4 4 4} 242 | 243 | ttk::style element create OptionMenu.button \ 244 | image [list $I(rect-basic) \ 245 | disabled $I(rect-basic) \ 246 | pressed $I(rect-basic) \ 247 | active $I(button-hover) \ 248 | ] -border 4 -sticky ewns 249 | 250 | ttk::style element create OptionMenu.indicator \ 251 | image [list $I(down) \ 252 | active $I(down) \ 253 | pressed $I(down) \ 254 | disabled $I(down) \ 255 | ] -width 15 -sticky e 256 | 257 | # AccentButton 258 | ttk::style configure Accent.TButton -padding {8 4 8 4} -width -10 -anchor center 259 | 260 | ttk::style element create AccentButton.button image \ 261 | [list $I(rect-accent) \ 262 | {selected disabled} $I(rect-accent-hover) \ 263 | disabled $I(rect-accent-hover) \ 264 | pressed $I(rect-accent) \ 265 | selected $I(rect-accent) \ 266 | active $I(rect-accent-hover) \ 267 | ] -border 4 -sticky ewns 268 | 269 | # Checkbutton 270 | ttk::style configure TCheckbutton -padding 4 271 | 272 | ttk::style element create Checkbutton.indicator image \ 273 | [list $I(box-basic) \ 274 | {alternate disabled} $I(check-tri-basic) \ 275 | {selected disabled} $I(check-basic) \ 276 | disabled $I(box-basic) \ 277 | {pressed alternate} $I(check-tri-hover) \ 278 | {active alternate} $I(check-tri-hover) \ 279 | alternate $I(check-tri-accent) \ 280 | {pressed selected} $I(check-hover) \ 281 | {active selected} $I(check-hover) \ 282 | selected $I(check-accent) \ 283 | {pressed !selected} $I(rect-hover) \ 284 | active $I(box-hover) \ 285 | ] -width 26 -sticky w 286 | 287 | # Switch 288 | ttk::style element create Switch.indicator image \ 289 | [list $I(off-basic) \ 290 | {selected disabled} $I(on-basic) \ 291 | disabled $I(off-basic) \ 292 | {pressed selected} $I(on-accent) \ 293 | {active selected} $I(on-accent) \ 294 | selected $I(on-accent) \ 295 | {pressed !selected} $I(off-basic) \ 296 | active $I(off-basic) \ 297 | ] -width 46 -sticky w 298 | 299 | # ToggleButton 300 | ttk::style configure Toggle.TButton -padding {8 4 8 4} -width -10 -anchor center 301 | 302 | ttk::style element create ToggleButton.button image \ 303 | [list $I(rect-basic) \ 304 | {selected disabled} $I(rect-accent-hover) \ 305 | disabled $I(rect-basic) \ 306 | {pressed selected} $I(rect-basic) \ 307 | {active selected} $I(rect-accent) \ 308 | selected $I(rect-accent) \ 309 | {pressed !selected} $I(rect-accent) \ 310 | active $I(rect-basic) \ 311 | ] -border 4 -sticky ewns 312 | 313 | # Radiobutton 314 | ttk::style configure TRadiobutton -padding 4 315 | 316 | ttk::style element create Radiobutton.indicator image \ 317 | [list $I(outline-basic) \ 318 | {alternate disabled} $I(radio-tri-basic) \ 319 | {selected disabled} $I(radio-basic) \ 320 | disabled $I(outline-basic) \ 321 | {pressed alternate} $I(radio-tri-hover) \ 322 | {active alternate} $I(radio-tri-hover) \ 323 | alternate $I(radio-tri-accent) \ 324 | {pressed selected} $I(radio-hover) \ 325 | {active selected} $I(radio-hover) \ 326 | selected $I(radio-accent) \ 327 | {pressed !selected} $I(circle-hover) \ 328 | active $I(outline-hover) \ 329 | ] -width 26 -sticky w 330 | 331 | # Scrollbar 332 | ttk::style element create Horizontal.Scrollbar.trough image $I(hor-basic) \ 333 | -sticky ew 334 | 335 | ttk::style element create Horizontal.Scrollbar.thumb \ 336 | image [list $I(hor-accent) \ 337 | disabled $I(hor-basic) \ 338 | pressed $I(hor-hover) \ 339 | active $I(hor-hover) \ 340 | ] -sticky ew 341 | 342 | ttk::style element create Vertical.Scrollbar.trough image $I(vert-basic) \ 343 | -sticky ns 344 | 345 | ttk::style element create Vertical.Scrollbar.thumb \ 346 | image [list $I(vert-accent) \ 347 | disabled $I(vert-basic) \ 348 | pressed $I(vert-hover) \ 349 | active $I(vert-hover) \ 350 | ] -sticky ns 351 | 352 | # Scale 353 | ttk::style element create Horizontal.Scale.trough image $I(scale-hor) \ 354 | -border 5 -padding 0 355 | 356 | ttk::style element create Horizontal.Scale.slider \ 357 | image [list $I(circle-accent) \ 358 | disabled $I(circle-basic) \ 359 | pressed $I(circle-hover) \ 360 | active $I(circle-hover) \ 361 | ] -sticky {} 362 | 363 | ttk::style element create Vertical.Scale.trough image $I(scale-vert) \ 364 | -border 5 -padding 0 365 | 366 | ttk::style element create Vertical.Scale.slider \ 367 | image [list $I(circle-accent) \ 368 | disabled $I(circle-basic) \ 369 | pressed $I(circle-hover) \ 370 | active $I(circle-hover) \ 371 | ] -sticky {} 372 | 373 | # Tickscale 374 | ttk::style element create Horizontal.TickScale.trough image $I(scale-hor) \ 375 | -border 5 -padding 0 376 | 377 | ttk::style element create Horizontal.TickScale.slider \ 378 | image [list $I(tick-hor-accent) \ 379 | disabled $I(tick-hor-basic) \ 380 | pressed $I(tick-hor-hover) \ 381 | active $I(tick-hor-hover) \ 382 | ] -sticky {} 383 | 384 | ttk::style element create Vertical.TickScale.trough image $I(scale-vert) \ 385 | -border 5 -padding 0 386 | 387 | ttk::style element create Vertical.TickScale.slider \ 388 | image [list $I(tick-vert-accent) \ 389 | disabled $I(tick-vert-basic) \ 390 | pressed $I(tick-vert-hover) \ 391 | active $I(tick-vert-hover) \ 392 | ] -sticky {} 393 | 394 | # Progressbar 395 | ttk::style element create Horizontal.Progressbar.trough image $I(hor-basic) \ 396 | -sticky ew 397 | 398 | ttk::style element create Horizontal.Progressbar.pbar image $I(hor-accent) \ 399 | -sticky ew 400 | 401 | ttk::style element create Vertical.Progressbar.trough image $I(vert-basic) \ 402 | -sticky ns 403 | 404 | ttk::style element create Vertical.Progressbar.pbar image $I(vert-accent) \ 405 | -sticky ns 406 | 407 | # Entry 408 | ttk::style element create Entry.field \ 409 | image [list $I(box-basic) \ 410 | {focus hover} $I(box-accent) \ 411 | invalid $I(box-invalid) \ 412 | disabled $I(box-basic) \ 413 | focus $I(box-accent) \ 414 | hover $I(box-hover) \ 415 | ] -border 5 -padding {8} -sticky news 416 | 417 | # Combobox 418 | ttk::style map TCombobox -selectbackground [list \ 419 | {!focus} $colors(-selectbg) \ 420 | {readonly hover} $colors(-selectbg) \ 421 | {readonly focus} $colors(-selectbg) \ 422 | ] 423 | 424 | ttk::style map TCombobox -selectforeground [list \ 425 | {!focus} $colors(-selectfg) \ 426 | {readonly hover} $colors(-selectfg) \ 427 | {readonly focus} $colors(-selectfg) \ 428 | ] 429 | 430 | ttk::style element create Combobox.field \ 431 | image [list $I(box-basic) \ 432 | {readonly disabled} $I(rect-basic) \ 433 | {readonly pressed} $I(rect-basic) \ 434 | {readonly focus hover} $I(button-hover) \ 435 | {readonly focus} $I(button-hover) \ 436 | {readonly hover} $I(button-hover) \ 437 | {focus hover} $I(box-accent) \ 438 | readonly $I(rect-basic) \ 439 | invalid $I(box-invalid) \ 440 | disabled $I(box-basic) \ 441 | focus $I(box-accent) \ 442 | hover $I(box-hover) \ 443 | ] -border 5 -padding {8} 444 | 445 | ttk::style element create Combobox.button \ 446 | image [list $I(combo-button-basic) \ 447 | {!readonly focus} $I(combo-button-focus) \ 448 | {readonly focus} $I(combo-button-hover) \ 449 | {readonly hover} $I(combo-button-hover) 450 | ] -border 5 -padding {2 6 6 6} 451 | 452 | ttk::style element create Combobox.arrow image $I(down) \ 453 | -width 15 -sticky e 454 | 455 | # Spinbox 456 | ttk::style element create Spinbox.field \ 457 | image [list $I(box-basic) \ 458 | invalid $I(box-invalid) \ 459 | disabled $I(box-basic) \ 460 | focus $I(box-accent) \ 461 | hover $I(box-hover) \ 462 | ] -border 5 -padding {8} -sticky news 463 | 464 | ttk::style element create Spinbox.uparrow \ 465 | image [list $I(up) \ 466 | disabled $I(up) \ 467 | pressed $I(up-accent) \ 468 | active $I(up-accent) \ 469 | ] -border 4 -width 15 -sticky e 470 | 471 | ttk::style element create Spinbox.downarrow \ 472 | image [list $I(down) \ 473 | disabled $I(down) \ 474 | pressed $I(down-accent) \ 475 | active $I(down-accent) \ 476 | ] -border 4 -width 15 -sticky e 477 | 478 | ttk::style element create Spinbox.button \ 479 | image [list $I(combo-button-basic) \ 480 | {!readonly focus} $I(combo-button-focus) \ 481 | {readonly focus} $I(combo-button-hover) \ 482 | {readonly hover} $I(combo-button-hover) 483 | ] -border 5 -padding {2 6 6 6} 484 | 485 | # Sizegrip 486 | ttk::style element create Sizegrip.sizegrip image $I(size) \ 487 | -sticky ewns 488 | 489 | # Separator 490 | ttk::style element create Horizontal.separator image $I(separator) 491 | 492 | ttk::style element create Vertical.separator image $I(separator) 493 | 494 | # Card 495 | ttk::style element create Card.field image $I(card) \ 496 | -border 10 -padding 4 -sticky news 497 | 498 | # Labelframe 499 | ttk::style element create Labelframe.border image $I(card) \ 500 | -border 5 -padding 4 -sticky news 501 | 502 | # Notebook 503 | ttk::style element create Notebook.client \ 504 | image $I(notebook) -border 5 505 | 506 | ttk::style element create Notebook.tab \ 507 | image [list $I(tab-disabled) \ 508 | selected $I(tab-basic) \ 509 | active $I(tab-hover) \ 510 | ] -border 5 -padding {14 4} 511 | 512 | # Treeview 513 | ttk::style element create Treeview.field image $I(card) \ 514 | -border 5 515 | 516 | ttk::style element create Treeheading.cell \ 517 | image [list $I(tree-basic) \ 518 | pressed $I(tree-pressed) 519 | ] -border 5 -padding 4 -sticky ewns 520 | 521 | ttk::style element create Treeitem.indicator \ 522 | image [list $I(right) \ 523 | user2 $I(empty) \ 524 | user1 $I(down) \ 525 | ] -width 26 -sticky {} 526 | 527 | ttk::style configure Treeview -background $colors(-bg) 528 | ttk::style configure Treeview.Item -padding {2 0 0 0} 529 | ttk::style map Treeview \ 530 | -background [list selected $colors(-selectbg)] \ 531 | -foreground [list selected $colors(-selectfg)] 532 | 533 | # Panedwindow 534 | # Insane hack to remove clam's ugly sash 535 | ttk::style configure Sash -gripcount 0 536 | } 537 | } 538 | -------------------------------------------------------------------------------- /resources/theme/theme/light.tcl: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 rdbende 2 | 3 | # The Azure theme is a beautiful modern ttk theme inspired by Microsoft's fluent design. 4 | 5 | package require Tk 8.6 6 | 7 | namespace eval ttk::theme::azure-light { 8 | variable version 2.0 9 | package provide ttk::theme::azure-light $version 10 | 11 | ttk::style theme create azure-light -parent clam -settings { 12 | proc load_images {imgdir} { 13 | variable I 14 | foreach file [glob -directory $imgdir *.png] { 15 | set img [file tail [file rootname $file]] 16 | set I($img) [image create photo -file $file -format png] 17 | } 18 | } 19 | 20 | load_images [file join [file dirname [info script]] light] 21 | 22 | array set colors { 23 | -fg "#000000" 24 | -bg "#ffffff" 25 | -disabledfg "#737373" 26 | -disabledbg "#ffffff" 27 | -selectfg "#ffffff" 28 | -selectbg "#007fff" 29 | } 30 | 31 | ttk::style layout TButton { 32 | Button.button -children { 33 | Button.padding -children { 34 | Button.label -side left -expand true 35 | } 36 | } 37 | } 38 | 39 | ttk::style layout Toolbutton { 40 | Toolbutton.button -children { 41 | Toolbutton.padding -children { 42 | Toolbutton.label -side left -expand true 43 | } 44 | } 45 | } 46 | 47 | ttk::style layout TMenubutton { 48 | Menubutton.button -children { 49 | Menubutton.padding -children { 50 | Menubutton.indicator -side right 51 | Menubutton.label -side right -expand true 52 | } 53 | } 54 | } 55 | 56 | ttk::style layout TOptionMenu { 57 | OptionMenu.button -children { 58 | OptionMenu.padding -children { 59 | OptionMenu.indicator -side right 60 | OptionMenu.label -side right -expand true 61 | } 62 | } 63 | } 64 | 65 | ttk::style layout Accent.TButton { 66 | AccentButton.button -children { 67 | AccentButton.padding -children { 68 | AccentButton.label -side left -expand true 69 | } 70 | } 71 | } 72 | 73 | ttk::style layout TCheckbutton { 74 | Checkbutton.button -children { 75 | Checkbutton.padding -children { 76 | Checkbutton.indicator -side left 77 | Checkbutton.label -side right -expand true 78 | } 79 | } 80 | } 81 | 82 | ttk::style layout Switch.TCheckbutton { 83 | Switch.button -children { 84 | Switch.padding -children { 85 | Switch.indicator -side left 86 | Switch.label -side right -expand true 87 | } 88 | } 89 | } 90 | 91 | ttk::style layout Toggle.TButton { 92 | ToggleButton.button -children { 93 | ToggleButton.padding -children { 94 | ToggleButton.label -side left -expand true 95 | } 96 | } 97 | } 98 | 99 | ttk::style layout TRadiobutton { 100 | Radiobutton.button -children { 101 | Radiobutton.padding -children { 102 | Radiobutton.indicator -side left 103 | Radiobutton.label -side right -expand true 104 | } 105 | } 106 | } 107 | 108 | ttk::style layout Vertical.TScrollbar { 109 | Vertical.Scrollbar.trough -sticky ns -children { 110 | Vertical.Scrollbar.thumb -expand true 111 | } 112 | } 113 | 114 | ttk::style layout Horizontal.TScrollbar { 115 | Horizontal.Scrollbar.trough -sticky ew -children { 116 | Horizontal.Scrollbar.thumb -expand true 117 | } 118 | } 119 | 120 | ttk::style layout TCombobox { 121 | Combobox.field -sticky nswe -children { 122 | Combobox.padding -expand true -sticky nswe -children { 123 | Combobox.textarea -sticky nswe 124 | } 125 | } 126 | Combobox.button -side right -sticky ns -children { 127 | Combobox.arrow -sticky nsew 128 | } 129 | } 130 | 131 | ttk::style layout TSpinbox { 132 | Spinbox.field -sticky nsew -children { 133 | Spinbox.padding -expand true -sticky nswe -children { 134 | Spinbox.textarea -sticky nswe 135 | } 136 | 137 | } 138 | Spinbox.button -side right -sticky ns -children { 139 | null -side right -children { 140 | Spinbox.uparrow -side top 141 | Spinbox.downarrow -side bottom 142 | } 143 | } 144 | } 145 | 146 | ttk::style layout Horizontal.TSeparator { 147 | Horizontal.separator -sticky nswe 148 | } 149 | 150 | ttk::style layout Vertical.TSeparator { 151 | Vertical.separator -sticky nswe 152 | } 153 | 154 | ttk::style layout Horizontal.Tick.TScale { 155 | Horizontal.TickScale.trough -sticky ew -children { 156 | Horizontal.TickScale.slider -sticky w 157 | } 158 | } 159 | 160 | ttk::style layout Vertical.Tick.TScale { 161 | Vertical.TickScale.trough -sticky ns -children { 162 | Vertical.TickScale.slider -sticky n 163 | } 164 | } 165 | 166 | ttk::style layout Card.TFrame { 167 | Card.field { 168 | Card.padding -expand 1 169 | } 170 | } 171 | 172 | ttk::style layout TLabelframe { 173 | Labelframe.border { 174 | Labelframe.padding -expand 1 -children { 175 | Labelframe.label -side right 176 | } 177 | } 178 | } 179 | 180 | ttk::style layout TNotebook.Tab { 181 | Notebook.tab -children { 182 | Notebook.padding -side top -children { 183 | Notebook.label -side top -sticky {} 184 | } 185 | } 186 | } 187 | 188 | ttk::style layout Treeview.Item { 189 | Treeitem.padding -sticky nswe -children { 190 | Treeitem.indicator -side left -sticky {} 191 | Treeitem.image -side left -sticky {} 192 | Treeitem.text -side left -sticky {} 193 | } 194 | } 195 | 196 | 197 | # Elements 198 | 199 | # Button 200 | ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center 201 | 202 | ttk::style element create Button.button image \ 203 | [list $I(rect-basic) \ 204 | {selected disabled} $I(rect-basic) \ 205 | disabled $I(rect-basic) \ 206 | selected $I(rect-basic) \ 207 | pressed $I(rect-basic) \ 208 | active $I(button-hover) \ 209 | ] -border 4 -sticky ewns 210 | 211 | # Toolbutton 212 | ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center 213 | 214 | ttk::style element create Toolbutton.button image \ 215 | [list $I(empty) \ 216 | {selected disabled} $I(empty) \ 217 | disabled $I(empty) \ 218 | selected $I(rect-basic) \ 219 | pressed $I(rect-basic) \ 220 | active $I(rect-basic) \ 221 | ] -border 4 -sticky ewns 222 | 223 | # Menubutton 224 | ttk::style configure TMenubutton -padding {8 4 4 4} 225 | 226 | ttk::style element create Menubutton.button \ 227 | image [list $I(rect-basic) \ 228 | disabled $I(rect-basic) \ 229 | pressed $I(rect-basic) \ 230 | active $I(button-hover) \ 231 | ] -border 4 -sticky ewns 232 | 233 | ttk::style element create Menubutton.indicator \ 234 | image [list $I(down) \ 235 | active $I(down) \ 236 | pressed $I(down) \ 237 | disabled $I(down) \ 238 | ] -width 15 -sticky e 239 | 240 | # OptionMenu 241 | ttk::style configure TOptionMenu -padding {8 4 4 4} 242 | 243 | ttk::style element create OptionMenu.button \ 244 | image [list $I(rect-basic) \ 245 | disabled $I(rect-basic) \ 246 | pressed $I(rect-basic) \ 247 | active $I(button-hover) \ 248 | ] -border 4 -sticky ewns 249 | 250 | ttk::style element create OptionMenu.indicator \ 251 | image [list $I(down) \ 252 | active $I(down) \ 253 | pressed $I(down) \ 254 | disabled $I(down) \ 255 | ] -width 15 -sticky e 256 | 257 | # AccentButton 258 | ttk::style configure Accent.TButton -padding {8 4 8 4} -width -10 -anchor center 259 | 260 | ttk::style element create AccentButton.button image \ 261 | [list $I(rect-accent) \ 262 | {selected disabled} $I(rect-accent-hover) \ 263 | disabled $I(rect-accent-hover) \ 264 | selected $I(rect-accent) \ 265 | pressed $I(rect-accent) \ 266 | active $I(rect-accent-hover) \ 267 | ] -border 4 -sticky ewns 268 | 269 | # Checkbutton 270 | ttk::style configure TCheckbutton -padding 4 271 | 272 | ttk::style element create Checkbutton.indicator image \ 273 | [list $I(box-basic) \ 274 | {alternate disabled} $I(check-tri-basic) \ 275 | {selected disabled} $I(check-basic) \ 276 | disabled $I(box-basic) \ 277 | {pressed alternate} $I(check-tri-hover) \ 278 | {active alternate} $I(check-tri-hover) \ 279 | alternate $I(check-tri-accent) \ 280 | {pressed selected} $I(check-hover) \ 281 | {active selected} $I(check-hover) \ 282 | selected $I(check-accent) \ 283 | {pressed !selected} $I(rect-hover) \ 284 | active $I(box-hover) \ 285 | ] -width 26 -sticky w 286 | 287 | # Switch 288 | ttk::style element create Switch.indicator image \ 289 | [list $I(off-basic) \ 290 | {selected disabled} $I(on-basic) \ 291 | disabled $I(off-basic) \ 292 | {pressed selected} $I(on-hover) \ 293 | {active selected} $I(on-hover) \ 294 | selected $I(on-accent) \ 295 | {pressed !selected} $I(off-hover) \ 296 | active $I(off-hover) \ 297 | ] -width 46 -sticky w 298 | 299 | # ToggleButton 300 | ttk::style configure Toggle.TButton -padding {8 4 8 4} -width -10 -anchor center 301 | 302 | ttk::style element create ToggleButton.button image \ 303 | [list $I(rect-basic) \ 304 | {selected disabled} $I(rect-accent-hover) \ 305 | disabled $I(rect-basic) \ 306 | {pressed selected} $I(rect-basic) \ 307 | {active selected} $I(rect-accent) \ 308 | selected $I(rect-accent) \ 309 | {pressed !selected} $I(rect-accent) \ 310 | active $I(rect-basic) \ 311 | ] -border 4 -sticky ewns 312 | 313 | # Radiobutton 314 | ttk::style configure TRadiobutton -padding 4 315 | 316 | ttk::style element create Radiobutton.indicator image \ 317 | [list $I(outline-basic) \ 318 | {alternate disabled} $I(radio-tri-basic) \ 319 | {selected disabled} $I(radio-basic) \ 320 | disabled $I(outline-basic) \ 321 | {pressed alternate} $I(radio-tri-hover) \ 322 | {active alternate} $I(radio-tri-hover) \ 323 | alternate $I(radio-tri-accent) \ 324 | {pressed selected} $I(radio-hover) \ 325 | {active selected} $I(radio-hover) \ 326 | selected $I(radio-accent) \ 327 | {pressed !selected} $I(circle-hover) \ 328 | active $I(outline-hover) \ 329 | ] -width 26 -sticky w 330 | 331 | # Scrollbar 332 | ttk::style element create Horizontal.Scrollbar.trough image $I(hor-basic) \ 333 | -sticky ew 334 | 335 | ttk::style element create Horizontal.Scrollbar.thumb \ 336 | image [list $I(hor-accent) \ 337 | disabled $I(hor-basic) \ 338 | pressed $I(hor-hover) \ 339 | active $I(hor-hover) \ 340 | ] -sticky ew 341 | 342 | ttk::style element create Vertical.Scrollbar.trough image $I(vert-basic) \ 343 | -sticky ns 344 | 345 | ttk::style element create Vertical.Scrollbar.thumb \ 346 | image [list $I(vert-accent) \ 347 | disabled $I(vert-basic) \ 348 | pressed $I(vert-hover) \ 349 | active $I(vert-hover) \ 350 | ] -sticky ns 351 | 352 | # Scale 353 | ttk::style element create Horizontal.Scale.trough image $I(scale-hor) \ 354 | -border 5 -padding 0 355 | 356 | ttk::style element create Horizontal.Scale.slider \ 357 | image [list $I(circle-accent) \ 358 | disabled $I(circle-basic) \ 359 | pressed $I(circle-hover) \ 360 | active $I(circle-hover) \ 361 | ] -sticky {} 362 | 363 | ttk::style element create Vertical.Scale.trough image $I(scale-vert) \ 364 | -border 5 -padding 0 365 | 366 | ttk::style element create Vertical.Scale.slider \ 367 | image [list $I(circle-accent) \ 368 | disabled $I(circle-basic) \ 369 | pressed $I(circle-hover) \ 370 | active $I(circle-hover) \ 371 | ] -sticky {} 372 | 373 | # Tickscale 374 | ttk::style element create Horizontal.TickScale.trough image $I(scale-hor) \ 375 | -border 5 -padding 0 376 | 377 | ttk::style element create Horizontal.TickScale.slider \ 378 | image [list $I(tick-hor-accent) \ 379 | disabled $I(tick-hor-basic) \ 380 | pressed $I(tick-hor-hover) \ 381 | active $I(tick-hor-hover) \ 382 | ] -sticky {} 383 | 384 | ttk::style element create Vertical.TickScale.trough image $I(scale-vert) \ 385 | -border 5 -padding 0 386 | 387 | ttk::style element create Vertical.TickScale.slider \ 388 | image [list $I(tick-vert-accent) \ 389 | disabled $I(tick-vert-basic) \ 390 | pressed $I(tick-vert-hover) \ 391 | active $I(tick-vert-hover) \ 392 | ] -sticky {} 393 | 394 | # Progressbar 395 | ttk::style element create Horizontal.Progressbar.trough image $I(hor-basic) \ 396 | -sticky ew 397 | 398 | ttk::style element create Horizontal.Progressbar.pbar image $I(hor-accent) \ 399 | -sticky ew 400 | 401 | ttk::style element create Vertical.Progressbar.trough image $I(vert-basic) \ 402 | -sticky ns 403 | 404 | ttk::style element create Vertical.Progressbar.pbar image $I(vert-accent) \ 405 | -sticky ns 406 | 407 | # Entry 408 | ttk::style element create Entry.field \ 409 | image [list $I(box-basic) \ 410 | {focus hover} $I(box-accent) \ 411 | invalid $I(box-invalid) \ 412 | disabled $I(box-basic) \ 413 | focus $I(box-accent) \ 414 | hover $I(box-hover) \ 415 | ] -border 5 -padding {8} -sticky news 416 | 417 | # Combobox 418 | ttk::style map TCombobox -selectbackground [list \ 419 | {!focus} $colors(-selectbg) \ 420 | {readonly hover} $colors(-selectbg) \ 421 | {readonly focus} $colors(-selectbg) \ 422 | ] 423 | 424 | ttk::style map TCombobox -selectforeground [list \ 425 | {!focus} $colors(-selectfg) \ 426 | {readonly hover} $colors(-selectfg) \ 427 | {readonly focus} $colors(-selectfg) \ 428 | ] 429 | 430 | ttk::style element create Combobox.field \ 431 | image [list $I(box-basic) \ 432 | {readonly disabled} $I(rect-basic) \ 433 | {readonly pressed} $I(rect-basic) \ 434 | {readonly focus hover} $I(button-hover) \ 435 | {readonly focus} $I(button-hover) \ 436 | {readonly hover} $I(button-hover) \ 437 | {focus hover} $I(box-accent) \ 438 | readonly $I(rect-basic) \ 439 | invalid $I(box-invalid) \ 440 | disabled $I(box-basic) \ 441 | focus $I(box-accent) \ 442 | hover $I(box-hover) \ 443 | ] -border 5 -padding {8} 444 | 445 | ttk::style element create Combobox.button \ 446 | image [list $I(combo-button-basic) \ 447 | {!readonly focus} $I(combo-button-focus) \ 448 | {readonly focus} $I(combo-button-hover) \ 449 | {readonly hover} $I(combo-button-hover) 450 | ] -border 5 -padding {2 6 6 6} 451 | 452 | ttk::style element create Combobox.arrow image $I(down) \ 453 | -width 15 -sticky e 454 | 455 | # Spinbox 456 | ttk::style element create Spinbox.field \ 457 | image [list $I(box-basic) \ 458 | invalid $I(box-invalid) \ 459 | disabled $I(box-basic) \ 460 | focus $I(box-accent) \ 461 | hover $I(box-hover) \ 462 | ] -border 5 -padding {8} -sticky news 463 | 464 | ttk::style element create Spinbox.uparrow \ 465 | image [list $I(up) \ 466 | disabled $I(up) \ 467 | pressed $I(up-accent) \ 468 | active $I(up-accent) \ 469 | ] -border 4 -width 15 -sticky e 470 | 471 | ttk::style element create Spinbox.downarrow \ 472 | image [list $I(down) \ 473 | disabled $I(down) \ 474 | pressed $I(down-accent) \ 475 | active $I(down-accent) \ 476 | ] -border 4 -width 15 -sticky e 477 | 478 | ttk::style element create Spinbox.button \ 479 | image [list $I(combo-button-basic) \ 480 | {!readonly focus} $I(combo-button-focus) \ 481 | {readonly focus} $I(combo-button-hover) \ 482 | {readonly hover} $I(combo-button-hover) 483 | ] -border 5 -padding {2 6 6 6} 484 | 485 | # Sizegrip 486 | ttk::style element create Sizegrip.sizegrip image $I(size) \ 487 | -sticky ewns 488 | 489 | # Separator 490 | ttk::style element create Horizontal.separator image $I(separator) 491 | 492 | ttk::style element create Vertical.separator image $I(separator) 493 | 494 | # Card 495 | ttk::style element create Card.field image $I(card) \ 496 | -border 10 -padding 4 -sticky news 497 | 498 | # Labelframe 499 | ttk::style element create Labelframe.border image $I(card) \ 500 | -border 5 -padding 4 -sticky news 501 | 502 | # Notebook 503 | ttk::style element create Notebook.client \ 504 | image $I(notebook) -border 5 505 | 506 | ttk::style element create Notebook.tab \ 507 | image [list $I(tab-disabled) \ 508 | selected $I(tab-basic) \ 509 | active $I(tab-hover) \ 510 | ] -border 5 -padding {14 4} 511 | 512 | # Treeview 513 | ttk::style element create Treeview.field image $I(card) \ 514 | -border 5 515 | 516 | ttk::style element create Treeheading.cell \ 517 | image [list $I(tree-basic) \ 518 | pressed $I(tree-pressed) 519 | ] -border 5 -padding 4 -sticky ewns 520 | 521 | ttk::style element create Treeitem.indicator \ 522 | image [list $I(right) \ 523 | user2 $I(empty) \ 524 | user1 $I(down) \ 525 | ] -width 26 -sticky {} 526 | 527 | ttk::style configure Treeview -background $colors(-bg) 528 | ttk::style configure Treeview.Item -padding {2 0 0 0} 529 | ttk::style map Treeview \ 530 | -background [list selected #ccc] \ 531 | -foreground [list selected $colors(-fg)] 532 | 533 | # Panedwindow 534 | # Insane hack to remove clam's ugly sash 535 | ttk::style configure Sash -gripcount 0 536 | } 537 | } 538 | --------------------------------------------------------------------------------