chunky_png 1.3.13 → 1.3.14

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3700be7193fc5c0b3d545244b775b347ab31a3d87b41833eb9130bfb333350e3
4
- data.tar.gz: 910e7069e1a5a5d55df088a27d65a88abb04d0f631641f96665e46eb9c6af633
3
+ metadata.gz: 9f1e9dc8ce37f48273c35ab26355ae201c398a4cad9ef6feb9f840a5903b74d0
4
+ data.tar.gz: 74ec0f7d356ef55114127a9e92b00f536c85ab0b4fb02ca1ba7bccb8d2a602bb
5
5
  SHA512:
6
- metadata.gz: 5e1779c275eb27fdbf03ffdd94859ab3a781206e16ad71638aa43d7912e520ca34385e815117801752038bd6027fec87660931b8ed596685c41ee65993276b19
7
- data.tar.gz: 75ea37b5c3995bf899ee17bfca07983366a8311e64da2261aa6a67573ddf6d21270e2b3a5b9051c79a8c08ee4220ab184722ee32fd4215adbe30cd39e3fb8709
6
+ metadata.gz: 5c4bb42771731024576d6e113dd6dbf9290637c69c9a042927ac6bb5775db1e3724664d014900d52b3a5bc4b0db3d6d9bce4ec117948d16e80aca121de536ff6
7
+ data.tar.gz: 222ab95f557e231fc71b648629ea8275a74f3485e0d1589ff33fac316ad96b826a86b2c62d6cde7641abd64b11d4e815309f90b0653849b8a57e350996747aa5
@@ -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
@@ -10,3 +10,13 @@ end
10
10
  platform :rbx do
11
11
  gem "rubysl"
12
12
  end
13
+
14
+ group :jekyll do
15
+ gem "jekyll", "~> 3.3"
16
+ gem "kramdown-parser-gfm"
17
+ end
18
+
19
+ group :jekyll_plugins do
20
+ gem "jekyll-commonmark"
21
+ gem "jekyll-theme-cayman"
22
+ 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.
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
26
26
  alpha composition and cropping. Finally, it can import from and export to RMagick for
27
27
  interoperability.
28
28
 
29
- 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
30
30
  drop in mixin module that implements some of the ChunkyPNG algorithms in C, which
31
31
  provides a massive speed boost to encoding and decoding.
32
32
  EOT
@@ -0,0 +1,3 @@
1
+ .sass-cache/
2
+ .jekyll-metadata
3
+ _site/
@@ -0,0 +1 @@
1
+ chunkypng.com
@@ -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.
@@ -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. :-)
@@ -297,7 +297,7 @@ module ChunkyPNG
297
297
  # @return [String] A nicely formatted string representation of this canvas.
298
298
  # @private
299
299
  def inspect
300
- inspected = "<#{self.class.name} #{width}x#{height} ["
300
+ inspected = +"<#{self.class.name} #{width}x#{height} ["
301
301
  for y in 0...height
302
302
  inspected << "\n\t[" << row(y).map { |p| ChunkyPNG::Color.to_hex(p) }.join(" ") << "]"
303
303
  end
@@ -27,7 +27,7 @@ module ChunkyPNG
27
27
  # combined to form the original images.
28
28
  #
29
29
  # @see ChunkyPNG::Canvas::PNGEncoding
30
- # @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
31
31
  module PNGDecoding
32
32
  # Decodes a Canvas from a PNG encoded string.
33
33
  # @param [String] str The string to read from.
@@ -20,7 +20,7 @@ module ChunkyPNG
20
20
  # before the compression step.
21
21
  #
22
22
  # @see ChunkyPNG::Canvas::PNGDecoding
23
- # @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
24
24
  module PNGEncoding
25
25
  # The palette used for encoding the image.This is only in used for images
26
26
  # that get encoded using indexed colors.
@@ -119,6 +119,8 @@ module ChunkyPNG
119
119
  # PNG spec, except for color depth: Only 8-bit depth images are supported.
120
120
  # Note that it is still possible to access the chunk for such an image, but
121
121
  # ChunkyPNG will raise an exception if you try to access the pixel data.
122
+ #
123
+ # @see https://www.w3.org/TR/PNG/#11IHDR
122
124
  class Header < Base
