├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── codeql-analysis.yml │ ├── crunch-bench.yml │ ├── crunch-lints.yml │ ├── crunch-linux-ci.yml │ └── crunch-macos-ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── Makefile ├── README.md ├── benchmarks ├── Makefile ├── PngSuite.LICENSE ├── README.md └── img │ ├── basi0g01.png │ ├── basi0g02.png │ ├── basi0g04.png │ ├── basi0g08.png │ ├── basi0g16.png │ ├── basi2c08.png │ ├── basi2c16.png │ ├── basi3p01.png │ ├── basi3p02.png │ ├── basi3p04.png │ ├── basi3p08.png │ ├── basi4a08.png │ ├── basi4a16.png │ ├── basi6a08.png │ ├── basi6a16.png │ ├── basn0g01.png │ ├── basn0g02.png │ ├── basn0g04.png │ ├── basn0g08.png │ ├── basn0g16.png │ ├── basn2c08.png │ ├── basn2c16.png │ ├── basn3p01.png │ ├── basn3p02.png │ ├── basn3p04.png │ ├── basn3p08.png │ ├── basn4a08.png │ ├── basn4a16.png │ ├── basn6a08.png │ ├── basn6a16.png │ ├── bench.py │ ├── bgai4a08.png │ ├── bgai4a16.png │ ├── bgan6a08.png │ ├── bgan6a16.png │ ├── bgbn4a08.png │ ├── bggn4a16.png │ ├── bgwn6a08.png │ ├── bgyn6a16.png │ ├── ccwn2c08.png │ ├── ccwn3p08.png │ ├── cdfn2c08.png │ ├── cdhn2c08.png │ ├── cdsn2c08.png │ ├── cdun2c08.png │ ├── ch1n3p04.png │ ├── ch2n3p08.png │ ├── cm0n0g04.png │ ├── cm7n0g04.png │ ├── cm9n0g04.png │ ├── cs3n2c16.png │ ├── cs3n3p08.png │ ├── cs5n2c08.png │ ├── cs5n3p08.png │ ├── cs8n2c08.png │ ├── cs8n3p08.png │ ├── ct0n0g04.png │ ├── ct1n0g04.png │ ├── cten0g04.png │ ├── ctfn0g04.png │ ├── ctgn0g04.png │ ├── cthn0g04.png │ ├── ctjn0g04.png │ ├── ctzn0g04.png │ ├── exif2c08.png │ ├── f00n0g08.png │ ├── f00n2c08.png │ ├── f01n0g08.png │ ├── f01n2c08.png │ ├── f02n0g08.png │ ├── f02n2c08.png │ ├── f03n0g08.png │ ├── f03n2c08.png │ ├── f04n0g08.png │ ├── f04n2c08.png │ ├── f99n0g04.png │ ├── g03n0g16.png │ ├── g03n2c08.png │ ├── g03n3p04.png │ ├── g04n0g16.png │ ├── g04n2c08.png │ ├── g04n3p04.png │ ├── g05n0g16.png │ ├── g05n2c08.png │ ├── g05n3p04.png │ ├── g07n0g16.png │ ├── g07n2c08.png │ ├── g07n3p04.png │ ├── g10n0g16.png │ ├── g10n2c08.png │ ├── g10n3p04.png │ ├── g25n0g16.png │ ├── g25n2c08.png │ ├── g25n3p04.png │ ├── oi1n0g16.png │ ├── oi1n2c16.png │ ├── oi2n0g16.png │ ├── oi2n2c16.png │ ├── oi4n0g16.png │ ├── oi4n2c16.png │ ├── oi9n0g16.png │ ├── oi9n2c16.png │ ├── pp0n2c16.png │ ├── pp0n6a08.png │ ├── ps1n0g08.png │ ├── ps1n2c16.png │ ├── ps2n0g08.png │ ├── ps2n2c16.png │ ├── s01i3p01.png │ ├── s01n3p01.png │ ├── s02i3p01.png │ ├── s02n3p01.png │ ├── s03i3p01.png │ ├── s03n3p01.png │ ├── s04i3p01.png │ ├── s04n3p01.png │ ├── s05i3p02.png │ ├── s05n3p02.png │ ├── s06i3p02.png │ ├── s06n3p02.png │ ├── s07i3p02.png │ ├── s07n3p02.png │ ├── s08i3p02.png │ ├── s08n3p02.png │ ├── s09i3p02.png │ ├── s09n3p02.png │ ├── s32i3p04.png │ ├── s32n3p04.png │ ├── s33i3p04.png │ ├── s33n3p04.png │ ├── s34i3p04.png │ ├── s34n3p04.png │ ├── s35i3p04.png │ ├── s35n3p04.png │ ├── s36i3p04.png │ ├── s36n3p04.png │ ├── s37i3p04.png │ ├── s37n3p04.png │ ├── s38i3p04.png │ ├── s38n3p04.png │ ├── s39i3p04.png │ ├── s39n3p04.png │ ├── s40i3p04.png │ ├── s40n3p04.png │ ├── tbbn0g04.png │ ├── tbbn2c16.png │ ├── tbbn3p08.png │ ├── tbgn2c16.png │ ├── tbgn3p08.png │ ├── tbrn2c08.png │ ├── tbwn0g16.png │ ├── tbwn3p08.png │ ├── tbyn3p08.png │ ├── tm3n3p02.png │ ├── tp0n0g08.png │ ├── tp0n2c08.png │ ├── tp0n3p08.png │ ├── tp1n3p08.png │ ├── z00n2c08.png │ ├── z03n2c08.png │ ├── z06n2c08.png │ └── z09n2c08.png ├── bin └── Crunch.app │ └── Contents │ ├── Info.plist │ ├── MacOS │ └── Crunch │ └── Resources │ ├── 01-Load-Up@2x.gif │ ├── 02-Waiting@2x.gif │ ├── 03-Crunching@2x.gif │ ├── 04-Error@2x.gif │ ├── 04-Success@2x.gif │ ├── AppSettings.plist │ ├── Credits.html │ ├── MainMenu.nib │ ├── designable.nib │ └── keyedobjects.nib │ ├── appIcon.icns │ ├── clear.html │ ├── complete-error.html │ ├── complete-success.html │ ├── crunch.py │ ├── execution.html │ ├── pngquant │ ├── script │ ├── start.html │ ├── waiting.html │ └── zopflipng ├── coverage.sh ├── dmg-builder.sh ├── docs ├── BENCHMARKS.md ├── Credits.html ├── EXECUTABLE.md ├── MACOSGUI.md └── SERVICE.md ├── html ├── clear.html ├── complete-error.html ├── complete-success.html ├── execution.html ├── start.html └── waiting.html ├── img ├── Crunch-icon-2.png ├── Crunch-icon-3.png ├── CrunchIcon.icns ├── CrunchIcon.iconset │ ├── icon_128x128.png │ ├── icon_128x128@2x.png │ ├── icon_16x16.png │ ├── icon_16x16@2x.png │ ├── icon_256x256.png │ ├── icon_256x256@2x.png │ ├── icon_32x32.png │ └── icon_32x32@2x.png ├── animations │ ├── 01-Load-Up@1x.gif │ ├── 01-Load-Up@2x.gif │ ├── 02-Waiting@1x.gif │ ├── 02-Waiting@2x.gif │ ├── 03-Crunching@1x.gif │ ├── 03-Crunching@2x.gif │ ├── 04-Error@1x.gif │ ├── 04-Error@2x.gif │ ├── 04-Success@1x.gif │ └── 04-Success@2x.gif ├── cat-1285634_640-crunch.png ├── cat-1285634_640.png ├── colors-157474_640-crunch.png ├── colors-157474_640.png ├── crunch-ss-2.gif ├── dmg-installer-bg.png ├── fastdots.gif ├── flowers-67839_640-crunch.png ├── flowers-67839_640.png ├── header-arrow-crunch.png ├── header-arrow.psd ├── header-img-crunch.png ├── header-img.psd ├── prairie-679014_640-crunch.png ├── prairie-679014_640.png ├── robot-1214536_640-crunch.png ├── robot-1214536_640.png ├── slowdots.gif ├── suns-rays-478249_640-crunch.png └── suns-rays-478249_640.png ├── profile └── Crunch.platypus ├── service └── Crunch Image(s).workflow │ └── Contents │ ├── Info.plist │ ├── QuickLook │ └── Preview.png │ └── document.wflow ├── setup.cfg ├── src ├── __init__.py ├── crunch-gui.sh ├── crunch.py ├── include │ ├── licenses │ │ ├── pngquant_license.txt │ │ └── zopflipng_license.txt │ ├── pngquant │ └── zopflipng ├── install-dependencies.sh ├── test_crunch_errors.py ├── test_crunch_execution.py ├── test_crunch_obj.py └── utils │ ├── dssim-comparisons.sh │ └── image-compare.py ├── testfiles ├── cat-cr.png ├── cat.png ├── img with spaces.png ├── robot.png └── test.txt ├── tox.ini └── ui └── MainMenu.nib ├── designable.nib └── keyedobjects.nib /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help the project improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is including the Crunch application that you are using and the version number of the Crunch application. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Open 'application...' 13 | 2. Do 'this...' 14 | 3. I see 'this...' 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots and/or Log Text** 20 | If applicable, add screenshots to help explain your problem. For macOS GUI and macOS right-click menu service problems, please copy and paste the text from the log file `~/.crunch/crunch.log` into your bug report (requires Crunch v3.0.0 or higher). 21 | 22 | **Desktop (please complete the following information):** 23 | - OS: [e.g. macOS] 24 | - Version 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # ******** NOTE ******** 12 | 13 | name: "CodeQL" 14 | 15 | on: 16 | push: 17 | branches: [ master ] 18 | pull_request: 19 | # The branches below must be a subset of the branches above 20 | branches: [ master ] 21 | schedule: 22 | - cron: '36 6 * * 4' 23 | 24 | jobs: 25 | analyze: 26 | name: Analyze 27 | runs-on: ubuntu-latest 28 | 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | language: [ 'python' ] 33 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 34 | # Learn more... 35 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 36 | 37 | steps: 38 | - name: Checkout repository 39 | uses: actions/checkout@v2 40 | 41 | # Initializes the CodeQL tools for scanning. 42 | - name: Initialize CodeQL 43 | uses: github/codeql-action/init@v1 44 | with: 45 | languages: ${{ matrix.language }} 46 | # If you wish to specify custom queries, you can do so here or in a config file. 47 | # By default, queries listed here will override any specified in a config file. 48 | # Prefix the list here with "+" to use these queries and those in the config file. 49 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 50 | 51 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 52 | # If this step fails, then you should remove it and run the build manually (see below) 53 | - name: Autobuild 54 | uses: github/codeql-action/autobuild@v1 55 | 56 | # ℹ️ Command-line programs to run using the OS shell. 57 | # 📚 https://git.io/JvXDl 58 | 59 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 60 | # and modify them (or add more) to build your code if your project 61 | # uses a compiled language 62 | 63 | #- run: | 64 | # make bootstrap 65 | # make release 66 | 67 | - name: Perform CodeQL Analysis 68 | uses: github/codeql-action/analyze@v1 69 | -------------------------------------------------------------------------------- /.github/workflows/crunch-bench.yml: -------------------------------------------------------------------------------- 1 | name: Benchmarks 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, macos-latest] 11 | python-version: [3.9] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - name: Display Python version & architecture 20 | run: | 21 | python -c "import sys; print(sys.version)" 22 | python -c "import struct; print(struct.calcsize('P') * 8)" 23 | - name: Install testing dependencies 24 | run: | 25 | pip install --upgrade numpy 26 | - name: Install project dependencies and executable 27 | run: | 28 | make build-dependencies 29 | make install-executable 30 | - name: Run benchmarks 31 | run: | 32 | make benchmark 33 | -------------------------------------------------------------------------------- /.github/workflows/crunch-lints.yml: -------------------------------------------------------------------------------- 1 | name: Lints 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest] 11 | python-version: [3.9] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - name: Display Python version & architecture 20 | run: | 21 | python -c "import sys; print(sys.version)" 22 | python -c "import struct; print(struct.calcsize('P') * 8)" 23 | - name: Install testing dependencies 24 | run: | 25 | sudo apt-get install -y shellcheck 26 | pip install --upgrade flake8 27 | - name: Lints 28 | run: | 29 | flake8 --ignore=E501,W503 src/crunch.py 30 | shellcheck --exclude=2046 src/*.sh 31 | -------------------------------------------------------------------------------- /.github/workflows/crunch-linux-ci.yml: -------------------------------------------------------------------------------- 1 | name: Linux CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest] 11 | python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - name: Display Python version & architecture 20 | run: | 21 | python -c "import sys; print(sys.version)" 22 | python -c "import struct; print(struct.calcsize('P') * 8)" 23 | - name: Install testing dependencies 24 | run: | 25 | pip install --upgrade pytest 26 | sudo apt-get install -y pngcheck 27 | - name: Install project dependencies and executable 28 | run: | 29 | make build-dependencies 30 | make install-executable 31 | - name: Python unit tests 32 | run: | 33 | pytest src 34 | make test-valid-png-output 35 | -------------------------------------------------------------------------------- /.github/workflows/crunch-macos-ci.yml: -------------------------------------------------------------------------------- 1 | name: macOS CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [macos-latest] 11 | python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - name: Display Python version & architecture 20 | run: | 21 | python -c "import sys; print(sys.version)" 22 | python -c "import struct; print(struct.calcsize('P') * 8)" 23 | - name: Install testing dependencies 24 | run: | 25 | pip install --upgrade pytest 26 | brew install pngcheck 27 | - name: Install project dependencies and executable 28 | run: | 29 | make build-dependencies 30 | make install-executable 31 | - name: Python unit tests 32 | run: | 33 | pytest src 34 | make test-valid-png-output 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .pytest_cache 3 | __pycache__ 4 | *.pyc 5 | .tox 6 | .coverage 7 | \.DS_Store 8 | *.dmg 9 | *-checksum.txt -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### v5.0.0 4 | 5 | - Remove command line executable cPython 2 support 6 | - Fix GUI fails on recent versions of macOS (reported on macOS 12.3.1) (#92, fix #94) 7 | - Add Apple iconset application icons 8 | - Add icns application icon package and convert to use of this format for production Crunch application 9 | - Convert to use of https://github.com/sindresorhus/create-dmg for GUI macOS dmg installer prep 10 | - Add Makefile build-macos-icns target 11 | - Add Makefile build-macos-installer target 12 | - Add GitHub Actions CI testing 13 | - Remove Travis CI testing configuration file 14 | - Update GUI installation documentation 15 | - Remove dmg installer file from version control (will still be included in releases) 16 | - Remove dmg installer checksum file from version control (will still be included in releases) 17 | 18 | ### v4.0.0 19 | 20 | - Updated pngquant to v2.12.5 21 | - Updated libpng to v1.6.37 22 | - Updated zopflipng to v2.2.0 (@chrissimpkins derivative) - upstream updates through https://github.com/google/zopfli/commit/5d9b71b3c636e9e14a8f7a3f983ff93a1a3793ac 23 | - crunch executable : added ANSI color support in stdout / stderr messages 24 | - crunch.py : PEP 8 source code formatting refactor with `black` 25 | - crunch.py : refactor logging setup approach 26 | - FIX Crunch macOS service : fixed bug in processing of png image file paths that include spaces (thanks Changyoung!) 27 | - FIX crunch executable: command line error handling when no arguments are passed to the command line `crunch` executable 28 | - Added Makefile dist target 29 | - Added Makefile benchmark target 30 | - Added Makefile clean target 31 | - Updated Makefile flake8 linting target 32 | - Updated dmg-builder.sh dmg installer script 33 | - Added new image-compare.py script for comparison of test image file sizes 34 | - Added new dssim-comparisons.sh script for DSSIM analysis of pre/post compression test images 35 | - Added new suite of reference PNG images and benchmarking support in `bench.py` script 36 | - Added continuous benchmarking through Travis CI 37 | 38 | ### v3.0.1 39 | 40 | - modified the macOS GUI idle animation to reduce CPU usage during the application idle stage (issue report #66) 41 | 42 | ### v3.0.0 43 | 44 | - improved quality of pngquant quantization of PNG images across all file sizes 45 | - upgraded embedded pngquant to v2.12.0 (includes reduced pngquant optimization times) 46 | - converted to custom build of zopflipng that is modified for use in the Crunch applications (forked from google/zopfli at git tag zopfli-1.0.2) at git version tag v2.1.0 (source repository is chrissimpkins/zopfli) 47 | - improved zopfli compression ratios for post-quantized and non-quantized in-file sizes under 350kB. Many files are ~33% original file size after they are quantized with pngquant so this affects pre-optimization files up to just over ~1MB in size when the pngquant step is completed (the quantize step yields a modified image binary when it does not lead to larger file size or image quality below Crunch project thresholds, when this does not occur the original file at the original file size is used as the in-file to zopflipng) 48 | - improved zopfli compression speed for post-quantized and non-quantized in-file sizes over 750kB 49 | - eliminate optional PNG chunks by default in all files (reduces file size) 50 | - converted to use of PNG filter = 0 for zopflipng compression of all quantized files (increases compression speed) 51 | - use automated detection of best PNG filter for zopfli compression in all non-quantized files (improves compression) 52 | - remove hidden colors behind alpha channel 0 in files that are not quantized due to low quality or increased file size following pngquant runs 53 | - added new macOS GUI animations with success and fail indicators (thanks Gary Jacobs!) 54 | - added logging of compression data and errors in macOS GUI and macOS right-click menu service tools in a new log file that is generated on the path `~/.crunch/crunch.log` 55 | - updated redirect to /dev/null in install-dependencies.sh compile script for POSIX compliance 56 | - refactored command line option parsing code (thanks Chris Clauss!) 57 | - added new bug reporting template 58 | 59 | ### v2.1.0 60 | 61 | - added automated detection of png image types through read of PNG file signatures 62 | - removed testing for *.png file extension to process files 63 | - added support for simple modification of pnquant and zopflipng paths that are used for optimization, convert to system PATH installed versions by modification of the Python script (issue #40) 64 | - bugfix for macOS GUI and right-click menu service failures with absolute file paths that contain multiple directory levels with space characters 65 | 66 | ### v2.0.2 67 | 68 | - bugfix for failed image optimization with macOS GUI and right-click menu service tools when spaces are included in absolute file paths (issue report #30) 69 | 70 | ### v2.0.1 71 | 72 | - bugfix for failed image optimization on pngquant execution that leads to file sizes below min acceptable (issue report #25) 73 | 74 | ### v2.0.0 75 | 76 | - new `crunch` executable that supports parallel PNG image optimization on *nix platforms (including macOS, Linux, and POSIX compliant application environments on Windows such as Cygwin) 77 | - parallel image processing support added to the Crunch macOS GUI application 78 | - parallel image processing support added to the Crunch Image(s) macOS right-click menu service 79 | - updated embedded pngquant executable to v2.11.7 (January 2018) with statically compiled library dependencies 80 | - updated embedded zopflipng executable to git tag `zopfli-1.0.1` 81 | - convert Crunch macOS GUI files to binary (from XML text files) 82 | - new make target and shell script for pngquant dependency source compile support for *nix platforms 83 | - new make target and shell script for zopflipng dependency source compile support for *nix platforms 84 | - new make target for pngquant dependency uninstall 85 | - new make target for zopflipng depdendency uninstall 86 | - new make target for command line executable install 87 | - new make target for command line executable uninstall 88 | - new make target for Python script testing 89 | - new make target for shell script linting 90 | - fix for make target install of macOS right-click service over a previous install 91 | 92 | ### v1.1.0 93 | 94 | - added a new macOS right click menu service for Crunch PNG image optimization named "Crunch Image(s)" 95 | - added Makefile with target support for macOS service installs and uninstalls 96 | - reformatted About menu text 97 | - added Upgrade documentation to the About menu 98 | - new source repository macOS service documentation, `docs/SERVICE.md` 99 | - repository documentation updates 100 | 101 | ### v1.0.1 102 | 103 | - fix for pngquant library dependencies bug ([Issue report #7](https://github.com/chrissimpkins/Crunch/issues/7)) 104 | 105 | ### v1.0.0 106 | 107 | - swanky new UI design with conversion to WebView and animations 108 | - new application icon colors 109 | - modified nib file to fix window size 110 | 111 | ### v0.10.0 112 | 113 | - updated zopflipng to commit https://github.com/google/zopfli/commit/64c6f362fefd56dccbf31906fdb3e31f6a6faf80 114 | - updated pngquant to commit https://github.com/kornelski/pngquant/commit/e50eb86a2f15f05da02fc4343a9d36ebb7d6d790 115 | 116 | ### v0.9.0 117 | 118 | - initial release -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | benchmark: 3 | cd benchmarks && $(MAKE) $@ 4 | 5 | build-dependencies: 6 | src/install-dependencies.sh 7 | 8 | build-macos-icns: 9 | rm -rf img/CrunchIcon.iconset 10 | mkdir img/CrunchIcon.iconset 11 | sips -z 16 16 img/Crunch-icon-3.png --out img/CrunchIcon.iconset/icon_16x16.png 12 | sips -z 32 32 img/Crunch-icon-3.png --out img/CrunchIcon.iconset/icon_16x16@2x.png 13 | sips -z 32 32 img/Crunch-icon-3.png --out img/CrunchIcon.iconset/icon_32x32.png 14 | sips -z 64 64 img/Crunch-icon-3.png --out img/CrunchIcon.iconset/icon_32x32@2x.png 15 | sips -z 128 128 img/Crunch-icon-3.png --out img/CrunchIcon.iconset/icon_128x128.png 16 | sips -z 256 256 img/Crunch-icon-3.png --out img/CrunchIcon.iconset/icon_128x128@2x.png 17 | sips -z 256 256 img/Crunch-icon-3.png --out img/CrunchIcon.iconset/icon_256x256.png 18 | sips -z 512 512 img/Crunch-icon-3.png --out img/CrunchIcon.iconset/icon_256x256@2x.png 19 | cd img && iconutil -c icns CrunchIcon.iconset 20 | 21 | build-macos-installer: 22 | # https://github.com/sindresorhus/create-dmg 23 | -rm bin/*.dmg 24 | -cd bin && create-dmg Crunch.app 25 | # create checksum file for the installer 26 | cd bin && mv Crunch*.dmg Crunch-Installer.dmg 27 | cd bin && shasum -a 256 Crunch-Installer.dmg > Crunch-Installer-checksum.txt 28 | 29 | clean: 30 | rm benchmarks/img/*-crunch.png 31 | 32 | dist: 33 | ./dmg-builder.sh 34 | 35 | dist-homebrew: 36 | cask-repair crunch 37 | 38 | install-executable: 39 | sudo cp src/crunch.py /usr/local/bin/crunch 40 | @echo " " 41 | @echo "[*] crunch executable installed on path /usr/local/bin/crunch" 42 | @echo "[*] Usage: $ crunch [image path 1]...[image path n]" 43 | 44 | install-macos-service: 45 | - sudo rm -rf ~/Library/Services/Crunch\ Image\(s\).workflow 46 | sudo cp -R service/Crunch\ Image\(s\).workflow ~/Library/Services/Crunch\ Image\(s\).workflow 47 | @echo " " 48 | @echo "[*] Crunch Image(s) macOS service installed on the path ~/Library/Services/Crunch\ Image\(s\).workflow" 49 | @echo " " 50 | @echo "[*] You can use the Crunch service by right clicking on one or more PNG files, then select Services > Crunch Image(s)" 51 | 52 | uninstall-dependencies: 53 | sudo rm -rf ~/pngquant 54 | sudo rm -rf ~/zopfli 55 | @echo " " 56 | @echo "[*] Dependency removal complete." 57 | 58 | uninstall-executable: 59 | sudo rm /usr/local/bin/crunch 60 | @echo " " 61 | @echo "[*] crunch executable uninstall complete." 62 | 63 | uninstall-macos-service: 64 | sudo rm -rf ~/Library/Services/Crunch\ Image\(s\).workflow 65 | @echo " " 66 | @echo "[*] The Crunch Image(s) macOS service was removed from your system" 67 | 68 | test-coverage: 69 | ./coverage.sh 70 | 71 | test-python: 72 | tox 73 | flake8 src/crunch.py 74 | 75 | test-shell: 76 | shellcheck --exclude=2046 src/*.sh 77 | 78 | test-valid-png-output: 79 | crunch testfiles/*.png 80 | pngcheck testfiles/*-crunch.png 81 | rm testfiles/*-crunch.png 82 | 83 | test: test-python test-shell test-valid-png-output 84 | 85 | 86 | .PHONY: benchmark build-dependencies install-executable install-macos-service uninstall-executable uninstall-macos-service test test-coverage test-python test-shell test-valid-png-output dist -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Crunch PNG file optimizationCrunch PNG file optimization 2 | Crunch PNG file optimization 3 | 4 | [![GitHub release](https://img.shields.io/github/release/chrissimpkins/Crunch.svg?style=flat-square)](https://github.com/chrissimpkins/Crunch/releases/latest) 5 | ![Linux Python CI](https://github.com/chrissimpkins/Crunch/workflows/Linux%20CI/badge.svg) 6 | ![macOS Python CI](https://github.com/chrissimpkins/Crunch/workflows/macOS%20CI/badge.svg) 7 | ![Lints](https://github.com/chrissimpkins/Crunch/workflows/Lints/badge.svg) 8 | ![Benchmarks](https://github.com/chrissimpkins/Crunch/workflows/Benchmarks/badge.svg) 9 | 10 | ## About 11 | 12 | Crunch is a tool for lossy PNG image file optimization. It combines selective bit depth, color type, and color palette reduction with zopfli DEFLATE compression algorithm encoding using the pngquant and zopflipng PNG optimization tools. This approach leads to a significant file size gain relative to lossless approaches at the expense of a relatively modest decrease in image quality (see [example images](#examples) below). 13 | 14 | [Continuous benchmark testing](https://github.com/chrissimpkins/Crunch/actions/workflows/crunch-bench.yml) is available in our GitHub Actions CI. Please see the benchmarks directory of this repository for details about the benchmarking approach and instructions on how to execute benchmarks locally on the reference images distributed in this repository or with your own image files. 15 | 16 | Crunch PNG image optimization is available through the following applications that are distributed in this repository: 17 | 18 | - [`crunch`](docs/EXECUTABLE.md) - a *nix command line executable that can be used on macOS, Linux, and Windows POSIX application deployment environments such as Cygwin or the Windows subsystem for Linux 19 | - [Crunch GUI](docs/MACOSGUI.md) - a native macOS drag and drop GUI tool 20 | - [Crunch Image(s)](docs/SERVICE.md) service - a macOS right-click menu service for PNG images selected in the Finder 21 | 22 | ## Installation and Usage 23 | 24 | Installation and usage documentation links for each of the Crunch applications are available below. 25 | 26 | ## `crunch` Command Line Executable 27 | 28 | The `crunch` command line executable can be installed with `make` or manually. Please see the [Install documentation](docs/EXECUTABLE.md#install). 29 | 30 | Enter paths to your PNG image files as arguments to the `crunch` executable. Please see the [Usage documentation](docs/EXECUTABLE.md#usage). 31 | 32 | ## Crunch macOS GUI Application 33 | 34 | The Crunch native macOS GUI application can be installed with Homebrew or the dmg installer that can be downloaded from the [repository releases](https://github.com/chrissimpkins/Crunch/releases/latest). Please see the [Install documentation](docs/MACOSGUI.md#install). 35 | 36 | Drag and drop one or more images on the application window to process your PNG files. Please see the [Usage documentation](docs/MACOSGUI.md#usage). 37 | 38 | ## Crunch Image(s) macOS Right-Click Menu Service 39 | 40 | The macOS right-click menu service "Crunch Image(s)" can be installed with `make` or manually by drag and drop in the macOS Finder. Please see the [Install documentation](docs/SERVICE.md). 41 | 42 | Select one or more PNG images in the Finder, right-click, and select the `Services > Crunch Image(s)` menu item to process your files. Please see the [Usage documentation](docs/SERVICE.md). 43 | 44 | ## Contents 45 | 46 | - [Examples](#examples) 47 | - [Photography Examples](#photography-examples) 48 | - [Illustration Examples](#illustration-examples) 49 | - [Issue Reporting](#issue-reporting) 50 | - [Licenses](#licenses) 51 | - [Contributing](#contributing) 52 | - [Acknowlegments](#acknowledgments) 53 | 54 | ## Examples 55 | 56 | The following examples demonstrate the benefits and disadvantages of the current iteration of Crunch's aggressive space saving optimization strategy. The optimized image files are updated at every Crunch release. In many cases, the PNG optimization decreases file size with an imperceptible impact on image quality. In some cases, degradation of image quality is visible. Visual confirmation of image quality is highly recommended with lossy optimization tools in production settings. 57 | 58 | ## Photography Examples 59 | 60 | ### Cat Image 61 | 62 | - Original Size: 583,398 bytes 63 | - Optimized Size: 196,085 bytes 64 | - DSSIM similarity score: 0.001383 65 | - Percent original size: 33.61% 66 | 67 | ##### Original 68 | 69 | cat example pre optimization 70 | 71 | ##### Optimized 72 | 73 | cat example post optimization 74 | 75 | ### Sun's Rays 76 | 77 | - Original Size: 138,272 78 | - Optimized Size: 66,593 79 | - DSSIM similarity score: 0.000920 80 | - Percent original size: 48.16% 81 | 82 | ##### Original 83 | 84 | sun rays example pre optimization 85 | 86 | ##### Optimized 87 | 88 | sun rays example pre optimization 89 | 90 | 91 | ### Prairie Image 92 | 93 | - Original Size: 196,794 bytes 94 | - Optimized Size: 77,965 bytes 95 | - DSSIM similarity score: 0.002923 96 | - Percent original size: 39.62% 97 | 98 | ##### Original 99 | 100 | prarie example pre optimization 101 | 102 | ##### Optimized 103 | 104 | prarie example post optimization 105 | 106 | 107 | 108 | ## Illustration Examples 109 | 110 | ### Robot Image 111 | 112 | - Original Size: 197,193 bytes 113 | - Optimized Size: 67,596 bytes 114 | - DSSIM similarity score: 0.003047 115 | - Percent original size: 34.28% 116 | 117 | ##### Original 118 | 119 | robot example pre optimization 120 | 121 | ##### Optimized 122 | 123 | robot example post optimization 124 | 125 | ### Color Circle Image 126 | 127 | - Original Size: 249,251 bytes 128 | - Optimized Size: 67,135 bytes 129 | - DSSIM similarity score: 0.002450 130 | - Percent original size: 26.93% 131 | 132 | ##### Original 133 | 134 | colors example pre optimization 135 | 136 | ##### Optimized 137 | 138 | colors example post optimization 139 | 140 | 141 | ### Flowers Image 142 | 143 | - Original Size: 440,126 bytes 144 | - Optimized Size: 196,962 bytes 145 | - DSSIM similarity score: 0.001013 146 | - Percent original size: 44.75% 147 | 148 | ##### Original 149 | 150 | 748 151 | 152 | ##### Optimized 153 | 154 | 748 155 | 156 | All images above were obtained from [Pixabay](https://pixabay.com) and are dedicated to the public domain under the [CC0 Public Domain Dedication](https://creativecommons.org/publicdomain/zero/1.0/). 157 | 158 | DSSIM testing was performed with v2.10.0 of the [kornelski/dssim tool](https://github.com/kornelski/dssim). 159 | 160 | ## Issue Reporting 161 | 162 | Have you identified a problem? Please [create a new issue report](https://github.com/chrissimpkins/Crunch/issues/new/choose) on the Github issue tracker so that we can address it. 163 | 164 | ## Licenses 165 | 166 | Crunch is licensed under the [MIT license](https://github.com/chrissimpkins/Crunch/blob/master/LICENSE.md). 167 | 168 | ### Embedded Software 169 | 170 | pngquant is licensed under the [Gnu General Public License, version 3](https://github.com/pornel/pngquant/blob/master/COPYRIGHT). The pngquant source code is available [here](https://github.com/pornel/pngquant). 171 | 172 | zopflipng is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). The upstream zopflipng source code is available [here](https://github.com/google/zopfli). The source for the modified zopflipng fork that is used in this project is available [here](https://github.com/chrissimpkins/zopfli). 173 | 174 | See the [LICENSE.md](LICENSE.md) document for details and additional licensing information for this project. 175 | 176 | ## Contributing 177 | 178 | Contributions to the project are warmly welcomed. Please suggest enhancements as new issue reports on this repository. Source contributors should fork the git repository and submit changes as a Github pull request. 179 | 180 | ## Acknowledgments 181 | 182 | Crunch is a simple tool that makes excellent, free, open source software built by others more accessible. The hard work on the optimization engines that run Crunch has been performed by: 183 | 184 | - Lode Vandevenne, Jyrki Alakuijala, and the [zopfli project contributors](https://github.com/google/zopfli/graphs/contributors) 185 | - Kornel Lesiński and the [pngquant project contributors](https://github.com/kornelski/pngquant/graphs/contributors) 186 | 187 | The fantastic macOS GUI animations were designed by [Gary Jacobs](https://github.com/garyjacobs). 188 | -------------------------------------------------------------------------------- /benchmarks/Makefile: -------------------------------------------------------------------------------- 1 | 2 | benchmark: 3 | cd img && /usr/bin/time -p crunch *.png 4 | cd img && python3 bench.py 5 | 6 | 7 | .PHONY: benchmark -------------------------------------------------------------------------------- /benchmarks/PngSuite.LICENSE: -------------------------------------------------------------------------------- 1 | PngSuite 2 | -------- 3 | 4 | Permission to use, copy, modify and distribute these images for any 5 | purpose and without fee is hereby granted. 6 | 7 | 8 | (c) Willem van Schaik, 1996, 2011 9 | -------------------------------------------------------------------------------- /benchmarks/README.md: -------------------------------------------------------------------------------- 1 | ## Crunch Benchmarks 2 | 3 | This directory includes reference PNG files from http://www.schaik.com/pngsuite/ for benchmarking Crunch PNG optimization. The image files in this directory are distributed under the [PngSuite.LICENSE](PngSuite.LICENSE). 4 | 5 | Benchmarks are performed against all PNG image files released in the http://www.schaik.com/pngsuite/PngSuite-2017jul19.tgz archive *except* the [reference corrupted files](http://www.schaik.com/pngsuite/#corrupted). 6 | 7 | The [results of continuous benchmark testing are available on Travis CI in the "Benchmarks" build](https://travis-ci.org/chrissimpkins/Crunch). 8 | 9 | ## How to Execute Benchmarks Locally 10 | 11 | Clone this repository, build the dependency tools, install the `crunch` executable, and run benchmarks with the following set of commands: 12 | 13 | ``` 14 | $ git clone https://github.com/chrissimpkins/Crunch.git 15 | $ make build-dependencies 16 | $ make install-executable 17 | $ make benchmark 18 | ``` 19 | 20 | You will see the results in your terminal. 21 | 22 | The optimized image files remain in the `benchmarks/img` subdirectory after execution for your review. To clean the optimized files, use the following command: 23 | 24 | ``` 25 | $ make clean 26 | ``` 27 | 28 | ## How to Benchmark With Other Image Sets 29 | 30 | The Python 3 [`bench.py` script](img/bench.py) is portable. Download the script, drop it into the directory with your image files, execute `crunch` on all images in the directory, and execute the benchmark script with: 31 | 32 | ``` 33 | $ python3 bench.py 34 | ``` 35 | 36 | **Note**: The benchmarking script has a couple of requirements. Optimize **all files** that are included in the directory before you execute the script. Keep the original `*-crunch.png` file paths for all optimized image files. 37 | -------------------------------------------------------------------------------- /benchmarks/img/basi0g01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi0g01.png -------------------------------------------------------------------------------- /benchmarks/img/basi0g02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi0g02.png -------------------------------------------------------------------------------- /benchmarks/img/basi0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi0g04.png -------------------------------------------------------------------------------- /benchmarks/img/basi0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi0g08.png -------------------------------------------------------------------------------- /benchmarks/img/basi0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi0g16.png -------------------------------------------------------------------------------- /benchmarks/img/basi2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi2c08.png -------------------------------------------------------------------------------- /benchmarks/img/basi2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi2c16.png -------------------------------------------------------------------------------- /benchmarks/img/basi3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi3p01.png -------------------------------------------------------------------------------- /benchmarks/img/basi3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi3p02.png -------------------------------------------------------------------------------- /benchmarks/img/basi3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi3p04.png -------------------------------------------------------------------------------- /benchmarks/img/basi3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi3p08.png -------------------------------------------------------------------------------- /benchmarks/img/basi4a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi4a08.png -------------------------------------------------------------------------------- /benchmarks/img/basi4a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi4a16.png -------------------------------------------------------------------------------- /benchmarks/img/basi6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi6a08.png -------------------------------------------------------------------------------- /benchmarks/img/basi6a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basi6a16.png -------------------------------------------------------------------------------- /benchmarks/img/basn0g01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn0g01.png -------------------------------------------------------------------------------- /benchmarks/img/basn0g02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn0g02.png -------------------------------------------------------------------------------- /benchmarks/img/basn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn0g04.png -------------------------------------------------------------------------------- /benchmarks/img/basn0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn0g08.png -------------------------------------------------------------------------------- /benchmarks/img/basn0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn0g16.png -------------------------------------------------------------------------------- /benchmarks/img/basn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn2c08.png -------------------------------------------------------------------------------- /benchmarks/img/basn2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn2c16.png -------------------------------------------------------------------------------- /benchmarks/img/basn3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn3p01.png -------------------------------------------------------------------------------- /benchmarks/img/basn3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn3p02.png -------------------------------------------------------------------------------- /benchmarks/img/basn3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn3p04.png -------------------------------------------------------------------------------- /benchmarks/img/basn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn3p08.png -------------------------------------------------------------------------------- /benchmarks/img/basn4a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn4a08.png -------------------------------------------------------------------------------- /benchmarks/img/basn4a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn4a16.png -------------------------------------------------------------------------------- /benchmarks/img/basn6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn6a08.png -------------------------------------------------------------------------------- /benchmarks/img/basn6a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/basn6a16.png -------------------------------------------------------------------------------- /benchmarks/img/bench.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # ---------------------------------------- 4 | # 5 | # Copyright (c) 2019 Christopher Simpkins 6 | # MIT license 7 | # 8 | # --------------------------------------- 9 | 10 | 11 | import glob 12 | import os 13 | 14 | 15 | def grouped(iterable, n): 16 | return zip(*[iter(iterable)] * n) 17 | 18 | 19 | paths = sorted(glob.glob("*.png")) 20 | 21 | percent_list = [] 22 | pre_size_list = [] 23 | post_size_list = [] 24 | 25 | for path_a, path_b in grouped(paths, 2): 26 | if "-crunch" in path_a: 27 | post_path = path_a 28 | pre_path = path_b 29 | else: 30 | post_path = path_b 31 | pre_path = path_a 32 | 33 | # assert that we are testing the correct pairs of files 34 | assert f"{pre_path[:-4]}-crunch.png" == post_path 35 | 36 | pre_size = os.path.getsize(pre_path) 37 | post_size = os.path.getsize(post_path) 38 | percent_size = (post_size / pre_size) * 100 39 | 40 | percent_list.append(percent_size) 41 | pre_size_list.append(pre_size) 42 | post_size_list.append(post_size) 43 | 44 | print(f"{post_path}: {percent_size:.2f}%") 45 | 46 | 47 | mean = sum(percent_list) / len(percent_list) 48 | total_initial_size = sum(pre_size_list) 49 | total_final_size = sum(post_size_list) 50 | delta = total_initial_size - total_final_size 51 | 52 | print(f"\nInitial:\t{total_initial_size:>8} B") 53 | print(f"Final: \t{total_final_size:>8} B") 54 | print(f"Delta: -{delta} B") 55 | print(f"Mean: {mean:.2f}%") 56 | try: 57 | import numpy as np 58 | 59 | a = np.array(percent_list) 60 | stdev = np.std(a, dtype=np.float64) 61 | print(f"SD: {stdev:.2f}%") 62 | except Exception: 63 | pass 64 | -------------------------------------------------------------------------------- /benchmarks/img/bgai4a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/bgai4a08.png -------------------------------------------------------------------------------- /benchmarks/img/bgai4a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/bgai4a16.png -------------------------------------------------------------------------------- /benchmarks/img/bgan6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/bgan6a08.png -------------------------------------------------------------------------------- /benchmarks/img/bgan6a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/bgan6a16.png -------------------------------------------------------------------------------- /benchmarks/img/bgbn4a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/bgbn4a08.png -------------------------------------------------------------------------------- /benchmarks/img/bggn4a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/bggn4a16.png -------------------------------------------------------------------------------- /benchmarks/img/bgwn6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/bgwn6a08.png -------------------------------------------------------------------------------- /benchmarks/img/bgyn6a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/bgyn6a16.png -------------------------------------------------------------------------------- /benchmarks/img/ccwn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/ccwn2c08.png -------------------------------------------------------------------------------- /benchmarks/img/ccwn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/ccwn3p08.png -------------------------------------------------------------------------------- /benchmarks/img/cdfn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cdfn2c08.png -------------------------------------------------------------------------------- /benchmarks/img/cdhn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cdhn2c08.png -------------------------------------------------------------------------------- /benchmarks/img/cdsn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cdsn2c08.png -------------------------------------------------------------------------------- /benchmarks/img/cdun2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cdun2c08.png -------------------------------------------------------------------------------- /benchmarks/img/ch1n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/ch1n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/ch2n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/ch2n3p08.png -------------------------------------------------------------------------------- /benchmarks/img/cm0n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cm0n0g04.png -------------------------------------------------------------------------------- /benchmarks/img/cm7n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cm7n0g04.png -------------------------------------------------------------------------------- /benchmarks/img/cm9n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cm9n0g04.png -------------------------------------------------------------------------------- /benchmarks/img/cs3n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cs3n2c16.png -------------------------------------------------------------------------------- /benchmarks/img/cs3n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cs3n3p08.png -------------------------------------------------------------------------------- /benchmarks/img/cs5n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cs5n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/cs5n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cs5n3p08.png -------------------------------------------------------------------------------- /benchmarks/img/cs8n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cs8n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/cs8n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cs8n3p08.png -------------------------------------------------------------------------------- /benchmarks/img/ct0n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/ct0n0g04.png -------------------------------------------------------------------------------- /benchmarks/img/ct1n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/ct1n0g04.png -------------------------------------------------------------------------------- /benchmarks/img/cten0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cten0g04.png -------------------------------------------------------------------------------- /benchmarks/img/ctfn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/ctfn0g04.png -------------------------------------------------------------------------------- /benchmarks/img/ctgn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/ctgn0g04.png -------------------------------------------------------------------------------- /benchmarks/img/cthn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/cthn0g04.png -------------------------------------------------------------------------------- /benchmarks/img/ctjn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/ctjn0g04.png -------------------------------------------------------------------------------- /benchmarks/img/ctzn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/ctzn0g04.png -------------------------------------------------------------------------------- /benchmarks/img/exif2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/exif2c08.png -------------------------------------------------------------------------------- /benchmarks/img/f00n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/f00n0g08.png -------------------------------------------------------------------------------- /benchmarks/img/f00n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/f00n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/f01n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/f01n0g08.png -------------------------------------------------------------------------------- /benchmarks/img/f01n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/f01n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/f02n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/f02n0g08.png -------------------------------------------------------------------------------- /benchmarks/img/f02n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/f02n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/f03n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/f03n0g08.png -------------------------------------------------------------------------------- /benchmarks/img/f03n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/f03n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/f04n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/f04n0g08.png -------------------------------------------------------------------------------- /benchmarks/img/f04n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/f04n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/f99n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/f99n0g04.png -------------------------------------------------------------------------------- /benchmarks/img/g03n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g03n0g16.png -------------------------------------------------------------------------------- /benchmarks/img/g03n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g03n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/g03n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g03n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/g04n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g04n0g16.png -------------------------------------------------------------------------------- /benchmarks/img/g04n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g04n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/g04n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g04n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/g05n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g05n0g16.png -------------------------------------------------------------------------------- /benchmarks/img/g05n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g05n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/g05n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g05n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/g07n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g07n0g16.png -------------------------------------------------------------------------------- /benchmarks/img/g07n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g07n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/g07n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g07n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/g10n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g10n0g16.png -------------------------------------------------------------------------------- /benchmarks/img/g10n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g10n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/g10n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g10n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/g25n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g25n0g16.png -------------------------------------------------------------------------------- /benchmarks/img/g25n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g25n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/g25n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/g25n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/oi1n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/oi1n0g16.png -------------------------------------------------------------------------------- /benchmarks/img/oi1n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/oi1n2c16.png -------------------------------------------------------------------------------- /benchmarks/img/oi2n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/oi2n0g16.png -------------------------------------------------------------------------------- /benchmarks/img/oi2n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/oi2n2c16.png -------------------------------------------------------------------------------- /benchmarks/img/oi4n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/oi4n0g16.png -------------------------------------------------------------------------------- /benchmarks/img/oi4n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/oi4n2c16.png -------------------------------------------------------------------------------- /benchmarks/img/oi9n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/oi9n0g16.png -------------------------------------------------------------------------------- /benchmarks/img/oi9n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/oi9n2c16.png -------------------------------------------------------------------------------- /benchmarks/img/pp0n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/pp0n2c16.png -------------------------------------------------------------------------------- /benchmarks/img/pp0n6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/pp0n6a08.png -------------------------------------------------------------------------------- /benchmarks/img/ps1n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/ps1n0g08.png -------------------------------------------------------------------------------- /benchmarks/img/ps1n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/ps1n2c16.png -------------------------------------------------------------------------------- /benchmarks/img/ps2n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/ps2n0g08.png -------------------------------------------------------------------------------- /benchmarks/img/ps2n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/ps2n2c16.png -------------------------------------------------------------------------------- /benchmarks/img/s01i3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s01i3p01.png -------------------------------------------------------------------------------- /benchmarks/img/s01n3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s01n3p01.png -------------------------------------------------------------------------------- /benchmarks/img/s02i3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s02i3p01.png -------------------------------------------------------------------------------- /benchmarks/img/s02n3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s02n3p01.png -------------------------------------------------------------------------------- /benchmarks/img/s03i3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s03i3p01.png -------------------------------------------------------------------------------- /benchmarks/img/s03n3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s03n3p01.png -------------------------------------------------------------------------------- /benchmarks/img/s04i3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s04i3p01.png -------------------------------------------------------------------------------- /benchmarks/img/s04n3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s04n3p01.png -------------------------------------------------------------------------------- /benchmarks/img/s05i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s05i3p02.png -------------------------------------------------------------------------------- /benchmarks/img/s05n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s05n3p02.png -------------------------------------------------------------------------------- /benchmarks/img/s06i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s06i3p02.png -------------------------------------------------------------------------------- /benchmarks/img/s06n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s06n3p02.png -------------------------------------------------------------------------------- /benchmarks/img/s07i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s07i3p02.png -------------------------------------------------------------------------------- /benchmarks/img/s07n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s07n3p02.png -------------------------------------------------------------------------------- /benchmarks/img/s08i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s08i3p02.png -------------------------------------------------------------------------------- /benchmarks/img/s08n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s08n3p02.png -------------------------------------------------------------------------------- /benchmarks/img/s09i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s09i3p02.png -------------------------------------------------------------------------------- /benchmarks/img/s09n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s09n3p02.png -------------------------------------------------------------------------------- /benchmarks/img/s32i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s32i3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s32n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s32n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s33i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s33i3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s33n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s33n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s34i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s34i3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s34n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s34n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s35i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s35i3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s35n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s35n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s36i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s36i3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s36n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s36n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s37i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s37i3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s37n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s37n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s38i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s38i3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s38n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s38n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s39i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s39i3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s39n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s39n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s40i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s40i3p04.png -------------------------------------------------------------------------------- /benchmarks/img/s40n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/s40n3p04.png -------------------------------------------------------------------------------- /benchmarks/img/tbbn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/tbbn0g04.png -------------------------------------------------------------------------------- /benchmarks/img/tbbn2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/tbbn2c16.png -------------------------------------------------------------------------------- /benchmarks/img/tbbn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/tbbn3p08.png -------------------------------------------------------------------------------- /benchmarks/img/tbgn2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/tbgn2c16.png -------------------------------------------------------------------------------- /benchmarks/img/tbgn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/tbgn3p08.png -------------------------------------------------------------------------------- /benchmarks/img/tbrn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/tbrn2c08.png -------------------------------------------------------------------------------- /benchmarks/img/tbwn0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/tbwn0g16.png -------------------------------------------------------------------------------- /benchmarks/img/tbwn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/tbwn3p08.png -------------------------------------------------------------------------------- /benchmarks/img/tbyn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/tbyn3p08.png -------------------------------------------------------------------------------- /benchmarks/img/tm3n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/tm3n3p02.png -------------------------------------------------------------------------------- /benchmarks/img/tp0n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/tp0n0g08.png -------------------------------------------------------------------------------- /benchmarks/img/tp0n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/tp0n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/tp0n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/tp0n3p08.png -------------------------------------------------------------------------------- /benchmarks/img/tp1n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/tp1n3p08.png -------------------------------------------------------------------------------- /benchmarks/img/z00n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/z00n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/z03n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/z03n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/z06n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/z06n2c08.png -------------------------------------------------------------------------------- /benchmarks/img/z09n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/benchmarks/img/z09n2c08.png -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/bin/Crunch.app/Contents/Info.plist -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/MacOS/Crunch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/bin/Crunch.app/Contents/MacOS/Crunch -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/01-Load-Up@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/bin/Crunch.app/Contents/Resources/01-Load-Up@2x.gif -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/02-Waiting@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/bin/Crunch.app/Contents/Resources/02-Waiting@2x.gif -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/03-Crunching@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/bin/Crunch.app/Contents/Resources/03-Crunching@2x.gif -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/04-Error@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/bin/Crunch.app/Contents/Resources/04-Error@2x.gif -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/04-Success@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/bin/Crunch.app/Contents/Resources/04-Success@2x.gif -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/AppSettings.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/bin/Crunch.app/Contents/Resources/AppSettings.plist -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/Credits.html: -------------------------------------------------------------------------------- 1 |
3 |

