chunky_png 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
- data/LICENSE +20 -0
- data/README.rdoc +24 -0
- data/Rakefile +4 -0
- data/chunky_png.gemspec +25 -0
- data/lib/chunky_png.rb +34 -0
- data/lib/chunky_png/chunk.rb +104 -0
- data/lib/chunky_png/datastream.rb +72 -0
- data/lib/chunky_png/image.rb +37 -0
- data/lib/chunky_png/palette.rb +91 -0
- data/lib/chunky_png/pixel_matrix.rb +157 -0
- data/spec/integration/image_spec.rb +22 -0
- data/spec/resources/indexed_10x10.png +0 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/unit/pixel_matrix_spec.rb +71 -0
- data/tasks/github-gem.rake +323 -0
- metadata +86 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Willem van Bergen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= Chunky PNG
|
2
|
+
|
3
|
+
This library can read and write PNG files. It is written in pure Ruby for
|
4
|
+
maximum portability. It requires the standard Zlib library.
|
5
|
+
|
6
|
+
== Usage
|
7
|
+
|
8
|
+
require 'chunky_png'
|
9
|
+
|
10
|
+
png = ChunkyPNG.load('filename.png')
|
11
|
+
puts png.chunks.inspect
|
12
|
+
puts png.pixel_matrix.inspect
|
13
|
+
|
14
|
+
# do something with the PNG file
|
15
|
+
|
16
|
+
File.open('out.png', 'w') { |f| png.write(f) }
|
17
|
+
|
18
|
+
(Note: this is subject to change while work is in progress)
|
19
|
+
|
20
|
+
== About
|
21
|
+
|
22
|
+
The library is written by Willem van Bergen for Floorplanner.com, and released
|
23
|
+
under the MIT license (see LICENSE). Please contact me for questions or
|
24
|
+
remarks. Patches are greatly appreciated! :-)
|
data/Rakefile
ADDED
data/chunky_png.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'chunky_png'
|
3
|
+
|
4
|
+
# Do not change the version and date fields by hand. This will be done
|
5
|
+
# automatically by the gem release script.
|
6
|
+
s.version = "0.0.1"
|
7
|
+
s.date = "2010-01-10"
|
8
|
+
|
9
|
+
s.summary = "Pure ruby library for read/write, chunk-level access to PNG files"
|
10
|
+
s.description = "Pure ruby library for read/write, chunk-level access to PNG files"
|
11
|
+
|
12
|
+
s.authors = ['Willem van Bergen']
|
13
|
+
s.email = ['willem@railsdoctors.com']
|
14
|
+
s.homepage = 'http://wiki.github.com/wvanbergen/chunky_png'
|
15
|
+
|
16
|
+
s.add_development_dependency('rspec', '>= 1.2.9')
|
17
|
+
|
18
|
+
s.rdoc_options << '--title' << s.name << '--main' << 'README.rdoc' << '--line-numbers' << '--inline-source'
|
19
|
+
s.extra_rdoc_files = ['README.rdoc']
|
20
|
+
|
21
|
+
# Do not change the files and test_files fields by hand. This will be done
|
22
|
+
# automatically by the gem release script.
|
23
|
+
s.files = %w(spec/spec_helper.rb .gitignore lib/chunky_png/pixel_matrix.rb LICENSE lib/chunky_png/chunk.rb Rakefile README.rdoc spec/resources/indexed_10x10.png spec/integration/image_spec.rb lib/chunky_png/palette.rb lib/chunky_png/datastream.rb chunky_png.gemspec tasks/github-gem.rake lib/chunky_png/image.rb spec/unit/pixel_matrix_spec.rb lib/chunky_png.rb)
|
24
|
+
s.test_files = %w(spec/integration/image_spec.rb spec/unit/pixel_matrix_spec.rb)
|
25
|
+
end
|
data/lib/chunky_png.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'zlib'
|
3
|
+
|
4
|
+
require 'chunky_png/datastream'
|
5
|
+
require 'chunky_png/chunk'
|
6
|
+
require 'chunky_png/palette'
|
7
|
+
require 'chunky_png/pixel_matrix'
|
8
|
+
require 'chunky_png/image'
|
9
|
+
|
10
|
+
module ChunkyPNG
|
11
|
+
extend self
|
12
|
+
|
13
|
+
def load_from_io(io)
|
14
|
+
ChunkyPNG::Datastream.read(io)
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_from_file(file)
|
18
|
+
File.open(file, 'r') { |f| load_from_io(f) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_from_memory(string)
|
22
|
+
load_from_io(StringIO.new(string))
|
23
|
+
end
|
24
|
+
|
25
|
+
def load(arg)
|
26
|
+
if arg.respond_to?(:read)
|
27
|
+
load_from_io(arg)
|
28
|
+
elsif File.exists?(arg)
|
29
|
+
load_from_file(arg)
|
30
|
+
else
|
31
|
+
load_from_memory(arg)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module ChunkyPNG
|
2
|
+
class Chunk
|
3
|
+
|
4
|
+
def self.read(io)
|
5
|
+
|
6
|
+
length, type = io.read(8).unpack('Na4')
|
7
|
+
content = io.read(length)
|
8
|
+
crc = io.read(4).unpack('N').first
|
9
|
+
|
10
|
+
# verify_crc!(type, content, crc)
|
11
|
+
|
12
|
+
CHUNK_TYPES.fetch(type, Generic).read(type, content)
|
13
|
+
end
|
14
|
+
|
15
|
+
class Base
|
16
|
+
attr_accessor :type
|
17
|
+
|
18
|
+
def initialize(type, attributes = {})
|
19
|
+
self.type = type
|
20
|
+
attributes.each { |k, v| send("#{k}=", v) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def write_with_crc(io, content)
|
24
|
+
io << [content.length].pack('N') << type << content
|
25
|
+
io << [Zlib.crc32(content, Zlib.crc32(type))].pack('N')
|
26
|
+
end
|
27
|
+
|
28
|
+
def write(io)
|
29
|
+
write_with_crc(io, content || '')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Generic < Base
|
34
|
+
|
35
|
+
attr_accessor :content
|
36
|
+
|
37
|
+
def initialize(type, content = '')
|
38
|
+
super(type, :content => content)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.read(type, content)
|
42
|
+
self.new(type, content)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Header < Base
|
47
|
+
|
48
|
+
COLOR_GRAYSCALE = 0
|
49
|
+
COLOR_TRUECOLOR = 2
|
50
|
+
COLOR_INDEXED = 3
|
51
|
+
COLOR_GRAYSCALE_ALPHA = 4
|
52
|
+
COLOR_TRUECOLOR_ALPHA = 6
|
53
|
+
|
54
|
+
attr_accessor :width, :height, :depth, :color, :compression, :filtering, :interlace
|
55
|
+
|
56
|
+
def initialize(attrs = {})
|
57
|
+
super('IHDR', attrs)
|
58
|
+
@depth ||= 8
|
59
|
+
@color ||= COLOR_TRUECOLOR
|
60
|
+
@compression ||= 0
|
61
|
+
@filtering ||= 0
|
62
|
+
@interlace ||= 0
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.read(type, content)
|
66
|
+
fields = content.unpack('NNC5')
|
67
|
+
self.new(:width => fields[0], :height => fields[1], :depth => fields[2], :color => fields[3],
|
68
|
+
:compression => fields[4], :filtering => fields[5], :interlace => fields[6])
|
69
|
+
end
|
70
|
+
|
71
|
+
def content
|
72
|
+
[width, height, depth, color, compression, filtering, interlace].pack('NNC5')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class End < Base
|
77
|
+
def initialize
|
78
|
+
super('IEND')
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.read(type, content)
|
82
|
+
raise 'The IEND chunk should be empty!' if content != ''
|
83
|
+
self.new
|
84
|
+
end
|
85
|
+
|
86
|
+
def content
|
87
|
+
''
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class Palette < Generic
|
92
|
+
end
|
93
|
+
|
94
|
+
class ImageData < Generic
|
95
|
+
end
|
96
|
+
|
97
|
+
# Maps chunk types to classes.
|
98
|
+
# If a chunk type is not given in this hash, a generic chunk type will be used.
|
99
|
+
CHUNK_TYPES = {
|
100
|
+
'IHDR' => Header, 'IEND' => End, 'IDAT' => ImageData, 'PLTE' => Palette
|
101
|
+
}
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module ChunkyPNG
|
2
|
+
|
3
|
+
class Datastream
|
4
|
+
|
5
|
+
SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10].pack('C8')
|
6
|
+
|
7
|
+
attr_accessor :header_chunk
|
8
|
+
attr_accessor :other_chunks
|
9
|
+
attr_accessor :palette_chunk
|
10
|
+
attr_accessor :data_chunks
|
11
|
+
attr_accessor :end_chunk
|
12
|
+
|
13
|
+
def self.read(io)
|
14
|
+
verify_signature!(io)
|
15
|
+
|
16
|
+
ds = self.new
|
17
|
+
until io.eof?
|
18
|
+
chunk = ChunkyPNG::Chunk.read(io)
|
19
|
+
case chunk
|
20
|
+
when ChunkyPNG::Chunk::Header then ds.header_chunk = chunk
|
21
|
+
when ChunkyPNG::Chunk::Palette then ds.palette_chunk = chunk
|
22
|
+
when ChunkyPNG::Chunk::ImageData then ds.data_chunks << chunk
|
23
|
+
when ChunkyPNG::Chunk::End then ds.end_chunk = chunk
|
24
|
+
else ds.other_chunks << chunk
|
25
|
+
end
|
26
|
+
end
|
27
|
+
return ds
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.verify_signature!(io)
|
31
|
+
signature = io.read(ChunkyPNG::Datastream::SIGNATURE.length)
|
32
|
+
raise "PNG signature not found!" unless signature == ChunkyPNG::Datastream::SIGNATURE
|
33
|
+
end
|
34
|
+
|
35
|
+
def chunks
|
36
|
+
cs = [header_chunk]
|
37
|
+
cs += other_chunks
|
38
|
+
cs << palette_chunk if palette_chunk
|
39
|
+
cs += data_chunks
|
40
|
+
cs << end_chunk
|
41
|
+
return cs
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
@other_chunks = []
|
46
|
+
@data_chunks = []
|
47
|
+
end
|
48
|
+
|
49
|
+
def write(io)
|
50
|
+
io << SIGNATURE
|
51
|
+
chunks.each { |c| c.write(io) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def idat_chunks(data)
|
55
|
+
streamdata = Zlib::Deflate.deflate(data)
|
56
|
+
# TODO: Split long streamdata over multiple chunks
|
57
|
+
return [ ChunkyPNG::Chunk::ImageData.new('IDAT', streamdata) ]
|
58
|
+
end
|
59
|
+
|
60
|
+
def pixel_matrix=(pixel_matrix)
|
61
|
+
@pixel_matrix = pixel_matrix
|
62
|
+
end
|
63
|
+
|
64
|
+
def pixel_matrix
|
65
|
+
@pixel_matrix ||= begin
|
66
|
+
data = data_chunks.map(&:content).join('')
|
67
|
+
streamdata = Zlib::Inflate.inflate(data)
|
68
|
+
matrix = ChunkyPNG::PixelMatrix.load(header, streamdata)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ChunkyPNG
|
2
|
+
class Image
|
3
|
+
|
4
|
+
attr_reader :pixels
|
5
|
+
|
6
|
+
def initialize(width, height, background_color = ChunkyPNG::Color::WHITE)
|
7
|
+
@pixels = ChunkyPNG::PixelMatrix.new(width, height, background_color)
|
8
|
+
end
|
9
|
+
|
10
|
+
def width
|
11
|
+
pixels.width
|
12
|
+
end
|
13
|
+
|
14
|
+
def height
|
15
|
+
pixels.height
|
16
|
+
end
|
17
|
+
|
18
|
+
def write(io)
|
19
|
+
datastream = ChunkyPNG::Datastream.new
|
20
|
+
|
21
|
+
palette = pixels.palette
|
22
|
+
if palette.indexable?
|
23
|
+
datastream.header_chunk = ChunkyPNG::Chunk::Header.new(:width => width, :height => height, :color => ChunkyPNG::Chunk::Header::COLOR_INDEXED)
|
24
|
+
datastream.palette_chunk = palette.to_plte_chunk
|
25
|
+
datastream.data_chunks = datastream.idat_chunks(pixels.to_indexed_pixelstream(palette))
|
26
|
+
datastream.end_chunk = ChunkyPNG::Chunk::End.new
|
27
|
+
else
|
28
|
+
raise 'd'
|
29
|
+
datastream.header_chunk = ChunkyPNG::Chunk::Header.new(:width => width, :height => height)
|
30
|
+
datastream.data_chunks = datastream.idat_chunks(pixels.to_rgb_pixelstream)
|
31
|
+
datastream.end_chunk = ChunkyPNG::Chunk::End.new
|
32
|
+
end
|
33
|
+
|
34
|
+
datastream.write(io)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module ChunkyPNG
|
2
|
+
|
3
|
+
###########################################
|
4
|
+
# PALETTE CLASS
|
5
|
+
###########################################
|
6
|
+
|
7
|
+
class Palette < Set
|
8
|
+
|
9
|
+
def self.from_pixel_matrix(pixel_matrix)
|
10
|
+
from_pixels(pixel_matrix.pixels)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_pixels(pixels)
|
14
|
+
from_colors(pixels.map(&:color))
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.from_colors(colors)
|
18
|
+
palette = self.new
|
19
|
+
colors.each { |color| palette << color }
|
20
|
+
palette
|
21
|
+
end
|
22
|
+
|
23
|
+
def indexable?
|
24
|
+
size < 256
|
25
|
+
end
|
26
|
+
|
27
|
+
def index(color)
|
28
|
+
@color_map[color]
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_plte_chunk
|
32
|
+
@color_map = {}
|
33
|
+
colors = []
|
34
|
+
|
35
|
+
each_with_index do |color, index|
|
36
|
+
@color_map[color] = index
|
37
|
+
colors += color.to_rgb_array
|
38
|
+
end
|
39
|
+
|
40
|
+
ChunkyPNG::Chunk::Generic.new('PLTE', colors.pack('C*'))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
###########################################
|
45
|
+
# COLOR CLASS
|
46
|
+
###########################################
|
47
|
+
|
48
|
+
class Color
|
49
|
+
|
50
|
+
attr_accessor :r, :g, :b
|
51
|
+
|
52
|
+
def initialize(r, g, b)
|
53
|
+
@r, @g, @b = r, g, b
|
54
|
+
end
|
55
|
+
|
56
|
+
### CONSTRUCTORS ###########################################
|
57
|
+
|
58
|
+
def self.rgb(r, g, b)
|
59
|
+
new(r, g, b)
|
60
|
+
end
|
61
|
+
|
62
|
+
### COLOR CONSTANTS ###########################################
|
63
|
+
|
64
|
+
BLACK = rgb( 0, 0, 0)
|
65
|
+
WHITE = rgb(255, 255, 255)
|
66
|
+
|
67
|
+
### CONVERSION ###########################################
|
68
|
+
|
69
|
+
def index(palette)
|
70
|
+
palette.index(self)
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_rgb_array
|
74
|
+
[r, g, b]
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_rgb
|
78
|
+
to_rgb_array.pack('CCC')
|
79
|
+
end
|
80
|
+
|
81
|
+
def inspect
|
82
|
+
'#%02x%02x%02x' % [r, g, b]
|
83
|
+
end
|
84
|
+
|
85
|
+
### EQUALITY ###########################################
|
86
|
+
|
87
|
+
def ==(other)
|
88
|
+
other.kind_of?(self.class) && to_rgb_array == other.to_rgb_array
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module ChunkyPNG
|
2
|
+
|
3
|
+
class PixelMatrix
|
4
|
+
|
5
|
+
FILTER_NONE = 0
|
6
|
+
FILTER_SUB = 1
|
7
|
+
FILTER_UP = 2
|
8
|
+
FILTER_AVERAGE = 3
|
9
|
+
FILTER_PAETH = 4
|
10
|
+
|
11
|
+
attr_reader :width, :height, :pixels
|
12
|
+
|
13
|
+
def self.load(header, content)
|
14
|
+
matrix = self.new(header.width, header.height)
|
15
|
+
matrix.decode_pixelstream(content, header)
|
16
|
+
return matrix
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](x, y)
|
20
|
+
@pixels[y * width + x]
|
21
|
+
end
|
22
|
+
|
23
|
+
def each_scanline(&block)
|
24
|
+
height.times do |i|
|
25
|
+
scanline = @pixels[width * i, width]
|
26
|
+
yield(scanline)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def []=(x, y, color)
|
31
|
+
@pixels[y * width + x] = Pixel.new(color)
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(width, height, background_color = ChunkyPNG::Color::WHITE)
|
35
|
+
@width, @height = width, height
|
36
|
+
@pixels = Array.new(width * height, Pixel.new(background_color))
|
37
|
+
end
|
38
|
+
|
39
|
+
def decode_pixelstream(stream, header = nil)
|
40
|
+
verify_length!(stream.length)
|
41
|
+
@pixels = []
|
42
|
+
|
43
|
+
decoded_bytes = Array.new(header.width * 3, 0)
|
44
|
+
height.times do |line_no|
|
45
|
+
position = line_no * (width * 3 + 1)
|
46
|
+
line_length = header.width * 3
|
47
|
+
bytes = stream.unpack("@#{position}CC#{line_length}")
|
48
|
+
filter = bytes.shift
|
49
|
+
decoded_bytes = decode_scanline(filter, bytes, decoded_bytes, header)
|
50
|
+
decoded_colors = decode_colors(decoded_bytes, header)
|
51
|
+
@pixels += decoded_colors.map { |c| Pixel.new(c) }
|
52
|
+
end
|
53
|
+
|
54
|
+
raise "Invalid amount of pixels" if @pixels.size != width * height
|
55
|
+
end
|
56
|
+
|
57
|
+
def decode_colors(bytes, header)
|
58
|
+
(0...width).map { |i| ChunkyPNG::Color.rgb(bytes[i*3+0], bytes[i*3+1], bytes[i*3+2]) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def decode_scanline(filter, bytes, previous_bytes, header = nil)
|
62
|
+
case filter
|
63
|
+
when FILTER_NONE then decode_scanline_none( bytes, previous_bytes, header)
|
64
|
+
when FILTER_SUB then decode_scanline_sub( bytes, previous_bytes, header)
|
65
|
+
when FILTER_UP then decode_scanline_up( bytes, previous_bytes, header)
|
66
|
+
when FILTER_AVERAGE then raise "Average filter are not yet supported!"
|
67
|
+
when FILTER_PAETH then raise "Paeth filter are not yet supported!"
|
68
|
+
else raise "Unknown filter type"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def decode_scanline_none(bytes, previous_bytes, header = nil)
|
73
|
+
bytes
|
74
|
+
end
|
75
|
+
|
76
|
+
def decode_scanline_sub(bytes, previous_bytes, header = nil)
|
77
|
+
bytes.each_with_index { |b, i| bytes[i] = (b + (i >= 3 ? bytes[i-3] : 0)) % 256 }
|
78
|
+
bytes
|
79
|
+
end
|
80
|
+
|
81
|
+
def decode_scanline_up(bytes, previous_bytes, header = nil)
|
82
|
+
bytes.each_with_index { |b, i| bytes[i] = (b + previous_bytes[i]) % 256 }
|
83
|
+
bytes
|
84
|
+
end
|
85
|
+
|
86
|
+
def verify_length!(bytes_count)
|
87
|
+
raise "Invalid stream length!" unless bytes_count == width * height * 3 + height
|
88
|
+
end
|
89
|
+
|
90
|
+
def encode_scanline(filter, bytes, previous_bytes, header = nil)
|
91
|
+
case filter
|
92
|
+
when FILTER_NONE then encode_scanline_none( bytes, previous_bytes, header)
|
93
|
+
when FILTER_SUB then encode_scanline_sub( bytes, previous_bytes, header)
|
94
|
+
when FILTER_UP then encode_scanline_up( bytes, previous_bytes, header)
|
95
|
+
when FILTER_AVERAGE then raise "Average filter are not yet supported!"
|
96
|
+
when FILTER_PAETH then raise "Paeth filter are not yet supported!"
|
97
|
+
else raise "Unknown filter type"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def encode_scanline_none(bytes, previous_bytes, header = nil)
|
102
|
+
[FILTER_NONE] + bytes
|
103
|
+
end
|
104
|
+
|
105
|
+
def encode_scanline_sub(bytes, previous_bytes, header = nil)
|
106
|
+
encoded = (3...bytes.length).map { |n| (bytes[n-3] - bytes[n]) % 256 }
|
107
|
+
[FILTER_SUB] + bytes[0...3] + encoded
|
108
|
+
end
|
109
|
+
|
110
|
+
def encode_scanline_up(bytes, previous_bytes, header = nil)
|
111
|
+
encoded = (0...bytes.length).map { |n| previous_bytes[n] - bytes[n] % 256 }
|
112
|
+
[FILTER_UP] + encoded
|
113
|
+
end
|
114
|
+
|
115
|
+
def palette
|
116
|
+
ChunkyPNG::Palette.from_pixels(@pixels)
|
117
|
+
end
|
118
|
+
|
119
|
+
def indexable?
|
120
|
+
palette.indexable?
|
121
|
+
end
|
122
|
+
|
123
|
+
def to_indexed_pixelstream(palette)
|
124
|
+
stream = ""
|
125
|
+
each_scanline do |line|
|
126
|
+
bytes = line.map { |p| p.color.index(palette) }
|
127
|
+
stream << encode_scanline(FILTER_NONE, bytes, nil, nil).pack('C*')
|
128
|
+
end
|
129
|
+
return stream
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_rgb_pixelstream
|
133
|
+
stream = ""
|
134
|
+
each_scanline do |line|
|
135
|
+
bytes = line.map { |p| p.color.to_rgb_array }.flatten
|
136
|
+
stream << encode_scanline(FILTER_NONE, bytes, nil, nil).pack('C*')
|
137
|
+
end
|
138
|
+
return stream
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class Pixel
|
143
|
+
attr_accessor :color, :alpha
|
144
|
+
|
145
|
+
def initialize(color, alpha = 255)
|
146
|
+
@color, @alpha = color, alpha
|
147
|
+
end
|
148
|
+
|
149
|
+
def opaque?
|
150
|
+
alpha == 255
|
151
|
+
end
|
152
|
+
|
153
|
+
def make_opaque!
|
154
|
+
alpha = 255
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.expand_path('../spec_helper.rb', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe ChunkyPNG::Image do
|
4
|
+
|
5
|
+
it "should write a valid PNG image using an indexed palette" do
|
6
|
+
image = ChunkyPNG::Image.new(10, 20, ChunkyPNG::Color.rgb(10, 100, 255))
|
7
|
+
filename = resource_file('testing.png')
|
8
|
+
File.open(filename, 'w') { |f| image.write(f) }
|
9
|
+
|
10
|
+
png = ChunkyPNG.load(filename)
|
11
|
+
png.header_chunk.width.should == 10
|
12
|
+
png.header_chunk.height.should == 20
|
13
|
+
png.header_chunk.color.should == ChunkyPNG::Chunk::Header::COLOR_INDEXED
|
14
|
+
|
15
|
+
png.palette_chunk.should_not be_nil
|
16
|
+
png.data_chunks.should_not be_empty
|
17
|
+
# `open #{filename}`
|
18
|
+
|
19
|
+
File.unlink(filename)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
Binary file
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'spec'
|
5
|
+
|
6
|
+
require 'chunky_png'
|
7
|
+
|
8
|
+
module ResourceFileHelper
|
9
|
+
def resource_file(name)
|
10
|
+
File.expand_path("./resources/#{name}", File.dirname(__FILE__))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
Spec::Runner.configure do |config|
|
16
|
+
config.include ResourceFileHelper
|
17
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.expand_path('../spec_helper.rb', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe ChunkyPNG::PixelMatrix do
|
4
|
+
|
5
|
+
describe '#decode_scanline' do
|
6
|
+
before(:each) do
|
7
|
+
@matrix = ChunkyPNG::PixelMatrix.new(3, 3)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should decode a line without filtering as is" do
|
11
|
+
bytes = Array.new(@matrix.width * 3, ChunkyPNG::Color.rgb(10, 20, 30))
|
12
|
+
@matrix.decode_scanline(ChunkyPNG::PixelMatrix::FILTER_NONE, bytes, nil).should == bytes
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should decode a line with sub filtering correctly" do
|
16
|
+
# all white pixels
|
17
|
+
bytes = [255, 255, 255, 0, 0, 0, 0, 0, 0]
|
18
|
+
decoded_bytes = @matrix.decode_scanline(ChunkyPNG::PixelMatrix::FILTER_SUB, bytes, nil)
|
19
|
+
decoded_bytes.should == [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
20
|
+
|
21
|
+
# all black pixels
|
22
|
+
bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
23
|
+
decoded_bytes = @matrix.decode_scanline(ChunkyPNG::PixelMatrix::FILTER_SUB, bytes, nil)
|
24
|
+
decoded_bytes.should == [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
25
|
+
|
26
|
+
# various colors
|
27
|
+
bytes = [255, 0, 45, 0, 255, 0, 112, 200, 178]
|
28
|
+
decoded_bytes = @matrix.decode_scanline(ChunkyPNG::PixelMatrix::FILTER_SUB, bytes, nil)
|
29
|
+
decoded_bytes.should == [255, 0, 45, 255, 255, 45, 111, 199, 223]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should decode a line with up filtering correctly" do
|
33
|
+
# previous line is all black
|
34
|
+
previous_bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
35
|
+
bytes = [255, 255, 255, 127, 127, 127, 0, 0, 0]
|
36
|
+
decoded_bytes = @matrix.decode_scanline(ChunkyPNG::PixelMatrix::FILTER_UP, bytes, previous_bytes)
|
37
|
+
decoded_bytes.should == [255, 255, 255, 127, 127, 127, 0, 0, 0]
|
38
|
+
|
39
|
+
# previous line has various pixels
|
40
|
+
previous_bytes = [255, 255, 255, 127, 127, 127, 0, 0, 0]
|
41
|
+
bytes = [0, 127, 255, 0, 127, 255, 0, 127, 255]
|
42
|
+
decoded_bytes = @matrix.decode_scanline(ChunkyPNG::PixelMatrix::FILTER_UP, bytes, previous_bytes)
|
43
|
+
decoded_bytes.should == [255, 126, 254, 127, 254, 126, 0, 127, 255]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#encode_scanline' do
|
48
|
+
before(:each) do
|
49
|
+
@matrix = ChunkyPNG::PixelMatrix.new(3, 3)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should encode a scanline without filtering correctly" do
|
53
|
+
bytes = [0, 0, 0, 1, 1, 1, 2, 2, 2]
|
54
|
+
encoded_bytes = @matrix.encode_scanline(ChunkyPNG::PixelMatrix::FILTER_NONE, bytes, nil, nil)
|
55
|
+
encoded_bytes.should == [0, 0, 0, 0, 1, 1, 1, 2, 2, 2]
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should encode a scanline with sub filtering correctly" do
|
59
|
+
bytes = [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
60
|
+
encoded_bytes = @matrix.encode_scanline(ChunkyPNG::PixelMatrix::FILTER_SUB, bytes, nil, nil)
|
61
|
+
encoded_bytes.should == [1, 255, 255, 255, 0, 0, 0, 0, 0, 0]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should encode a scanline with up filtering correctly" do
|
65
|
+
bytes = [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
66
|
+
previous_bytes = [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
67
|
+
encoded_bytes = @matrix.encode_scanline(ChunkyPNG::PixelMatrix::FILTER_UP, bytes, previous_bytes, nil)
|
68
|
+
encoded_bytes.should == [2, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,323 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/tasklib'
|
4
|
+
require 'date'
|
5
|
+
require 'git'
|
6
|
+
|
7
|
+
module GithubGem
|
8
|
+
|
9
|
+
# Detects the gemspc file of this project using heuristics.
|
10
|
+
def self.detect_gemspec_file
|
11
|
+
FileList['*.gemspec'].first
|
12
|
+
end
|
13
|
+
|
14
|
+
# Detects the main include file of this project using heuristics
|
15
|
+
def self.detect_main_include
|
16
|
+
if detect_gemspec_file =~ /^(\.*)\.gemspec$/ && File.exist?("lib/#{$1}.rb")
|
17
|
+
"lib/#{$1}.rb"
|
18
|
+
elsif FileList['lib/*.rb'].length == 1
|
19
|
+
FileList['lib/*.rb'].first
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class RakeTasks
|
26
|
+
|
27
|
+
attr_reader :gemspec, :modified_files, :git
|
28
|
+
attr_accessor :gemspec_file, :task_namespace, :main_include, :root_dir, :spec_pattern, :test_pattern, :remote, :remote_branch, :local_branch
|
29
|
+
|
30
|
+
# Initializes the settings, yields itself for configuration
|
31
|
+
# and defines the rake tasks based on the gemspec file.
|
32
|
+
def initialize(task_namespace = :gem)
|
33
|
+
@gemspec_file = GithubGem.detect_gemspec_file
|
34
|
+
@task_namespace = task_namespace
|
35
|
+
@main_include = GithubGem.detect_main_include
|
36
|
+
@modified_files = []
|
37
|
+
@root_dir = Dir.pwd
|
38
|
+
@test_pattern = 'test/**/*_test.rb'
|
39
|
+
@spec_pattern = 'spec/**/*_spec.rb'
|
40
|
+
@local_branch = 'master'
|
41
|
+
@remote = 'origin'
|
42
|
+
@remote_branch = 'master'
|
43
|
+
|
44
|
+
yield(self) if block_given?
|
45
|
+
|
46
|
+
@git = Git.open(@root_dir)
|
47
|
+
load_gemspec!
|
48
|
+
define_tasks!
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
# Define Unit test tasks
|
54
|
+
def define_test_tasks!
|
55
|
+
require 'rake/testtask'
|
56
|
+
|
57
|
+
namespace(:test) do
|
58
|
+
Rake::TestTask.new(:basic) do |t|
|
59
|
+
t.pattern = test_pattern
|
60
|
+
t.verbose = true
|
61
|
+
t.libs << 'test'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
desc "Run all unit tests for #{gemspec.name}"
|
66
|
+
task(:test => ['test:basic'])
|
67
|
+
end
|
68
|
+
|
69
|
+
# Defines RSpec tasks
|
70
|
+
def define_rspec_tasks!
|
71
|
+
require 'spec/rake/spectask'
|
72
|
+
|
73
|
+
namespace(:spec) do
|
74
|
+
desc "Verify all RSpec examples for #{gemspec.name}"
|
75
|
+
Spec::Rake::SpecTask.new(:basic) do |t|
|
76
|
+
t.spec_files = FileList[spec_pattern]
|
77
|
+
end
|
78
|
+
|
79
|
+
desc "Verify all RSpec examples for #{gemspec.name} and output specdoc"
|
80
|
+
Spec::Rake::SpecTask.new(:specdoc) do |t|
|
81
|
+
t.spec_files = FileList[spec_pattern]
|
82
|
+
t.spec_opts << '--format' << 'specdoc' << '--color'
|
83
|
+
end
|
84
|
+
|
85
|
+
desc "Run RCov on specs for #{gemspec.name}"
|
86
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
87
|
+
t.spec_files = FileList[spec_pattern]
|
88
|
+
t.rcov = true
|
89
|
+
t.rcov_opts = ['--exclude', '"spec/*,gems/*"', '--rails']
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "Verify all RSpec examples for #{gemspec.name} and output specdoc"
|
94
|
+
task(:spec => ['spec:specdoc'])
|
95
|
+
end
|
96
|
+
|
97
|
+
# Defines the rake tasks
|
98
|
+
def define_tasks!
|
99
|
+
|
100
|
+
define_test_tasks! if has_tests?
|
101
|
+
define_rspec_tasks! if has_specs?
|
102
|
+
|
103
|
+
namespace(@task_namespace) do
|
104
|
+
desc "Updates the filelist in the gemspec file"
|
105
|
+
task(:manifest) { manifest_task }
|
106
|
+
|
107
|
+
desc "Builds the .gem package"
|
108
|
+
task(:build => :manifest) { build_task }
|
109
|
+
|
110
|
+
desc "Sets the version of the gem in the gemspec"
|
111
|
+
task(:set_version => [:check_version, :check_current_branch]) { version_task }
|
112
|
+
task(:check_version => :fetch_origin) { check_version_task }
|
113
|
+
|
114
|
+
task(:fetch_origin) { fetch_origin_task }
|
115
|
+
task(:check_current_branch) { check_current_branch_task }
|
116
|
+
task(:check_clean_status) { check_clean_status_task }
|
117
|
+
task(:check_not_diverged => :fetch_origin) { check_not_diverged_task }
|
118
|
+
|
119
|
+
checks = [:check_current_branch, :check_clean_status, :check_not_diverged, :check_version]
|
120
|
+
checks.unshift('spec:basic') if has_specs?
|
121
|
+
checks.unshift('test:basic') if has_tests?
|
122
|
+
checks.push << [:check_rubyforge] if gemspec.rubyforge_project
|
123
|
+
|
124
|
+
desc "Perform all checks that would occur before a release"
|
125
|
+
task(:release_checks => checks)
|
126
|
+
|
127
|
+
release_tasks = [:release_checks, :set_version, :build, :github_release]
|
128
|
+
release_tasks << [:rubyforge_release] if gemspec.rubyforge_project
|
129
|
+
|
130
|
+
desc "Release a new verison of the gem"
|
131
|
+
task(:release => release_tasks) { release_task }
|
132
|
+
|
133
|
+
task(:check_rubyforge) { check_rubyforge_task }
|
134
|
+
task(:rubyforge_release) { rubyforge_release_task }
|
135
|
+
task(:github_release => [:commit_modified_files, :tag_version]) { github_release_task }
|
136
|
+
task(:tag_version) { tag_version_task }
|
137
|
+
task(:commit_modified_files) { commit_modified_files_task }
|
138
|
+
|
139
|
+
desc "Updates the gem release tasks with the latest version on Github"
|
140
|
+
task(:update_tasks) { update_tasks_task }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Updates the files list and test_files list in the gemspec file using the list of files
|
145
|
+
# in the repository and the spec/test file pattern.
|
146
|
+
def manifest_task
|
147
|
+
# Load all the gem's files using "git ls-files"
|
148
|
+
repository_files = git.ls_files.keys
|
149
|
+
test_files = Dir[test_pattern] + Dir[spec_pattern]
|
150
|
+
|
151
|
+
update_gemspec(:files, repository_files)
|
152
|
+
update_gemspec(:test_files, repository_files & test_files)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Builds the gem
|
156
|
+
def build_task
|
157
|
+
sh "gem build -q #{gemspec_file}"
|
158
|
+
Dir.mkdir('pkg') unless File.exist?('pkg')
|
159
|
+
sh "mv #{gemspec.name}-#{gemspec.version}.gem pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
160
|
+
end
|
161
|
+
|
162
|
+
# Updates the version number in the gemspec file, the VERSION constant in the main
|
163
|
+
# include file and the contents of the VERSION file.
|
164
|
+
def version_task
|
165
|
+
update_gemspec(:version, ENV['VERSION']) if ENV['VERSION']
|
166
|
+
update_gemspec(:date, Date.today)
|
167
|
+
|
168
|
+
update_version_file(gemspec.version)
|
169
|
+
update_version_constant(gemspec.version)
|
170
|
+
end
|
171
|
+
|
172
|
+
def check_version_task
|
173
|
+
raise "#{ENV['VERSION']} is not a valid version number!" if ENV['VERSION'] && !Gem::Version.correct?(ENV['VERSION'])
|
174
|
+
proposed_version = Gem::Version.new(ENV['VERSION'] || gemspec.version)
|
175
|
+
# Loads the latest version number using the created tags
|
176
|
+
newest_version = git.tags.map { |tag| tag.name.split('-').last }.compact.map { |v| Gem::Version.new(v) }.max
|
177
|
+
raise "This version (#{proposed_version}) is not higher than the highest tagged version (#{newest_version})" if newest_version && newest_version >= proposed_version
|
178
|
+
end
|
179
|
+
|
180
|
+
# Checks whether the current branch is not diverged from the remote branch
|
181
|
+
def check_not_diverged_task
|
182
|
+
raise "The current branch is diverged from the remote branch!" if git.log.between('HEAD', git.remote(remote).branch(remote_branch).gcommit).any?
|
183
|
+
end
|
184
|
+
|
185
|
+
# Checks whether the repository status ic clean
|
186
|
+
def check_clean_status_task
|
187
|
+
raise "The current working copy contains modifications" if git.status.changed.any?
|
188
|
+
end
|
189
|
+
|
190
|
+
# Checks whether the current branch is correct
|
191
|
+
def check_current_branch_task
|
192
|
+
raise "Currently not on #{local_branch} branch!" unless git.branch.name == local_branch.to_s
|
193
|
+
end
|
194
|
+
|
195
|
+
# Fetches the latest updates from Github
|
196
|
+
def fetch_origin_task
|
197
|
+
git.fetch('origin')
|
198
|
+
end
|
199
|
+
|
200
|
+
# Commits every file that has been changed by the release task.
|
201
|
+
def commit_modified_files_task
|
202
|
+
if modified_files.any?
|
203
|
+
modified_files.each { |file| git.add(file) }
|
204
|
+
git.commit("Released #{gemspec.name} gem version #{gemspec.version}")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Adds a tag for the released version
|
209
|
+
def tag_version_task
|
210
|
+
git.add_tag("#{gemspec.name}-#{gemspec.version}")
|
211
|
+
end
|
212
|
+
|
213
|
+
# Pushes the changes and tag to github
|
214
|
+
def github_release_task
|
215
|
+
git.push(remote, remote_branch, true)
|
216
|
+
end
|
217
|
+
|
218
|
+
# Checks whether Rubyforge is configured properly
|
219
|
+
def check_rubyforge_task
|
220
|
+
# Login no longer necessary when using rubyforge 2.0.0 gem
|
221
|
+
# raise "Could not login on rubyforge!" unless `rubyforge login 2>&1`.strip.empty?
|
222
|
+
output = `rubyforge names`.split("\n")
|
223
|
+
raise "Rubyforge group not found!" unless output.any? { |line| %r[^groups\s*\:.*\b#{Regexp.quote(gemspec.rubyforge_project)}\b.*] =~ line }
|
224
|
+
raise "Rubyforge package not found!" unless output.any? { |line| %r[^packages\s*\:.*\b#{Regexp.quote(gemspec.name)}\b.*] =~ line }
|
225
|
+
end
|
226
|
+
|
227
|
+
# Task to release the .gem file toRubyforge.
|
228
|
+
def rubyforge_release_task
|
229
|
+
sh 'rubyforge', 'add_release', gemspec.rubyforge_project, gemspec.name, gemspec.version.to_s, "pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
230
|
+
end
|
231
|
+
|
232
|
+
# Gem release task.
|
233
|
+
# All work is done by the task's dependencies, so just display a release completed message.
|
234
|
+
def release_task
|
235
|
+
puts
|
236
|
+
puts '------------------------------------------------------------'
|
237
|
+
puts "Released #{gemspec.name} version #{gemspec.version}"
|
238
|
+
end
|
239
|
+
|
240
|
+
private
|
241
|
+
|
242
|
+
# Checks whether this project has any RSpec files
|
243
|
+
def has_specs?
|
244
|
+
FileList[spec_pattern].any?
|
245
|
+
end
|
246
|
+
|
247
|
+
# Checks whether this project has any unit test files
|
248
|
+
def has_tests?
|
249
|
+
FileList[test_pattern].any?
|
250
|
+
end
|
251
|
+
|
252
|
+
# Loads the gemspec file
|
253
|
+
def load_gemspec!
|
254
|
+
@gemspec = eval(File.read(@gemspec_file))
|
255
|
+
end
|
256
|
+
|
257
|
+
# Updates the VERSION file with the new version
|
258
|
+
def update_version_file(version)
|
259
|
+
if File.exists?('VERSION')
|
260
|
+
File.open('VERSION', 'w') { |f| f << version.to_s }
|
261
|
+
modified_files << 'VERSION'
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Updates the VERSION constant in the main include file if it exists
|
266
|
+
def update_version_constant(version)
|
267
|
+
if main_include && File.exist?(main_include)
|
268
|
+
file_contents = File.read(main_include)
|
269
|
+
if file_contents.sub!(/^(\s+VERSION\s*=\s*)[^\s].*$/) { $1 + version.to_s.inspect }
|
270
|
+
File.open(main_include, 'w') { |f| f << file_contents }
|
271
|
+
modified_files << main_include
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Updates an attribute of the gemspec file.
|
277
|
+
# This function will open the file, and search/replace the attribute using a regular expression.
|
278
|
+
def update_gemspec(attribute, new_value, literal = false)
|
279
|
+
|
280
|
+
unless literal
|
281
|
+
new_value = case new_value
|
282
|
+
when Array then "%w(#{new_value.join(' ')})"
|
283
|
+
when Hash, String then new_value.inspect
|
284
|
+
when Date then new_value.strftime('%Y-%m-%d').inspect
|
285
|
+
else raise "Cannot write value #{new_value.inspect} to gemspec file!"
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
spec = File.read(gemspec_file)
|
290
|
+
regexp = Regexp.new('^(\s+\w+\.' + Regexp.quote(attribute.to_s) + '\s*=\s*)[^\s].*$')
|
291
|
+
if spec.sub!(regexp) { $1 + new_value }
|
292
|
+
File.open(gemspec_file, 'w') { |f| f << spec }
|
293
|
+
modified_files << gemspec_file
|
294
|
+
|
295
|
+
# Reload the gemspec so the changes are incorporated
|
296
|
+
load_gemspec!
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Updates the tasks file using the latest file found on Github
|
301
|
+
def update_tasks_task
|
302
|
+
require 'net/http'
|
303
|
+
|
304
|
+
server = 'github.com'
|
305
|
+
path = '/wvanbergen/github-gem/raw/master/tasks/github-gem.rake'
|
306
|
+
|
307
|
+
Net::HTTP.start(server) do |http|
|
308
|
+
response = http.get(path)
|
309
|
+
open(__FILE__, "w") { |file| file.write(response.body) }
|
310
|
+
end
|
311
|
+
|
312
|
+
relative_file = File.expand_path(__FILE__).sub(%r[^#{git.dir.path}/], '')
|
313
|
+
if git.status[relative_file] && git.status[relative_file].type == 'M'
|
314
|
+
git.add(relative_file)
|
315
|
+
git.commit("Updated to latest gem release management tasks.")
|
316
|
+
puts "Updated to latest version of gem release management tasks."
|
317
|
+
else
|
318
|
+
puts "Release managament tasks already are at the latest version."
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
end
|
323
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chunky_png
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Willem van Bergen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-10 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.9
|
24
|
+
version:
|
25
|
+
description: Pure ruby library for read/write, chunk-level access to PNG files
|
26
|
+
email:
|
27
|
+
- willem@railsdoctors.com
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files:
|
33
|
+
- README.rdoc
|
34
|
+
files:
|
35
|
+
- spec/spec_helper.rb
|
36
|
+
- .gitignore
|
37
|
+
- lib/chunky_png/pixel_matrix.rb
|
38
|
+
- LICENSE
|
39
|
+
- lib/chunky_png/chunk.rb
|
40
|
+
- Rakefile
|
41
|
+
- README.rdoc
|
42
|
+
- spec/resources/indexed_10x10.png
|
43
|
+
- spec/integration/image_spec.rb
|
44
|
+
- lib/chunky_png/palette.rb
|
45
|
+
- lib/chunky_png/datastream.rb
|
46
|
+
- chunky_png.gemspec
|
47
|
+
- tasks/github-gem.rake
|
48
|
+
- lib/chunky_png/image.rb
|
49
|
+
- spec/unit/pixel_matrix_spec.rb
|
50
|
+
- lib/chunky_png.rb
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: http://wiki.github.com/wvanbergen/chunky_png
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options:
|
57
|
+
- --title
|
58
|
+
- chunky_png
|
59
|
+
- --main
|
60
|
+
- README.rdoc
|
61
|
+
- --line-numbers
|
62
|
+
- --inline-source
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "0"
|
70
|
+
version:
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: "0"
|
76
|
+
version:
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.3.5
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: Pure ruby library for read/write, chunk-level access to PNG files
|
84
|
+
test_files:
|
85
|
+
- spec/integration/image_spec.rb
|
86
|
+
- spec/unit/pixel_matrix_spec.rb
|