chunky_png 1.3.12 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +35 -0
  3. data/CHANGELOG.rdoc +3 -3
  4. data/Gemfile +10 -2
  5. data/README.md +13 -12
  6. data/Rakefile +2 -0
  7. data/chunky_png.gemspec +5 -1
  8. data/docs/.gitignore +3 -0
  9. data/docs/CNAME +1 -0
  10. data/docs/_config.yml +9 -0
  11. data/docs/_posts/2010-01-14-memory-efficiency-when-using-ruby.md +136 -0
  12. data/docs/_posts/2010-01-17-ode-to-array-pack-and-string-unpack.md +82 -0
  13. data/docs/_posts/2014-11-07-the-value-of-a-pure-ruby-library.md +61 -0
  14. data/docs/index.md +88 -0
  15. data/lib/chunky_png/canvas/adam7_interlacing.rb +2 -0
  16. data/lib/chunky_png/canvas/data_url_exporting.rb +2 -0
  17. data/lib/chunky_png/canvas/data_url_importing.rb +2 -0
  18. data/lib/chunky_png/canvas/drawing.rb +2 -0
  19. data/lib/chunky_png/canvas/masking.rb +2 -0
  20. data/lib/chunky_png/canvas/operations.rb +2 -0
  21. data/lib/chunky_png/canvas/png_decoding.rb +4 -2
  22. data/lib/chunky_png/canvas/png_encoding.rb +5 -3
  23. data/lib/chunky_png/canvas/resampling.rb +2 -0
  24. data/lib/chunky_png/canvas/stream_exporting.rb +2 -0
  25. data/lib/chunky_png/canvas/stream_importing.rb +3 -1
  26. data/lib/chunky_png/canvas.rb +3 -1
  27. data/lib/chunky_png/chunk.rb +32 -6
  28. data/lib/chunky_png/color.rb +7 -6
  29. data/lib/chunky_png/datastream.rb +4 -8
  30. data/lib/chunky_png/dimension.rb +2 -0
  31. data/lib/chunky_png/image.rb +2 -0
  32. data/lib/chunky_png/palette.rb +9 -5
  33. data/lib/chunky_png/point.rb +2 -0
  34. data/lib/chunky_png/rmagick.rb +2 -0
  35. data/lib/chunky_png/vector.rb +2 -0
  36. data/lib/chunky_png/version.rb +3 -1
  37. data/lib/chunky_png.rb +3 -7
  38. data/spec/chunky_png/canvas_spec.rb +6 -0
  39. metadata +15 -8
  40. data/.travis.yml +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3886829e15fc95fe5233136b1f7ecd373b6996aa7d272ea23a7118b2f686da9
4
- data.tar.gz: f43c2996448bce3e0dd614b19e228ead043be3f1fbc60ca128d7a242f4f74692
3
+ metadata.gz: 83fc1d331bcc0afc882b5ff2df1c0601e42f6551cfaff2b7c7ae9d0b4386b122
4
+ data.tar.gz: 1f80013e9aeb9daa25ed0a968b5d82877483113ebd22442226e0e8ee269a3105
5
5
  SHA512:
6
- metadata.gz: 610eacb3d1e369c824bd4b7e9305358790be1837281be84f44ff12e4020bced6fd9ef44b10391578eb74b50a0e28b24595ac0aa64b82433477a05b43a37bd491
7
- data.tar.gz: e4857a8bb6217e5416b93bcd125dd7e99a90f0bb2714628016d6e44af9b34a03230913e30955a4f72ff2005fb9b9e6f50d1087d6cb89d9370cc735288d98193b
6
+ metadata.gz: 1fcb07937d255014cba405bc018c9889b5fd7cd652e25f0768b21ef58a48f604033551f3fa822329387a3dcecb46263906c1043ecb5a7e0007fba22f90dbeb5f
7
+ data.tar.gz: dd57be8a9ce7db0333dad21f03f9ea056feaed4a785f3e0d21b7a1efb3123bacf6fb58f73cbde9d46f2aa2032b39dd5e06e25a4e48aeb9d5dad993ad99f886e9
@@ -0,0 +1,35 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: ["master"]
13
+ pull_request:
14
+ branches: ["master"]
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ matrix:
21
+ ruby: [ '2.5', '2.6', '2.7', 'ruby-head' ]
22
+
23
+ steps:
24
+ - uses: actions/checkout@v2
25
+ - name: Set up Ruby
26
+ uses: ruby/setup-ruby@v1
27
+ with:
28
+ ruby-version: ${{ matrix.ruby }}
29
+ - name: Install dependencies
30
+ run: bundle install
31
+ - name: Run tests
32
+ run: bin/rake
33
+ # Skip the linter for now.
34
+ # - name: Lint Ruby code
35
+ # run: bin/standardrb
data/CHANGELOG.rdoc CHANGED
@@ -46,7 +46,7 @@ The file documents the changes to this library over the different versions.
46
46
  === 1.3.4 - 2015-02-16
47
47
 
48
48
  - Assert compatibility with Ruby 2.2
49
- - Improved documentation using RDoc, so it is included on http://www.rubydoc.info/gems/chunky_png
49
+ - Improved documentation using RDoc, so it is included on https://www.rubydoc.info/gems/chunky_png
50
50
  - Update chunkypng.com website; migrate some stuff from the wiki.
51
51
 
52
52
  === 1.3.3 - 2014-10-24
@@ -154,7 +154,7 @@ There are some API changes for this release. If you are using <tt>Canvas#compose
154
154
  - Added a list of HTML named colors. Get them by calling <tt>ChunkyPNG::Color(:teal)</tt> or <tt>ChunkyPNG::Color('red @ 0.8')</tt>
155
155
  - Added encoding support for 1-, 2-, and 4-bit grayscale images.
156
156
  - Cleaned up auto-detection of color mode settings. It will now choose 1 bit grayscale mode if an image only contains black and white. (The other low bitrate grayscale modes are never chosen automatically.)
157
- - RDoc improvements. See http://rdoc.info/gems/chunky_png/frames.
157
+ - RDoc improvements. See https://rdoc.info/gems/chunky_png.
158
158
  - ChunkyPNG is now also tested on Ruby 1.8.6.
159
159
 
160
160
  === 0.12.0 - 2010-12-12
@@ -214,7 +214,7 @@ There are some API changes for this release. If you are using <tt>Canvas#compose
214
214
 
215
215
  === 0.7.3 - 2010-04-28
216
216
 