123
125
  attr_accessor :width, :height, :depth, :color, :compression, :filtering, :interlace
124
126
 
@@ -168,6 +170,8 @@ module ChunkyPNG
168
170
 
169
171
  # The End (IEND) chunk indicates the last chunk of a PNG stream. It does
170
172
  # not contain any data.
173
+ #
174
+ # @see https://www.w3.org/TR/PNG/#11IEND
171
175
  class End < Base
172
176
  def initialize
173
177
  super("IEND")
@@ -195,6 +199,7 @@ module ChunkyPNG
195
199
  # The Palette (PLTE) chunk contains the image's palette, i.e. the
196
200
  # 8-bit RGB colors this image is using.
197
201
  #
202
+ # @see https://www.w3.org/TR/PNG/#11PLTE
198
203
  # @see ChunkyPNG::Chunk::Transparency
199
204
  # @see ChunkyPNG::Palette
200
205
  class Palette < Generic
@@ -212,6 +217,7 @@ module ChunkyPNG
212
217
  # Images having a color mode that already includes an alpha channel, this
213
218
  # chunk should not be included.
214
219
  #
220
+ # @see https://www.w3.org/TR/PNG/#11tRNS
215
221
  # @see ChunkyPNG::Chunk::Palette
216
222
  # @see ChunkyPNG::Palette
217
223
  class Transparency < Generic
@@ -251,7 +257,18 @@ module ChunkyPNG
251
257
  end
252
258
  end
253
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
254
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
255
272
  def self.combine_chunks(data_chunks)
256
273
  zstream = Zlib::Inflate.new
257
274
  data_chunks.each { |c| zstream << c.content }
@@ -260,6 +277,12 @@ module ChunkyPNG
260
277
  inflated
261
278
  end
262
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.
263
286
  def self.split_in_chunks(data, level = Zlib::DEFAULT_COMPRESSION, chunk_size = 2147483647)
264
287
  streamdata = Zlib::Deflate.deflate(data, level)
265
288
  # TODO: Split long streamdata over multiple chunks
@@ -268,11 +291,12 @@ module ChunkyPNG
268
291
  end
269
292
 
270
293
  # The Text (tEXt) chunk contains keyword/value metadata about the PNG
271
- # stream. In this chunk, the value is stored uncompressed.
294
+ # stream. In this chunk, the value is stored uncompressed.
272
295
  #
273
296
  # The tEXt chunk only supports Latin-1 encoded textual data. If you need
274
297
  # UTF-8 support, check out the InternationalText chunk type.
275
298
  #
299
+ # @see https://www.w3.org/TR/PNG/#11tEXt
276
300
  # @see ChunkyPNG::Chunk::CompressedText
277
301
  # @see ChunkyPNG::Chunk::InternationalText
278
302
  class Text < Base
@@ -301,6 +325,7 @@ module ChunkyPNG
301
325
  # PNG stream. In this chunk, the value is compressed using Deflate
302
326
  # compression.
303
327
  #
328
+ # @see https://www.w3.org/TR/PNG/#11zTXt
304
329
  # @see ChunkyPNG::Chunk::CompressedText
305
330
  # @see ChunkyPNG::Chunk::InternationalText
306
331
  class CompressedText < Base
@@ -333,7 +358,7 @@ module ChunkyPNG
333
358
  # The Physical (pHYs) chunk specifies the intended pixel size or aspect
334
359
  # ratio for display of the image.
335
360
  #
336
- # http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.pHYs
361
+ # @see https://www.w3.org/TR/PNG/#11pHYs
337
362
  class Physical < Base
338
363
  attr_accessor :ppux, :ppuy, :unit
339
364
 
@@ -376,8 +401,7 @@ module ChunkyPNG
376
401
  # a translation of the keyword name. Finally, it supports bot compressed
377
402
  # and uncompressed values.
378
403
  #
379
- # http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.iTXt
380
- #
404
+ # @see https://www.w3.org/TR/PNG/#11iTXt
381
405
  # @see ChunkyPNG::Chunk::Text
382
406
  # @see ChunkyPNG::Chunk::CompressedText
383
407
  class InternationalText < Base
@@ -393,7 +417,7 @@ module ChunkyPNG
393
417
  @compression = compression
