Commit graph

55 commits

Author SHA1 Message Date
andrews05
aebddf443a
Allow brute filter configuration (#720)
This PR allows the Brute filter parameters to be configured rather than
using fixed constants. It also tweaks the presets to take advantage of
these new options:
o3: level=1, lines=3 (fractionally worse/faster)
o4: level=1, lines=4 (same as before)
o5: level=4, lines=4 (fractionally better/slower)
o6: level=5, lines=8 (slightly better/slower)

The parameters can be explicitly set in both the API and the CLI.
2025-09-25 23:20:29 +02:00
andrews05
14532e8bf5
BC breaks for v10 (#715)
This is a collection of all the BC breaks mentioned in #714, except for
11 which I'm not including for now.

Fixes #658.
Fixes #660.

It might be best to review each commit individually, referencing the
notes in #714 and #660 (I just didn't want to create a dozen separate
PRs).
2025-09-20 17:16:22 +02:00
andrews05
71290acc62
Reduce memory usage of fast evaluation (#713)
This is an experiment I started a while ago before life happened. It
reduces memory usage of fast evaluation (-o2 and lower), bringing it
inline with normal (slow) evaluation. It does this by not retaining the
filtered image data of the evaluations, but instead retaining the row
filters that were used in each line so it can be quickly re-filtered
when required. This does incur a tiny performance penalty, but it's
negligible even at `-o0`.
Although this is fully functional, there are a few rough spots in the
code so I'm just opening it as a draft for now.

```
PR -sao6
       70.08 real       614.36 user         2.66 sys
          2024243200  maximum resident set size
          1839339520  peak memory footprint

PR -sao2
       11.16 real        60.85 user         1.33 sys
          1982562304  maximum resident set size
          1842173824  peak memory footprint

PR -sao2 -t1
       55.11 real        53.85 user         0.76 sys
           429457408  maximum resident set size
           245008064  peak memory footprint

master -sao6
       67.70 real       616.07 user         2.72 sys
          2043379712  maximum resident set size
          1838340416  peak memory footprint

master -sao2
       11.53 real        60.63 user         1.25 sys
          2753396736  maximum resident set size
          2283741440  peak memory footprint

master -sao2 -t1
       54.29 real        53.55 user         0.72 sys
           626311168  maximum resident set size
           305252544  peak memory footprint
```

Note that this involves some refactoring of `RowFilter` and the new
`FilterStrategy`. These are breaking changes so it will ultimately be
destined for v10. One advantage to this new structure is it opens the
door for future changes such as allowing the Brute strategy to take a
parameter for the number of lines.
2025-09-19 20:36:17 +02:00
andrews05
c60dbd12a5
Update runners, rust, and dependencies (#724)
#719 is failing tests due to requiring a newer version of rust than we
currently specify. This PR updates to 1.85.1 and sets the edition to
2024.
I've also updated dependencies and runner images, using the ubuntu arm
runner which removes the need for qemu and other hacks.

Closes #719.
2025-08-25 14:49:59 +02:00
andrews05
1188b9a91e
Reduction performance tweaks (#676)
Two key changes here:
- Removed some defunct reduction benches and added some more alpha
reduction benches.
- Changed use of `chunks()` to `chunks_exact()` where appropriate to
improve performance. (As a byproduct, this also fixes a potential crash
if a malformed file has a PLTE chunk that isn't a multiple of 3.)
2025-02-08 18:49:04 +01:00
andrews05
174f36ad8f
Improve indexed to grayscale conversion (#674)
If no palette reduction occurred (or palette reductions were off), a
fully transparent palette entry may not have been zeroed to black. By
ensuring this gets cleaned in the `indexed_to_channels` transformation,
we may able to achieve a grayscale conversion that would otherwise have
been RGB.

This is the final piece of the puzzle in #649 to achieve better file
sizes on the first run and avoid further changes on a second run.

Results from the images in #649 (combining this PR and #673):
9.1.3 1st run: 2,191,773
9.1.3 2nd run: 2,191,629
PR 1st run: 2,191,380
PR 2nd run: 2,191,380
2025-02-02 18:26:18 +01:00
Alejandro González
5f0a0d6938
Fix new Clippy lints 2025-01-30 18:39:23 +01:00
andrews05
bbde68ddce
Refactor optimize_raw (#670)
Code always tends to get messy over time. I've found the `optimize_raw`
function increasingly harder to read, particularly after the addition of
fast mode, so I've taken some time to refactor and simplify it.

One change of note here is the main compression trials now use the
Evaluator. This means verbose output is a little different which is
shown below.

There is no change to performance or output size.

`-vvo2`: master
```
Processing: tests/files/rgba_8_should_be_palette_4.png
    500x400 pixels, PNG format
    8-bit RGB + Alpha, non-interlaced
    IDAT size = 2757 bytes
    File size = 18109 bytes
Eval: 4-bit Indexed (5 colors)      None       1837 bytes
Eval: 8-bit Indexed (5 colors)      None       1988 bytes
Eval: 4-bit Indexed (5 colors)      Bigrams   >1837 bytes
Eval: 8-bit Indexed (5 colors)      Bigrams   >1837 bytes
Transformed image to 4-bit Indexed (5 colors), non-interlaced
Evaluating: 2 filters
Eval: 4-bit Indexed (5 colors)      Sub       >1810 bytes
Eval: 4-bit Indexed (5 colors)      Entropy   >1810 bytes
Trying: None
    zc = 11  f = None      1583 bytes
Found better combination:
    zc = 11  f = None      1583 bytes
    IDAT size = 1583 bytes (1174 bytes decrease)
    file size = 16962 bytes (1147 bytes = 6.33% decrease)
16962 bytes (6.33% smaller): Running in pretend mode, no output
```

`-vvo2`: PR
```
Processing: tests/files/rgba_8_should_be_palette_4.png
    500x400 pixels, PNG format
    8-bit RGB + Alpha, non-interlaced
    IDAT size = 2757 bytes
    File size = 18109 bytes
Eval: 4-bit Indexed (5 colors)      None       1837 bytes
Eval: 8-bit Indexed (5 colors)      None       1988 bytes
Eval: 4-bit Indexed (5 colors)      Bigrams   >1837 bytes
Eval: 8-bit Indexed (5 colors)      Bigrams   >1837 bytes
Transformed image to 4-bit Indexed (5 colors), non-interlaced
Evaluating 2 filters
Eval: 4-bit Indexed (5 colors)      Sub       >1810 bytes
Eval: 4-bit Indexed (5 colors)      Entropy   >1810 bytes
Trying filter None with zc = 11
1610 bytes
Found better result:
    zc = 11, f = None
    IDAT size = 1583 bytes (1174 bytes decrease)
    file size = 16962 bytes (1147 bytes = 6.33% decrease)
16962 bytes (6.33% smaller): Running in pretend mode, no output
```

`-vvZo5`: master
```
Processing: tests/files/rgba_8_should_be_palette_4.png
    500x400 pixels, PNG format
    8-bit RGB + Alpha, non-interlaced
    IDAT size = 2757 bytes
    File size = 18109 bytes
Eval: 8-bit Indexed (battiato sort) None       1821 bytes
Eval: 4-bit Indexed (5 colors)      None       1657 bytes
Eval: 8-bit Indexed (mzeng sort)    None       1821 bytes
Eval: 8-bit Indexed (5 colors)      None       1821 bytes
Eval: 8-bit Indexed (battiato sort) Bigrams   >1821 bytes
Eval: 4-bit Indexed (5 colors)      Bigrams   >1657 bytes
Eval: 8-bit Indexed (mzeng sort)    Bigrams   >1657 bytes
Eval: 8-bit Indexed (5 colors)      Bigrams   >1657 bytes
Transformed image to 4-bit Indexed (5 colors), non-interlaced
Trying: 8 filters
    zc = zopfli  f = Brute     1562 bytes
    zc = zopfli  f = Sub      >1562 bytes
    zc = zopfli  f = Bigrams  >1562 bytes
    zc = zopfli  f = None      1407 bytes
    zc = zopfli  f = Up       >1407 bytes
    zc = zopfli  f = MinSum   >1407 bytes
    zc = zopfli  f = BigEnt   >1407 bytes
    zc = zopfli  f = Entropy  >1407 bytes
Found better combination:
    zc = zopfli  f = None      1407 bytes
    IDAT size = 1407 bytes (1350 bytes decrease)
    file size = 16786 bytes (1323 bytes = 7.31% decrease)
16786 bytes (7.31% smaller): Running in pretend mode, no output
```

`-vvZo5`: PR
```
Processing: tests/files/rgba_8_should_be_palette_4.png
    500x400 pixels, PNG format
    8-bit RGB + Alpha, non-interlaced
    IDAT size = 2757 bytes
    File size = 18109 bytes
Eval: 8-bit Indexed (battiato sort) None       1821 bytes
Eval: 4-bit Indexed (5 colors)      None       1657 bytes
Eval: 8-bit Indexed (mzeng sort)    None       1821 bytes
Eval: 8-bit Indexed (5 colors)      None       1821 bytes
Eval: 8-bit Indexed (battiato sort) Bigrams   >1657 bytes
Eval: 4-bit Indexed (5 colors)      Bigrams   >1657 bytes
Eval: 8-bit Indexed (mzeng sort)    Bigrams   >1657 bytes
Eval: 8-bit Indexed (5 colors)      Bigrams   >1657 bytes
Transformed image to 4-bit Indexed (5 colors), non-interlaced
Trying 8 filters with zopfli, zi = 15
Eval: 4-bit Indexed (5 colors)      Brute      1589 bytes
Eval: 4-bit Indexed (5 colors)      Bigrams    1641 bytes
Eval: 4-bit Indexed (5 colors)      Sub        1711 bytes
Eval: 4-bit Indexed (5 colors)      None       1434 bytes
Eval: 4-bit Indexed (5 colors)      Up         1764 bytes
Eval: 4-bit Indexed (5 colors)      MinSum     1760 bytes
Eval: 4-bit Indexed (5 colors)      BigEnt     1742 bytes
Eval: 4-bit Indexed (5 colors)      Entropy    1748 bytes
Found better result:
    zopfli, zi = 15, f = None
    IDAT size = 1407 bytes (1350 bytes decrease)
    file size = 16786 bytes (1323 bytes = 7.31% decrease)
16786 bytes (7.31% smaller): Running in pretend mode, no output
```
2025-01-29 20:53:11 +01:00
Kornel
a72bc945fc Clippy 2025-01-02 09:15:24 +13:00
andrews05
2d3555fe0f
Add modified zeng palette sorting method (#602)
This PR adds the modified zeng ("mzeng") palette sorting method, in
addition to the existing luma and battiato methods. Speed is very
similar to the battiato method with slightly better results on average.

Resulting sizes from two different image sets (all indexed or able to be
indexed):
| | master | PR |
|-|-|-|
| Set 1 | 29,647,156 | 29,555,697 |
| Set 2 | 23,732,133 | 23,570,862 |

Additionally, I've added a new "first colour" heuristic for both the
mzeng and battiato methods: We use the most popular colour overall, but
only if it covers at least 15% of the image. This provided 13k savings
on Set 2 vs the edge colour heuristic (which is still used in the luma
sort).
2024-04-06 01:16:29 +02:00
Alejandro González
0608ab9cab
Fix some new nightly Clippy lints and run rustfmt with prettier config 2024-02-20 19:12:35 +01:00
andrews05
b883c660fc
Additional palette sorting algorithm (#514)
This adds a new palette sorting algorithm that attempts to minimise
entropy by an approximate solution to the Traveling Salesman Problem.
The algorithm comes from "An efficient Re-indexing algorithm for
color-mapped images" by Battiato et al
(https://ieeexplore.ieee.org/document/1344033).
It's fast and effective and works in addition to the luma sort (which
remains the single most effective sort). In order to keep lower presets
fast though, I've only enabled this for o3 and higher.

Results on a set of 190 indexed images at `-o5`:
18,932,727 bytes - master
18,578,306 bytes - PR
18,559,863 bytes - PR + #509
(These images may be particularly suited to alternative sorting methods
- the gains here are not necessarily what should be expected on average)

Note I looked into the 120 different palette sorting methods from
TruePNG, as mentioned in #74 (and seen in action in the Zopfli KrzYmod
fork). They're... largely ineffective. The combination of all 120
methods are outperformed by just the existing luma sort plus this new
one. That's not to say there's nothing further to be gained from them,
but trying to brute force all the combinations definitely seems like a
bad idea. There are other algorithms I hope to explore in future...

@ace-dent Thought this might interest you


UPDATE: I realised a quick tweak to alpha values in the luma sort can
provide a great improvement on images with transparency. The following
numbers were taken with PR #509 as base.
`-o2`:
19,065,549 bytes - base (luma sort)
18,949,747 bytes - modified luma sort

`-o5`:
18,922,165 bytes - base (luma sort)
18,559,863 bytes - new sorting algorithm + luma sort
18,544,813 bytes - new sorting algorithm + modified luma sort
2023-07-11 12:33:57 -04:00
Josh Holmer
31e796c4e8 Revert "Provide BufferedZopfliDeflater and allow user to pass in a custom Deflater (#530)"
This reverts commit 2a59419bdf.
2023-07-09 13:40:20 -04:00
Josh Holmer
2a59419bdf
Provide BufferedZopfliDeflater and allow user to pass in a custom Deflater (#530)
* Add .whitesource configuration file

* Experimental: allow Zopfli to use any size BufWriter

* Allow user to specify the output buffer size as well

* Allow user to specify maximum block splits

* Reformat and fix warnings

* Use deflater on iCCP chunk as well

* Bug fix: need to implement Zlib format

* Make functions const when possible

* Switch to using zopfli::Options in prep for https://github.com/zopfli-rs/zopfli/pull/21

* Switch to using zopfli::Options in prep for https://github.com/zopfli-rs/zopfli/pull/21

* Cargo fmt

* Fix compilation

* Fix tests

* Fix more lints

* Fix more lints

* Fix compilation more

---------

Co-authored-by: mend-bolt-for-github[bot] <42819689+mend-bolt-for-github[bot]@users.noreply.github.com>
Co-authored-by: Chris Hennick <hennickc@amazon.com>
Co-authored-by: Chris Hennick <4961925+Pr0methean@users.noreply.github.com>
2023-07-07 22:35:01 -04:00
andrews05
96122fa45a
Expand low depths to 8-bit (#516)
* Keep track of number of pixels in each scanline

* Expand depth to 8-bit

* Attempt expand bit depth

* Simplify tracking of reduction_occurred

* Fix test

* Simplify depth handling in reductions

* Add tests for disabled reductions
2023-06-18 01:32:47 -04:00
Andrew
c71fd44454 Prevent conversion from grayscale when disabled 2023-06-18 01:02:11 -04:00
Andrew
86fccf082a Fix grayscale_reduction option 2023-05-28 16:11:41 -04:00
Andrew
88b930b5b1 Add scale16 option to force 16-bit reduction 2023-05-24 10:21:42 -04:00
andrews05
ea5f1884be
Refactor aux chunk handling (#505) 2023-05-21 15:34:23 -04:00
andrews05
d8b7ebaf47
Further small reduction improvements (#504) 2023-05-16 20:53:05 -04:00
andrews05
2f622fc7bd
Minor reduction improvements (#502)
* Fix bKGD conversion from gray to palette

* Allow grayscale reduction from 16 to 4 or less

* Refactor reduction evaluation sequence

* Separate palette into new file
2023-05-07 17:46:58 -04:00
andrews05
a3b104a2ed
Faster reductions (#479)
* Add grayscale depth tests and benches

* Don't include filter byte in PngImage.data

* Simplify reductions by not using scan lines

* Faster grayscale reduction

* Simplify reduce_color_type

* Faster depth reduction
2022-12-22 16:56:36 -05:00
andrews05
2599b9fe82
Tweaks to interlacing and format display (#476) 2022-12-12 09:54:35 -05:00
andrews05
131a3c6af7
🚀 Revamp alpha optimisation (#475) 2022-12-10 21:14:50 -05:00
andrews05
a41d7de348
New filter strategies (#461)
* Refactor filters as enum

* Include filter byte in filter output

* Add entropy filter

* Add bigrams filter

* Add bigram entropy filter

* Add brute filter

* Replace bit-vec

* Add tests and benches

* Show filters in help

* Use FxHasher in color to palette

* Use windows function for minor improvement
2022-11-19 08:49:25 -05:00
andrews05
446c788eb3
Full switch to Libdeflater (#457)
* Switch main compressor to libdeflate

* Use libdeflater in evaluate

* Use libdeflater to inflate

* Use libdeflater crc

* Tidy up

* Fix benches

* Allow libdeflater/freestanding feature

* Fix building without zopfli
2022-11-01 09:02:08 -04:00
andrews05
f688d20fe6
Allow setting libdeflate compression level (#455)
* Allow --zc with libdeflater

* Update libdeflater

* Allocate sufficient space for libdefate
2022-10-28 21:54:04 -04:00
Alejandro González
84bbec0666
Add initial support for changing Zopfli iterations (#446)
* Update and optimize dependencies

These changes update the dependencies to their latest versions, fixing
some known issues that prevented doing so in the first place.

In addition, the direct dependency on byteorder was dropped in favor
of stdlib functions that have been stabilized for some time in Rust, and
the transitive dependency on chrono, pulled by stderrlog, was also
dropped, which had been affected by security issues and improperly
maintained in the past:

- https://github.com/cardoe/stderrlog-rs/issues/31
- https://www.reddit.com/r/rust/comments/ts84n4/chrono_or_time_03/

* Run rustfmt

* Bump MSRV to 1.56.1

Updating to this patch version should not be cumbersome for end-users,
and it is required by a transitive dependency.

* Bump MSRV to 1.57.0

os_str_bytes requires it.

* Add initial support for changing Zopfli iterations

PR https://github.com/shssoichiro/oxipng/pull/445 did some dependency
updates, which included using the latest zopfli version. The latest
version of this crate exposes new options in its API that allow users to
choose the desired number of Zopfli compression iterations, which
may greatly affect execution time. In fact, other optimizers such as
zopflipng dynamically select this number depending on the input file
size (see: https://github.com/shssoichiro/oxipng/issues/414).

As a first step towards making OxiPNG deal with Zopfli better, let's add
the necessary options for libraries to be able to choose the number of
iterations. This number is still fixed to 15 as before when using the
CLI.

* Fix Clippy lint

Co-authored-by: Josh Holmer <jholmer.in@gmail.com>
2022-09-05 12:50:13 -04:00
Alejandro González
491d753edc
Add option to skip grayscale reduction of RGB(A) images (#409)
For RGB(A) images that contain gray colors, this reduction can achieve
significant space savings. However, in the absence of gamma correction
data, some PNG decoders assume more exotic color spaces for grayscale
images instead of the ubiquitous sRGB. This results in gamma
miscorrection, and for the end user this means that colors will look
wrong, like "washed-out". Java's ImageIO class, which is popular in the
JVM world to read PNG files, uses rather unconventional defaults, as
explained in this StackOverflow question: https://stackoverflow.com/questions/31312645/java-imageio-grayscale-png-issue

Gamma miscorrection problems aside, OxiPNG currently tries hard to
reduce RGB(A) images to grayscale, because it expects that reduction to
be quite effective. However, in some cases, OxiPNG generates smaller
PNG files when reducing grasycale RGB(A) images to paletted color than
actual grayscale color. For example, let's say that "~/gray.png" is a
256x256 RGBA image entirely filled with (119, 119, 119, 255) pixels.
OxiPNG, by default, reduces this image to grayscale and achieves a
68.23% decrease:

$ cargo build --release && target/release/oxipng -omax --out ~/out.png ~/gray.png
Processing: /home/user/gray.png
    256x256 pixels, PNG format
    4x8 bits/pixel, RGBA
    IDAT size = 604 bytes
    File size = 661 bytes
Reducing image to 1x4 bits/pixel, Grayscale
Trying: 144 combinations
Found better combination:
    zc = 6  zs = 0  f = 0        153 bytes
    IDAT size = 153 bytes (451 bytes decrease)
    file size = 210 bytes (451 bytes = 68.23% decrease)
Output: /home/user/out.png

However, if the --ng option that this commit adds is used to skip the
grayscale reduction step, OxiPNG reduces to a single color palette
instead, which is much more efficient, achieving a 84.42% decrease:

$ cargo build --release && target/release/oxipng -omax --ng --out ~/out.png ~/gray.png
Processing: /home/alejandro/gray.png
    256x256 pixels, PNG format
    4x8 bits/pixel, RGBA
    IDAT size = 604 bytes
    File size = 661 bytes
Reducing image to 1 bits/pixel, 1 colors in palette
Trying: 144 combinations
Found better combination:
    zc = 3  zs = 3  f = 0        31 bytes
    IDAT size = 31 bytes (573 bytes decrease)
    file size = 103 bytes (558 bytes = 84.42% decrease)
Output: /home/alejandro/out.png

While OxiPNG should arguably be made smarter to better handle these
cases, in the meantime, adding an option to manually skip that grayscale
reduction can't hurt. In fact, it may even help users achieving the most
out of current versions of OxiPNG, and developers reasoning about what
makes a grayscale-like RGB(A) image compress better with a color
palette.

Due to the reasons stated above, this adds a simple "grayscale_reduction"
option to the Options struct, and a "no-grayscale-reduction" command
line switch, that makes OxiPNG not try this problematic grayscale
reduction on RGB(A) images.
2021-07-11 23:21:38 -04:00
Ingvar Stepanyan
23ae9c302f
Switch to standard logging library (#218)
This allows to configure or compile away logging in the library from a single place in Rust apps.

For the CLI side, the usage and output remained the same, except it's now colour-coded.

Fixes #217.
2020-04-18 18:33:48 -04:00
Josh Holmer
139b2878d0 Fix benchmark compilation
Closes #197
2020-04-15 13:26:28 -04:00
Ingvar Stepanyan
121558bf5c
Add libdeflater as an option (#203)
libdeflater is a Rust wrapper around
[libdeflate](https://github.com/ebiggers/libdeflate) - an alternative
heavily optimised library for deflate/zlib/gzip compression and
decompression that is intended for situations where upper bounds of the
output are well-known.

In my benchmarks on test files in the repo it has shown to be usually
both slightly faster and providing better compressed output than
cloudflare-zlib, but in some cases showing the opposite, so rather
than swapping defaults, it's currently provided as another option,
similarly to zopfli.

Since it's not strictly better in all cases, I'm not providing median
numbers, but you can check distribution histograms for time and size
differences here (all using `oxipng -o 6 -t 6 -P`):
https://docs.google.com/spreadsheets/d/1WOKgeYZBhLkQvMGAC36snN4azilElzOFhx63RJu0EZY/edit?usp=sharing
2020-03-31 20:24:57 -04:00
Josh Holmer
d86bc80f43 Prepare for next release 2019-02-01 17:27:03 -05:00
Kornel
9af874d21b Make reductions return a new uncompressed image (#158)
* Fix verbose message

* No cloning when restoring original data

* Make reductions return a new uncompressed image

Partially fixes #145

* Async reduction evaluator

* Assert

* Faster bit depth check

* Also try 4-bit depth for small-depth images

* Skip test when using miniz

* Ensure palette is trimmed after depth reduction

Fixes #159

* Fudge factor for reductions to prefer better reductions even if gzip estimation says otherwise
2019-01-27 03:22:49 -05:00
Kornel
55d85df9fc Non-self-mutable reductions (#154)
* Move reductions to a module. Make copy instead of changing in-place.

* Alpha reductions

* Immutable color reductions

* Immutable interlace reductions
2019-01-12 05:07:57 -05:00
Joshua Holmer
1305b5cf17 Fix clippy lints and run rustfmt 2018-11-26 13:41:14 -05:00
Kornel
588d5bd52b Tweaks and Microoptimizations (#146)
* Fixed alpha benchmark

* Dedupe function

* Use integer math when rounding

* Fewer temporaries when slicing palette

* Filtering microoptimizations
2018-11-20 08:39:46 -05:00
Josh Holmer
d110949d2a Version 2.0.0 2018-07-20 19:19:20 -04:00
Kornel
c99b3778b2 Expose only one internal module specifically for tests (#123)
Closes #98
2018-07-17 23:38:11 -04:00
Kornel
442eaf766c Bail early (#104) 2018-06-08 00:09:22 -04:00
Kornel Lesiński
ef763a8550 Return result from bench iter to avoid unused results 2018-06-01 12:18:39 +01:00
Josh Holmer
fe9cd01b82 Replace miniz_sys with miniz_oxide, a Rust implementation of miniz
This also improves decompression performance by 15%, and obsoletes the
`--zm` parameter.

Closes #57
2018-01-30 23:52:37 -05:00
Josh Holmer
88f5bb0931 Hide modules from documentation, and only export structs used in options or return types 2018-01-10 10:37:01 -05:00
Josh Holmer
0db03c6faf Fix new clippy lints 2017-09-03 23:36:38 -04:00
Josh Holmer
ec65bf7676 Code cleanup 2017-07-26 07:57:13 -04:00
Josh Holmer
f41ef551d3 Enable option for multiple alpha transformations 2017-07-26 00:37:55 -04:00
Josh Holmer
413f1aac7c Automatically change transparent pixels to black 2017-07-24 08:47:27 -04:00
Josh Holmer
84cf6b09c2 Run rustfmt since it updated to v0.8.0.
I don't really like the new changes, but we might as well follow along.
2017-03-08 11:14:33 -05:00
Josh Holmer
af76f11f36 Bump dependencies 2017-02-13 08:51:00 -05:00
Josh Holmer
33f1fad711 Minor refactoring 2016-12-03 15:18:11 -05:00