A PNG file optimization tool built on pngquant and zopflipng 4 |

16 |

17 | 18 |

Upgrade

19 |

The latest release can be found 20 | 21 | here 22 | . A 23 | 24 | changelog 25 | is available for review of changes to the application since your installed version was released. 26 |

27 |

To upgrade with Homebrew, enter the following command in your terminal:

28 |

29 | $ brew cask upgrade 30 |

31 | 32 |

Other Crunch Tools

33 |

The Crunch image optimization approach is also available through a macOS right-click menu service and a command line 34 | executable. See the 35 | 36 | documentation on the repository README 37 | for installation and usage details. 38 |

39 | 40 |

Issue Reporting

41 |

Please use 42 | 43 | the issue tracker on the Github repository 44 | to report any problems that you have with the Crunch application.

45 | 46 |

Embedded Software Licenses

47 | 60 |

61 |

Animations

62 |

The Crunch GUI animations were designed by 63 | Gary Jacobs and are licensed under the MIT License.

64 |
-------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/MainMenu.nib/keyedobjects.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/bin/Crunch.app/Contents/Resources/MainMenu.nib/keyedobjects.nib -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/appIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/bin/Crunch.app/Contents/Resources/appIcon.icns -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/clear.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Start 7 | 8 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/complete-error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Start 7 | 8 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/complete-success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Start 7 | 8 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/crunch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # ================================================================== 4 | # crunch 5 | # A PNG file optimization tool built on pngquant and zopflipng 6 | # 7 | # Copyright 2019 Christopher Simpkins 8 | # MIT License 9 | # 10 | # Source Repository: https://github.com/chrissimpkins/Crunch 11 | # ================================================================== 12 | 13 | import sys 14 | import os 15 | import shutil 16 | import struct 17 | import subprocess 18 | import time 19 | from subprocess import CalledProcessError 20 | 21 | from multiprocessing import Lock, Pool, cpu_count 22 | 23 | # Locks 24 | stdstream_lock = Lock() 25 | logging_lock = Lock() 26 | 27 | # Application Constants 28 | VERSION = "5.0.0" 29 | VERSION_STRING = "crunch v" + VERSION 30 | 31 | # Processor Constant 32 | # - Modify this to an integer value if you want to fix the number of 33 | # processes spawned during execution. The process number is 34 | # automatically defined during source execution when this is defined 35 | # as a value of 0 36 | PROCESSES = 0 37 | 38 | # Dependency Path Constants for Command Line Executable 39 | # - Redefine these path strings to use system-installed versions of 40 | # pngquant and zopflipng (e.g. to "/usr/local/bin/[executable]") 41 | PNGQUANT_CLI_PATH = os.path.join(os.path.expanduser("~"), "pngquant", "pngquant") 42 | ZOPFLIPNG_CLI_PATH = os.path.join(os.path.expanduser("~"), "zopfli", "zopflipng") 43 | 44 | # Crunch Directory (dot directory in $HOME) 45 | CRUNCH_DOT_DIRECTORY = os.path.join(os.path.expanduser("~"), ".crunch") 46 | 47 | # Log File Path Constants 48 | LOGFILE_PATH = os.path.join(CRUNCH_DOT_DIRECTORY, "crunch.log") 49 | 50 | HELP_STRING = """ 51 | ================================================== 52 | crunch 53 | Copyright 2019 Christopher Simpkins 54 | MIT License 55 | 56 | Source: https://github.com/chrissimpkins/Crunch 57 | ================================================== 58 | 59 | crunch is a command line executable that performs lossy optimization of one or more png image files with pngquant and zopflipng. 60 | 61 | Usage: 62 | $ crunch [image path 1]...[image path n] 63 | 64 | Options: 65 | --help, -h application help 66 | --usage application usage 67 | --version, -v application version 68 | """ 69 | 70 | USAGE = "$ crunch [image path 1]...[image path n]" 71 | 72 | 73 | def main(argv): 74 | 75 | # Create the Crunch dot directory in $HOME if it does not exist 76 | # Only used for macOS GUI and macOS right-click menu service execution 77 | if len(argv) > 0 and argv[0] in ("--gui", "--service"): 78 | if not os.path.isdir(CRUNCH_DOT_DIRECTORY): 79 | os.makedirs(CRUNCH_DOT_DIRECTORY) 80 | # clear the text in the log file before every script execution 81 | # logging is only maintained for the last execution of the script 82 | open(LOGFILE_PATH, "w").close() 83 | 84 | # //////////////////////// 85 | # ANSI COLOR DEFINITIONS 86 | # //////////////////////// 87 | if not is_gui(sys.argv): 88 | ERROR_STRING = "[ " + format_ansi_red("!") + " ]" 89 | else: 90 | ERROR_STRING = "[ ! ]" 91 | 92 | # ////////////////////////////////// 93 | # CONFIRM ARGUMENT PRESENT 94 | # ////////////////////////////////// 95 | 96 | if len(argv) == 0: 97 | sys.stderr.write( 98 | ERROR_STRING + " Please include one or more paths to PNG image files as " 99 | "arguments to the script." + os.linesep 100 | ) 101 | sys.exit(1) 102 | 103 | # ////////////////////////////////////// 104 | # HELP, USAGE, VERSION option handling 105 | # ////////////////////////////////////// 106 | 107 | if argv[0] in ("-v", "--version"): 108 | print(VERSION_STRING) 109 | sys.exit(0) 110 | elif argv[0] in ("-h", "--help"): 111 | print(HELP_STRING) 112 | sys.exit(0) 113 | elif argv[0] == "--usage": 114 | print(USAGE) 115 | sys.exit(0) 116 | 117 | # //////////////////////// 118 | # DEFINE DEPENDENCY PATHS 119 | # //////////////////////// 120 | PNGQUANT_EXE_PATH = get_pngquant_path() 121 | ZOPFLIPNG_EXE_PATH = get_zopflipng_path() 122 | 123 | # //////////////////// 124 | # PARSE PNG_PATH_LIST 125 | # //////////////////// 126 | 127 | if is_gui(argv): 128 | png_path_list = argv[1:] 129 | else: 130 | png_path_list = argv 131 | 132 | # ////////////////////////////////// 133 | # COMMAND LINE ERROR HANDLING 134 | # ////////////////////////////////// 135 | 136 | NOTPNG_ERROR_FOUND = False 137 | for png_path in png_path_list: 138 | # Not a file test 139 | if not os.path.isfile(png_path): # is not an existing file 140 | sys.stderr.write( 141 | ERROR_STRING 142 | + " '" 143 | + png_path 144 | + "' does not appear to be a valid path to a PNG file" 145 | + os.linesep 146 | ) 147 | sys.exit(1) # not a file, abort immediately 148 | # PNG validity test 149 | if not is_valid_png(png_path): 150 | sys.stderr.write( 151 | ERROR_STRING 152 | + " '" 153 | + png_path 154 | + "' is not a valid PNG file." 155 | + os.linesep 156 | ) 157 | if is_gui(argv): 158 | log_error(png_path + " is not a valid PNG file.") 159 | NOTPNG_ERROR_FOUND = True 160 | 161 | # Exit after checking all file requests and reporting on all invalid file paths (above) 162 | if NOTPNG_ERROR_FOUND is True: 163 | sys.stderr.write( 164 | "The request was not executed successfully. Please try again with one or more valid PNG files." 165 | + os.linesep 166 | ) 167 | if is_gui(argv): 168 | log_error( 169 | "The request was not executed successfully. Please try again with one or more valid PNG files." 170 | ) 171 | sys.exit(1) 172 | 173 | # Dependency error handling 174 | if not os.path.exists(PNGQUANT_EXE_PATH): 175 | sys.stderr.write( 176 | ERROR_STRING 177 | + " pngquant executable was not identified on path '" 178 | + PNGQUANT_EXE_PATH 179 | + "'" 180 | + os.linesep 181 | ) 182 | if is_gui(argv): 183 | log_error( 184 | "pngquant was not found on the expected path " + PNGQUANT_EXE_PATH 185 | ) 186 | sys.exit(1) 187 | elif not os.path.exists(ZOPFLIPNG_EXE_PATH): 188 | sys.stderr.write( 189 | ERROR_STRING 190 | + " zopflipng executable was not identified on path '" 191 | + ZOPFLIPNG_EXE_PATH 192 | + "'" 193 | + os.linesep 194 | ) 195 | if is_gui(argv): 196 | log_error( 197 | "zopflipng was not found on the expected path " + ZOPFLIPNG_EXE_PATH 198 | ) 199 | sys.exit(1) 200 | 201 | # //////////////////////////////////// 202 | # OPTIMIZATION PROCESSING 203 | # //////////////////////////////////// 204 | print("Crunching ...") 205 | 206 | if len(png_path_list) == 1: 207 | # there is only one PNG file, skip spawning of processes and just optimize it 208 | optimize_png(png_path_list[0]) 209 | else: 210 | processes = PROCESSES 211 | # if not defined by user, start by defining spawned processes as number of available cores 212 | if processes == 0: 213 | processes = cpu_count() 214 | 215 | # if total cores available is greater than number of files requested, limit to the latter number 216 | if processes > len(png_path_list): 217 | processes = len(png_path_list) 218 | 219 | print( 220 | "Spawning " 221 | + str(processes) 222 | + " processes to optimize " 223 | + str(len(png_path_list)) 224 | + " image files..." 225 | ) 226 | p = Pool(processes) 227 | try: 228 | p.map(optimize_png, png_path_list) 229 | except Exception as e: 230 | stdstream_lock.acquire() 231 | sys.stderr.write("-----" + os.linesep) 232 | sys.stderr.write( 233 | ERROR_STRING 234 | + " Error detected during execution of the request." 235 | + os.linesep 236 | ) 237 | sys.stderr.write(str(e) + os.linesep) 238 | stdstream_lock.release() 239 | if is_gui(argv): 240 | log_error(str(e)) 241 | sys.exit(1) 242 | 243 | # end of successful processing, exit code 0 244 | if is_gui(argv): 245 | log_info("Crunch execution ended.") 246 | sys.exit(0) 247 | 248 | 249 | # /////////////////////// 250 | # FUNCTION DEFINITIONS 251 | # /////////////////////// 252 | 253 | 254 | def optimize_png(png_path): 255 | img = ImageFile(png_path) 256 | 257 | # define pngquant and zopflipng paths 258 | PNGQUANT_EXE_PATH = get_pngquant_path() 259 | ZOPFLIPNG_EXE_PATH = get_zopflipng_path() 260 | 261 | # //////////////////////// 262 | # ANSI COLOR DEFINITIONS 263 | # //////////////////////// 264 | if not is_gui(sys.argv): 265 | ERROR_STRING = "[ " + format_ansi_red("!") + " ]" 266 | else: 267 | ERROR_STRING = "[ ! ]" 268 | 269 | # -------------- 270 | # pngquant stage 271 | # -------------- 272 | pngquant_options = ( 273 | " --quality=80-98 --skip-if-larger --force --strip --speed 1 --ext -crunch.png " 274 | ) 275 | pngquant_command = ( 276 | PNGQUANT_EXE_PATH + pngquant_options + shellquote(img.pre_filepath) 277 | ) 278 | try: 279 | subprocess.check_output(pngquant_command, stderr=subprocess.STDOUT, shell=True) 280 | except CalledProcessError as cpe: 281 | if cpe.returncode == 98: 282 | # this is the status code when file size increases with execution of pngquant. 283 | # ignore at this stage, original file copied at beginning of zopflipng processing 284 | # below if it is not present due to these errors 285 | pass 286 | elif cpe.returncode == 99: 287 | # this is the status code when the image quality falls below the set min value 288 | # ignore at this stage, original lfile copied at beginning of zopflipng processing 289 | # below if it is not present to these errors 290 | pass 291 | else: 292 | stdstream_lock.acquire() 293 | sys.stderr.write( 294 | ERROR_STRING 295 | + " " 296 | + img.pre_filepath 297 | + " processing failed at the pngquant stage." 298 | + os.linesep 299 | ) 300 | stdstream_lock.release() 301 | if is_gui(sys.argv): 302 | log_error( 303 | img.pre_filepath 304 | + " processing failed at the pngquant stage. " 305 | + os.linesep 306 | + str(cpe) 307 | ) 308 | return None 309 | else: 310 | raise cpe 311 | except Exception as e: 312 | if is_gui(sys.argv): 313 | log_error( 314 | img.pre_filepath 315 | + " processing failed at the pngquant stage. " 316 | + os.linesep 317 | + str(e) 318 | ) 319 | return None 320 | else: 321 | raise e 322 | 323 | # --------------- 324 | # zopflipng stage 325 | # --------------- 326 | # use --filters=0 by default for quantized PNG files (based upon testing by CS) 327 | zopflipng_options = " -y --filters=0 " 328 | # confirm that a file with proper path was generated by pngquant 329 | # pngquant does not write expected file path if the file was larger after processing 330 | if not os.path.exists(img.post_filepath): 331 | shutil.copy(img.pre_filepath, img.post_filepath) 332 | # If pngquant did not quantize the file, permit zopflipng to attempt compression with mulitple 333 | # filters. This achieves better compression than the default approach for non-quantized PNG 334 | # files, but takes significantly longer (based upon testing by CS) 335 | zopflipng_options = " -y --lossy_transparent " 336 | zopflipng_command = ( 337 | ZOPFLIPNG_EXE_PATH 338 | + zopflipng_options 339 | + shellquote(img.post_filepath) 340 | + " " 341 | + shellquote(img.post_filepath) 342 | ) 343 | try: 344 | subprocess.check_output(zopflipng_command, stderr=subprocess.STDOUT, shell=True) 345 | except CalledProcessError as cpe: 346 | stdstream_lock.acquire() 347 | sys.stderr.write( 348 | ERROR_STRING 349 | + " " 350 | + img.pre_filepath 351 | + " processing failed at the zopflipng stage." 352 | + os.linesep 353 | ) 354 | stdstream_lock.release() 355 | if is_gui(sys.argv): 356 | log_error( 357 | img.pre_filepath 358 | + " processing failed at the zopflipng stage. " 359 | + os.linesep 360 | + str(cpe) 361 | ) 362 | return None 363 | else: 364 | raise cpe 365 | except Exception as e: 366 | if is_gui(sys.argv): 367 | log_error( 368 | img.pre_filepath 369 | + " processing failed at the pngquant stage. " 370 | + os.linesep 371 | + str(e) 372 | ) 373 | return None 374 | else: 375 | raise e 376 | 377 | # Check file size post-optimization and report comparison with pre-optimization file 378 | img.get_post_filesize() 379 | percent = img.get_compression_percent() 380 | percent_string = "{0:.2f}%".format(percent) 381 | # if compression occurred, color the percent string green 382 | # otherwise, leave it default text color 383 | if not is_gui(sys.argv) and percent < 100: 384 | percent_string = format_ansi_green(percent_string) 385 | 386 | # report percent original file size / post file path / size (bytes) to stdout (command line executable) 387 | stdstream_lock.acquire() 388 | print( 389 | "[ " 390 | + percent_string 391 | + " ] " 392 | + img.post_filepath 393 | + " (" 394 | + str(img.post_size) 395 | + " bytes)" 396 | ) 397 | stdstream_lock.release() 398 | 399 | # report percent original file size / post file path / size (bytes) to log file (macOS GUI + right-click service) 400 | if is_gui(sys.argv): 401 | log_info( 402 | "[ " 403 | + percent_string 404 | + " ] " 405 | + img.post_filepath 406 | + " (" 407 | + str(img.post_size) 408 | + " bytes)" 409 | ) 410 | 411 | 412 | # ----------- 413 | # Utilities 414 | # ----------- 415 | 416 | 417 | def fix_filepath_args(args): 418 | arg_list = [] 419 | parsed_filepath = "" 420 | for arg in args: 421 | if arg[0] == "-": 422 | # add command line options 423 | arg_list.append(arg) 424 | elif len(arg) > 2 and "." in arg[1:]: 425 | # if format is `\w+\.\w+`, then this is a filename, not directory 426 | # this is the end of a filepath string that may have had 427 | # spaces in directories prior to this level. Let's recreate 428 | # the entire original path 429 | filepath = parsed_filepath + arg 430 | arg_list.append(filepath) 431 | # reset the temp string that is used to reconstruct the filepaths 432 | parsed_filepath = "" 433 | else: 434 | # if the argument does not end with a .png, then there must have 435 | # been a space in the directory paths, let's add it back 436 | parsed_filepath = parsed_filepath + arg + " " 437 | # return new argument list with fixed filepaths to calling code 438 | return arg_list 439 | 440 | 441 | def get_pngquant_path(): 442 | if sys.argv[1] == "--gui": 443 | return "./pngquant" 444 | elif sys.argv[1] == "--service": 445 | return "/Applications/Crunch.app/Contents/Resources/pngquant" 446 | else: 447 | return PNGQUANT_CLI_PATH 448 | 449 | 450 | def get_zopflipng_path(): 451 | if sys.argv[1] == "--gui": 452 | return "./zopflipng" 453 | elif sys.argv[1] == "--service": 454 | return "/Applications/Crunch.app/Contents/Resources/zopflipng" 455 | else: 456 | return ZOPFLIPNG_CLI_PATH 457 | 458 | 459 | def is_gui(arglist): 460 | return "--gui" in arglist or "--service" in arglist 461 | 462 | 463 | def is_valid_png(filepath): 464 | # The PNG byte signature (https://www.w3.org/TR/PNG/#5PNG-file-signature) 465 | expected_signature = struct.pack("8B", 137, 80, 78, 71, 13, 10, 26, 10) 466 | # open the file and read first 8 bytes 467 | with open(filepath, "rb") as filer: 468 | signature = filer.read(8) 469 | # return boolean test result for first eight bytes == expected PNG byte signature 470 | return signature == expected_signature 471 | 472 | 473 | def log_error(errmsg): 474 | current_time = time.strftime("%m-%d-%y %H:%M:%S") 475 | logging_lock.acquire() 476 | with open(LOGFILE_PATH, "a") as filewriter: 477 | filewriter.write(current_time + "\tERROR\t" + errmsg + os.linesep) 478 | filewriter.flush() 479 | os.fsync(filewriter.fileno()) 480 | logging_lock.release() 481 | 482 | 483 | def log_info(infomsg): 484 | current_time = time.strftime("%m-%d-%y %H:%M:%S") 485 | logging_lock.acquire() 486 | with open(LOGFILE_PATH, "a") as filewriter: 487 | filewriter.write(current_time + "\tINFO\t" + infomsg + os.linesep) 488 | filewriter.flush() 489 | os.fsync(filewriter.fileno()) 490 | logging_lock.release() 491 | return None 492 | 493 | 494 | def shellquote(filepath): 495 | return "'" + filepath.replace("'", "'\\''") + "'" 496 | 497 | 498 | def format_ansi_red(text): 499 | if sys.stdout.isatty(): 500 | return "\033[0;31m" + text + "\033[0m" 501 | else: 502 | return text 503 | 504 | 505 | def format_ansi_green(text): 506 | if sys.stdout.isatty(): 507 | return "\033[0;32m" + text + "\033[0m" 508 | else: 509 | return text 510 | 511 | 512 | # /////////////////////// 513 | # OBJECT DEFINITIONS 514 | # /////////////////////// 515 | 516 | 517 | class ImageFile(object): 518 | def __init__(self, filepath): 519 | self.pre_filepath = filepath 520 | self.post_filepath = self._get_post_filepath() 521 | self.pre_size = self._get_filesize(self.pre_filepath) 522 | self.post_size = 0 523 | 524 | def _get_filesize(self, file_path): 525 | return os.path.getsize(file_path) 526 | 527 | def _get_post_filepath(self): 528 | path, extension = os.path.splitext(self.pre_filepath) 529 | return path + "-crunch" + extension 530 | 531 | def get_post_filesize(self): 532 | self.post_size = self._get_filesize(self.post_filepath) 533 | 534 | def get_compression_percent(self): 535 | ratio = float(self.post_size) / float(self.pre_size) 536 | percent = ratio * 100 537 | return percent 538 | 539 | 540 | if __name__ == "__main__": 541 | # bugfix for macOS GUI / right-click service filepath issue 542 | # when spaces are included in the absolute path to the image 543 | # file. https://github.com/chrissimpkins/Crunch/issues/30 544 | # This workaround reconstructs the original filepaths 545 | # that are split by the shell script into separate arguments 546 | # when there are spaces in the macOS file path 547 | if len(sys.argv) > 1 and sys.argv[1] in ("--gui", "--service"): 548 | arg_list = fix_filepath_args(sys.argv[1:]) 549 | main(arg_list) 550 | else: 551 | # the command line executable assumes that users will appropriately quote 552 | # or escape special characters (including spaces) on the command line, 553 | # no need for the special parsing treatment above 554 | main(sys.argv[1:]) 555 | -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/execution.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Start 7 | 8 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/pngquant: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/bin/Crunch.app/Contents/Resources/pngquant -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/script: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ////////////////////////////////////////////////////////// 4 | # 5 | # crunch-gui.sh 6 | # A shell script that executes Crunch PNG optimization 7 | # for the Crunch macOS GUI application 8 | # Copyright 2018 Christopher Simpkins and contributors 9 | # MIT License 10 | # 11 | # Source: https://github.com/chrissimpkins/Crunch 12 | # 13 | # Note: this file is not intended for direct execution 14 | # on the command line 15 | # 16 | # /////////////////////////////////////////////////////////// 17 | 18 | # UNCOMMENT FOR TESTING ONLY 19 | # python crunch.py --gui "$@" 20 | # exit 0 21 | 22 | # Message on application open (no arguments passed to script on initial open) 23 | if [ $# -eq 0 ]; then 24 | cat waiting.html 25 | exit 0 26 | fi 27 | 28 | cat execution.html 29 | 30 | if ./crunch.py --gui "$@" >/dev/null 2>&1; then 31 | cat clear.html 32 | cat complete-success.html 33 | sleep 2 34 | cat clear.html 35 | cat start.html 36 | sleep 0.8 37 | cat waiting.html 38 | exit 0 39 | else 40 | sleep 0.6 41 | cat clear.html 42 | cat complete-error.html 43 | sleep 2 44 | cat clear.html 45 | cat start.html 46 | sleep 0.8 47 | cat waiting.html 48 | exit 1 49 | fi 50 | -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/start.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Start 7 | 19 | 20 | 21 | 22 | 23 |
24 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/waiting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Start 7 | 18 | 19 | 20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /bin/Crunch.app/Contents/Resources/zopflipng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/bin/Crunch.app/Contents/Resources/zopflipng -------------------------------------------------------------------------------- /coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | coverage run --include="src/crunch.py" -m py.test src 4 | coverage report -m 5 | -------------------------------------------------------------------------------- /dmg-builder.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script uses the `create-dmg` script from https://github.com/andreyvit/create-dmg 4 | 5 | create-dmg \ 6 | --volname "Crunch Installer" \ 7 | --volicon "/Users/chris/Library/Application Support/Platypus/PlatypusIcon-13299.icns" \ 8 | --background "img/dmg-installer-bg.png" \ 9 | --window-pos 200 120 \ 10 | --window-size 800 400 \ 11 | --icon-size 100 \ 12 | --icon Crunch.app 200 190 \ 13 | --hide-extension Crunch.app \ 14 | --app-drop-link 600 185 \ 15 | Crunch-Installer.dmg \ 16 | /Users/chris/code/Crunch/bin 17 | 18 | # create checksum file for the installer 19 | mv Crunch-Installer.dmg installer/Crunch-Installer.dmg 20 | cd installer || exit 1 21 | shasum Crunch-Installer.dmg > Crunch-Installer-checksum.txt 22 | shasum -c Crunch-Installer-checksum.txt -------------------------------------------------------------------------------- /docs/BENCHMARKS.md: -------------------------------------------------------------------------------- 1 | ## Crunch Benchmarks 2 | 3 | **This is of historical interest only. The versions of tools in this comparison are outdated (including Crunch) and these should no longer be considered valid comparisons of current era tools. Please see the benchmarks directory of the repository for updated benchmark results.** 4 | 5 | PNG optimization benchmarks were executed with Crunch v0.9.0. They use [the PNG Test Corpus files](http://css-ig.net/images/png-test-corpus.zip) that are maintained by Cédric Louvrier on css-ig.net. A broad analysis of available PNG optimization tools is available [here](http://css-ig.net/png-tools-overview) for additional reference. 6 | 7 | In the following table, `Size In` and `Size Out` represent the original and optimized file size in bytes, respectively. `%Original Size` is the percent of the original file size in bytes represented by the final optimized file size. A lower value is better. 8 | 9 | The `DSSIM` value is a measure of dissimilarity between two PNG images. It uses [the SSIM algorithm](https://ece.uwaterloo.ca/%7Ez70wang/research/ssim/) and is calculated with [the open source dssim tool](https://github.com/pornel/dssim) by [@pornel](https://github.com/pornel). This is calculated from the optimized file using the original file as a reference. Interpretation is as follows (from the dssim README): 10 | 11 | > The value returned is 1/SSIM-1, where 0 means identical image, and >0 (unbounded) is amount of difference. 12 | 13 | 14 | | File | Size In | Size Out | % Original Size | DSSIM | 15 | | :-----------------: | ------------:| ----------------:| :----------------: | :------------: | 16 | | 01-c3-c0.png | 18623 | 9325 | 50.07% | 0.00027151 | 17 | | 02-c4-c0.png | 2794 | 1100 | 39.37% | 0.00000754 | 18 | | 03-c6-c2.png | 12771 | 4175 | 32.69% | 0.00000769 | 19 | | 04-c2-f0-f5.png | 1539 | 569 | 36.97% | 0.00000000 | 20 | | 05-c3-c2.png | 1416 | 751 | 53.04% | 0.00012144 | 21 | | 06-c6-c3.png | 12346 | 6809 | 55.15% | 0.00008878 | 22 | | 07-c4-c3.png | 15280 | 7167 | 46.90% | 0.00002324 | 23 | | 08-c0-c3.png | 15855 | 11836 | 74.65% | 0.00008729 | 24 | | 09-c3-tronc.png | 3842 | 2437 | 63.43% | 0.00005157 | 25 | | 10-c3-t-first.png | 2893 | 1214 | 41.96% | 0.00001440 | 26 | | 11-8-to-4bit.png | 3036 | 1346 | 44.33% | 0.00000000 | 27 | | 12-4-to-8bit.png | 11667 | 9875 | 84.64% | 0.00006822 | 28 | | 13-idat.png | 7828 | 997 | 12.74% | 0.00000994 | 29 | | 14-chunks.png | 2032 | 997 | 49.06% | 0.00000994 | 30 | | 15-pal-org.png | 24551 | 16017 | 65.24% | 0.00016014 | 31 | | 16-pal-org-t.png | 24795 | 16133 | 65.07% | 0.00014012 | 32 | | 17-pal-dup.png | 3032 | 1346 | 44.39% | 0.00000000 | 33 | | 18-c2-c3-f.png | 28840 | 12206 | 42.32% | 0.00017952 | 34 | | 19-c6-c3-f.png | 31691 | 11829 | 37.33% | 0.00016088 | 35 | | 20-c6-c4.png | 34975 | 16717 | 47.80% | 0.00003876 | 36 | | 21-rgbdata.png | 49515 | 37759 | 76.26% | 0.00000000 | 37 | | **Mean** | -- | -- | **50.64%** | -- | 38 | 39 | 40 | ## Crunch Optimization vs. Other Commonly Used Applications 41 | 42 | The values in the following table are the percent of the original file size in the resulting image file following optimization with the respective tool. Lower values are better. 43 | 44 | Tests were performed using commands that permit direct comparison to the reference tables in the [PNG Optimization Tools Overview](http://css-ig.net/png-tools-overview) that is maintained by Cédric Louvrier. 45 | 46 | #### ImageOptim Tests 47 | 48 | ImageOptim v1.6.1 was executed via drag and drop into the GUI application. 49 | 50 | #### OptiPNG Tests 51 | 52 | `optipng` v0.7.5 was executed with the command: 53 | 54 | ``` 55 | $ optipng -o3 [filepath] 56 | ``` 57 | 58 | #### PNGOUT Tests 59 | 60 | `pngout` vSep 20 2015 was executed with the command: 61 | 62 | ``` 63 | $ pngout [filepath] 64 | ``` 65 | 66 | #### PNGCrush Tests 67 | 68 | `pngcrush` v1.8.0 was executed with the command: 69 | 70 | ``` 71 | $ pngcrush -brute -blacken -reduce [filepath] 72 | ``` 73 | 74 | Please note that Crunch uses a *lossy* PNG optimization approach that is based upon minimum acceptable image quality whereas the comparison tools are all executed with *lossless* PNG optimization. The selectively lossy approach in Crunch is intentional, explains the file size difference, and should be taken into consideration as you interpret the findings and make decisions about use cases for the tool. 75 | 76 | 77 | | File | Crunch | ImageOptim | OptiPNG | PNGOUT | PNGCrush | 78 | | :-----------------: | :------------:| :---------------:| :----------------: | :------------: | :---------: | 79 | | 01-c3-c0.png | 50.07% | 58.70% | 64.17% | 83.83% | 88.35% | 80 | | 02-c4-c0.png | 39.37% | 45.92% | 48.85% | 76.77% | 66.00% | 81 | | 03-c6-c2.png | 32.69% | 62.03% | 70.56% | 81.87% | 70.60% | 82 | | 04-c2-f0-f5.png | 36.97% | 35.61% | 37.23% | 83.82% | 37.17% | 83 | | 05-c3-c2.png | 53.04% | 37.50% | 78.67% | 100.00% | 78.53% | 84 | | 06-c6-c3.png | 55.15% | 61.03% | 63.67% | 75.06% | 80.77% | 85 | | 07-c4-c3.png | 46.90% | 68.63% | 74.24% | 79.99% | 85.62% | 86 | | 08-c0-c3.png | 74.65% | 85.64% | 88.47% | 93.52% | 88.49% | 87 | | 09-c3-tronc.png | 63.43% | 66.74% | 73.06% | 67.10% | 73.19% | 88 | | 10-c3-t-first.png | 41.96% | 76.36% | 79.47% | 81.68% | 79.47% | 89 | | 11-8-to-4bit.png | 44.33% | 44.01% | 73.62% | 50.92% | 57.35% | 90 | | 12-4-to-8bit.png | 84.64% | 82.72% | 89.23% | 87.98% | 89.23% | 91 | | 13-idat.png | 12.74% | 15.24% | 16.88% | 15.69% | 16.88% | 92 | | 14-chunks.png | 49.06% | 58.56% | 88.93% | 60.43% | 87.70% | 93 | | 15-pal-org.png | 65.24% | 90.17% | 96.51% | 91.54% | 95.65% | 94 | | 16-pal-org-t.png | 65.07% | 91.27% | 97.81% | 91.49% | 96.97% | 95 | | 17-pal-dup.png | 44.39% | 44.36% | 49.80% | 50.36% | 57.85% | 96 | | 18-c2-c3-f.png | 42.32% | 61.76% | 61.24% | 78.54% | 83.42% | 97 | | 19-c6-c3-f.png | 37.33% | 56.62% | 50.11% | 77.10% | 79.23% | 98 | | 20-c6-c4.png | 47.80% | 59.95% | 64.19% | 78.64% | 64.31% | 99 | | 21-rgbdata.png | 76.26% | 76.26% | 88.24% | 85.78% | 82.53% | 100 | | **Mean** | **50.64%** | **60.91%** | **69.28%** | **75.81%** | **74.25%** | 101 | -------------------------------------------------------------------------------- /docs/Credits.html: -------------------------------------------------------------------------------- 1 |
3 |