394
418
  end
395
419
 
396
- # Reads the tTXt chunk.
420
+ # Reads the iTXt chunk.
397
421
  # @param type [String] The four character chunk type indicator (= "iTXt").
398
422
  # @param content [String] The content read from the chunk.
399
423
  # @return [ChunkyPNG::Chunk::InternationalText] The new End chunk instance.
@@ -190,7 +190,7 @@ module ChunkyPNG
190
190
  # @param [Fixnum] alpha Defaults to opaque (255).
191
191
  # @return [Integer] The newly constructed color value.
192
192
  # @raise [ArgumentError] if the hsv triple is invalid.
193
- # @see http://en.wikipedia.org/wiki/HSL_and_HSV
193
+ # @see https://en.wikipedia.org/wiki/HSL_and_HSV
194
194
  def from_hsv(hue, saturation, value, alpha = 255)
195
195
  raise ArgumentError, "Hue must be between 0 and 360" unless (0..360).cover?(hue)
196
196
  raise ArgumentError, "Saturation must be between 0 and 1" unless (0..1).cover?(saturation)
@@ -216,7 +216,7 @@ module ChunkyPNG
216
216
  # @param [Fixnum] alpha Defaults to opaque (255).
217
217
  # @return [Integer] The newly constructed color value.
218
218
  # @raise [ArgumentError] if the hsl triple is invalid.
219
- # @see http://en.wikipedia.org/wiki/HSL_and_HSV
219
+ # @see https://en.wikipedia.org/wiki/HSL_and_HSV
220
220
  def from_hsl(hue, saturation, lightness, alpha = 255)
221
221
  raise ArgumentError, "Hue #{hue} was not between 0 and 360" unless (0..360).cover?(hue)
222
222
  raise ArgumentError, "Saturation #{saturation} was not between 0 and 1" unless (0..1).cover?(saturation)
@@ -247,8 +247,7 @@ module ChunkyPNG
247
247
  # @param [Fixnum] chroma The associated chroma value.
248
248
  # @return [Array<Fixnum>] A scaled r,g,b triple. Scheme-dependent
249
249
  # adjustments are still needed to reach the true r,g,b values.
250
- # @see http://en.wikipedia.org/wiki/HSL_and_HSV
251
- # @see http://www.tomjewett.com/colors/hsb.html
250
+ # @see https://en.wikipedia.org/wiki/HSL_and_HSV
252
251
  # @private
253
252
  def cylindrical_to_cubic(hue, saturation, y_component, chroma)
254
253
  hue_prime = hue.fdiv(60)
@@ -592,7 +591,7 @@ module ChunkyPNG
592
591
  # @return [Array[2]] The value of the color (0-1)
593
592
  # @return [Array[3]] Optional fourth element for alpha, included if
594
593
  # include_alpha=true (0-255)
595
- # @see http://en.wikipedia.org/wiki/HSL_and_HSV
594
+ # @see https://en.wikipedia.org/wiki/HSL_and_HSV
596
595
  def to_hsv(color, include_alpha = false)
597
596
  hue, chroma, max, _ = hue_and_chroma(color)
598
597
  value = max
@@ -621,7 +620,7 @@ module ChunkyPNG
621
620
  # @return [Array<Fixnum>[2]] The lightness of the color (0-1)
622
621
  # @return [Array<Fixnum>[3]] Optional fourth element for alpha, included if
623
622
  # include_alpha=true (0-255)
624
- # @see http://en.wikipedia.org/wiki/HSL_and_HSV
623
+ # @see https://en.wikipedia.org/wiki/HSL_and_HSV
625
624
  def to_hsl(color, include_alpha = false)
626
625
  hue, chroma, max, min = hue_and_chroma(color)
627
626
  lightness = 0.5 * (max + min)
@@ -3,5 +3,5 @@
3
3
  module ChunkyPNG
4
4
  # The current version of ChunkyPNG.
5
5
  # Set it and commit the change this before running rake release.
6
- VERSION = "1.3.13"
6
+ VERSION = "1.3.14"
7
7
  end
@@ -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.13
4
+ version: 1.3.14
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-10-23 00:00:00.000000000 Z
11
+ date: 2020-10-27 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:
@@ -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
@@ -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: