zpng 0.4.4 → 0.4.6
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.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +75 -30
- data/VERSION +1 -1
- data/lib/zpng/bmp/reader.rb +12 -7
- data/lib/zpng/cli.rb +2 -2
- data/lib/zpng/color.rb +92 -18
- data/lib/zpng/image.rb +182 -14
- data/lib/zpng/jpeg/chunks.rb +164 -0
- data/lib/zpng/jpeg/reader.rb +55 -0
- data/lib/zpng/scan_line.rb +38 -5
- data/lib/zpng.rb +2 -1
- data/spec/bad_samples_spec.rb +12 -0
- data/spec/cli_spec.rb +0 -2
- data/spec/image_spec.rb +99 -24
- data/spec/pixel_access_spec.rb +34 -0
- data/spec/rotate_spec.rb +62 -0
- data/zpng.gemspec +9 -5
- metadata +20 -4
- data/lib/zpng/readable_struct.rb +0 -56
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 45cbe6009082c09bd91263c35aafcde94fe9a244f30999a8181a42cde9d8c591
|
|
4
|
+
data.tar.gz: bfb0767ed14b07544ca7159454b9dbe5ccdd404f98f7e293dddb52b13abf43aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f3929af614a96f549e331a65c15f210bc96bd52c5d88625d85156eb507773b89fc010d968ceddd25de213124db0dc935821dc02f255555eff4780ff20b87eb91
|
|
7
|
+
data.tar.gz: 1d5ab750a6f0aa24fe710ee53b53240d0b3a4335e3484c6c24eb99419413acc3701af9bd7aa5c3928b6971344e0759ee1843e5dbeed09004d06906ef5f9d0a4c
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
GEM
|
|
2
2
|
remote: http://rubygems.org/
|
|
3
3
|
specs:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
activesupport (7.1.6)
|
|
5
|
+
base64
|
|
6
|
+
benchmark (>= 0.3)
|
|
7
|
+
bigdecimal
|
|
8
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
9
|
+
connection_pool (>= 2.2.5)
|
|
10
|
+
drb
|
|
11
|
+
i18n (>= 1.6, < 2)
|
|
12
|
+
logger (>= 1.4.2)
|
|
13
|
+
minitest (>= 5.1)
|
|
14
|
+
mutex_m
|
|
15
|
+
securerandom (>= 0.3)
|
|
16
|
+
tzinfo (~> 2.0)
|
|
17
|
+
addressable (2.8.8)
|
|
18
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
19
|
+
base64 (0.3.0)
|
|
20
|
+
benchmark (0.5.0)
|
|
21
|
+
bigdecimal (4.0.1)
|
|
22
|
+
builder (3.3.0)
|
|
23
|
+
cgi (0.5.1)
|
|
24
|
+
concurrent-ruby (1.3.6)
|
|
25
|
+
connection_pool (2.5.5)
|
|
26
|
+
date (3.5.1)
|
|
7
27
|
descendants_tracker (0.0.4)
|
|
8
28
|
thread_safe (~> 0.3, >= 0.3.1)
|
|
9
|
-
diff-lcs (1.
|
|
10
|
-
|
|
29
|
+
diff-lcs (1.6.2)
|
|
30
|
+
drb (2.2.3)
|
|
31
|
+
erb (4.0.4)
|
|
32
|
+
cgi (>= 0.3.3)
|
|
33
|
+
faraday (1.10.4)
|
|
11
34
|
faraday-em_http (~> 1.0)
|
|
12
35
|
faraday-em_synchrony (~> 1.0)
|
|
13
36
|
faraday-excon (~> 1.1)
|
|
@@ -20,18 +43,20 @@ GEM
|
|
|
20
43
|
faraday-retry (~> 1.0)
|
|
21
44
|
ruby2_keywords (>= 0.0.4)
|
|
22
45
|
faraday-em_http (1.0.0)
|
|
23
|
-
faraday-em_synchrony (1.0.
|
|
46
|
+
faraday-em_synchrony (1.0.1)
|
|
24
47
|
faraday-excon (1.1.0)
|
|
25
48
|
faraday-httpclient (1.0.1)
|
|
26
|
-
faraday-multipart (1.0
|
|
27
|
-
multipart-post (~> 2)
|
|
28
|
-
faraday-net_http (1.0.
|
|
49
|
+
faraday-multipart (1.2.0)
|
|
50
|
+
multipart-post (~> 2.0)
|
|
51
|
+
faraday-net_http (1.0.2)
|
|
29
52
|
faraday-net_http_persistent (1.2.0)
|
|
30
53
|
faraday-patron (1.0.0)
|
|
31
54
|
faraday-rack (1.0.0)
|
|
32
55
|
faraday-retry (1.0.3)
|
|
33
|
-
git (
|
|
56
|
+
git (2.3.3)
|
|
57
|
+
activesupport (>= 5.0)
|
|
34
58
|
addressable (~> 2.8)
|
|
59
|
+
process_executer (~> 1.1)
|
|
35
60
|
rchardet (~> 1.8)
|
|
36
61
|
github_api (0.19.0)
|
|
37
62
|
addressable (~> 2.4)
|
|
@@ -40,7 +65,12 @@ GEM
|
|
|
40
65
|
hashie (~> 3.5, >= 3.5.2)
|
|
41
66
|
oauth2 (~> 1.0)
|
|
42
67
|
hashie (3.6.0)
|
|
43
|
-
highline (
|
|
68
|
+
highline (3.1.2)
|
|
69
|
+
reline
|
|
70
|
+
i18n (1.14.8)
|
|
71
|
+
concurrent-ruby (~> 1.0)
|
|
72
|
+
io-console (0.8.2)
|
|
73
|
+
iostruct (0.7.0)
|
|
44
74
|
juwelier (2.4.9)
|
|
45
75
|
builder
|
|
46
76
|
bundler
|
|
@@ -53,15 +83,19 @@ GEM
|
|
|
53
83
|
rake
|
|
54
84
|
rdoc
|
|
55
85
|
semver2
|
|
56
|
-
jwt (2.
|
|
86
|
+
jwt (2.10.2)
|
|
87
|
+
base64
|
|
57
88
|
kamelcase (0.0.2)
|
|
58
89
|
semver2 (~> 3)
|
|
59
|
-
|
|
60
|
-
|
|
90
|
+
logger (1.7.0)
|
|
91
|
+
mini_portile2 (2.8.9)
|
|
92
|
+
minitest (5.26.1)
|
|
93
|
+
multi_json (1.19.1)
|
|
61
94
|
multi_xml (0.6.0)
|
|
62
|
-
multipart-post (2.
|
|
63
|
-
|
|
64
|
-
|
|
95
|
+
multipart-post (2.4.1)
|
|
96
|
+
mutex_m (0.3.0)
|
|
97
|
+
nokogiri (1.17.2)
|
|
98
|
+
mini_portile2 (~> 2.8.2)
|
|
65
99
|
racc (~> 1.4)
|
|
66
100
|
oauth2 (1.4.11)
|
|
67
101
|
faraday (>= 0.17.3, < 3.0)
|
|
@@ -69,45 +103,56 @@ GEM
|
|
|
69
103
|
multi_json (~> 1.3)
|
|
70
104
|
multi_xml (~> 0.5)
|
|
71
105
|
rack (>= 1.2, < 4)
|
|
72
|
-
|
|
106
|
+
process_executer (1.1.2)
|
|
107
|
+
psych (5.3.1)
|
|
108
|
+
date
|
|
73
109
|
stringio
|
|
74
|
-
public_suffix (
|
|
75
|
-
racc (1.
|
|
76
|
-
rack (3.
|
|
110
|
+
public_suffix (6.0.2)
|
|
111
|
+
racc (1.8.1)
|
|
112
|
+
rack (3.2.4)
|
|
77
113
|
rainbow (3.1.1)
|
|
78
|
-
rake (13.
|
|
79
|
-
rchardet (1.
|
|
80
|
-
rdoc (
|
|
114
|
+
rake (13.3.1)
|
|
115
|
+
rchardet (1.10.0)
|
|
116
|
+
rdoc (7.1.0)
|
|
117
|
+
erb
|
|
81
118
|
psych (>= 4.0.0)
|
|
119
|
+
tsort
|
|
120
|
+
reline (0.6.3)
|
|
121
|
+
io-console (~> 0.5)
|
|
82
122
|
rspec (3.11.0)
|
|
83
123
|
rspec-core (~> 3.11.0)
|
|
84
124
|
rspec-expectations (~> 3.11.0)
|
|
85
125
|
rspec-mocks (~> 3.11.0)
|
|
86
126
|
rspec-core (3.11.0)
|
|
87
127
|
rspec-support (~> 3.11.0)
|
|
88
|
-
rspec-expectations (3.11.
|
|
128
|
+
rspec-expectations (3.11.1)
|
|
89
129
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
90
130
|
rspec-support (~> 3.11.0)
|
|
91
|
-
rspec-its (1.3.
|
|
131
|
+
rspec-its (1.3.1)
|
|
92
132
|
rspec-core (>= 3.0.0)
|
|
93
133
|
rspec-expectations (>= 3.0.0)
|
|
94
|
-
rspec-mocks (3.11.
|
|
134
|
+
rspec-mocks (3.11.2)
|
|
95
135
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
96
136
|
rspec-support (~> 3.11.0)
|
|
97
|
-
rspec-support (3.11.
|
|
137
|
+
rspec-support (3.11.1)
|
|
98
138
|
ruby2_keywords (0.0.5)
|
|
139
|
+
securerandom (0.3.2)
|
|
99
140
|
semver2 (3.4.2)
|
|
100
|
-
stringio (3.0
|
|
141
|
+
stringio (3.2.0)
|
|
101
142
|
thread_safe (0.3.6)
|
|
143
|
+
tsort (0.2.0)
|
|
144
|
+
tzinfo (2.0.6)
|
|
145
|
+
concurrent-ruby (~> 1.0)
|
|
102
146
|
|
|
103
147
|
PLATFORMS
|
|
104
148
|
ruby
|
|
105
149
|
|
|
106
150
|
DEPENDENCIES
|
|
151
|
+
iostruct (>= 0.7.0)
|
|
107
152
|
juwelier (~> 2.4.9)
|
|
108
153
|
rainbow (~> 3.1.1)
|
|
109
154
|
rspec (~> 3.11.0)
|
|
110
155
|
rspec-its (~> 1.3.0)
|
|
111
156
|
|
|
112
157
|
BUNDLED WITH
|
|
113
|
-
2.
|
|
158
|
+
2.4.22
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.4.
|
|
1
|
+
0.4.6
|
data/lib/zpng/bmp/reader.rb
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
|
+
# -*- coding:binary; frozen_string_literal: true -*-
|
|
2
|
+
require 'iostruct'
|
|
3
|
+
|
|
1
4
|
module ZPNG
|
|
2
5
|
module BMP
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
MAGIC = "BM"
|
|
8
|
+
|
|
9
|
+
class BITMAPFILEHEADER < IOStruct.new 'VvvV',
|
|
10
|
+
#:bfType, # read separately as magic bytes
|
|
11
|
+
:bfSize, # the size of the BMP file in bytes
|
|
7
12
|
:bfReserved1,
|
|
8
13
|
:bfReserved2,
|
|
9
|
-
:bfOffBits
|
|
14
|
+
:bfOffBits # imagedata offset
|
|
10
15
|
|
|
11
16
|
def inspect
|
|
12
17
|
"<" + super.partition(self.class.to_s.split('::').last)[1..-1].join
|
|
13
18
|
end
|
|
14
19
|
end
|
|
15
20
|
|
|
16
|
-
class BITMAPINFOHEADER <
|
|
21
|
+
class BITMAPINFOHEADER < IOStruct.new 'Vl2v2V2l2V2', # l2 for signed width/height and pels/meter
|
|
17
22
|
:biSize, # BITMAPINFOHEADER::SIZE
|
|
18
|
-
:biWidth,
|
|
19
|
-
:biHeight,
|
|
23
|
+
:biWidth, # can be negative for top-down DIB
|
|
24
|
+
:biHeight, # can be negative for top-down DIB
|
|
20
25
|
:biPlanes,
|
|
21
26
|
:biBitCount,
|
|
22
27
|
:biCompression,
|
data/lib/zpng/cli.rb
CHANGED
|
@@ -177,11 +177,11 @@ module ZPNG
|
|
|
177
177
|
|
|
178
178
|
def info
|
|
179
179
|
color = %w'COLOR_GRAYSCALE COLOR_RGB COLOR_INDEXED COLOR_GRAY_ALPHA COLOR_RGBA'.find do |k|
|
|
180
|
-
@img.hdr
|
|
180
|
+
@img.hdr&.color == ZPNG.const_get(k)
|
|
181
181
|
end
|
|
182
182
|
puts "[.] image size #{@img.width || '?'}x#{@img.height || '?'}, #{@img.bpp || '?'}bpp, #{color}"
|
|
183
183
|
puts "[.] palette = #{@img.palette}" if @img.palette
|
|
184
|
-
puts "[.] uncompressed imagedata size = #{@img.imagedata_size
|
|
184
|
+
puts "[.] uncompressed imagedata size = #{@img.imagedata_size} bytes" if @img.imagedata_size.to_i > 0
|
|
185
185
|
_conditional_hexdump(@img.imagedata, 3) if @options[:verbose] > 0
|
|
186
186
|
end
|
|
187
187
|
|
data/lib/zpng/color.rb
CHANGED
|
@@ -6,6 +6,8 @@ module ZPNG
|
|
|
6
6
|
|
|
7
7
|
include DeepCopyable
|
|
8
8
|
|
|
9
|
+
MAX_VALUES = 17.times.map{ |x| (2**x)-1 }.freeze
|
|
10
|
+
|
|
9
11
|
def initialize *a
|
|
10
12
|
h = a.last.is_a?(Hash) ? a.pop : {}
|
|
11
13
|
@r,@g,@b,@a = *a
|
|
@@ -14,11 +16,15 @@ module ZPNG
|
|
|
14
16
|
@depth = h[:depth] || 8
|
|
15
17
|
|
|
16
18
|
# default ALPHA = 0xff - opaque
|
|
17
|
-
@a ||= h[:alpha] || h[:a] ||
|
|
19
|
+
@a ||= h[:alpha] || h[:a] || max_value
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def max_value
|
|
23
|
+
MAX_VALUES[@depth]
|
|
18
24
|
end
|
|
19
25
|
|
|
20
26
|
def a= a
|
|
21
|
-
@a = a ||
|
|
27
|
+
@a = a || max_value # NULL alpha means fully opaque
|
|
22
28
|
end
|
|
23
29
|
alias :alpha :a
|
|
24
30
|
alias :alpha= :a=
|
|
@@ -64,8 +70,7 @@ module ZPNG
|
|
|
64
70
|
end
|
|
65
71
|
|
|
66
72
|
def white?
|
|
67
|
-
|
|
68
|
-
r == max && g == max && b == max
|
|
73
|
+
r == max_value && g == max_value && b == max_value
|
|
69
74
|
end
|
|
70
75
|
|
|
71
76
|
def black?
|
|
@@ -77,7 +82,7 @@ module ZPNG
|
|
|
77
82
|
end
|
|
78
83
|
|
|
79
84
|
def opaque?
|
|
80
|
-
a.nil? || a ==
|
|
85
|
+
a.nil? || a == max_value
|
|
81
86
|
end
|
|
82
87
|
|
|
83
88
|
def to_grayscale
|
|
@@ -135,7 +140,7 @@ module ZPNG
|
|
|
135
140
|
# try to convert to one pseudographics ASCII character
|
|
136
141
|
def to_ascii map=ASCII_MAP
|
|
137
142
|
#p self
|
|
138
|
-
map[self.to_grayscale*(map.size-1)/
|
|
143
|
+
map[self.to_grayscale*(map.size-1)/max_value, 1]
|
|
139
144
|
end
|
|
140
145
|
|
|
141
146
|
# convert to ANSI color name
|
|
@@ -162,7 +167,7 @@ module ZPNG
|
|
|
162
167
|
color = Color.new :depth => new_depth
|
|
163
168
|
if new_depth > self.depth
|
|
164
169
|
%w'r g b a'.each do |part|
|
|
165
|
-
color.send("#{part}=", (2**new_depth-1)/
|
|
170
|
+
color.send("#{part}=", (2**new_depth-1)/max_value*self.send(part))
|
|
166
171
|
end
|
|
167
172
|
else
|
|
168
173
|
# new_depth < self.depth
|
|
@@ -242,27 +247,96 @@ module ZPNG
|
|
|
242
247
|
end
|
|
243
248
|
|
|
244
249
|
# Op! op! op! Op!! Oppan Gangnam Style!!
|
|
245
|
-
def op op, c=nil
|
|
246
|
-
#
|
|
247
|
-
max = 2**depth-1
|
|
250
|
+
def op op, c=nil, op2=:&
|
|
251
|
+
# alpha is kept from 1st color
|
|
248
252
|
if c
|
|
249
253
|
c = c.to_depth(depth)
|
|
250
254
|
Color.new(
|
|
251
|
-
@r.send(op, c.r)
|
|
252
|
-
@g.send(op, c.g)
|
|
253
|
-
@b.send(op, c.b)
|
|
254
|
-
|
|
255
|
+
@r.send(op, c.r).send(op2, max_value),
|
|
256
|
+
@g.send(op, c.g).send(op2, max_value),
|
|
257
|
+
@b.send(op, c.b).send(op2, max_value),
|
|
258
|
+
# [0, [@r.send(op, c.r), max_value].min].max,
|
|
259
|
+
# [0, [@g.send(op, c.g), max_value].min].max,
|
|
260
|
+
# [0, [@b.send(op, c.b), max_value].min].max,
|
|
261
|
+
depth: depth,
|
|
262
|
+
alpha: alpha
|
|
255
263
|
)
|
|
256
264
|
else
|
|
257
265
|
Color.new(
|
|
258
|
-
@r.send(op)
|
|
259
|
-
@g.send(op)
|
|
260
|
-
@b.send(op)
|
|
261
|
-
|
|
266
|
+
@r.send(op).send(op2, max_value),
|
|
267
|
+
@g.send(op).send(op2, max_value),
|
|
268
|
+
@b.send(op).send(op2, max_value),
|
|
269
|
+
# [0, [@r.send(op), max_value].min].max,
|
|
270
|
+
# [0, [@g.send(op), max_value].min].max,
|
|
271
|
+
# [0, [@b.send(op), max_value].min].max,
|
|
272
|
+
depth: depth,
|
|
273
|
+
alpha: alpha
|
|
262
274
|
)
|
|
263
275
|
end
|
|
264
276
|
end
|
|
265
277
|
|
|
278
|
+
# multiplies the pixel values of the upper layer with those of the layer below it and then divides the result by MAX_VALUE
|
|
279
|
+
def * c
|
|
280
|
+
c = c.to_depth(depth)
|
|
281
|
+
Color.new(
|
|
282
|
+
(@r * c.r) / max_value,
|
|
283
|
+
(@g * c.g) / max_value,
|
|
284
|
+
(@b * c.b) / max_value,
|
|
285
|
+
depth: depth,
|
|
286
|
+
alpha: alpha
|
|
287
|
+
)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def / c
|
|
291
|
+
c = c.to_depth(depth)
|
|
292
|
+
Color.new(
|
|
293
|
+
[max_value, (max_value*@r/c.r)].min,
|
|
294
|
+
[max_value, (max_value*@g/c.g)].min,
|
|
295
|
+
[max_value, (max_value*@b/c.b)].min,
|
|
296
|
+
# (max_value*@r/c.r),
|
|
297
|
+
# (max_value*@g/c.g),
|
|
298
|
+
# (max_value*@b/c.b),
|
|
299
|
+
depth: depth,
|
|
300
|
+
alpha: alpha
|
|
301
|
+
)
|
|
302
|
+
rescue ZeroDivisionError
|
|
303
|
+
c = c.dup
|
|
304
|
+
c.r = 1 if c.r == 0 # XXX or it should be max_value ?
|
|
305
|
+
c.g = 1 if c.g == 0
|
|
306
|
+
c.b = 1 if c.b == 0
|
|
307
|
+
return Color.new(
|
|
308
|
+
[max_value, (max_value*@r/c.r)].min,
|
|
309
|
+
[max_value, (max_value*@g/c.g)].min,
|
|
310
|
+
[max_value, (max_value*@b/c.b)].min,
|
|
311
|
+
depth: depth,
|
|
312
|
+
alpha: alpha
|
|
313
|
+
)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def divmul c1, c2
|
|
317
|
+
c1 = c1.to_depth(depth)
|
|
318
|
+
c2 = c2.to_depth(depth)
|
|
319
|
+
Color.new(
|
|
320
|
+
[max_value, (c2.r*@r/c1.r)].min,
|
|
321
|
+
[max_value, (c2.g*@g/c1.g)].min,
|
|
322
|
+
[max_value, (c2.b*@b/c1.b)].min,
|
|
323
|
+
depth: depth,
|
|
324
|
+
alpha: alpha
|
|
325
|
+
)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# http://www.pegtop.net/delphi/articles/blendmodes/screen.htm
|
|
329
|
+
def screen c
|
|
330
|
+
c = c.to_depth(depth)
|
|
331
|
+
Color.new(
|
|
332
|
+
max_value - (((max_value-@r) * (max_value-c.r)) >> depth),
|
|
333
|
+
max_value - (((max_value-@g) * (max_value-c.g)) >> depth),
|
|
334
|
+
max_value - (((max_value-@b) * (max_value-c.b)) >> depth),
|
|
335
|
+
depth: depth,
|
|
336
|
+
alpha: alpha
|
|
337
|
+
)
|
|
338
|
+
end
|
|
339
|
+
|
|
266
340
|
# for Array.uniq()
|
|
267
341
|
def hash
|
|
268
342
|
self.to_i
|
data/lib/zpng/image.rb
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
# -*- coding:binary; frozen_string_literal: true -*-
|
|
1
2
|
require 'stringio'
|
|
2
3
|
|
|
3
4
|
module ZPNG
|
|
4
5
|
class Image
|
|
5
|
-
attr_accessor :chunks, :scanlines, :
|
|
6
|
+
attr_accessor :chunks, :scanlines, :extradata, :format, :verbose
|
|
6
7
|
|
|
7
8
|
# now only for (limited) BMP support
|
|
8
9
|
attr_accessor :color_class
|
|
@@ -12,15 +13,21 @@ module ZPNG
|
|
|
12
13
|
alias :dup :deep_copy
|
|
13
14
|
|
|
14
15
|
include BMP::Reader
|
|
16
|
+
include JPEG::Reader
|
|
15
17
|
|
|
16
|
-
PNG_HDR = "\x89PNG\x0d\x0a\x1a\x0a"
|
|
17
|
-
BMP_HDR = "BM".force_encoding('binary')
|
|
18
|
+
PNG_HDR = "\x89PNG\x0d\x0a\x1a\x0a"
|
|
18
19
|
|
|
19
20
|
# possible input params:
|
|
20
21
|
# IO of opened image file
|
|
21
22
|
# String with image file already readed
|
|
22
23
|
# Hash of image parameters to create new blank image
|
|
24
|
+
# width, height
|
|
23
25
|
def initialize x, h={}
|
|
26
|
+
if x.is_a?(Numeric) && h.is_a?(Numeric)
|
|
27
|
+
x = { width: x, height: h }
|
|
28
|
+
h = {}
|
|
29
|
+
end
|
|
30
|
+
|
|
24
31
|
@chunks = []
|
|
25
32
|
@extradata = []
|
|
26
33
|
@color_class = Color
|
|
@@ -74,6 +81,18 @@ module ZPNG
|
|
|
74
81
|
end
|
|
75
82
|
alias :load_file :load
|
|
76
83
|
alias :from_file :load # as in ChunkyPNG
|
|
84
|
+
|
|
85
|
+
def from_rgb data, width:, height:
|
|
86
|
+
img = new(width: width, height: height, bpp: 24)
|
|
87
|
+
img.scanlines = height.times.map{ |i| ScanLine.new(img, i, decoded_bytes: data[width*3*i, width*3]) }
|
|
88
|
+
img
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def from_rgba data, width:, height:
|
|
92
|
+
img = new(width: width, height: height, bpp: 32)
|
|
93
|
+
img.scanlines = height.times.map{ |i| ScanLine.new(img, i, decoded_bytes: data[width*4*i, width*4]) }
|
|
94
|
+
img
|
|
95
|
+
end
|
|
77
96
|
end
|
|
78
97
|
|
|
79
98
|
# save image to file
|
|
@@ -154,11 +173,13 @@ module ZPNG
|
|
|
154
173
|
# Once a stream is in binary mode, it cannot be reset to nonbinary mode.
|
|
155
174
|
io.binmode
|
|
156
175
|
|
|
157
|
-
hdr = io.read(
|
|
158
|
-
if hdr ==
|
|
176
|
+
hdr = io.read(BMP::MAGIC.size)
|
|
177
|
+
if hdr == BMP::MAGIC
|
|
159
178
|
_read_bmp io
|
|
179
|
+
elsif hdr == JPEG::MAGIC
|
|
180
|
+
_read_jpeg io
|
|
160
181
|
else
|
|
161
|
-
hdr << io.read(PNG_HDR.size -
|
|
182
|
+
hdr << io.read(PNG_HDR.size - BMP::MAGIC.size)
|
|
162
183
|
if hdr == PNG_HDR
|
|
163
184
|
_read_png io
|
|
164
185
|
else
|
|
@@ -276,7 +297,7 @@ module ZPNG
|
|
|
276
297
|
# on errors keep going and try to return maximum possible data
|
|
277
298
|
def _safe_inflate data
|
|
278
299
|
zi = Zlib::Inflate.new
|
|
279
|
-
pos = 0; r =
|
|
300
|
+
pos = 0; r = String.new
|
|
280
301
|
begin
|
|
281
302
|
# save some memory by not using String#[] when not necessary
|
|
282
303
|
r << zi.inflate(pos==0 ? data : data[pos..-1])
|
|
@@ -326,6 +347,11 @@ module ZPNG
|
|
|
326
347
|
end
|
|
327
348
|
end
|
|
328
349
|
|
|
350
|
+
def imagedata= data
|
|
351
|
+
@scanlines = nil
|
|
352
|
+
@imagedata = data
|
|
353
|
+
end
|
|
354
|
+
|
|
329
355
|
def imagedata_size
|
|
330
356
|
if new_image?
|
|
331
357
|
@scanlines.map(&:size).inject(&:+)
|
|
@@ -409,6 +435,18 @@ module ZPNG
|
|
|
409
435
|
end
|
|
410
436
|
end
|
|
411
437
|
|
|
438
|
+
def to_ansi wide: false
|
|
439
|
+
spc = wide ? " " : " "
|
|
440
|
+
r = String.new
|
|
441
|
+
height.times do |y|
|
|
442
|
+
width.times do |x|
|
|
443
|
+
r << spc.background(self[x,y].to_ansi)
|
|
444
|
+
end
|
|
445
|
+
r << "\n"
|
|
446
|
+
end
|
|
447
|
+
r
|
|
448
|
+
end
|
|
449
|
+
|
|
412
450
|
def extract_block x,y=nil,w=nil,h=nil
|
|
413
451
|
if x.is_a?(Hash)
|
|
414
452
|
Block.new(self,x[:x], x[:y], x[:width], x[:height])
|
|
@@ -487,11 +525,10 @@ module ZPNG
|
|
|
487
525
|
end
|
|
488
526
|
|
|
489
527
|
# returns new image
|
|
490
|
-
def
|
|
491
|
-
|
|
492
|
-
# deep copy first, then crop!
|
|
493
|
-
deep_copy.crop!(params)
|
|
528
|
+
def cropped **args
|
|
529
|
+
dup.crop!(**args)
|
|
494
530
|
end
|
|
531
|
+
alias crop cropped
|
|
495
532
|
|
|
496
533
|
def pixels
|
|
497
534
|
Pixels.new(self)
|
|
@@ -508,11 +545,14 @@ module ZPNG
|
|
|
508
545
|
end
|
|
509
546
|
|
|
510
547
|
def each_pixel &block
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
548
|
+
e = Enumerator.new do |ee|
|
|
549
|
+
height.times do |y|
|
|
550
|
+
width.times do |x|
|
|
551
|
+
ee.yield(self[x,y], x, y)
|
|
552
|
+
end
|
|
514
553
|
end
|
|
515
554
|
end
|
|
555
|
+
block_given? ? e.each(&block) : e
|
|
516
556
|
end
|
|
517
557
|
|
|
518
558
|
# returns new deinterlaced image if deinterlaced
|
|
@@ -544,5 +584,133 @@ module ZPNG
|
|
|
544
584
|
|
|
545
585
|
new_img
|
|
546
586
|
end
|
|
587
|
+
|
|
588
|
+
def _normalize_rotate x
|
|
589
|
+
x = x.to_i
|
|
590
|
+
x %= 360
|
|
591
|
+
x += 360 if x < 0
|
|
592
|
+
raise "invalid rotate: #{x}" if x%90 != 0
|
|
593
|
+
x
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
# always returns a copy
|
|
597
|
+
def rotated degrees
|
|
598
|
+
degrees = _normalize_rotate(degrees)
|
|
599
|
+
return dup if degrees == 0
|
|
600
|
+
|
|
601
|
+
dst = self
|
|
602
|
+
while degrees > 0
|
|
603
|
+
dst = dst.rotated_90_cw
|
|
604
|
+
degrees -= 90
|
|
605
|
+
end
|
|
606
|
+
dst
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# returns new image rotated 90 degrees clockwise
|
|
610
|
+
def rotated_90_cw
|
|
611
|
+
dst = Image.new(width: height, height: width, bpp: bpp)
|
|
612
|
+
each_pixel do |c,x,y|
|
|
613
|
+
dst[height-y-1,x] = c
|
|
614
|
+
end
|
|
615
|
+
dst
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
def copy_from(src, copy_transparent: false,
|
|
619
|
+
src_x: 0, src_y: 0, src_width: src.width, src_height: src.height,
|
|
620
|
+
dst_x: 0, dst_y: 0, dst_width: src_width, dst_height: src_height)
|
|
621
|
+
|
|
622
|
+
dst_height.times do |iy|
|
|
623
|
+
dy = dst_y + iy
|
|
624
|
+
next if dy >= height
|
|
625
|
+
sy = src_y + iy * src_height / dst_height
|
|
626
|
+
next if sy >= src.height
|
|
627
|
+
dst_width.times do |ix|
|
|
628
|
+
dx = dst_x + ix
|
|
629
|
+
next if dx >= width
|
|
630
|
+
sx = src_x + ix * src_width / dst_width
|
|
631
|
+
next if sx >= src.width
|
|
632
|
+
c = src[sx,sy]
|
|
633
|
+
next if c.transparent? && !copy_transparent
|
|
634
|
+
self[dx,dy] = c
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
self
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
# op is a symbol of operation, like :+, :-, :* ...
|
|
641
|
+
def op_from(src, op,
|
|
642
|
+
src_x: 0, src_y: 0, src_width: src.width, src_height: src.height,
|
|
643
|
+
dst_x: 0, dst_y: 0, dst_width: src_width, dst_height: src_height)
|
|
644
|
+
|
|
645
|
+
dst_height.times do |iy|
|
|
646
|
+
dy = dst_y + iy
|
|
647
|
+
next if dy >= height
|
|
648
|
+
sy = src_y + iy * src_height / dst_height
|
|
649
|
+
next if sy >= src.height
|
|
650
|
+
dst_width.times do |ix|
|
|
651
|
+
dx = dst_x + ix
|
|
652
|
+
next if dx >= width
|
|
653
|
+
sx = src_x + ix * src_width / dst_width
|
|
654
|
+
next if sx >= src.width
|
|
655
|
+
c = src[sx,sy]
|
|
656
|
+
self[dx,dy] = self[dx,dy].send(op, c)
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
self
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
def scale(x, y=x)
|
|
663
|
+
dst = Image.new(width: width*x, height: height*y, bpp: bpp)
|
|
664
|
+
dst.copy_from(self, dst_width: dst.width, dst_height: dst.height)
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
alias scaled scale
|
|
668
|
+
|
|
669
|
+
def shear(mx, my)
|
|
670
|
+
src = self
|
|
671
|
+
dst = Image.new(width: width+(mx*height).abs, height: height+(my*width).abs, bpp: bpp)
|
|
672
|
+
xadd = mx < 0 ? src.height: 0
|
|
673
|
+
yadd = my < 0 ? src.width : 0
|
|
674
|
+
each_pixel do |c,x,y|
|
|
675
|
+
dst[xadd + (x+mx*y).to_i, yadd + (y+my*x).to_i] = c
|
|
676
|
+
end
|
|
677
|
+
dst
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
def empty?
|
|
681
|
+
pixels.all?(&:transparent?)
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
def * value; op(:*, value); end
|
|
685
|
+
def / value; op(:/, value); end
|
|
686
|
+
def + value; op(:+, value); end
|
|
687
|
+
def - value; op(:-, value); end
|
|
688
|
+
|
|
689
|
+
def op op, value
|
|
690
|
+
case value
|
|
691
|
+
when Image
|
|
692
|
+
dst = Image.new(width: width, height: height, bpp: bpp)
|
|
693
|
+
each_pixel do |c,x,y|
|
|
694
|
+
dst[x,y] = c.send(op, value[x,y])
|
|
695
|
+
end
|
|
696
|
+
dst
|
|
697
|
+
when Color
|
|
698
|
+
dst = Image.new(width: width, height: height, bpp: bpp)
|
|
699
|
+
each_pixel do |c,x,y|
|
|
700
|
+
dst[x,y] = c.send(op, value)
|
|
701
|
+
end
|
|
702
|
+
dst
|
|
703
|
+
else
|
|
704
|
+
raise ArgumentError, "cannot #{op} Image by #{value}"
|
|
705
|
+
end
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
def divmul c1, c2
|
|
709
|
+
dst = Image.new(width: width, height: height, bpp: bpp)
|
|
710
|
+
each_pixel do |c,x,y|
|
|
711
|
+
dst[x,y] = c.divmul(c1, c2)
|
|
712
|
+
end
|
|
713
|
+
dst
|
|
714
|
+
end
|
|
547
715
|
end
|
|
548
716
|
end
|