A PNG file optimization tool built on pngquant and zopflipng 4 |

16 |

17 | 18 |

Upgrade

19 |

The latest release can be found 20 | 21 | here 22 | . A 23 | 24 | changelog 25 | is available for review of changes to the application since your installed version was released. 26 |

27 |

To upgrade with Homebrew, enter the following command in your terminal:

28 |

29 | $ brew cask upgrade 30 |

31 | 32 |

Other Crunch Tools

33 |

The Crunch image optimization approach is also available through a macOS right-click menu service and a command line 34 | executable. See the 35 | 36 | documentation on the repository README 37 | for installation and usage details. 38 |

39 | 40 |

Issue Reporting

41 |

Please use 42 | 43 | the issue tracker on the Github repository 44 | to report any problems that you have with the Crunch application.

45 | 46 |

Embedded Software Licenses

47 | 60 |

61 |

Animations

62 |

The Crunch GUI animations were designed by 63 | Gary Jacobs and are licensed under the MIT License.

64 |
-------------------------------------------------------------------------------- /docs/EXECUTABLE.md: -------------------------------------------------------------------------------- 1 | # The `crunch` Command Line Executable 2 | 3 | The `crunch` command line executable is a *nix executable that supports parallel PNG image optimization with local (off of the system PATH) installs of the pngquant and zopflipng project dependencies. Compilation of pngquant and zopflipng from source is supported (and mandatory) as part of the installation process. Please review the documentation below for details. 4 | 5 | ## Contents 6 | 7 | - [Install documentation](#install) 8 | - [Upgrade documentation](#upgrade) 9 | - [Usage documentation](#usage) 10 | 11 | ## What Happens During the Installation? 12 | 13 | Three executable files are installed on your machine. The `crunch` executable is installed on your system PATH at `/usr/local/bin/crunch`, `pngquant` is built on the path `$HOME/pngquant/pngquant`, and zopflipng is built on the path `$HOME/zopfli/zopflipng`. This involves source compiles of pngquant and zopflipng. You will see lengthy text output over ~30 seconds as these builds take place. Don't fret. This is perfectly normal and to be expected during the install. See the bottom of this document for uninstall documentation. It is simple to both install and uninstall all files and directories that are distributed with the Crunch tools. 14 | 15 | There is a method to the madness of the install paths. `crunch` is installed on your system PATH so that it can be executed in your terminal with a command like this: 16 | 17 | ``` 18 | $ crunch myimage.png 19 | ``` 20 | 21 | The `pngquant` and `zopflipng` executables are installed off of your system PATH (in subdirectories of your $HOME directory) in order to pin the versions of the applications to the same git commits that are distributed with the rest of the Crunch project tools. These may or may not be the most current releases of the two project dependencies. Maintaining an always current dependency state is less important to me than that they are tested as part of this project and will allow you to reproduce the same optimized images irrespective of the Crunch tool that you choose to use (and that the data that are displayed on the repository are valid across all of the tools). The off system PATH install approach for the project dependencies also provides you with the option to install different versions of `pngquant` and `zopflipng` on your system PATH (e.g., with a package manager) should you want to use different versions on the command line. 22 | 23 | ## Install 24 | 25 | *Please note: The `crunch` executable and build dependencies should be installed with the user account that is used to execute the application. See [issue #68](https://github.com/chrissimpkins/Crunch/issues/68#issuecomment-412406663) for an approach to installation if this is not possible.* 26 | 27 | Clone the Crunch repository with the following command: 28 | 29 | ``` 30 | $ git clone https://github.com/chrissimpkins/Crunch.git 31 | ``` 32 | 33 | Then install the `crunch` command line executable with one of the approaches below: 34 | 35 | ### 1. Install with `make` (Recommended) 36 | 37 | ``` 38 | $ make build-dependencies 39 | $ make install-executable 40 | ``` 41 | 42 | During the `make build-dependencies` step, your terminal window will display lengthy text output over the 30+ seconds that it takes to compile the `zopflipng` and `pngquant` applications. This is normal and expected during the installation process. 43 | 44 | sudo permissions are required to move the executable to your `/usr/bin/local` directory. Please enter your password when it is requested. 45 | 46 | ### 2. Install manually 47 | 48 | ``` 49 | $ src/install-dependencies.sh 50 | $ sudo cp src/crunch.py /usr/local/bin/crunch 51 | ``` 52 | 53 | During the `install-dependencies.sh` script execution, your terminal window will display lengthy text output over the 30+ seconds that it takes to compile the `zopflipng` and `pngquant` applications. This is normal and expected during the installation process. 54 | 55 | sudo permissions are required to move the executable to your `/usr/bin/local` directory. Please enter your password when it is requested. 56 | 57 | Confirm your installation with the following command: 58 | 59 | ``` 60 | $ crunch --help 61 | ``` 62 | 63 | This command should display the in-application help message for the `crunch` executable. If you see this text, you are all set to use `crunch`. 64 | 65 | ## Upgrade 66 | 67 | Upgrade the `crunch` executable by cloning the git repository, rebuilding the `pngquant` and `zopflipng` dependencies, and installing the `crunch` executable file. 68 | 69 | You can perform these steps with the following set of commands ([download this gist to execute as a shell script](https://gist.github.com/chrissimpkins/280e2590383a167665942a6a24c837e6)): 70 | 71 | ``` 72 | $ git clone https://github.com/chrissimpkins/Crunch.git 73 | $ cd Crunch 74 | $ rm -rf ~/pngquant 75 | $ rm -rf ~/zopfli 76 | $ make build-dependencies 77 | $ make install-executable 78 | ``` 79 | 80 | You will be prompted for your password with the final command above. Enter it and confirm the install by entering the following command: 81 | 82 | ``` 83 | $ crunch --version 84 | ``` 85 | 86 | You should see the expected version of the `crunch` tool installed on your system. You may delete the entire Crunch git repository after you complete these steps (or save it and pull new changes from the master branch to build the next version if you plan to stay current with releases). New releases are indicated with git tags. 87 | 88 | 89 | 90 | ## Usage 91 | 92 | Image processing is executed by requesting one or more PNG image paths as arguments to the `crunch` executable: 93 | 94 | ``` 95 | $ crunch [PNG image path 1]...[PNG image path n] 96 | ``` 97 | 98 | You can use shell wildcards with the executable. For instance, to process all PNG image files in the working directory, you can use the following: 99 | 100 | ``` 101 | $ crunch *.png 102 | ``` 103 | 104 | At the completion of processing of each of the requested images, the application reports the following data: 105 | 106 | - percent of original image size 107 | - optimized image file path 108 | - final optimized image size in bytes 109 | 110 | Optimized files are saved in the same directory as the original with the modified path `[original filename]-crunch.png`. 111 | 112 | ### Options 113 | 114 | The following options are available for use with the `crunch` executable: 115 | 116 | ``` 117 | --help, -h application help 118 | --usage application usage 119 | --version, -v application version 120 | ``` 121 | 122 | ## Uninstall `crunch` and Dependencies 123 | 124 | pngquant is compiled in a directory on the path `$HOME/pngquant`. zopflipng is compiled in a directory on the path `$HOME/zopfli`. 125 | 126 | You can uninstall the pngquant and zopflipng dependencies by executing the following command from the root of the Crunch git repository: 127 | 128 | ``` 129 | $ make uninstall-dependencies 130 | ``` 131 | 132 | The `crunch` executable file is installed on the path `/usr/local/bin/crunch`. 133 | 134 | You can uninstall the `crunch` executable by executing the following command from the root of the Crunch git repository: 135 | 136 | ``` 137 | $ make uninstall-executable 138 | ``` 139 | -------------------------------------------------------------------------------- /docs/MACOSGUI.md: -------------------------------------------------------------------------------- 1 | # Crunch native macOS GUI Application 2 | 3 | The Crunch native macOS GUI application supports drag and drop PNG image optimization on macOS systems. The application provides support for parallel execution of image optimization on systems with multiple processors. 4 | 5 | ## Contents 6 | 7 | - [Install documentation](#install) 8 | - [Usage documentation](#usage) 9 | 10 | ## Install 11 | 12 | Choose one of the methods below: 13 | 14 | ### 1. Install with Homebrew Cask Package Manager (Recommended) 15 | 16 | This approach requires a previous install of the Homebrew package manager on your macOS system. Please refer to the Homebrew documentation for details. 17 | 18 | ``` 19 | $ brew install --cask crunch 20 | ``` 21 | 22 | Note that you must use `brew install --cask` and not `brew install`! 23 | 24 | Automatically upgrade a previous install to the latest version with: 25 | 26 | ``` 27 | $ brew upgrade --cask 28 | ``` 29 | 30 | ### 2. Install with dmg Installer 31 | 32 | [Download the latest release of the dmg installer](https://github.com/chrissimpkins/Crunch/releases/latest), click it, and drag the Crunch icon in the Installer window to the Applications directory displayed in the Installer window. 33 | 34 | Upgrade by following the same instructions and allowing the new version to replace the old version on your system. 35 | 36 | ## Usage 37 | 38 | Drag and drop your PNG images onto the Crunch window: 39 | 40 | Crunch PNG image optimization usage 41 | 42 | Optimized files are saved in the same directory as the original with the modified path `[original filename]-crunch.png`. 43 | 44 | ## Uninstall Crunch 45 | 46 | If you used Homebrew to install Crunch, uninstall it with the following command: 47 | 48 | ``` 49 | $ brew uninstall --cask crunch 50 | ``` 51 | 52 | If you used the dmg installer to install Crunch, you can uninstall it by opening your Finder and selecting your Applications directory. Locate Crunch in the Finder window and move it to the Trash. Empty your Trash, and the uninstall is complete. 53 | -------------------------------------------------------------------------------- /docs/SERVICE.md: -------------------------------------------------------------------------------- 1 | ## Crunch Image(s) macOS Right Click Menu Service 2 | 3 | The Crunch Image(s) macOS service supports parallel PNG image optimization through a right-click Services menu item after you select one or more images in the macOS Finder window. You must install the Crunch macOS GUI application before use of this macOS service because the GUI application package contains the pngquant and zopflipng dependencies that are required for execution of the image optimization. 4 | 5 | ### Quick macOS Service Install 6 | 7 | Install the Crunch GUI application (see [installation documentation for the macOS GUI application](MACOSGUI.md)). [Download the Crunch repository source](https://github.com/chrissimpkins/Crunch/releases/latest), unpack the source archive, and move the workflow directory located on the repository path `service/Crunch Image(s).workflow` to your system on the path `~/Library/Services/Crunch Image(s).workflow`. 8 | 9 | ![crunch-service-install](https://user-images.githubusercontent.com/4249591/38065494-9e80fb6a-32d1-11e8-88da-0f9c014cc510.gif) 10 | 11 | The installation is complete, and the service is ready for use. You may now delete the Crunch source repository. 12 | 13 | ### Quick macOS Service Usage 14 | 15 | Select one or more PNG images in the Finder, right-click, and select "Crunch Image(s)" under the Services menu item. An animated gear appears in your status bar during the file processing and disappears when your optimized images are ready. Optimized files are saved in the same directory as the original with the modified path `[original filename]-crunch.png`. 16 | 17 | ## Detailed macOS Service Installation 18 | 19 | Detailed instructions follow if you need additional information to complete the macOS service installation process. 20 | 21 | ### Install Crunch 22 | 23 | To use the macOS service, you must install the Crunch GUI tool. The macOS service depends upon the embedded versions of the PNG image optimization applications that are used by Crunch to modify your image files. 24 | 25 | See the install instructions in the [Crunch macOS GUI application documentation](MACOSGUI.md). You may use either the Homebrew or .dmg installer approach. When the Crunch install is complete, continue with the instructions below. Please do not modify the default Crunch.app install location if you intend to use the macOS service. 26 | 27 | ### Install the Crunch Image(s) macOS Service 28 | 29 | First, [download the latest release version of the Crunch repository source](https://github.com/chrissimpkins/Crunch/releases/latest). You can use either the .zip or .tar.gz download link. Unpack the Crunch repository source archive in any directory on your system. 30 | 31 | #### 1) Install by Drag and Drop in the Finder 32 | 33 | - Open the Crunch source repository in the Finder. Open the `service` directory that is located in the root of the repository directory. The `Crunch Image(s).workflow` directory is contained in the `service` directory. 34 | - Open a new tab in the Finder with `CMD-T` and select Go > Go to Folder in the Finder menu (or type `SHIFT-CMD-G`) 35 | - In the open "Go to the folder:" free text prompt, enter the following: `~/Library/Services` 36 | - Switch back to the first tab that is located inside the Crunch source repository and drag the `Crunch Image(s).workflow` directory to the second tab so that it is installed on the path `~/Library/Services/Crunch Image(s).workflow`. 37 | - You may now delete the Crunch source repository. 38 | 39 | #### 2) Install with `make` 40 | 41 | If `make` is installed on your macOS system, you can use the Crunch Makefile to install the macOS service. 42 | 43 | - Open a terminal in the root of the Crunch source repository 44 | - Enter the following command to install the macOS service in the directory `~/Library/Services`: 45 | 46 | ``` 47 | $ make install-macos-service 48 | ``` 49 | 50 | `sudo` permission is necessary to complete the copy of the macOS service to this directory on your system. Enter your password when prompted during the install. 51 | 52 | You may delete the Crunch source repository after the completion of the above step. 53 | 54 | ## Detailed macOS Service Uninstall 55 | 56 | Use one of the following approaches to remove the installed macOS service from your system: 57 | #### 1) Uninstall with Finder 58 | 59 | - Open the Finder and select Go > Go to Folder in the Finder menu (or type `SHIFT-CMD-G`) 60 | - In the open "Go to the folder:" free text prompt, enter the following: `~/Library/Services` 61 | - Delete the workflow directory `Crunch Image(s).workflow` that is located in the `~/Library/Services` directory. 62 | 63 | #### 2) Uninstall with `make` 64 | 65 | Download the source repository. Enter the following command in a terminal at the root of the source repository: 66 | 67 | ``` 68 | $ make uninstall-macos-service 69 | ``` 70 | 71 | `sudo` permission is necessary to remove this directory. Enter your password when prompted. 72 | -------------------------------------------------------------------------------- /html/clear.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Start 7 | 8 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /html/complete-error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Start 7 | 8 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /html/complete-success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Start 7 | 8 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /html/execution.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Start 7 | 8 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /html/start.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Start 7 | 19 | 20 | 21 | 22 | 23 |
24 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /html/waiting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Start 7 | 18 | 19 | 20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /img/Crunch-icon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/Crunch-icon-2.png -------------------------------------------------------------------------------- /img/Crunch-icon-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/Crunch-icon-3.png -------------------------------------------------------------------------------- /img/CrunchIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/CrunchIcon.icns -------------------------------------------------------------------------------- /img/CrunchIcon.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/CrunchIcon.iconset/icon_128x128.png -------------------------------------------------------------------------------- /img/CrunchIcon.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/CrunchIcon.iconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /img/CrunchIcon.iconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/CrunchIcon.iconset/icon_16x16.png -------------------------------------------------------------------------------- /img/CrunchIcon.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/CrunchIcon.iconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /img/CrunchIcon.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/CrunchIcon.iconset/icon_256x256.png -------------------------------------------------------------------------------- /img/CrunchIcon.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/CrunchIcon.iconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /img/CrunchIcon.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/CrunchIcon.iconset/icon_32x32.png -------------------------------------------------------------------------------- /img/CrunchIcon.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/CrunchIcon.iconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /img/animations/01-Load-Up@1x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/animations/01-Load-Up@1x.gif -------------------------------------------------------------------------------- /img/animations/01-Load-Up@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/animations/01-Load-Up@2x.gif -------------------------------------------------------------------------------- /img/animations/02-Waiting@1x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/animations/02-Waiting@1x.gif -------------------------------------------------------------------------------- /img/animations/02-Waiting@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/animations/02-Waiting@2x.gif -------------------------------------------------------------------------------- /img/animations/03-Crunching@1x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/animations/03-Crunching@1x.gif -------------------------------------------------------------------------------- /img/animations/03-Crunching@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/animations/03-Crunching@2x.gif -------------------------------------------------------------------------------- /img/animations/04-Error@1x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/animations/04-Error@1x.gif -------------------------------------------------------------------------------- /img/animations/04-Error@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/animations/04-Error@2x.gif -------------------------------------------------------------------------------- /img/animations/04-Success@1x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/animations/04-Success@1x.gif -------------------------------------------------------------------------------- /img/animations/04-Success@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/animations/04-Success@2x.gif -------------------------------------------------------------------------------- /img/cat-1285634_640-crunch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/cat-1285634_640-crunch.png -------------------------------------------------------------------------------- /img/cat-1285634_640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/cat-1285634_640.png -------------------------------------------------------------------------------- /img/colors-157474_640-crunch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/colors-157474_640-crunch.png -------------------------------------------------------------------------------- /img/colors-157474_640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/colors-157474_640.png -------------------------------------------------------------------------------- /img/crunch-ss-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/crunch-ss-2.gif -------------------------------------------------------------------------------- /img/dmg-installer-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/dmg-installer-bg.png -------------------------------------------------------------------------------- /img/fastdots.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/fastdots.gif -------------------------------------------------------------------------------- /img/flowers-67839_640-crunch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/flowers-67839_640-crunch.png -------------------------------------------------------------------------------- /img/flowers-67839_640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/flowers-67839_640.png -------------------------------------------------------------------------------- /img/header-arrow-crunch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/header-arrow-crunch.png -------------------------------------------------------------------------------- /img/header-arrow.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/header-arrow.psd -------------------------------------------------------------------------------- /img/header-img-crunch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/header-img-crunch.png -------------------------------------------------------------------------------- /img/header-img.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/header-img.psd -------------------------------------------------------------------------------- /img/prairie-679014_640-crunch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/prairie-679014_640-crunch.png -------------------------------------------------------------------------------- /img/prairie-679014_640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/prairie-679014_640.png -------------------------------------------------------------------------------- /img/robot-1214536_640-crunch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/robot-1214536_640-crunch.png -------------------------------------------------------------------------------- /img/robot-1214536_640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/robot-1214536_640.png -------------------------------------------------------------------------------- /img/slowdots.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/slowdots.gif -------------------------------------------------------------------------------- /img/suns-rays-478249_640-crunch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/suns-rays-478249_640-crunch.png -------------------------------------------------------------------------------- /img/suns-rays-478249_640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/img/suns-rays-478249_640.png -------------------------------------------------------------------------------- /profile/Crunch.platypus: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AcceptsFiles 6 | 7 | AcceptsText 8 | 9 | Authentication 10 | 11 | Author 12 | Christopher Simpkins 13 | BundledFiles 14 | 15 | /Users/chris/code/Crunch/docs/Credits.html 16 | /Users/chris/code/Crunch/html/execution.html 17 | /Users/chris/code/Crunch/html/start.html 18 | /Users/chris/code/Crunch/src/include/pngquant 19 | /Users/chris/code/Crunch/src/include/zopflipng 20 | /Users/chris/code/Crunch/html/clear.html 21 | /Users/chris/code/Crunch/ui/MainMenu.nib 22 | /Users/chris/code/Crunch/src/crunch.py 23 | /Users/chris/code/Crunch/img/animations/01-Load-Up@2x.gif 24 | /Users/chris/code/Crunch/img/animations/02-Waiting@2x.gif 25 | /Users/chris/code/Crunch/img/animations/03-Crunching@2x.gif 26 | /Users/chris/code/Crunch/img/animations/04-Error@2x.gif 27 | /Users/chris/code/Crunch/img/animations/04-Success@2x.gif 28 | /Users/chris/code/Crunch/html/waiting.html 29 | /Users/chris/code/Crunch/html/complete-error.html 30 | /Users/chris/code/Crunch/html/complete-success.html 31 | 32 | Creator 33 | Platypus-5.3 34 | DeclareService 35 | 36 | Destination 37 | /Users/chris/Desktop/Application.app 38 | DevelopmentVersion 39 | 40 | DocIconPath 41 | 42 | Droppable 43 | 44 | ExecutablePath 45 | /usr/local/share/platypus/ScriptExec 46 | IconPath 47 | /Users/chris/code/Crunch/img/CrunchIcon.icns 48 | Identifier 49 | com.csimpkins.Crunch 50 | InterfaceType 51 | Web View 52 | InterpreterArgs 53 | 54 | InterpreterPath 55 | /bin/sh 56 | Name 57 | Crunch 58 | NibPath 59 | /usr/local/share/platypus/MainMenu.nib 60 | OptimizeApplication 61 | 62 | Overwrite 63 | 64 | PromptForFileOnLaunch 65 | 66 | RemainRunning 67 | 68 | RunInBackground 69 | 70 | ScriptArgs 71 | 72 | ScriptPath 73 | /Users/chris/code/Crunch/src/crunch-gui.sh 74 | StatusItemDisplayType 75 | Text 76 | StatusItemIcon 77 | 78 | TU0AKgAADygAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 79 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 80 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 81 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 82 | AAAAAAAAAAAAAAAAACgAdACyANYA8wD6APoA9ADXALQAdwAqAAAAAAAAAAAAAAAAAAAA 83 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARAHAA1gD/ 84 | AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wDZAHQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 85 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAHkA9QD/AP8A/wD/AP8A9gDkANYA1gDi 86 | APUA/wD/AP8A/wD/APcAgAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 87 | AAAAAAAAAAAAAEMA5wD/AP8A/wD/AMQAZgAvAAkAAAAAAAAAAAAIAC0AYwDBAP8A/wD/ 88 | AP8A6gBJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAD/AP8A 89 | /wD+AJAAJgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAIkA/AD/AP8A/wCBAAAAAAAA 90 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVAP8A/wD/ALgAIgAAAAAAAAAAAAAA 91 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AsAD/AP8A/wCfAAAAAAAAAAAAAAAAAAAAAAAA 92 | AAAAAAAAAAAAAAAAAJYA/wD/AP8AawAAAAAAAABuAEEAAAAAAAAAAAAAAAAAAAAAAAAA 93 | AAAAAAAAAAAAAAAAZAD/AP8A/wCiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdwD/ 94 | AP8A/wBEAAAAAAAdAOIAwgDzAEoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 95 | PAD/AP8A/wCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AP8A/wD/AEIAAAAAAAAAyACp 96 | AAAAgQDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOQD/AP8A/wA/AAAA 97 | AAAAAAAAAAAAAAAAAAAAAAAA8AD/AP8AZwAAAAAAAAALANgADQAAAFQAxQAAAAAAAAAA 98 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXwD/AP8A9QADAAAAAAAAAAAAAAAAAAAA 99 | AAB3AP8A/wC+AAAAAAAAAAAAGQDMAAcAAAA8ANEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 100 | AAAAAAAAAAAAAAAAAAAAsgD/AP8AhAAAAAAAAAAAAAAAAAAAAAoA/AD/AP8AFAAAAAAA 101 | AAAAAAMA0AAnAAAAEADeABIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 102 | AA0A/AD/AP8AEQAAAAAAAAAAAAAAAABuAP8A/wCOAAAAAAAAAAAAAAAAAMcAdQAAAAAA 103 | 0QB3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIIA/wD/AHkAAAAA 104 | AAAAAAAAAAAA3QD/AP8AFQAAAAAAAAAAAAAAAACFAMEAAAAAAFsA5gAAAAAAAAAAAAAA 105 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAP8A/wDmAAAAAAAAAAAAAAAhAP8A/wDG 106 | AAAAAAAAAAAAAAAAAAAAIwDVAAgAAAAAAOUAbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 107 | AAAAAAAAAAAAAAAAAAC9AP8A/wAkAAAAAAAAAAAAdAD/AP8AYQAAAAAAAAAAAAAAAAAA 108 | AAAAzwAyAAAAAAApAP0AIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJQB3AAAAAAAAAAAA 109 | XAD/AP8AfwAAAAAAAAAAALQA/wD/ACgAAAAAAAAAAAAAAAAAAAAeAMwAAAAAAAAAIQD8 110 | ACIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKwA/wAAAAAAAAAAAB0A/wD/AMAAAAAAAAAA 111 | AADXAP8A9gADAAAAAAAAAAAAAAAAAAAAZwDBAD4AWwCQAvgS/wZqAAAAAAAAAAAAAAAA 112 | AAAAAAAAAAAAAADgAP8ACAAAAAAAAAAAAPAA/wDiAAAAAAAAAAAA9AD/AOEAAAAAAAAA 113 | AAAAAAAAAAAAAFYA/AD/AP8A/wL/Rf82oQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AD/ 114 | ABEAAAAAAAAAAADYAP8A+AAAAAAAAAAAAP0A/wDTAAAAAAAAAAAAAAAAAAAAAAAHAPEq 115 | /0T/A/8A/wT/B68AAAAAAAAAAAAAAAAAAAAAAAAAAAAUAP8A/gALAAAAAAAAAAAAygD/ 116 | APwAAAAAAAAAAAD9AP8A0wAAAAAAAAAAAAAAAAAAAAAACQDzGf88/wL/AP8A/wD8AAQA 117 | AAAAAAAAAAAAAAAAAAAAAAAAVgD/AOkAAAAAAAAAAAAAAMoA/wD8AAAAAAAAAAAA9QD/ 118 | AOAAAAAAAAAAAAAAAAAAAAAAAC0A/wD/AP8A/wD/AP8A/wCdAAAAAAAAAAAAAAAAAAAA 119 | AAAAAM4A/wDKAAAAAAAAAAAAAADYAP8A+AAAAAAAAAAAANgA/wD2AAMAAAAAAAAAAAAA 120 | AAAAAAAnAP8A/wD/AP8A/wD/AP8A/wB5AAAAAAAAAAAAAAAAAAAAhAD/AP8AmwAAAAAA 121 | AAAAAAAA8AD/AOIAAAAAAAAAAAC2AP8A/wAmAAAAAAAAAAAAAAAAAAAABwD7AP8A/wD/ 122 | AP8A/wD/AP8A/wC8AEgADQAAAAYANAClAP8A/wD/AF4AAAAAAAAAAAAaAP8A/wDBAAAA 123 | AAAAAAAAdwD/AP8AXwAAAAAAAAAAAAAAAAAAAAAAsQD/AP8A/wD/AP8A/wD/AP8A/wD/ 124 | APMA3wDsAP8A/wD/AP8A/wAPAAAAAAAAAAAAWQD/AP8AggAAAAAAAAAAACMA/wD/AMIA 125 | AAAAAAAAAAAAAAAAAgARAA4A7AD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/ 126 | AKQAAAAAAAAAAAAAALkA/wD/ACYAAAAAAAAAAAAAAOAA/wD/ABIAAAAAAAAAAAAAAEkA 127 | 9QCeAOAA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wAYAAAAAAAAAAAACwD+ 128 | AP8A6QAAAAAAAAAAAAAAAAByAP8A/wCJAAAAAAAAAAAAAABnAP8A/wD/AP8A/wD/AP8A 129 | /wD/AP8A/wD/AP8A/wD/AP8A/wD/AGQAAAAAAAAAAAAAAHwA/wD/AH8AAAAAAAAAAAAA 130 | AAAADQD9AP8A/gAQAAAAAAAAAAAADACAAP8A/wBrALoA/wD/AP8A/wD/AP8A/wD/AP8A 131 | /wD/AP8AgAAAAAAAAAAAAAAACQD7AP8A/wATAAAAAAAAAAAAAAAAAAAAfgD/AP8AtgAA 132 | AAAAAAAAAAAAAABTAN4AEAAAALEA/wD/AP8A/wD/AP8A/wD/AP8A1gBCAAAAAAAAAAAA 133 | AAAAAKkA/wD/AI0AAAAAAAAAAAAAAAAAAAAAAAEA9AD/AP8AYAAAAAAAAAAAAAAAAAAD 134 | AAQAAAAAAEQAoADaAP4A/wD/AP8A/wD/AMkAoACFAAAAAAAAAAAAWAD/AP8A+AAGAAAA 135 | AAAAAAAAAAAAAAAAAAAAAD4A/wD/AP8AOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ 136 | ADAANAAjAJIA/wD/AP0ATQAAAAAAAAAxAP8A/wD/AEYAAAAAAAAAAAAAAAAAAAAAAAAA 137 | AAAAAIAA/wD/AP8AOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACZAP8A3gBy 138 | AAAAAAAAADMA/AD/AP8AiwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAA/wD/AP8A 139 | YwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEAvgCuABsACgAAAAAAXQD/AP8A/wCr 140 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKEA/wD/AP8ArQAZAAAAAAAAAAAA 141 | AAAAAAAAAAAAAAAAAAAmADsAAAAAAAAAFgCmAP8A/wD/AKoAAAAAAAAAAAAAAAAAAAAA 142 | AAAAAAAAAAAAAAAAAAAAAAAAAIIA/wD/AP8A+QCCAB0AAAAAAAAAAAAAAAAAAAAAAAAA 143 | AAAAAAAAGgB7APcA/wD/AP8AjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 144 | AAAAAAAAAEoA7AD/AP8A/wD9ALwAYQAmAAIAAAAAAAAAAAABACQAXgC4APwA/wD/AP8A 145 | 8ABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAhgD6 146 | AP8A/wD/AP8A/wDwANsAzQDMANsA7wD/AP8A/wD/AP8A/QCNAA0AAAAAAAAAAAAAAAAA 147 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGgB6AN4A/wD/AP8A/wD/ 148 | AP8A/wD/AP8A/wD/AP8A4QCAAB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 149 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAfwC9AN8A9QD7APsA9gDgAL4AgwAu 150 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 151 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 152 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 153 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 154 | AAAAAAAQAQAAAwAAAAEALAAAAQEAAwAAAAEALAAAAQIAAwAAAAIACAAIAQMAAwAAAAEA 155 | AQAAAQYAAwAAAAEAAQAAAQoAAwAAAAEAAQAAAREABAAAAAEAAAAIARIAAwAAAAEAAQAA 156 | ARUAAwAAAAEAAgAAARYAAwAAAAEALAAAARcABAAAAAEAAA8gARwAAwAAAAEAAQAAASgA 157 | AwAAAAEAAgAAAVIAAwAAAAEAAgAAAVMAAwAAAAIAAQABh3MABwAAEZwAAA/uAAAAAAAA 158 | EZxhcHBsAgAAAG1udHJHUkFZWFlaIAfcAAgAFwAPAC4AD2Fjc3BBUFBMAAAAAG5vbmUA 159 | AAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAA 160 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABWRlc2MAAADAAAAAeWRzY20AAAE8AAAI 161 | GmNwcnQAAAlYAAAAI3d0cHQAAAl8AAAAFGtUUkMAAAmQAAAIDGRlc2MAAAAAAAAAH0dl 162 | bmVyaWMgR3JheSBHYW1tYSAyLjIgUHJvZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 163 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 164 | AAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAAC4AAAGEZGFESwAAADoAAAGyY2FF 165 | UwAAADgAAAHsdmlWTgAAAEAAAAIkcHRCUgAAAEoAAAJkdWtVQQAAACwAAAKuZnJGVQAA 166 | AD4AAALaaHVIVQAAADQAAAMYemhUVwAAABoAAANMa29LUgAAACIAAANmbmJOTwAAADoA 167 | AAOIY3NDWgAAACgAAAPCaGVJTAAAACQAAAPqcm9STwAAACoAAAQOZGVERQAAAE4AAAQ4 168 | aXRJVAAAAE4AAASGc3ZTRQAAADgAAATUemhDTgAAABoAAAUMamFKUAAAACYAAAUmZWxH 169 | UgAAACoAAAVMcHRQTwAAAFIAAAV2bmxOTAAAAEAAAAXIZXNFUwAAAEwAAAYIdGhUSAAA 170 | ADIAAAZUdHJUUgAAACQAAAaGZmlGSQAAAEYAAAaqaHJIUgAAAD4AAAbwcGxQTAAAAEoA 171 | AAcuYXJFRwAAACwAAAd4cnVSVQAAADoAAAekZW5VUwAAADwAAAfeAFYBYQBlAG8AYgBl 172 | AGMAbgDhACAAcwBpAHYA4QAgAGcAYQBtAGEAIAAyACwAMgBHAGUAbgBlAHIAaQBzAGsA 173 | IABnAHIA5QAgADIALAAyACAAZwBhAG0AbQBhAC0AcAByAG8AZgBpAGwARwBhAG0AbQBh 174 | ACAAZABlACAAZwByAGkAcwBvAHMAIABnAGUAbgDoAHIAaQBjAGEAIAAyAC4AMgBDHqUA 175 | dQAgAGgA7ABuAGgAIABNAOAAdQAgAHgA4QBtACAAQwBoAHUAbgBnACAARwBhAG0AbQBh 176 | ACAAMgAuADIAUABlAHIAZgBpAGwAIABHAGUAbgDpAHIAaQBjAG8AIABkAGEAIABHAGEA 177 | bQBhACAAZABlACAAQwBpAG4AegBhAHMAIAAyACwAMgQXBDAEMwQwBDsETAQ9BDAAIABH 178 | AHIAYQB5AC0EMwQwBDwEMAAgADIALgAyAFAAcgBvAGYAaQBsACAAZwDpAG4A6QByAGkA 179 | cQB1AGUAIABnAHIAaQBzACAAZwBhAG0AbQBhACAAMgAsADIAwQBsAHQAYQBsAOEAbgBv 180 | AHMAIABzAHoA/AByAGsAZQAgAGcAYQBtAG0AYQAgADIALgAykBp1KHBwlo5RSV6mADIA 181 | LgAygnJfaWPPj/DHfLwYACDWjMDJACCsELnIACAAMgAuADIAINUEuFzTDMd8AEcAZQBu 182 | AGUAcgBpAHMAawAgAGcAcgDlACAAZwBhAG0AbQBhACAAMgAsADIALQBwAHIAbwBmAGkA 183 | bABPAGIAZQBjAG4A4QAgAWEAZQBkAOEAIABnAGEAbQBhACAAMgAuADIF0gXQBd4F1AAg 184 | BdAF5AXVBegAIAXbBdwF3AXZACAAMgAuADIARwBhAG0AYQAgAGcAcgBpACAAZwBlAG4A 185 | ZQByAGkAYwEDACAAMgAsADIAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAARwByAGEAdQBz 186 | AHQAdQBmAGUAbgAtAFAAcgBvAGYAaQBsACAARwBhAG0AbQBhACAAMgAsADIAUAByAG8A 187 | ZgBpAGwAbwAgAGcAcgBpAGcAaQBvACAAZwBlAG4AZQByAGkAYwBvACAAZABlAGwAbABh 188 | ACAAZwBhAG0AbQBhACAAMgAsADIARwBlAG4AZQByAGkAcwBrACAAZwByAOUAIAAyACwA 189 | MgAgAGcAYQBtAG0AYQBwAHIAbwBmAGkAbGZukBpwcF6mfPtlcAAyAC4AMmPPj/Blh072 190 | TgCCLDCwMOwwpDCsMPMw3gAgADIALgAyACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wA 191 | IAOTA7oDwQO5ACADkwOsA7wDvAOxACAAMgAuADIAUABlAHIAZgBpAGwAIABnAGUAbgDp 192 | AHIAaQBjAG8AIABkAGUAIABjAGkAbgB6AGUAbgB0AG8AcwAgAGQAYQAgAEcAYQBtAG0A 193 | YQAgADIALAAyAEEAbABnAGUAbQBlAGUAbgAgAGcAcgBpAGoAcwAgAGcAYQBtAG0AYQAg 194 | ADIALAAyAC0AcAByAG8AZgBpAGUAbABQAGUAcgBmAGkAbAAgAGcAZQBuAOkAcgBpAGMA 195 | bwAgAGQAZQAgAGcAYQBtAG0AYQAgAGQAZQAgAGcAcgBpAHMAZQBzACAAMgAsADIOIw4x 196 | DgcOKg41DkEOAQ4hDiEOMg5ADgEOIw4iDkwOFw4xDkgOJw5EDhsAIAAyAC4AMgBHAGUA 197 | bgBlAGwAIABHAHIAaQAgAEcAYQBtAGEAIAAyACwAMgBZAGwAZQBpAG4AZQBuACAAaABh 198 | AHIAbQBhAGEAbgAgAGcAYQBtAG0AYQAgADIALAAyACAALQBwAHIAbwBmAGkAaQBsAGkA 199 | RwBlAG4AZQByAGkBDQBrAGkAIABHAHIAYQB5ACAARwBhAG0AbQBhACAAMgAuADIAIABw 200 | AHIAbwBmAGkAbABVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBmAGkAbAAgAHMA 201 | egBhAHIAbwFbAGMAaQAgAGcAYQBtAG0AYQAgADIALAAyBjoGJwZFBicAIAAyAC4AMgAg 202 | BkQGSAZGACAGMQZFBicGLwZKACAGOQYnBkUEHgQxBEkEMARPACAEQQQ1BEAEMARPACAE 203 | MwQwBDwEPAQwACAAMgAsADIALQQ/BEAEPgREBDgEOwRMAEcAZQBuAGUAcgBpAGMAIABH 204 | AHIAYQB5ACAARwBhAG0AbQBhACAAMgAuADIAIABQAHIAbwBmAGkAbABlAAB0ZXh0AAAA 205 | AENvcHlyaWdodCBBcHBsZSBJbmMuLCAyMDEyAABYWVogAAAAAAAA81EAAQAAAAEWzGN1 206 | cnYAAAAAAAAEAAAAAAUACgAPABQAGQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4A 207 | YwBoAG0AcgB3AHwAgQCGAIsAkACVAJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDg 208 | AOUA6wDwAPYA+wEBAQcBDQETARkBHwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwB 209 | gwGLAZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwCFAIdAiYCLwI4AkECSwJU 210 | Al0CZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oD 211 | ZgNyA34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSo 212 | BLYExATTBOEE8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicG 213 | NwZIBlkGagZ7BowGnQavBsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4 214 | CAsIHwgyCEYIWghuCIIIlgiqCL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsK 215 | EQonCj0KVApqCoEKmAquCsUK3ArzCwsLIgs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxc 216 | DHUMjgynDMAM2QzzDQ0NJg1ADVoNdA2ODakNww3eDfgOEw4uDkkOZA5/DpsOtg7SDu4P 217 | CQ8lD0EPXg96D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPURExExEU8RbRGMEaoRyRHo 218 | EgcSJhJFEmQShBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIV 219 | NBVWFXgVmxW9FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihiv 220 | GNUY+hkgGUUZaxmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHsc 221 | oxzMHPUdHh1HHXAdmR3DHeweFh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDE 222 | IPAhHCFIIXUhoSHOIfsiJyJVIoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTgl 223 | aCWXJccl9yYnJlcmhya3JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1 224 | KmgqmyrPKwIrNitpK50r0SwFLDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ov 225 | kS/HL/4wNTBsMKQw2zESMUoxgjG6MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUT 226 | NU01hzXCNf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87 227 | LTtrO6o76DwnPGU8pDzjPSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFq 228 | QaxB7kIwQnJCtUL3QzpDfUPARANER0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fASAVI 229 | S0iRSNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9J 230 | T5NP3VAnUHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW 231 | 91dEV5JX4FgvWH1Yy1kaWWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69 232 | Xw9fYV+zYAVgV2CqYPxhT2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhn 233 | PWeTZ+loP2iWaOxpQ2maafFqSGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/R 234 | cCtwhnDgcTpxlXHwcktypnMBc11zuHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5 235 | KnmJeed6RnqlewR7Y3vCfCF8gXzhfUF9oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKS 236 | gvSDV4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6JM4mZif6KZIrKizCLlov8jGOM 237 | yo0xjZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSVX5XJljSWn5cK 238 | l3WX4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobai 239 | JqKWowajdqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1E 240 | rbiuLa6hrxavi7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5 241 | SrnCuju6tbsuu6e8IbybvRW9j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVL 242 | xcjGRsbDx0HHv8g9yLzJOsm5yjjKt8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7S 243 | P9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p 244 | 36/gNuC94UThzOJT4tvjY+Pr5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXrcOv77Ibt 245 | Ee2c7ijutO9A78zwWPDl8XLx//KM8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn 246 | +3f8B/yY/Sn9uv5L/tz/bf// 247 | 248 | StatusItemIconIsTemplate 249 | 250 | StatusItemTitle 251 | Title 252 | StatusItemUseSystemFont 253 | 254 | Suffixes 255 | 256 | TextBackground 257 | #ffffff 258 | TextFont 259 | Monaco 260 | TextForeground 261 | #000000 262 | TextSize 263 | 13 264 | URISchemes 265 | 266 | UniformTypes 267 | 268 | public.item 269 | public.folder 270 | 271 | Version 272 | 5.0.0 273 | 274 | 275 | -------------------------------------------------------------------------------- /service/Crunch Image(s).workflow/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSServices 6 | 7 | 8 | NSMenuItem 9 | 10 | default 11 | Crunch Image(s) 12 | 13 | NSMessage 14 | runWorkflowAsService 15 | NSRequiredContext 16 | 17 | NSApplicationIdentifier 18 | com.apple.finder 19 | 20 | NSSendFileTypes 21 | 22 | public.image 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /service/Crunch Image(s).workflow/Contents/QuickLook/Preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/service/Crunch Image(s).workflow/Contents/QuickLook/Preview.png -------------------------------------------------------------------------------- /service/Crunch Image(s).workflow/Contents/document.wflow: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AMApplicationBuild 6 | 428 7 | AMApplicationVersion 8 | 2.7 9 | AMDocumentVersion 10 | 2 11 | actions 12 | 13 | 14 | action 15 | 16 | AMAccepts 17 | 18 | Container 19 | List 20 | Optional 21 | 22 | Types 23 | 24 | com.apple.cocoa.string 25 | 26 | 27 | AMActionVersion 28 | 2.0.3 29 | AMApplication 30 | 31 | Automator 32 | 33 | AMParameterProperties 34 | 35 | COMMAND_STRING 36 | 37 | CheckedForUserDefaultShell 38 | 39 | inputMethod 40 | 41 | shell 42 | 43 | source 44 | 45 | 46 | AMProvides 47 | 48 | Container 49 | List 50 | Types 51 | 52 | com.apple.cocoa.string 53 | 54 | 55 | ActionBundlePath 56 | /System/Library/Automator/Run Shell Script.action 57 | ActionName 58 | Run Shell Script 59 | ActionParameters 60 | 61 | COMMAND_STRING 62 | 63 | 64 | /Applications/Crunch.app/Contents/Resources/crunch.py --service "$@" 65 | CheckedForUserDefaultShell 66 | 67 | inputMethod 68 | 1 69 | shell 70 | /bin/sh 71 | source 72 | 73 | 74 | BundleIdentifier 75 | com.apple.RunShellScript 76 | CFBundleVersion 77 | 2.0.3 78 | CanShowSelectedItemsWhenRun 79 | 80 | CanShowWhenRun 81 | 82 | Category 83 | 84 | AMCategoryUtilities 85 | 86 | Class Name 87 | RunShellScriptAction 88 | InputUUID 89 | 16953775-D408-4D20-961F-AAFA2FFB2EEA 90 | Keywords 91 | 92 | Shell 93 | Script 94 | Command 95 | Run 96 | Unix 97 | 98 | OutputUUID 99 | 2C1D5AD7-8CD0-48A9-A388-29534835C3BE 100 | UUID 101 | 4D4C0544-BBDF-40C8-8673-4203B660D33F 102 | UnlocalizedApplications 103 | 104 | Automator 105 | 106 | arguments 107 | 108 | 0 109 | 110 | default value 111 | 0 112 | name 113 | inputMethod 114 | required 115 | 0 116 | type 117 | 0 118 | uuid 119 | 0 120 | 121 | 1 122 | 123 | default value 124 | 125 | name 126 | source 127 | required 128 | 0 129 | type 130 | 0 131 | uuid 132 | 1 133 | 134 | 2 135 | 136 | default value 137 | 138 | name 139 | CheckedForUserDefaultShell 140 | required 141 | 0 142 | type 143 | 0 144 | uuid 145 | 2 146 | 147 | 3 148 | 149 | default value 150 | 151 | name 152 | COMMAND_STRING 153 | required 154 | 0 155 | type 156 | 0 157 | uuid 158 | 3 159 | 160 | 4 161 | 162 | default value 163 | /bin/sh 164 | name 165 | shell 166 | required 167 | 0 168 | type 169 | 0 170 | uuid 171 | 4 172 | 173 | 174 | isViewVisible 175 | 176 | location 177 | 504.500000:433.000000 178 | nibPath 179 | /System/Library/Automator/Run Shell Script.action/Contents/Resources/English.lproj/main.nib 180 | 181 | isViewVisible 182 | 183 | 184 | 185 | connectors 186 | 187 | workflowMetaData 188 | 189 | serviceApplicationBundleID 190 | com.apple.finder 191 | serviceApplicationPath 192 | /System/Library/CoreServices/Finder.app 193 | serviceInputTypeIdentifier 194 | com.apple.Automator.fileSystemObject.image 195 | serviceOutputTypeIdentifier 196 | com.apple.Automator.nothing 197 | serviceProcessesInput 198 | 0 199 | workflowTypeIdentifier 200 | com.apple.Automator.servicesMenu 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 90 3 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/src/__init__.py -------------------------------------------------------------------------------- /src/crunch-gui.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ////////////////////////////////////////////////////////// 4 | # 5 | # crunch-gui.sh 6 | # A shell script that executes Crunch PNG optimization 7 | # for the Crunch macOS GUI application 8 | # Copyright 2018 Christopher Simpkins and contributors 9 | # MIT License 10 | # 11 | # Source: https://github.com/chrissimpkins/Crunch 12 | # 13 | # Note: this file is not intended for direct execution 14 | # on the command line 15 | # 16 | # /////////////////////////////////////////////////////////// 17 | 18 | # UNCOMMENT FOR TESTING ONLY 19 | # python crunch.py --gui "$@" 20 | # exit 0 21 | 22 | # Message on application open (no arguments passed to script on initial open) 23 | if [ $# -eq 0 ]; then 24 | cat waiting.html 25 | exit 0 26 | fi 27 | 28 | cat execution.html 29 | 30 | if ./crunch.py --gui "$@" >/dev/null 2>&1; then 31 | cat clear.html 32 | cat complete-success.html 33 | sleep 2 34 | cat clear.html 35 | cat start.html 36 | sleep 0.8 37 | cat waiting.html 38 | exit 0 39 | else 40 | sleep 0.6 41 | cat clear.html 42 | cat complete-error.html 43 | sleep 2 44 | cat clear.html 45 | cat start.html 46 | sleep 0.8 47 | cat waiting.html 48 | exit 1 49 | fi 50 | -------------------------------------------------------------------------------- /src/crunch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # ================================================================== 4 | # crunch 5 | # A PNG file optimization tool built on pngquant and zopflipng 6 | # 7 | # Copyright 2019 Christopher Simpkins 8 | # MIT License 9 | # 10 | # Source Repository: https://github.com/chrissimpkins/Crunch 11 | # ================================================================== 12 | 13 | import sys 14 | import os 15 | import shutil 16 | import struct 17 | import subprocess 18 | import time 19 | from subprocess import CalledProcessError 20 | 21 | from multiprocessing import Lock, Pool, cpu_count 22 | 23 | # Locks 24 | stdstream_lock = Lock() 25 | logging_lock = Lock() 26 | 27 | # Application Constants 28 | VERSION = "5.0.0" 29 | VERSION_STRING = "crunch v" + VERSION 30 | 31 | # Processor Constant 32 | # - Modify this to an integer value if you want to fix the number of 33 | # processes spawned during execution. The process number is 34 | # automatically defined during source execution when this is defined 35 | # as a value of 0 36 | PROCESSES = 0 37 | 38 | # Dependency Path Constants for Command Line Executable 39 | # - Redefine these path strings to use system-installed versions of 40 | # pngquant and zopflipng (e.g. to "/usr/local/bin/[executable]") 41 | PNGQUANT_CLI_PATH = os.path.join(os.path.expanduser("~"), "pngquant", "pngquant") 42 | ZOPFLIPNG_CLI_PATH = os.path.join(os.path.expanduser("~"), "zopfli", "zopflipng") 43 | 44 | # Crunch Directory (dot directory in $HOME) 45 | CRUNCH_DOT_DIRECTORY = os.path.join(os.path.expanduser("~"), ".crunch") 46 | 47 | # Log File Path Constants 48 | LOGFILE_PATH = os.path.join(CRUNCH_DOT_DIRECTORY, "crunch.log") 49 | 50 | HELP_STRING = """ 51 | ================================================== 52 | crunch 53 | Copyright 2019 Christopher Simpkins 54 | MIT License 55 | 56 | Source: https://github.com/chrissimpkins/Crunch 57 | ================================================== 58 | 59 | crunch is a command line executable that performs lossy optimization of one 60 | or more png image files with pngquant and zopflipng. 61 | 62 | Usage: 63 | $ crunch [image path 1]...[image path n] 64 | 65 | Options: 66 | --help, -h application help 67 | --usage application usage 68 | --version, -v application version 69 | """ 70 | 71 | USAGE = "$ crunch [image path 1]...[image path n]" 72 | 73 | 74 | def main(argv): 75 | 76 | # Create the Crunch dot directory in $HOME if it does not exist 77 | # Only used for macOS GUI and macOS right-click menu service execution 78 | if len(argv) > 0 and argv[0] in ("--gui", "--service"): 79 | if not os.path.isdir(CRUNCH_DOT_DIRECTORY): 80 | os.makedirs(CRUNCH_DOT_DIRECTORY) 81 | # clear the text in the log file before every script execution 82 | # logging is only maintained for the last execution of the script 83 | open(LOGFILE_PATH, "w").close() 84 | 85 | # //////////////////////// 86 | # ANSI COLOR DEFINITIONS 87 | # //////////////////////// 88 | if not is_gui(sys.argv): 89 | ERROR_STRING = "[ " + format_ansi_red("!") + " ]" 90 | else: 91 | ERROR_STRING = "[ ! ]" 92 | 93 | # ////////////////////////////////// 94 | # CONFIRM ARGUMENT PRESENT 95 | # ////////////////////////////////// 96 | 97 | if len(argv) == 0: 98 | sys.stderr.write( 99 | f"{ERROR_STRING} Please include one or more paths to PNG image files as " 100 | "arguments to the script.{os.linesep}" 101 | ) 102 | sys.exit(1) 103 | 104 | # ////////////////////////////////////// 105 | # HELP, USAGE, VERSION option handling 106 | # ////////////////////////////////////// 107 | 108 | if argv[0] in ("-v", "--version"): 109 | print(VERSION_STRING) 110 | sys.exit(0) 111 | elif argv[0] in ("-h", "--help"): 112 | print(HELP_STRING) 113 | sys.exit(0) 114 | elif argv[0] == "--usage": 115 | print(USAGE) 116 | sys.exit(0) 117 | 118 | # //////////////////////// 119 | # DEFINE DEPENDENCY PATHS 120 | # //////////////////////// 121 | PNGQUANT_EXE_PATH = get_pngquant_path() 122 | ZOPFLIPNG_EXE_PATH = get_zopflipng_path() 123 | 124 | # //////////////////// 125 | # PARSE PNG_PATH_LIST 126 | # //////////////////// 127 | 128 | if is_gui(argv): 129 | png_path_list = argv[1:] 130 | else: 131 | png_path_list = argv 132 | 133 | # ////////////////////////////////// 134 | # COMMAND LINE ERROR HANDLING 135 | # ////////////////////////////////// 136 | 137 | NOTPNG_ERROR_FOUND = False 138 | for png_path in png_path_list: 139 | # Not a file test 140 | if not os.path.isfile(png_path): # is not an existing file 141 | sys.stderr.write( 142 | f"{ERROR_STRING} '{png_path}' does not appear to be a " 143 | f"valid path to a PNG file{os.linesep}" 144 | ) 145 | sys.exit(1) # not a file, abort immediately 146 | # PNG validity test 147 | if not is_valid_png(png_path): 148 | sys.stderr.write( 149 | f"{ERROR_STRING} '{png_path}' is not a valid PNG file.{os.linesep}" 150 | ) 151 | if is_gui(argv): 152 | log_error(f"{png_path} is not a valid PNG file.") 153 | NOTPNG_ERROR_FOUND = True 154 | 155 | # Exit after checking all file requests and reporting on all invalid 156 | # file paths (above) 157 | if NOTPNG_ERROR_FOUND is True: 158 | sys.stderr.write( 159 | f"The request was not executed successfully. Please try again " 160 | f"with one or more valid PNG files.{os.linesep}" 161 | ) 162 | if is_gui(argv): 163 | log_error( 164 | "The request was not executed successfully. Please try " 165 | "again with one or more valid PNG files." 166 | ) 167 | sys.exit(1) 168 | 169 | # Dependency error handling 170 | if not os.path.exists(PNGQUANT_EXE_PATH): 171 | sys.stderr.write( 172 | f"{ERROR_STRING} pngquant executable was not identified on path " 173 | f"'{PNGQUANT_EXE_PATH}'{os.linesep}" 174 | ) 175 | if is_gui(argv): 176 | log_error(f"pngquant was not found on the expected path {PNGQUANT_EXE_PATH}") 177 | sys.exit(1) 178 | elif not os.path.exists(ZOPFLIPNG_EXE_PATH): 179 | sys.stderr.write( 180 | f"{ERROR_STRING} zopflipng executable was not identified on path " 181 | f"'{ZOPFLIPNG_EXE_PATH}'{os.linesep}" 182 | ) 183 | if is_gui(argv): 184 | log_error( 185 | f"zopflipng was not found on the expected path {ZOPFLIPNG_EXE_PATH}" 186 | ) 187 | sys.exit(1) 188 | 189 | # //////////////////////////////////// 190 | # OPTIMIZATION PROCESSING 191 | # //////////////////////////////////// 192 | print("Crunching ...") 193 | 194 | if len(png_path_list) == 1: 195 | # there is only one PNG file, skip spawning of processes and just optimize it 196 | optimize_png(png_path_list[0]) 197 | else: 198 | processes = PROCESSES 199 | # if not defined by user, start by defining spawned processes as number 200 | # of available cores 201 | if processes == 0: 202 | processes = cpu_count() 203 | 204 | # if total cores available is greater than number of files requested, 205 | # limit to the latter number 206 | if processes > len(png_path_list): 207 | processes = len(png_path_list) 208 | 209 | print( 210 | f"Spawning {processes} processes to optimize {len(png_path_list)} " 211 | f"image files..." 212 | ) 213 | p = Pool(processes) 214 | try: 215 | p.map(optimize_png, png_path_list) 216 | except Exception as e: 217 | stdstream_lock.acquire() 218 | sys.stderr.write(f"-----{os.linesep}") 219 | sys.stderr.write( 220 | f"{ERROR_STRING} Error detected during execution of the request." 221 | f"{os.linesep}" 222 | ) 223 | sys.stderr.write(f"{e}{os.linesep}") 224 | stdstream_lock.release() 225 | if is_gui(argv): 226 | log_error(str(e)) 227 | sys.exit(1) 228 | 229 | # end of successful processing, exit code 0 230 | if is_gui(argv): 231 | log_info("Crunch execution ended.") 232 | sys.exit(0) 233 | 234 | 235 | # /////////////////////// 236 | # FUNCTION DEFINITIONS 237 | # /////////////////////// 238 | 239 | 240 | def optimize_png(png_path): 241 | img = ImageFile(png_path) 242 | 243 | # define pngquant and zopflipng paths 244 | PNGQUANT_EXE_PATH = get_pngquant_path() 245 | ZOPFLIPNG_EXE_PATH = get_zopflipng_path() 246 | 247 | # //////////////////////// 248 | # ANSI COLOR DEFINITIONS 249 | # //////////////////////// 250 | if not is_gui(sys.argv): 251 | ERROR_STRING = "[ " + format_ansi_red("!") + " ]" 252 | else: 253 | ERROR_STRING = "[ ! ]" 254 | 255 | # -------------- 256 | # pngquant stage 257 | # -------------- 258 | pngquant_options = ( 259 | " --quality=80-98 --skip-if-larger --force --strip --speed 1 --ext -crunch.png " 260 | ) 261 | pngquant_command = PNGQUANT_EXE_PATH + pngquant_options + shellquote(img.pre_filepath) 262 | try: 263 | subprocess.check_output(pngquant_command, stderr=subprocess.STDOUT, shell=True) 264 | except CalledProcessError as cpe: 265 | if cpe.returncode == 98: 266 | # this is the status code when file size increases with execution of pngquant. 267 | # ignore at this stage, original file copied at beginning of zopflipng 268 | # processing below if it is not present due to these errors 269 | pass 270 | elif cpe.returncode == 99: 271 | # this is the status code when the image quality falls below the set min value 272 | # ignore at this stage, original lfile copied at beginning of zopflipng 273 | # processing below if it is not present to these errors 274 | pass 275 | else: 276 | stdstream_lock.acquire() 277 | sys.stderr.write( 278 | f"{ERROR_STRING} {img.pre_filepath} processing failed at the pngquant " 279 | f"stage.{os.linesep}" 280 | ) 281 | stdstream_lock.release() 282 | if is_gui(sys.argv): 283 | log_error( 284 | f"{img.pre_filepath} processing failed at the pngquant stage." 285 | f"{os.linesep}{cpe}" 286 | ) 287 | return None 288 | else: 289 | raise cpe 290 | except Exception as e: 291 | if is_gui(sys.argv): 292 | log_error( 293 | f"{img.pre_filepath} processing failed at the pngquant stage." 294 | f"{os.linesep}{e}" 295 | ) 296 | return None 297 | else: 298 | raise e 299 | 300 | # --------------- 301 | # zopflipng stage 302 | # --------------- 303 | # use --filters=0 by default for quantized PNG files (based upon testing by CS) 304 | zopflipng_options = " -y --filters=0 " 305 | # confirm that a file with proper path was generated by pngquant 306 | # pngquant does not write expected file path if the file was larger after processing 307 | if not os.path.exists(img.post_filepath): 308 | shutil.copy(img.pre_filepath, img.post_filepath) 309 | # If pngquant did not quantize the file, permit zopflipng to attempt compression 310 | # with mulitple filters. This achieves better compression than the default 311 | # approach for non-quantized PNG files, but takes significantly longer 312 | # (based upon testing by CS) 313 | zopflipng_options = " -y --lossy_transparent " 314 | zopflipng_command = ( 315 | ZOPFLIPNG_EXE_PATH 316 | + zopflipng_options 317 | + shellquote(img.post_filepath) 318 | + " " 319 | + shellquote(img.post_filepath) 320 | ) 321 | try: 322 | subprocess.check_output(zopflipng_command, stderr=subprocess.STDOUT, shell=True) 323 | except CalledProcessError as cpe: 324 | stdstream_lock.acquire() 325 | sys.stderr.write( 326 | f"{ERROR_STRING} {img.pre_filepath} processing failed at the zopflipng " 327 | f"stage.{os.linesep}" 328 | ) 329 | stdstream_lock.release() 330 | if is_gui(sys.argv): 331 | log_error( 332 | f"{img.pre_filepath} processing failed at the zopflipng stage." 333 | f"{os.linesep}{cpe}" 334 | ) 335 | return None 336 | else: 337 | raise cpe 338 | except Exception as e: 339 | if is_gui(sys.argv): 340 | log_error( 341 | f"{img.pre_filepath} processing failed at the zopflipng stage." 342 | f"{os.linesep}{e}" 343 | ) 344 | return None 345 | else: 346 | raise e 347 | 348 | # Check file size post-optimization and report comparison with pre-optimization file 349 | img.get_post_filesize() 350 | percent = img.get_compression_percent() 351 | percent_string = "{0:.2f}%".format(percent) 352 | # if compression occurred, color the percent string green 353 | # otherwise, leave it default text color 354 | if not is_gui(sys.argv) and percent < 100: 355 | percent_string = format_ansi_green(percent_string) 356 | 357 | # report percent original file size / post file path / size (bytes) to 358 | # stdout (command line executable) 359 | stdstream_lock.acquire() 360 | print( 361 | "[ " 362 | + percent_string 363 | + " ] " 364 | + img.post_filepath 365 | + " (" 366 | + str(img.post_size) 367 | + " bytes)" 368 | ) 369 | stdstream_lock.release() 370 | 371 | # report percent original file size / post file path / size (bytes) to log file 372 | # (macOS GUI + right-click service) 373 | if is_gui(sys.argv): 374 | log_info( 375 | "[ " 376 | + percent_string 377 | + " ] " 378 | + img.post_filepath 379 | + " (" 380 | + str(img.post_size) 381 | + " bytes)" 382 | ) 383 | 384 | 385 | # ----------- 386 | # Utilities 387 | # ----------- 388 | 389 | 390 | def fix_filepath_args(args): 391 | arg_list = [] 392 | parsed_filepath = "" 393 | for arg in args: 394 | if arg[0] == "-": 395 | # add command line options 396 | arg_list.append(arg) 397 | elif len(arg) > 2 and "." in arg[1:]: 398 | # if format is `\w+\.\w+`, then this is a filename, not directory 399 | # this is the end of a filepath string that may have had 400 | # spaces in directories prior to this level. Let's recreate 401 | # the entire original path 402 | filepath = parsed_filepath + arg 403 | arg_list.append(filepath) 404 | # reset the temp string that is used to reconstruct the filepaths 405 | parsed_filepath = "" 406 | else: 407 | # if the argument does not end with a .png, then there must have 408 | # been a space in the directory paths, let's add it back 409 | parsed_filepath = parsed_filepath + arg + " " 410 | # return new argument list with fixed filepaths to calling code 411 | return arg_list 412 | 413 | 414 | def get_pngquant_path(): 415 | if sys.argv[1] == "--gui": 416 | return "./pngquant" 417 | elif sys.argv[1] == "--service": 418 | return "/Applications/Crunch.app/Contents/Resources/pngquant" 419 | else: 420 | return PNGQUANT_CLI_PATH 421 | 422 | 423 | def get_zopflipng_path(): 424 | if sys.argv[1] == "--gui": 425 | return "./zopflipng" 426 | elif sys.argv[1] == "--service": 427 | return "/Applications/Crunch.app/Contents/Resources/zopflipng" 428 | else: 429 | return ZOPFLIPNG_CLI_PATH 430 | 431 | 432 | def is_gui(arglist): 433 | return "--gui" in arglist or "--service" in arglist 434 | 435 | 436 | def is_valid_png(filepath): 437 | # The PNG byte signature (https://www.w3.org/TR/PNG/#5PNG-file-signature) 438 | expected_signature = struct.pack("8B", 137, 80, 78, 71, 13, 10, 26, 10) 439 | # open the file and read first 8 bytes 440 | with open(filepath, "rb") as filer: 441 | signature = filer.read(8) 442 | # return boolean test result for first eight bytes == expected PNG byte signature 443 | return signature == expected_signature 444 | 445 | 446 | def log_error(errmsg): 447 | current_time = time.strftime("%m-%d-%y %H:%M:%S") 448 | logging_lock.acquire() 449 | with open(LOGFILE_PATH, "a") as filewriter: 450 | filewriter.write(current_time + "\tERROR\t" + errmsg + os.linesep) 451 | filewriter.flush() 452 | os.fsync(filewriter.fileno()) 453 | logging_lock.release() 454 | 455 | 456 | def log_info(infomsg): 457 | current_time = time.strftime("%m-%d-%y %H:%M:%S") 458 | logging_lock.acquire() 459 | with open(LOGFILE_PATH, "a") as filewriter: 460 | filewriter.write(current_time + "\tINFO\t" + infomsg + os.linesep) 461 | filewriter.flush() 462 | os.fsync(filewriter.fileno()) 463 | logging_lock.release() 464 | return None 465 | 466 | 467 | def shellquote(filepath): 468 | return "'" + filepath.replace("'", "'\\''") + "'" 469 | 470 | 471 | def format_ansi_red(text): 472 | if sys.stdout.isatty(): 473 | return "\033[0;31m" + text + "\033[0m" 474 | else: 475 | return text 476 | 477 | 478 | def format_ansi_green(text): 479 | if sys.stdout.isatty(): 480 | return "\033[0;32m" + text + "\033[0m" 481 | else: 482 | return text 483 | 484 | 485 | # /////////////////////// 486 | # OBJECT DEFINITIONS 487 | # /////////////////////// 488 | 489 | 490 | class ImageFile(object): 491 | def __init__(self, filepath): 492 | self.pre_filepath = filepath 493 | self.post_filepath = self._get_post_filepath() 494 | self.pre_size = self._get_filesize(self.pre_filepath) 495 | self.post_size = 0 496 | 497 | def _get_filesize(self, file_path): 498 | return os.path.getsize(file_path) 499 | 500 | def _get_post_filepath(self): 501 | path, extension = os.path.splitext(self.pre_filepath) 502 | return path + "-crunch" + extension 503 | 504 | def get_post_filesize(self): 505 | self.post_size = self._get_filesize(self.post_filepath) 506 | 507 | def get_compression_percent(self): 508 | ratio = float(self.post_size) / float(self.pre_size) 509 | percent = ratio * 100 510 | return percent 511 | 512 | 513 | if __name__ == "__main__": 514 | # bugfix for macOS GUI / right-click service filepath issue 515 | # when spaces are included in the absolute path to the image 516 | # file. https://github.com/chrissimpkins/Crunch/issues/30 517 | # This workaround reconstructs the original filepaths 518 | # that are split by the shell script into separate arguments 519 | # when there are spaces in the macOS file path 520 | if len(sys.argv) > 1 and sys.argv[1] in ("--gui", "--service"): 521 | arg_list = fix_filepath_args(sys.argv[1:]) 522 | main(arg_list) 523 | else: 524 | # the command line executable assumes that users will appropriately quote 525 | # or escape special characters (including spaces) on the command line, 526 | # no need for the special parsing treatment above 527 | main(sys.argv[1:]) 528 | -------------------------------------------------------------------------------- /src/include/licenses/zopflipng_license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2013 Google Inc. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | // Author: lode.vandevenne@gmail.com (Lode Vandevenne) 16 | // Author: jyrki.alakuijala@gmail.com (Jyrki Alakuijala) 17 | 18 | // Library to recompress and optimize PNG images. Uses Zopfli as the compression 19 | // backend, chooses optimal PNG color model, and tries out several PNG filter 20 | // strategies. 21 | -------------------------------------------------------------------------------- /src/include/pngquant: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/src/include/pngquant -------------------------------------------------------------------------------- /src/include/zopflipng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/src/include/zopflipng -------------------------------------------------------------------------------- /src/install-dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ================================================================== 4 | # install-dependencies.sh 5 | # Install Crunch optimization binary dependencies 6 | # 7 | # Copyright 2018 Christopher Simpkins 8 | # MIT License 9 | # 10 | # Source Repository: https://github.com/chrissimpkins/Crunch 11 | # ================================================================== 12 | 13 | PNGQUANT_BUILD_DIR="$HOME/pngquant" 14 | PNGQUANT_EXE="$PNGQUANT_BUILD_DIR/pngquant" 15 | ZOPFLIPNG_BUILD_DIR="$HOME/zopfli" 16 | ZOPFLIPNG_EXE="$ZOPFLIPNG_BUILD_DIR/zopflipng" 17 | 18 | PNGQUANT_VERSION_TAG="2.12.5" 19 | ZOPFLIPNG_VERSION_TAG="v2.2.0" 20 | LIBPNG_VERSION="1.6.37" 21 | LIBPNG_VERSION_FILE="v$LIBPNG_VERSION.tar.gz" 22 | 23 | LITTLECMS_VERSION="2.9" 24 | LITTLECMS_FILE="lcms$LITTLECMS_VERSION.tar.gz" 25 | 26 | 27 | # //////////////////// 28 | # 29 | # BUILD pngquant 30 | # 31 | # //////////////////// 32 | 33 | # Clone pngquant source 34 | if [ -d "$PNGQUANT_BUILD_DIR" ]; then 35 | rm -rf "$PNGQUANT_BUILD_DIR" 36 | fi 37 | 38 | cd "$HOME" || exit 1 39 | 40 | git clone --recursive https://github.com/kornelski/pngquant.git 41 | cd "$PNGQUANT_BUILD_DIR" || exit 1 42 | git checkout $PNGQUANT_VERSION_TAG 43 | git submodule update 44 | 45 | # Clone libpng source as a subdirectory of pngquant source (as per pngquant static compilation documentation) 46 | curl -L -O "https://github.com/glennrp/libpng/archive/refs/tags/$LIBPNG_VERSION_FILE" 47 | tar -xf $LIBPNG_VERSION_FILE 48 | rm $LIBPNG_VERSION_FILE 49 | cd "libpng-$LIBPNG_VERSION" || exit 1 50 | # build local libpng 51 | ./configure && make 52 | 53 | # Clone local little-cms2 library source and compile 54 | cd "$PNGQUANT_BUILD_DIR" || exit 1 55 | curl -L -O "https://github.com/mm2/Little-CMS/archive/refs/tags/$LITTLECMS_FILE" 56 | tar -xf $LITTLECMS_FILE 57 | rm $LITTLECMS_FILE 58 | 59 | cd "Little-CMS-lcms$LITTLECMS_VERSION" || exit 1 60 | ./configure && make 61 | 62 | # Build local pngquant executable using local libpng 63 | cd "$PNGQUANT_BUILD_DIR" || exit 1 64 | LCMS2_STATIC=1 ./configure --with-libpng="libpng-$LIBPNG_VERSION" --with-lcms2 && make 65 | 66 | 67 | # ///////////////// 68 | # 69 | # BUILD zopflipng 70 | # 71 | # ///////////////// 72 | 73 | if [ -d "$ZOPFLIPNG_BUILD_DIR" ]; then 74 | rm -rf "$ZOPFLIPNG_BUILD_DIR" 75 | fi 76 | 77 | cd "$HOME" || exit 1 78 | 79 | git clone https://github.com/chrissimpkins/zopfli.git 80 | cd zopfli || exit 1 81 | git checkout "$ZOPFLIPNG_VERSION_TAG" 82 | 83 | make zopflipng 84 | 85 | # /////////////////////// 86 | # 87 | # Tests and user reports 88 | # 89 | # /////////////////////// 90 | 91 | # Test for expected install file paths and report outcome to user 92 | 93 | printf "\\n\\n------------------------------\\nTesting Builds...\\n------------------------------\\n" 94 | 95 | printf "[?] %s test...\\n\\n" "$PNGQUANT_EXE" 96 | if [ -f "$PNGQUANT_EXE" ]; then 97 | "$PNGQUANT_EXE" --help 98 | else 99 | printf "[ERROR]: pngquant executable was not found on the expected path: %s\\n" "$PNGQUANT_EXE" 100 | printf "The install attempt did not complete successfully. Please report this error.\\n" 101 | exit 1 102 | fi 103 | 104 | printf "\\n[?] %s test...\\n\\n" "$ZOPFLIPNG_EXE" 105 | if [ -f "$ZOPFLIPNG_EXE" ]; then 106 | "$ZOPFLIPNG_EXE" --help 107 | else 108 | printf "[ERROR]: zopflipng executable was not found on the expected path: %s\\n" "$PNGQUANT_EXE" 109 | printf "The install attempt did not complete successfully. Please report this error." 110 | exit 1 111 | fi 112 | 113 | printf "\\n\\n------------------------------\\nEnd Tests\\n------------------------------\\n" 114 | 115 | printf "\\n---------- BUILD PATHS ----------\\n" 116 | printf "[*] pngquant path: %s\\n" "$PNGQUANT_EXE" 117 | printf "[*] zopflipng path: %s\\n" "$ZOPFLIPNG_EXE" 118 | printf "\\n\\n[OK] Dependency installs complete.\\n" 119 | exit 0 120 | -------------------------------------------------------------------------------- /src/test_crunch_errors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import pytest 7 | 8 | import src.crunch 9 | 10 | # /////////////////////////////////////////////////////// 11 | # 12 | # pytest capsys capture tests 13 | # confirms capture of std output and std error streams 14 | # 15 | # /////////////////////////////////////////////////////// 16 | 17 | 18 | def test_pytest_capsys(capsys): 19 | print("bogus text for a test") 20 | sys.stderr.write("more text for a test") 21 | out, err = capsys.readouterr() 22 | assert out == "bogus text for a test\n" 23 | assert out != "something else" 24 | assert err == "more text for a test" 25 | assert err != "something else" 26 | 27 | 28 | # /////////////////////////////////////////////////////// 29 | # 30 | # Command line error tests 31 | # 32 | # /////////////////////////////////////////////////////// 33 | 34 | 35 | def test_crunch_missing_argument_error(capsys): 36 | with pytest.raises(SystemExit) as exit_info: 37 | src.crunch.main([]) 38 | 39 | out, err = capsys.readouterr() 40 | assert len(err) > 0 41 | assert err.startswith("[ ! ]") is True 42 | assert exit_info.value.code == 1 43 | 44 | 45 | def test_crunch_missing_file_error(capsys): 46 | with pytest.raises(SystemExit) as exit_info: 47 | src.crunch.main(["bogusfile.png"]) 48 | 49 | out, err = capsys.readouterr() 50 | assert len(err) > 0 51 | assert err.startswith("[ ! ]") is True 52 | assert exit_info.value.code == 1 53 | 54 | 55 | def test_crunch_bad_filepath_error(capsys): 56 | with pytest.raises(SystemExit) as exit_info: 57 | src.crunch.main(["src/test_crunch_errors.py"]) 58 | 59 | out, err = capsys.readouterr() 60 | assert len(err) > 0 61 | assert err.startswith("[ ! ]") is True 62 | assert exit_info.value.code == 1 63 | 64 | 65 | # /////////////////////////////////////////////////////// 66 | # 67 | # Missing dependency error tests 68 | # 69 | # /////////////////////////////////////////////////////// 70 | 71 | def test_crunch_missing_pngquant_error(capsys, monkeypatch): 72 | def return_bogus_path(): 73 | return os.path.join("bogus", "pngquant") 74 | monkeypatch.setattr(src.crunch, 'get_pngquant_path', return_bogus_path) 75 | testpath = os.path.join("testfiles", "robot.png") 76 | with pytest.raises(SystemExit) as exit_info: 77 | src.crunch.main([testpath]) 78 | 79 | out, err = capsys.readouterr() 80 | assert err.startswith("[ ! ]") is True 81 | assert exit_info.value.code == 1 82 | 83 | 84 | def test_crunch_missing_zopflipng_error(capsys, monkeypatch): 85 | def return_bogus_path(): 86 | return os.path.join("bogus", "zopflipng") 87 | monkeypatch.setattr(src.crunch, 'get_zopflipng_path', return_bogus_path) 88 | testpath = os.path.join("testfiles", "robot.png") 89 | with pytest.raises(SystemExit) as exit_info: 90 | src.crunch.main([testpath]) 91 | 92 | out, err = capsys.readouterr() 93 | assert err.startswith("[ ! ]") is True 94 | assert exit_info.value.code == 1 95 | 96 | 97 | # /////////////////////////////////////////////////////// 98 | # 99 | # Multiprocessing.Pool error tests 100 | # 101 | # /////////////////////////////////////////////////////// 102 | 103 | def test_crunch_exception_multiprocessing_pool(capsys, monkeypatch): 104 | def raise_ioerror(): 105 | raise IOError 106 | monkeypatch.setattr(src.crunch, 'optimize_png', raise_ioerror) 107 | testpath1 = os.path.join("testfiles", "robot.png") 108 | testpath2 = os.path.join("testfiles", "robot.png") 109 | with pytest.raises(SystemExit) as exit_info: 110 | src.crunch.main([testpath1, testpath2]) 111 | 112 | out, err = capsys.readouterr() 113 | assert "[ ! ]" in err 114 | assert exit_info.value.code == 1 115 | -------------------------------------------------------------------------------- /src/test_crunch_obj.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os.path 5 | import pytest 6 | 7 | from src.crunch import ImageFile 8 | 9 | 10 | def test_crunch_imagefile_obj_instantiation(): 11 | imgfile = ImageFile(os.path.join("img", "robot-1214536_640.png")) 12 | assert imgfile.pre_filepath == os.path.join("img", "robot-1214536_640.png") 13 | assert imgfile.post_filepath == os.path.join("img", "robot-1214536_640-crunch.png") 14 | assert imgfile.pre_size == 197193 15 | 16 | 17 | def test_crunch_imagefile_obj_get_post_filesize_method(): 18 | imgfile = ImageFile(os.path.join("img", "robot-1214536_640.png")) 19 | imgfile.get_post_filesize() 20 | assert imgfile.post_size == 67596 21 | 22 | 23 | def test_crunch_imagefile_obj_get_compression_percent_method(): 24 | imgfile = ImageFile(os.path.join("img", "robot-1214536_640.png")) 25 | imgfile.pre_size = 100 26 | imgfile.post_size = 10 27 | percent = imgfile.get_compression_percent() 28 | assert percent == float(10) 29 | 30 | -------------------------------------------------------------------------------- /src/utils/dssim-comparisons.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Cat image" 4 | dssim "../../img/cat-1285634_640.png" "../../img/cat-1285634_640-crunch.png" 5 | 6 | # sun's rays image 7 | echo "\n\nSun image" 8 | dssim "../../img/suns-rays-478249_640.png" "../../img/suns-rays-478249_640-crunch.png" 9 | 10 | # prairie image 11 | echo "\n\nPrairie image" 12 | dssim "../../img/prairie-679014_640.png" "../../img/prairie-679014_640-crunch.png" 13 | 14 | # Robot image 15 | echo "\n\nRobot image" 16 | dssim "../../img/robot-1214536_640.png" "../../img/robot-1214536_640-crunch.png" 17 | 18 | # Color circule image 19 | echo "\n\nColor circle image" 20 | dssim "../../img/colors-157474_640.png" "../../img/colors-157474_640-crunch.png" 21 | 22 | # Flowers image 23 | echo "\n\nFlowers image" 24 | dssim "../../img/flowers-67839_640.png" "../../img/flowers-67839_640-crunch.png" 25 | 26 | -------------------------------------------------------------------------------- /src/utils/image-compare.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | from pathlib import Path 5 | 6 | 7 | def print_results(size_pre, size_post): 8 | print(f"Pre: {size_pre}") 9 | print(f"Post: {size_post}") 10 | percent = (size_post / size_pre) * 100 11 | print(f"{percent}%") 12 | 13 | 14 | p = Path(".") 15 | 16 | # cat image 17 | print("Cat image") 18 | pre = os.path.getsize(list(p.glob("../../img/cat-1285634_640.png"))[0]) 19 | post = os.path.getsize(list(p.glob("../../img/cat-1285634_640-crunch.png"))[0]) 20 | print_results(pre, post) 21 | 22 | # sun's rays image 23 | print("\n\nSun image") 24 | pre = os.path.getsize(list(p.glob("../../img/suns-rays-478249_640.png"))[0]) 25 | post = os.path.getsize(list(p.glob("../../img/suns-rays-478249_640-crunch.png"))[0]) 26 | print_results(pre, post) 27 | 28 | # prairie image 29 | print("\n\nPrairie image") 30 | pre = os.path.getsize(list(p.glob("../../img/prairie-679014_640.png"))[0]) 31 | post = os.path.getsize(list(p.glob("../../img/prairie-679014_640-crunch.png"))[0]) 32 | print_results(pre, post) 33 | 34 | # Robot image 35 | print("\n\nRobot image") 36 | pre = os.path.getsize(list(p.glob("../../img/robot-1214536_640.png"))[0]) 37 | post = os.path.getsize(list(p.glob("../../img/robot-1214536_640-crunch.png"))[0]) 38 | print_results(pre, post) 39 | 40 | # Color circule image 41 | print("\n\nColor circle image") 42 | pre = os.path.getsize(list(p.glob("../../img/colors-157474_640.png"))[0]) 43 | post = os.path.getsize(list(p.glob("../../img/colors-157474_640-crunch.png"))[0]) 44 | print_results(pre, post) 45 | 46 | # Flowers image 47 | print("\n\nFlowers image") 48 | pre = os.path.getsize(list(p.glob("../../img/flowers-67839_640.png"))[0]) 49 | post = os.path.getsize(list(p.glob("../../img/flowers-67839_640-crunch.png"))[0]) 50 | print_results(pre, post) 51 | 52 | -------------------------------------------------------------------------------- /testfiles/cat-cr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/testfiles/cat-cr.png -------------------------------------------------------------------------------- /testfiles/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/testfiles/cat.png -------------------------------------------------------------------------------- /testfiles/img with spaces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/testfiles/img with spaces.png -------------------------------------------------------------------------------- /testfiles/robot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/testfiles/robot.png -------------------------------------------------------------------------------- /testfiles/test.txt: -------------------------------------------------------------------------------- 1 | test file -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | skipsdist = True 3 | envlist = py310 4 | 5 | [testenv] 6 | deps = pytest 7 | commands = 8 | pytest src 9 | -------------------------------------------------------------------------------- /ui/MainMenu.nib/keyedobjects.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrissimpkins/Crunch/a343532e128bef8d49636b7044b012bf05824658/ui/MainMenu.nib/keyedobjects.nib --------------------------------------------------------------------------------