217
- - Based on the suggestion of [Dirkjan Bussink](http://github.com/dbussink), introduced custom exception classes:
217
+ - Based on the suggestion of [Dirkjan Bussink](https://github.com/dbussink), introduced custom exception classes:
218
218
  - <tt>ChunkyPNG::SignatureMismatch</tt> is raised when the PNG signature could not be found. Usually this means the the file is not a PNG image.
219
219
  - <tt>ChunkyPNG::CRCMismatch</tt> is raised when the a CRC check for a chunk in the PNG file fails.
220
220
  - <tt>ChunkyPNG::NotSupported</tt> is raised when the PNG image uses a feature that ChunkyPNG does not support.
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
  gemspec
3
5
 
@@ -5,6 +7,12 @@ platforms :jruby do
5
7
  gem "jruby-openssl"
6
8
  end
7
9
 
8
- platform :rbx do
9
- gem "rubysl"
10
+ group :jekyll do
11
+ gem "jekyll", "~> 3.3"
12
+ gem "kramdown-parser-gfm"
10
13
  end
14
+
15
+ group :jekyll_plugins do
16
+ gem "jekyll-commonmark"
17
+ gem "jekyll-theme-cayman"
18
+ end
data/README.md CHANGED
@@ -1,13 +1,13 @@
1
- # ChunkyPNG [![Build Status](https://travis-ci.org/wvanbergen/chunky_png.svg?branch=master)](https://travis-ci.org/wvanbergen/chunky_png)
1
+ # ChunkyPNG
2
2
 
3
3
  This library can read and write PNG files. It is written in pure Ruby for
4
4
  maximum portability. Let me rephrase: it does NOT require RMagick or any other
5
5
  memory leaking image library.
6
6
 
7
- - [Source code](http://github.com/wvanbergen/chunky_png/tree)
8
- - [RDoc](http://rdoc.info/gems/chunky_png/frames)
9
- - [Wiki](http://github.com/wvanbergen/chunky_png/wiki)
10
- - [Issue tracker](http://github.com/wvanbergen/chunky_png/issues)
7
+ - [Source code](https://github.com/wvanbergen/chunky_png/tree/master)
8
+ - [RDoc](https://rdoc.info/gems/chunky_png)
9
+ - [Wiki](https://github.com/wvanbergen/chunky_png/wiki)
10
+ - [Issue tracker](https://github.com/wvanbergen/chunky_png/issues)
11
11
 
12
12
  ## Features
13
13
 
@@ -23,9 +23,10 @@ memory leaking image library.
23
23
  depending on the hardware)
24
24
  - Reasonably fast for Ruby standards, by only using integer math and a highly
25
25
  optimized saving routine.
26
+ - Works on every currently supported Ruby version (2.5+)
26
27
  - Interoperability with RMagick if you really have to.
27
28
 
28
- Also, have a look at [OilyPNG](http://github.com/wvanbergen/oily_png) which
29
+ Also, have a look at [OilyPNG](https://github.com/wvanbergen/oily_png) which
29
30
  is a mixin module that implements some of the ChunkyPNG algorithms in C, which
30
31
  provides a massive speed boost to encoding and decoding.
31
32
 
@@ -59,11 +60,11 @@ png_stream.each_chunk { |chunk| p chunk.type }
59
60
 
60
61
  Also check out the screencast on the ChunkyPNG homepage by John Davison,
61
62
  which illustrates basic usage of the library on the [ChunkyPNG
62
- website](http://chunkypng.com/).
63
+ website](https://chunkypng.com/).
63
64
 
64
65
  For more information, see the [project
65
66
  wiki](https://github.com/wvanbergen/chunky_png/wiki) or the [RDOC
66
- documentation](http://www.rubydoc.info/gems/chunky_png/frames).
67
+ documentation](https://www.rubydoc.info/gems/chunky_png).
67
68
 
68
69
  ## Security warning
69
70
 
@@ -80,11 +81,11 @@ background processing library.
80
81
 
81
82
  The library is written by Willem van Bergen for Floorplanner.com, and released
82
83
  under the MIT license (see LICENSE). Please contact me for questions or
83
- remarks.
84
+ remarks.
84
85
 
85
- I generally consider this library to be feature complete. I will gladly accept
86
- patches to fix bugs and improve performance, but I will generally be hesitant
87
- to accept new features or API endpoints. Before contributing, please read
86
+ I generally consider this library to be feature complete. I will gladly accept
87
+ patches to fix bugs and improve performance, but I will generally be hesitant
88
+ to accept new features or API endpoints. Before contributing, please read
88
89
  [CONTRIBUTING.rdoc](CONTRIBUTING.rdoc) that explains this in more detail.
89
90
 
90
91
  Please check out CHANGELOG.rdoc to see what changed in all versions.
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require "rspec/core/rake_task"
3
5
 
data/chunky_png.gemspec CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  lib = File.expand_path("../lib", __FILE__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
  require "chunky_png/version"
@@ -24,7 +26,7 @@ Gem::Specification.new do |s|
24
26
  alpha composition and cropping. Finally, it can import from and export to RMagick for
25
27
  interoperability.
26
28
 
27
- Also, have a look at OilyPNG at http://github.com/wvanbergen/oily_png. OilyPNG is a
29
+ Also, have a look at OilyPNG at https://github.com/wvanbergen/oily_png. OilyPNG is a
28
30
  drop in mixin module that implements some of the ChunkyPNG algorithms in C, which
29
31
  provides a massive speed boost to encoding and decoding.
30
32
  EOT
@@ -48,4 +50,6 @@ Gem::Specification.new do |s|
48
50
 
49
51
  s.files = `git ls-files`.split($/)
50
52
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
53
+
54
+ s.required_ruby_version = ">= 2.0"
51
55
  end
data/docs/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .sass-cache/
2
+ .jekyll-metadata
3
+ _site/
data/docs/CNAME ADDED
@@ -0,0 +1 @@
1
+ chunkypng.com
data/docs/_config.yml ADDED
@@ -0,0 +1,9 @@
1
+ theme: jekyll-theme-slate
2
+
3
+ title: ChunkyPNG
4
+ email: willem@vanbergen.org
5
+ description: Read/write access to PNG images in pure Ruby.
6
+ url: "https://www.chunkypng.com"
7
+
8
+ highlighter: rouge
9
+ show_downloads: true
@@ -0,0 +1,136 @@
1
+ ---
2
+ author: Willem van Bergen
3
+ title: Memory efficiency when using Ruby
4
+ ---
5
+
6
+ I have been spending some time creating [a pure Ruby PNG library](https://github.com/wvanbergen/chunky_png). For this library, I need to have some representation of the image, which is composed of RGB pixels, supporting an alpha channel. Because images can be composed of a lot of pixels, I want the implementation to be as memory efficient as possible. I also would like decent performance.
7
+
8
+ A very naive Ruby implementation for an image represents the red, green, blue and alpha channel using a floating point number between 0.0 and 1.0, and might look something like this:
9
+
10
+ {% highlight ruby %}
11
+ class Pixel
12
+ attr_reader :r, :g, :b, :a
13
+
14
+ def initialize(r, g, b, a = 1.0)
15
+ @r, @g, @b, @a = r, g, b, a
16
+ end
17
+ end
18
+
19
+ class Image
20
+ attr_reader :width, :height
21
+
22
+ def initialize(width, height)
23
+ @width, @height = width, height
24
+ @pixels = Array.new(width * height)
25
+ end
26
+
27
+ def [](x,y)
28
+ @pixels[y * width + x]
29
+ end
30
+
31
+ def []=(x,y, pixel)
32
+ @pixels[y * width + x] = pixel
33
+ end
34
+ end
35
+ {% endhighlight %}
36
+
37
+ For a 10×10 image, this representation requires 4 times 100 floating point numbers, which require 8 bytes each. That’s already over 3kB for such a small image just for the floating point numbers! Ouch.
38
+
39
+ A simple improvement is to decide that 8-bit color depth is enough in the case, in which case each channel can be represented by an integer between 0 and 255. Storing such a number only costs one byte of memory. Ruby’s Fixnum class typically uses 4-byte integers. If only the 4 channels of one byte each could be combined into a single Fixnum instance… Behold!
40
+
41
+ {% highlight ruby %}
42
+ class Pixel
43
+ attr_reader :value
44
+ alias :to_i :value
45
+
46
+ def initialize(value)
47
+ @value = value
48
+ end
49
+
50
+ def self.rgba(r, g, b, a = 255)
51
+ self.new(r << 24 | g << 16 | b << 8 | a)
52
+ end
53
+
54
+ def r; (@value & 0xff000000) >> 24; end
55
+ def g; (@value & 0x00ff0000) >> 16; end
56
+ def b; (@value & 0x0000ff00) >> 8; end
57
+ def a; (@value & 0x000000ff); end
58
+ end
59
+ {% endhighlight %}
60
+
61
+ Notice the bit operations, which are extremely fast. This only requires 100 times 4 bytes = 400 bytes for storing the RGBA values for a 10×10 image, an 8 times improvement!
62
+
63
+ This implementation wraps every pixel inside an object. This is nice, because I want to access the separate channels of every pixel easily using the r, g, b, and a methods, and every other method that is defined for every pixel. However, a Ruby object instance has an overhead of at least 20 bytes. That’s 20 times 100 is about 2kB for our 10×10 image!
64
+
65
+ To get rid of the object overhead, it is possible to simply store the Fixnum value for every pixel, and only wrapping it inside a Pixel object when it is accessed. This can be done by modifying the Image class:
66
+
67
+ {% highlight ruby %}
68
+ class Image
69
+ # ...
70
+
71
+ def [](x,y)
72
+ Pixel.new(@pixels[y * width + x]) # wrap
73
+ end
74
+
75
+ def []=(x,y, pixel)
76
+ @pixels[y * width + x] = pixel.to_i # unwrap
77
+ end
78
+ end
79
+ {% endhighlight %}
80
+
81
+ As you can see, some simply changes in the representation can really make a difference in the memory usage. Can this representation be improved further?
82
+
83
+ ## Integer math calculations
84
+
85
+ Because we are now using integers to represent a pixel, this can cause problems when the math requires you to use floating point numbers. For example, the formula for [alpha composition](https://en.wikipedia.org/wiki/Alpha_compositing) of two pixels is as follows:
86
+
87
+ \\[ C_o = C_a \alpha_a + C_b \alpha_b (1 - \alpha_a) \\]
88
+
89
+ in which \\(C_a\\) is the color component of the foreground pixel, \\(\alpha_a\\) the alpha channel of the foreground pixel, \\(C_b\\) and \\(\alpha_b\\) the same values for the background pixel, all of which should be values between 0 and 1.
90
+
91
+ A naive implementation could convert the integer numbers to their floating point equivalents:
92
+
93
+ {% highlight ruby %}
94
+ def compose(fg, bg)
95
+ return bg if fg.a == 0
96
+ return fg if fg.a == 255
97
+
98
+ fg_alpha = fg.a / 255.0
99
+ bg_alpha = fg.a / 255.0
100
+ alpha_complement = (1.0 - fg_alpha) * bg_alpha
101
+
102
+ new_r = (fg_alpha * fg.r + alpha_complement * bg.r).round
103
+ new_g = (fg_alpha * fg.g + alpha_complement * bg.g).round
104
+ new_b = (fg_alpha * fg.b + alpha_complement * bg.b).round
105
+ new_a = ((fg_alpha + alpha_complement) * 255).round
106
+
107
+ Pixel.rgba(new_r, new_g, new_b, new_a)
108
+ end
109
+ {% endhighlight %}
110
+
111
+ This implementation is already a little bit optimized: no unnecessary conversions and calculations are being performed. However, this composition can be done a lot quicker after realizing that 255 is almost a power of two, in which computers excel because it can use bitwise operators and shifting for some calculations.
112
+
113
+ My new approach uses a quicker implementation of multiplication of 8-bit integers that represent floating numbers between 0 and 1:
114
+
115
+ {% highlight ruby %}
116
+ def compose(fg, bg)
117
+ return bg if fg.a == 0
118
+ return fg if fg.a == 255
119
+
120
+ alpha_complement = multiply(255 - fg.a, bg.a)
121
+ new_r = multiply(fg.a, fg.r) + multiply(alpha_complement, bg.r)
122
+ new_g = multiply(fg.a, fg.g) + multiply(alpha_complement, bg.g)
123
+ new_b = multiply(fg.a, fg.b) + multiply(alpha_complement, bg.b)
124
+ new_a = fg.a + alpha_complement
125
+
126
+ Pixel.rgba(new_r, new_g, new_b, new_a)
127
+ end
128
+
129
+ # Quicker alternative for (a * b / 255.0).round
130
+ def multiply(a, b)
131
+ t = a * b + 0x80
132
+ ((t >> 8) + t) >> 8
133
+ end
134
+ {% endhighlight %}
135
+
136
+ Note that the new implementation is less precise in theory, but this precision is lost anyway because we have to convert the values back to 8 bit RGBA values. Your thoughts?
@@ -0,0 +1,82 @@
1
+ ---
2
+ author: Willem van Bergen
3
+ title: Ode to Array#pack and String#unpack
4
+ ---
5
+
6
+ Remember [my last post]({% post_url 2010-01-14-memory-efficiency-when-using-ruby %}), where I representing a pixel with a Fixnum, storing the R, G, B and A value in its 4 bytes of memory? Well, I have been working some more on [my PNG library](https://github.com/wvanbergen/chunky_png) and I am now trying loading and saving an image.
7
+
8
+ Using the [PNG specification](https://www.w3.org/TR/PNG/), building a PNG encoder/decoder isn’t that hard, but the required algorithmic calculations make sure that performance in Ruby is less than stellar. I have rewritten all calculations to only use fast integer math (plus, minus, multiply and bitwise operators), but simply the amount of code that is getting executed is slowing Ruby down. What more can I do to improve the performance?
9
+
10
+ ## Encoding RGBA images
11
+
12
+ Optimizing loading images is very hard, because PNG images can have many variations, and taking shortcuts means that some images are no longer supported. Not so with saving images: as long an image is saved using one of the valid variations, every PNG decoder will be able to read the file. Let’s see if it is possible to optimize one of these encoding variations.
13
+
14
+ During encoding, the image get splits up into scanlines (rows) of pixels, which in turn get converted into bytes. These bytes can be filtered for optimal compression. For a 3×3 8-bit RGBA image, the result looks like this:
15
+
16
+ F Rf Gf Bf Af Rf Gf Bf Af Rf Gf Bf Af
17
+ F Rf Gf Bf Af Rf Gf Bf Af Rf Gf Bf Af
18
+ F Rf Gf Bf Af Rf Gf Bf Af Rf Gf Bf Af
19
+
20
+ Every line starts with a byte F indicating the filter method, followed by the filtered R, G and B value for every pixel on that line. Now, if we choose filter method 0, which means no filtering, the result looks like this:
21
+
22
+ 0 Ro Go Bo Ao Ro Go Bo Ao Ro Go Bo Ao
23
+ 0 Ro Go Bo Ao Ro Go Bo Ao Ro Go Bo Ao
24
+ 0 Ro Go Bo Ao Ro Go Bo Ao Ro Go Bo Ao
25
+
26
+ Now, the original R, G, B and A byte from the original pixel’s Fixnum, occur in [big-endian or network byte order](https://en.wikipedia.org/wiki/Endianness), starting with the top left pixel, moving left to right and then top to bottom. Exactly like the pixels are stored in our image’s pixel array! This means that we can use the Array#pack method to encode into this format. The Array#pack-notation for this is "xN3" in which x get translated into a null byte, and every N as 4-byte integer in network byte order. For optimal performance, it is best to not split the original array in lines, but to pack the complete pixel array at once. So, we can encode all pixels with this command:
27
+
28
+ {% highlight ruby %}
29
+ pixeldata = pixels.pack("xN#{width}" * height)
30
+ {% endhighlight %}
31
+
32
+ This way, the splitting the image into lines, splitting the pixels into bytes, and filtering the bytes can be skipped. In Ruby 1.8.7, this means a speedup of over 1500% (no typo)! Of course, because no filtering applied, the subsequent compression is not optimal, but that is a tradeoff that I am willing to make.
33
+
34
+ ## Encoding RGB images
35
+
36
+ What about RGB images without alpha channel? We can simply choose to encode these using the RGBA method, but that increases the file size with roughly 25%. Can we fix this somehow?
37
+
38
+ The unfiltered pixel data should look something like this:
39
+
40
+ 0 Ro Go Bo Ro Go Bo Ro Go Bo
41
+ 0 Ro Go Bo Ro Go Bo Ro Go Bo
42
+ 0 Ro Go Bo Ro Go Bo Ro Go Bo
43
+
44
+ This means that for every pixel that is encoded as a 4-byte integer, the last byte should be ditched. Luckily, the `Array#pack` method offers a modifier that does just that: `X`. Packing a 3 pixel line can be done with `"xNXNXNX"`. Again we would like to pack the whole pixel array at once:
45
+
46
+ {% highlight ruby %}
47
+ pixeldata = pixels.pack(("x" + ('NX' * width)) * height)
48
+ {% endhighlight %}
49
+
50
+ Because all the encoding steps can get skipped once again, the speed improvement is again 1500%! And the result is 25% smaller than the RGBA method. This method is actually so speedy, that saving an image using Ruby 1.9.1 is only a little bit slower (< 10%) than saving a PNG image using RMagick! See my [performance comparison](https://github.com/wvanbergen/chunky_png/wiki/performance-comparison).
51
+
52
+ ## Loading image
53
+
54
+ Given the promising results of the Array#pack method, using its counterpart String#unpack looks promising for speedy image loading, if you know the image’s size and the encoding format beforehand.
55
+
56
+ An RGBA formatted stream can be loaded quickly with this command:
57
+
58
+ {% highlight ruby %}
59
+ pixels = rgba_pixeldata.unpack("N#{width * height}")
60
+ image = Image.new(width, height, pixels)
61
+ {% endhighlight %}
62
+
63
+ For an RGB formatted stream, we can use the X modifier again, but we have to make sure to set the alpha value for every pixel to 255:
64
+
65
+ {% highlight ruby %}
66
+ pixels = rgb_pixeldata.unpack("NX" * (width * height))
67
+ pixels.map! { |pixel| pixel | 0x000000ff }
68
+ image = Image.new(width, height, pixels)
69
+ {% endhighlight %}
70
+
71
+ You can even use little-endian integers to load streams in ABGR format!
72
+
73
+ {% highlight ruby %}
74
+ pixels = abgr_pixeldata.unpack("V#{width * height}")
75
+ image = Image.new(width, height, pixels)
76
+ {% endhighlight %}
77
+
78
+ Loading pixel data for an image like this is again over 1500% faster than decoding the same PNG image. However, this can only be applied if you have control over the input format of the image.
79
+
80
+ ## To conclude
81
+
82
+ `Array#pack` and `String#unpack` really have increased the performance for my code. If you can apply them for project, don’t hesitate and spread the love! For all other cases, use as little code as possible, and upgrade to Ruby 1.9 for improved algorithmic performance.
@@ -0,0 +1,61 @@
1
+ ---
2
+ author: Willem van Bergen
3
+ title: The value of a pure Ruby library
4
+ ---
5
+
6
+ In late 2009, my employer at the time &mdash; [Floorplanner](https://www.floorplanner.com) &mdash; was struggling with memory leaks in [RMagick](https://www.imagemagick.org/RMagick/doc/), a Ruby wrapper around the image manipulation library [ImageMagick](https://www.imagemagick.org/). Because we only needed a small subset of RMagick's functionality, I decided to write a simple library so we could get rid of RMagick. Not much later, [ChunkyPNG was born](https://github.com/wvanbergen/chunky_png/commit/aa8a9378eedfc02aa1d0d1e05c313badc76594a7).
7
+
8
+ Even though ChunkyPNG has grown in scope and complexity to cover the entire PNG standard, it still is a "pure Ruby" library: all of the code is Ruby, and it doesn't have any dependencies besides Ruby itself. Initially, this was purely for practical reasons: I knew Ruby wasn't the fastest language in the world, but I had no idea how to write Ruby C extensions. Performance was not an important concern for the problem at hand, and maybe RMagick being a C extension was the cause of its memory leaks? By writing pure Ruby, I could get results faster and let the Ruby interpreter do the hard work of managing memory for me. <sup>[1]</sup>
9
+
10
+ ### Performance becomes important
11
+
12
+ Mostly as a learning project, I ended up implementing the entire PNG standard. This made the library suitable for a broader set of problems, and more people started using it. Performance then became more important. I put a decent effort into optimizing the memory efficiency by [optimizing storing pixels in memory]({% post_url 2010-01-14-memory-efficiency-when-using-ruby %}), and I boosted performance by [short-circuiting the PNG encoding routine using Array#pack]({% post_url 2010-01-17-ode-to-array-pack-and-string-unpack %}).
13
+
14
+ Even though these efforts resulted in sizable improvements, it became clear that there are limits on how far you can push performance in Ruby. The fact that I am implementing a library that by nature requires a lot of memory and computation is not going to change.
15
+
16
+ So what are the options? I could recommend RMagick to people asking for more performance, but that is not going to happen after all my ImageMagick bashing. <sup>[2]</sup> In the end, I had to roll up my sleeves and program some C.
17
+
18
+ ### Being pure Ruby is a feature
19
+
20
+ To tackle the performance issue, I had the options of either implementing the C extension as part of ChunkyPNG, or build a separate library. <sup>[3]</sup> My initial gut feeling was to add a C extension to ChunkyPNG to give everyone a free performance boost. However, I soon discovered many people were using the library *because* it was pure Ruby. For me, it was a pragmatic implementation detail; for them, it was a feature.
21
+
22
+ Including a C extension would require everybody that wants to install ChunkyPNG to have a compiler toolchain installed. For me, installing a compiler toolchain is the first thing I do when I get a new machine. This is true for many Ruby developers, but it turns out that many of the library users are not Ruby developers at all. [Compass](http://compass-style.org/), a popular CSS authoring framework, uses ChunkyPNG to generate sprite images. Most Compass users are front-end developers who primarily use HTML, CSS and Javascript, and not Ruby. Because OS X comes with Ruby and Rubygems installed, running `gem install compass` works out of the box. Telling them to install a C compiler chain is simply an unacceptable installation requirement.
23
+
24
+ There are a couple of additional advantages of being a pure Ruby library. As an open source project ChunkyPNG can attract more contributors, because only a small percentage of Ruby developers are well-versed in C. Moreover, C extensions are MRI specific. This means that many C extensions won't work on Rubinius or JRuby, and I wanted my library to work in these environments as well. <sup>[4]</sup> Finally, libraries that require a C compiler inevitably get a lot of bug reports or support requests of people that are having issues installing the library, because of differences in development environments. <sup>[5]</sup>
25
+
26
+ ### OilyPNG: a mixin library
27
+
28
+ So instead of adding a C extension, I started working on a separate library: [OilyPNG](https://github.com/wvanbergen/oily_png). Rather than making this a standalone library, I designed it to be a mixin module that depends on ChunkyPNG.
29
+
30
+ The approach is simple: OilyPNG consists of modules that implement some of the methods of ChunkyPNG in C. When OilyPNG is loaded with `require 'oily_png'`, it first loads ChunkyPNG and uses `Module#include` and `Module#extend` to [overwrite some methods in ChunkyPNG with OilyPNG's faster implementation](https://github.com/wvanbergen/oily_png/blob/master/lib/oily_png.rb).
31
+
32
+ This approach allows us to keep ChunkyPNG pure Ruby, and make OilyPNG 100% API compatible with ChunkyPNG. It is even possible to make OilyPNG optional in your project:
33
+
34
+ {% highlight ruby %}
35
+ begin
36
+ require 'oily_png'
37
+ rescue LoadError
38
+ require 'chunky_png'
39
+ end
40
+ {% endhighlight %}
41
+
42
+ This approach has some other advantages as well. Instead of having to implement everything at once to get to a library that implements most of ChunkyPNG, we can do this step by step while always providing 100% functional parity. Profile ChunkyPNG to find a slow method, implement it in OilyPNG, and iterate. This way OilyPNG doesn't suffer from a bootstrapping problem of having to implement and minimum viable subset of ChunkyPNG right from the start. It can grow organically, one optimized method at the time.
43
+
44
+ And because we have a well tested, pure Ruby implementation available to which OilyPNG is supposed to be 100% compatible, testing OilyPNG is simple. We just call a method on ChunkyPNG, run the exact same call on an OilyPNG-enhanced ChunkyPNG, and compare the results.
45
+
46
+ ### To conclude
47
+
48
+ Being pure Ruby can be an important feature of a library for many of its users. Don't give it up too easily, even though Ruby's lacking performance may be an issue. Using a hybrid approach of a pure Ruby library with a native companion library is a great way to have the best of both worlds. <sup>[6]</sup>
49
+
50
+ ---------------------------------------
51
+
52
+ #### Footnotes
53
+
54
+ 1. This is also why I avoided using the [png gem](https://github.com/seattlerb/png), an "almost-pure-ruby" library that was available at the time. It uses [inline C](https://github.com/seattlerb/rubyinline) to speed up some of the algorithms.
55
+ 2. Disclaimer: I should note that I haven't used ImageMagick and RMagick since 2010. So my knowledge about the current state of these libraries is extremely outdated at this point.
56
+ 3. I could have leveraged the work of [libpng](http://www.libpng.org/pub/png/libpng.html) instead of implementing the algorithms myself. I decided not to, because libpng's API doesn't lend itself very well for the cherry-picking of hotspots approach I took with OilyPNG. You basically have to go all in if you want to use libpng. I think a Ruby PNG library that simply wraps libpng still has potential, but because of the reasons outlined in this article, I will leave that as an exercise to the reader. :)
57
+ 4. Rubinius since has implemented most of MRI's C API so you can compile many C extensions against Rubinius as well, including OilyPNG. As an interesting side note: the Rubinius and JRuby developers have used ChunkyPNG as a performance benchmarking tool, because it contains a non-trivial amount of code and is computation heavy.
58
+ 5. Unfortunately, OilyPNG is [not an exception](https://github.com/wvanbergen/oily_png/issues/12) to this rule.
59
+ 6. My current employer &mdash; [Shopify](https://www.shopify.com) &mdash; is using the same approach for [Liquid](https://shopify.github.io/liquid/) and its C companion library [liquid-c](https://github.com/Shopify/liquid-c) with great success. Even though this requires matching Liquid's parsing behavior in certain edge cases quirk by quirk in the C implementation.
60
+
61
+ Thanks to Simon Hørup Eskildsen, Emilie Noël, and Steven H. Noble for reviewing drafts of this post.
data/docs/index.md ADDED
@@ -0,0 +1,88 @@
1
+ ---
2
+ author: Willem van Bergen
3
+ title: ChunkyPNG
4
+ ---
5
+
6
+ # ChunkyPNG
7
+
8
+ This library can read and write PNG files. It is written in pure Ruby for
9
+ maximum portability. Let me rephrase: it does NOT require RMagick or any other
10
+ memory leaking image library.
11
+
12
+ - Source code: [https://github.com/wvanbergen/chunky_png/tree/master](http://github.com/wvanbergen/chunky_png/tree/master)
13
+ - RDoc: [https://rdoc.info/gems/chunky_png](https://rdoc.info/gems/chunky_png)
14
+ - Wiki: [https://github.com/wvanbergen/chunky_png/wiki](https://github.com/wvanbergen/chunky_png/wiki)
15
+ - Issue tracker: [https://github.com/wvanbergen/chunky_png/issues](https://github.com/wvanbergen/chunky_png/issues)
16
+
17
+ ## Features
18
+
19
+ - Decodes any image that the PNG standard allows. This includes all standard
20
+ color modes, all bit depths and all transparency, interlacing and filtering options.
21
+ - Encodes images supports all color modes (true color, grayscale and indexed)
22
+ and transparency for all these color modes. The best color mode will be
23
+ chosen automatically, based on the amount of used colors.
24
+ - R/W access to the image's pixels.
25
+ - R/W access to all image metadata that is stored in chunks.
26
+ - Memory efficient (uses a Fixnum, i.e. 4 or 8 bytes of memory per pixel, depending
27
+ on the hardware)
28
+ - Reasonably fast for Ruby standards, by only using integer math and a highly
29
+ optimized saving routine.
30
+ - Interoperability with RMagick if you really have to.
31
+
32
+ Also, have a look at [OilyPNG](http://github.com/wvanbergen/oily_png). OilyPNG is a
33
+ mixin module that implements some of the ChunkyPNG algorithms in C, which
34
+ provides a massive speed boost to encoding and decoding.
35
+
36
+ ## Usage
37
+
38
+ ``` ruby
39
+ require 'chunky_png'
40
+
41
+ # Creating an image from scratch, save as an interlaced PNG
42
+ png = ChunkyPNG::Image.new(16, 16, ChunkyPNG::Color::TRANSPARENT)
43
+ png[1,1] = ChunkyPNG::Color.rgba(10, 20, 30, 128)
44
+ png[2,1] = ChunkyPNG::Color('black @ 0.5')
45
+ png.save('filename.png', :interlace => true)
46
+
47
+ # Compose images using alpha blending.
48
+ avatar = ChunkyPNG::Image.from_file('avatar.png')
49
+ badge = ChunkyPNG::Image.from_file('no_ie_badge.png')
50
+ avatar.compose!(badge, 10, 10)
51
+ avatar.save('composited.png', :fast_rgba) # Force the fast saving routine.
52
+
53
+ # Accessing metadata
54
+ image = ChunkyPNG::Image.from_file('with_metadata.png')
55
+ puts image.metadata['Title']
56
+ image.metadata['Author'] = 'Willem van Bergen'
57
+ image.save('with_metadata.png') # Overwrite file
58
+
59
+ # Low level access to PNG chunks
60
+ png_stream = ChunkyPNG::Datastream.from_file('filename.png')
61
+ png_stream.each_chunk { |chunk| p chunk.type }
62
+ ```
63
+
64
+ For more information, see [the project wiki](http://github.com/wvanbergen/chunky_png/wiki)
65
+ or the RDOC documentation on [http://rdoc.info/gems/chunky_png](http://rdoc.info/gems/chunky_png)
66
+
67
+ ## Security warning
68
+
69
+ ChunkyPNG is vulnerable to decompression bombs, which means that ChunkyPNG is vulnerable to
70
+ DOS attacks by running out of memory when loading a specifically crafted PNG file. Because
71
+ of the pure-Ruby nature of the library it is very hard to fix this problem in the library
72
+ itself.
73
+
74
+ In order to safely deal with untrusted images, you should make sure to do the image
75
+ processing using ChunkyPNG in a separate process, e.g. by using fork or a background
76
+ processing library.
77
+
78
+ ## About
79
+
80
+ The library is written by Willem van Bergen for Floorplanner.com, and released
81
+ under the MIT license (see LICENSE). Please contact me for questions or
82
+ remarks. Patches are greatly appreciated!
83
+
84
+ Please check out the changelog on https://github.com/wvanbergen/chunky_png/wiki/Changelog
85
+ to see what changed in all versions.
86
+
87
+ P.S.: The name of this library is intentionally similar to Chunky Bacon and
88
+ Chunky GIF. Use Google if you want to know _why. :-)
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  class Canvas
3
5
  # Methods for decoding and encoding Adam7 interlacing.
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  class Canvas
3
5
  # Methods to export a canvas to a PNG data URL.
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  class Canvas
3
5
  # Methods to import a canvas from a PNG data URL.
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  class Canvas
3
5
  # Module that adds some primitive drawing methods to {ChunkyPNG::Canvas}.
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  class Canvas
3
5
  # The ChunkyPNG::Canvas::Masking module defines methods to perform masking
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  class Canvas
3
5
  # The ChunkyPNG::Canvas::Operations module defines methods to perform
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  class Canvas
3
5
  # The PNGDecoding contains methods for decoding PNG datastreams to create a
@@ -25,7 +27,7 @@ module ChunkyPNG
25
27
  # combined to form the original images.
26
28
  #
27
29
  # @see ChunkyPNG::Canvas::PNGEncoding
28
- # @see http://www.w3.org/TR/PNG/ The W3C PNG format specification
30
+ # @see https://www.w3.org/TR/PNG/ The W3C PNG format specification
29
31
  module PNGDecoding
30
32
  # Decodes a Canvas from a PNG encoded string.
31
33
  # @param [String] str The string to read from.
@@ -270,7 +272,7 @@ module ChunkyPNG
270
272
  # @params (see #decode_png_pixels_from_scanline_indexed_1bit)
271
273
  # @return (see #decode_png_pixels_from_scanline_indexed_1bit)
272
274
  def decode_png_pixels_from_scanline_truecolor_8bit(stream, pos, width, _decoding_palette)
273
- stream.unpack("@#{pos + 1}" << ("NX" * width)).map { |c| c | 0x000000ff }
275
+ stream.unpack("@#{pos + 1}#{"NX" * width}").map { |c| c | 0x000000ff }
274
276
  end
275
277
 
276
278
  # Decodes a scanline of a 16-bit, true color image into a row of pixels.
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  class Canvas
3
5
  # Methods for encoding a Canvas instance into a PNG datastream.
@@ -18,7 +20,7 @@ module ChunkyPNG
18
20
  # before the compression step.
19
21
  #
20
22
  # @see ChunkyPNG::Canvas::PNGDecoding
21
- # @see http://www.w3.org/TR/PNG/ The W3C PNG format specification
23
+ # @see https://www.w3.org/TR/PNG/ The W3C PNG format specification
22
24
  module PNGEncoding
23
25
  # The palette used for encoding the image.This is only in used for images
24
26
  # that get encoded using indexed colors.
@@ -171,7 +173,7 @@ module ChunkyPNG
171
173
  # @param [Integer] filtering The filtering method to use.
172
174
  # @return [String] The PNG encoded canvas as string.
173
175
  def encode_png_image_without_interlacing(color_mode, bit_depth = 8, filtering = ChunkyPNG::FILTER_NONE)
174
- stream = ChunkyPNG::Datastream.empty_bytearray
176
+ stream = "".b
175
177
  encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering)
176
178
  stream
177
179
  end
@@ -187,7 +189,7 @@ module ChunkyPNG
187
189
  # @param [Integer] filtering The filtering method to use.
188
190
  # @return [String] The PNG encoded canvas as string.
189
191
  def encode_png_image_with_interlacing(color_mode, bit_depth = 8, filtering = ChunkyPNG::FILTER_NONE)
190
- stream = ChunkyPNG::Datastream.empty_bytearray
192
+ stream = "".b
191
193
  0.upto(6) do |pass|
192
194
  subcanvas = self.class.adam7_extract_pass(pass, self)
193
195
  subcanvas.encoding_palette = encoding_palette
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  class Canvas
3
5
  # The ChunkyPNG::Canvas::Resampling module defines methods to perform image resampling to
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  class Canvas
3
5
  # Methods to save load a canvas from to stream, encoded in RGB, RGBA, BGR or ABGR format.
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  class Canvas
3
5
  # Methods to quickly load a canvas from a stream, encoded in RGB, RGBA, BGR or ABGR format.
@@ -51,7 +53,7 @@ module ChunkyPNG
51
53
  def from_bgr_stream(width, height, stream)
52
54
  string = ChunkyPNG::EXTRA_BYTE.dup # Add a first byte to the first BGR triple.
53
55
  string << (stream.respond_to?(:read) ? stream.read(3 * width * height) : stream.to_s[0, 3 * width * height])
54
- pixels = string.unpack("@1" << ("XV" * (width * height))).map { |color| color | 0x000000ff }
56
+ pixels = string.unpack("@1#{"XV" * (width * height)}").map { |color| color | 0x000000ff }
55
57
  new(width, height, pixels)
56
58
  end
57
59
 
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  require "chunky_png/canvas/png_encoding"
2
4
  require "chunky_png/canvas/png_decoding"
3
5
  require "chunky_png/canvas/adam7_interlacing"
@@ -295,7 +297,7 @@ module ChunkyPNG
295
297
  # @return [String] A nicely formatted string representation of this canvas.
296
298
  # @private
297
299
  def inspect
298
- inspected = "<#{self.class.name} #{width}x#{height} ["
300
+ inspected = +"<#{self.class.name} #{width}x#{height} ["
299
301
  for y in 0...height
300
302
  inspected << "\n\t[" << row(y).map { |p| ChunkyPNG::Color.to_hex(p) }.join(" ") << "]"
301
303
  end
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  # A PNG datastream consists of multiple chunks. This module, and the classes
3
5
  # contained within, help with handling these chunks. It supports both reading
@@ -117,6 +119,8 @@ module ChunkyPNG
117
119
  # PNG spec, except for color depth: Only 8-bit depth images are supported.
118
120
  # Note that it is still possible to access the chunk for such an image, but
119
121
  # ChunkyPNG will raise an exception if you try to access the pixel data.
122
+ #
123
+ # @see https://www.w3.org/TR/PNG/#11IHDR
120
124
  class Header < Base
121
125
  attr_accessor :width, :height, :depth, :color, :compression, :filtering, :interlace
122
126
 
@@ -166,6 +170,8 @@ module ChunkyPNG
166
170
 
167
171
  # The End (IEND) chunk indicates the last chunk of a PNG stream. It does
168
172
  # not contain any data.
173
+ #
174
+ # @see https://www.w3.org/TR/PNG/#11IEND
169
175
  class End < Base
170
176
  def initialize
171
177
  super("IEND")
@@ -186,13 +192,14 @@ module ChunkyPNG
186
192
  # Returns an empty string, because this chunk should always be empty.
187
193
  # @return [""] An empty string.
188
194
  def content
189
- ChunkyPNG::Datastream.empty_bytearray
195
+ "".b
190
196
  end
191
197
  end
192
198
 
193
199
  # The Palette (PLTE) chunk contains the image's palette, i.e. the
194
200
  # 8-bit RGB colors this image is using.
195
201
  #
202
+ # @see https://www.w3.org/TR/PNG/#11PLTE
196
203
  # @see ChunkyPNG::Chunk::Transparency
197
204
  # @see ChunkyPNG::Palette
198
205
  class Palette < Generic
@@ -210,6 +217,7 @@ module ChunkyPNG
210
217
  # Images having a color mode that already includes an alpha channel, this
211
218
  # chunk should not be included.
212
219
  #
220
+ # @see https://www.w3.org/TR/PNG/#11tRNS
213
221
  # @see ChunkyPNG::Chunk::Palette
214
222
  # @see ChunkyPNG::Palette
215
223
  class Transparency < Generic
@@ -249,7 +257,18 @@ module ChunkyPNG
249
257
  end
250
258
  end
251
259
 
260
+ # An image data (IDAT) chunk holds (part of) the compressed image pixel data.
261
+ #
262
+ # The data of an image can be split over multiple chunks, which will have to be combined
263
+ # and inflated in order to decode an image. See {{.combine_chunks}} to combine chunks
264
+ # to decode, and {{.split_in_chunks}} for encoding a pixeldata stream into IDAT chunks.
265
+ #
266
+ # @see https://www.w3.org/TR/PNG/#11IDAT
252
267
  class ImageData < Generic
268
+ # Combines the list of IDAT chunks and inflates their contents to produce the
269
+ # pixeldata stream for the image.
270
+ #
271
+ # @return [String] The combined, inflated pixeldata as binary string
253
272
  def self.combine_chunks(data_chunks)
254
273
  zstream = Zlib::Inflate.new
255
274
  data_chunks.each { |c| zstream << c.content }
@@ -258,6 +277,12 @@ module ChunkyPNG
258
277
  inflated
259
278
  end
260
279
 
280
+ # Splits and compresses a pixeldata stream into a list of IDAT chunks.
281
+ #
282
+ # @param data [String] The binary string of pixeldata
283
+ # @param level [Integer] The compression level to use.
284
+ # @param chunk_size [Integer] The maximum size of a chunk.
285
+ # @return Array<ChunkyPNG::Chunk::ImageData> The list of IDAT chunks.
261
286
  def self.split_in_chunks(data, level = Zlib::DEFAULT_COMPRESSION, chunk_size = 2147483647)
262
287
  streamdata = Zlib::Deflate.deflate(data, level)
263
288
  # TODO: Split long streamdata over multiple chunks
@@ -266,11 +291,12 @@ module ChunkyPNG
266
291
  end
267
292
 
268
293
  # The Text (tEXt) chunk contains keyword/value metadata about the PNG
269
- # stream. In this chunk, the value is stored uncompressed.
294
+ # stream. In this chunk, the value is stored uncompressed.
270
295
  #
271
296
  # The tEXt chunk only supports Latin-1 encoded textual data. If you need
272
297
  # UTF-8 support, check out the InternationalText chunk type.
273
298
  #
299
+ # @see https://www.w3.org/TR/PNG/#11tEXt
274
300
  # @see ChunkyPNG::Chunk::CompressedText
275
301
  # @see ChunkyPNG::Chunk::InternationalText
276
302
  class Text < Base
@@ -299,6 +325,7 @@ module ChunkyPNG
299
325
  # PNG stream. In this chunk, the value is compressed using Deflate
300
326
  # compression.
301
327
  #
328
+ # @see https://www.w3.org/TR/PNG/#11zTXt
302
329
  # @see ChunkyPNG::Chunk::CompressedText
303
330
  # @see ChunkyPNG::Chunk::InternationalText
304
331
  class CompressedText < Base
@@ -331,7 +358,7 @@ module ChunkyPNG
331
358
  # The Physical (pHYs) chunk specifies the intended pixel size or aspect
332
359
  # ratio for display of the image.
333
360
  #
334
- # http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.pHYs
361
+ # @see https://www.w3.org/TR/PNG/#11pHYs
335
362
  class Physical < Base
336
363
  attr_accessor :ppux, :ppuy, :unit
337
364
 
@@ -374,8 +401,7 @@ module ChunkyPNG
374
401
  # a translation of the keyword name. Finally, it supports bot compressed
375
402
  # and uncompressed values.
376
403
  #
377
- # http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.iTXt
378
- #
404
+ # @see https://www.w3.org/TR/PNG/#11iTXt
379
405
  # @see ChunkyPNG::Chunk::Text
380
406
  # @see ChunkyPNG::Chunk::CompressedText
381
407
  class InternationalText < Base
@@ -391,7 +417,7 @@ module ChunkyPNG
391
417
  @compression = compression
392
418
  end
393
419
 
394
- # Reads the tTXt chunk.
420
+ # Reads the iTXt chunk.
395
421
  # @param type [String] The four character chunk type indicator (= "iTXt").
396
422
  # @param content [String] The content read from the chunk.
397
423
  # @return [ChunkyPNG::Chunk::InternationalText] The new End chunk instance.
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  # Factory method to return a color value, based on the arguments given.
3
5
  #
@@ -188,7 +190,7 @@ module ChunkyPNG
188
190
  # @param [Fixnum] alpha Defaults to opaque (255).
189
191
  # @return [Integer] The newly constructed color value.
190
192
  # @raise [ArgumentError] if the hsv triple is invalid.
191
- # @see http://en.wikipedia.org/wiki/HSL_and_HSV
193
+ # @see https://en.wikipedia.org/wiki/HSL_and_HSV
192
194
  def from_hsv(hue, saturation, value, alpha = 255)
193
195
  raise ArgumentError, "Hue must be between 0 and 360" unless (0..360).cover?(hue)
194
196
  raise ArgumentError, "Saturation must be between 0 and 1" unless (0..1).cover?(saturation)
@@ -214,7 +216,7 @@ module ChunkyPNG
214
216
  # @param [Fixnum] alpha Defaults to opaque (255).
215
217
  # @return [Integer] The newly constructed color value.
216
218
  # @raise [ArgumentError] if the hsl triple is invalid.
217
- # @see http://en.wikipedia.org/wiki/HSL_and_HSV
219
+ # @see https://en.wikipedia.org/wiki/HSL_and_HSV
218
220
  def from_hsl(hue, saturation, lightness, alpha = 255)
219
221
  raise ArgumentError, "Hue #{hue} was not between 0 and 360" unless (0..360).cover?(hue)
220
222
  raise ArgumentError, "Saturation #{saturation} was not between 0 and 1" unless (0..1).cover?(saturation)
@@ -245,8 +247,7 @@ module ChunkyPNG
245
247
  # @param [Fixnum] chroma The associated chroma value.
246
248
  # @return [Array<Fixnum>] A scaled r,g,b triple. Scheme-dependent
247
249
  # adjustments are still needed to reach the true r,g,b values.
248
- # @see http://en.wikipedia.org/wiki/HSL_and_HSV
249
- # @see http://www.tomjewett.com/colors/hsb.html
250
+ # @see https://en.wikipedia.org/wiki/HSL_and_HSV
250
251
  # @private
251
252
  def cylindrical_to_cubic(hue, saturation, y_component, chroma)
252
253
  hue_prime = hue.fdiv(60)
@@ -590,7 +591,7 @@ module ChunkyPNG
590
591
  # @return [Array[2]] The value of the color (0-1)
591
592
  # @return [Array[3]] Optional fourth element for alpha, included if
592
593
  # include_alpha=true (0-255)
593
- # @see http://en.wikipedia.org/wiki/HSL_and_HSV
594
+ # @see https://en.wikipedia.org/wiki/HSL_and_HSV
594
595
  def to_hsv(color, include_alpha = false)
595
596
  hue, chroma, max, _ = hue_and_chroma(color)
596
597
  value = max
@@ -619,7 +620,7 @@ module ChunkyPNG
619
620
  # @return [Array<Fixnum>[2]] The lightness of the color (0-1)
620
621
  # @return [Array<Fixnum>[3]] Optional fourth element for alpha, included if
621
622
  # include_alpha=true (0-255)
622
- # @see http://en.wikipedia.org/wiki/HSL_and_HSV
623
+ # @see https://en.wikipedia.org/wiki/HSL_and_HSV
623
624
  def to_hsl(color, include_alpha = false)
624
625
  hue, chroma, max, min = hue_and_chroma(color)
625
626
  lightness = 0.5 * (max + min)
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  # The Datastream class represents a PNG formatted datastream. It supports
3
5
  # both reading from and writing to strings, streams and files.
@@ -9,7 +11,7 @@ module ChunkyPNG
9
11
  # @see ChunkyPNG::Chunk
10
12
  class Datastream
11
13
  # The signature that each PNG file or stream should begin with.
12
- SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10].pack("C8").force_encoding(Encoding::BINARY).freeze
14
+ SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10].pack("C8").force_encoding(::Encoding::BINARY).freeze
13
15
 
14
16
  # The header chunk of this datastream.
15
17
  # @return [ChunkyPNG::Chunk::Header]
@@ -72,7 +74,7 @@ module ChunkyPNG
72
74
  # @param [IO] io The stream to read from.
73
75
  # @return [ChunkyPNG::Datastream] The loaded datastream instance.
74
76
  def from_io(io)
75
- io.set_encoding(Encoding::BINARY)
77
+ io.set_encoding(::Encoding::BINARY)
76
78
  verify_signature!(io)
77
79
 
78
80
  ds = new
@@ -156,12 +158,6 @@ module ChunkyPNG
156
158
  # WRITING DATASTREAMS
157
159
  ##################################################################################
158
160
 
159
- # Returns an empty stream using binary encoding that can be used as stream to encode to.
160
- # @return [String] An empty, binary string.
161
- def self.empty_bytearray
162
- ChunkyPNG::EMPTY_BYTEARRAY.dup
163
- end
164
-
165
161
  # Writes the datastream to the given output stream.
166
162
  # @param [IO] io The output stream to write to.
167
163
  def write(io)
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  # Creates a {ChunkyPNG::Dimension} instance using arguments that can be interpreted
3
5
  # as width and height.
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  # ChunkyPNG::Image is an extension of the {ChunkyPNG::Canvas} class, that
3
5
  # also includes support for metadata.
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  # A palette describes the set of colors that is being used for an image.
3
5
  #
@@ -10,7 +12,7 @@ module ChunkyPNG
10
12
  # explicit palette (stores as PLTE & tRNS chunks in a PNG file).
11
13
  #
12
14
  # @see ChunkyPNG::Color
13
- class Palette < SortedSet
15
+ class Palette < Set
14
16
  # Builds a new palette given a set (Enumerable instance) of colors.
15
17
  #
16
18
  # @param enum [Enumerable<Integer>] The set of colors to include in this
@@ -19,8 +21,10 @@ module ChunkyPNG
19
21
  # which they appeared in the palette chunk, so that this array can be
20
22
  # used for decoding.
21
23
  def initialize(enum, decoding_map = nil)
22
- super(enum)
24
+ super(enum.sort.freeze)
23
25
  @decoding_map = decoding_map if decoding_map
26
+ @encoding_map = {}
27
+ freeze
24
28
  end
25
29
 
26
30
  # Builds a palette instance from a PLTE chunk and optionally a tRNS chunk
@@ -133,7 +137,7 @@ module ChunkyPNG
133
137
  # @return [true, false] True if a encoding map was built when this palette
134
138
  # was loaded.
135
139
  def can_encode?
136
- !@encoding_map.nil?
140
+ !@encoding_map.empty?
137
141
  end
138
142
 
139
143
  # Returns a color, given the position in the original palette chunk.
@@ -174,8 +178,8 @@ module ChunkyPNG
174
178
  # @return [ChunkyPNG::Chunk::Palette] The PLTE chunk.
175
179
  # @see ChunkyPNG::Palette#can_encode?
176
180
  def to_plte_chunk
177
- @encoding_map = {}
178
- colors = []
181
+ @encoding_map.clear
182
+ colors = []
179
183
 
180
184
  each_with_index do |color, index|
181
185
  @encoding_map[color] = index
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  # Factory method to create {ChunkyPNG::Point} instances.
3
5
  #
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  require "rmagick"
2
4
 
3
5
  module ChunkyPNG
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  # Factory method for {ChunkyPNG::Vector} instances.
3
5
  #
@@ -1,5 +1,7 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  # The current version of ChunkyPNG.
3
5
  # Set it and commit the change this before running rake release.
4
- VERSION = "1.3.12"
6
+ VERSION = "1.4.0"
5
7
  end
data/lib/chunky_png.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  # Basic requirements from standard library
2
4
  require "set"
3
5
  require "zlib"
@@ -138,16 +140,10 @@ module ChunkyPNG
138
140
  class UnitsUnknown < ChunkyPNG::Exception
139
141
  end
140
142
 
141
- # Empty byte array. This basically is an empty string, but with the encoding
142
- # set correctly to ASCII-8BIT (binary) in Ruby 1.9.
143
- # @return [String] An empty string, with encoding set to binary in Ruby 1.9
144
- # @private
145
- EMPTY_BYTEARRAY = "".force_encoding(Encoding::BINARY).freeze
146
-
147
143
  # Null-byte, with the encoding set correctly to ASCII-8BIT (binary) in Ruby 1.9.
148
144
  # @return [String] A binary string, consisting of one NULL-byte.
149
145
  # @private
150
- EXTRA_BYTE = "\0".force_encoding(Encoding::BINARY).freeze
146
+ EXTRA_BYTE = "\0".b
151
147
  end
152
148
 
153
149
  require "chunky_png/version"
@@ -231,4 +231,10 @@ describe ChunkyPNG::Canvas do
231
231
  expect(subject.send(:replace_canvas!, 2, 2, [1, 2, 3, 4])).to equal(subject)
232
232
  end
233
233
  end
234
+
235
+ describe "#inspect" do
236
+ it "should give a string description of the canvas" do
237
+ expect(subject.inspect).to eql "<ChunkyPNG::Canvas 1x1 [\n\t[#ffffffff]\n]>"
238
+ end
239
+ end
234
240
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chunky_png
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.12
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willem van Bergen
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-03 00:00:00.000000000 Z
11
+ date: 2020-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -80,7 +80,7 @@ description: |2
80
80
  alpha composition and cropping. Finally, it can import from and export to RMagick for
81
81
  interoperability.
82
82
 
83
- Also, have a look at OilyPNG at http://github.com/wvanbergen/oily_png. OilyPNG is a
83
+ Also, have a look at OilyPNG at https://github.com/wvanbergen/oily_png. OilyPNG is a
84
84
  drop in mixin module that implements some of the ChunkyPNG algorithms in C, which
85
85
  provides a massive speed boost to encoding and decoding.
86
86
  email:
@@ -93,9 +93,9 @@ extra_rdoc_files:
93
93
  - CONTRIBUTING.rdoc
94
94
  - CHANGELOG.rdoc
95
95
  files:
96
+ - ".github/workflows/ruby.yml"
96
97
  - ".gitignore"
97
98
  - ".standard.yml"
98
- - ".travis.yml"
99
99
  - ".yardopts"
100
100
  - BENCHMARKING.rdoc
101
101
  - CHANGELOG.rdoc
@@ -110,6 +110,13 @@ files:
110
110
  - bin/rake
111
111
  - bin/standardrb
112
112
  - chunky_png.gemspec
113
+ - docs/.gitignore
114
+ - docs/CNAME
115
+ - docs/_config.yml
116
+ - docs/_posts/2010-01-14-memory-efficiency-when-using-ruby.md
117
+ - docs/_posts/2010-01-17-ode-to-array-pack-and-string-unpack.md
118
+ - docs/_posts/2014-11-07-the-value-of-a-pure-ruby-library.md
119
+ - docs/index.md
113
120
  - lib/chunky_png.rb
114
121
  - lib/chunky_png/canvas.rb
115
122
  - lib/chunky_png/canvas/adam7_interlacing.rb
@@ -425,7 +432,7 @@ licenses:
425
432
  metadata:
426
433
  source_code_uri: https://github.com/wvanbergen/chunky_png
427
434
  wiki_uri: https://github.com/wvanbergen/chunky_png/wiki
428
- post_install_message:
435
+ post_install_message:
429
436
  rdoc_options:
430
437
  - "--title"
431
438
  - chunky_png
@@ -439,7 +446,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
439
446
  requirements:
440
447
  - - ">="
441
448
  - !ruby/object:Gem::Version
442
- version: '0'
449
+ version: '2.0'
443
450
  required_rubygems_version: !ruby/object:Gem::Requirement
444
451
  requirements:
445
452
  - - ">="
@@ -447,7 +454,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
447
454
  version: '0'
448
455
  requirements: []
449
456
  rubygems_version: 3.0.3
450
- signing_key:
457
+ signing_key:
451
458
  specification_version: 4
452
459
  summary: Pure ruby library for read/write, chunk-level access to PNG files
453
460
  test_files:
data/.travis.yml DELETED
@@ -1,18 +0,0 @@
1
- language: ruby
2
-
3
- script:
4
- - bin/rake
5
- - bin/standardrb
6
-
7
- rvm:
8
- - "2.2"
9
- - "2.3"
10
- - "2.4"
11
- - "2.5"
12
- - "2.6"
13
- - ruby-head
14
- - jruby-19mode
15
-
16
- matrix:
17
- allow_failures:
18
- - rvm: ruby-head