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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b35d6d1f87f6a32f1de261d07be9a5670cad8892fdf8033a46c6189ac60c6c5
4
- data.tar.gz: 584811c71db552f0b07f701f324b3e359d101c7650c975280f6c529cc8ecb246
3
+ metadata.gz: 45cbe6009082c09bd91263c35aafcde94fe9a244f30999a8181a42cde9d8c591
4
+ data.tar.gz: bfb0767ed14b07544ca7159454b9dbe5ccdd404f98f7e293dddb52b13abf43aa
5
5
  SHA512:
6
- metadata.gz: e22d560929ca762532c078ac6ab7732788032ec58e86d20156c35169f0ebf44c498b69069adcb563b70ab8f770aa20116c5c5da4e9bd2847a716c504bc164c5f
7
- data.tar.gz: b34d79aa9ca9b1c4bfa8770ca59c463eff6155687e27891f143b19fc142e07c914e1d7412f51446bacb6ba637b3879c9e662c06cb0003b51fff4d102b2ec5d8c
6
+ metadata.gz: f3929af614a96f549e331a65c15f210bc96bd52c5d88625d85156eb507773b89fc010d968ceddd25de213124db0dc935821dc02f255555eff4780ff20b87eb91
7
+ data.tar.gz: 1d5ab750a6f0aa24fe710ee53b53240d0b3a4335e3484c6c24eb99419413acc3701af9bd7aa5c3928b6971344e0759ee1843e5dbeed09004d06906ef5f9d0a4c
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ source "http://rubygems.org"
3
3
  # Example:
4
4
  # gem "activesupport", ">= 2.3.5"
5
5
  gem 'rainbow', '~> 3.1.1'
6
+ gem "iostruct", ">= 0.7.0"
6
7
 
7
8
  # Add dependencies to develop your gem here.
8
9
  # Include everything needed to run rake, tests, features, etc.
data/Gemfile.lock CHANGED
@@ -1,13 +1,36 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- addressable (2.8.0)
5
- public_suffix (>= 2.0.2, < 5.0)
6
- builder (3.2.4)
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.5.0)
10
- faraday (1.10.0)
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.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.4)
27
- multipart-post (~> 2)
28
- faraday-net_http (1.0.1)
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 (1.13.1)
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 (2.0.3)
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.4.1)
86
+ jwt (2.10.2)
87
+ base64
57
88
  kamelcase (0.0.2)
58
89
  semver2 (~> 3)
59
- mini_portile2 (2.8.1)
60
- multi_json (1.15.0)
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.2.3)
63
- nokogiri (1.14.2)
64
- mini_portile2 (~> 2.8.0)
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
- psych (4.0.4)
106
+ process_executer (1.1.2)
107
+ psych (5.3.1)
108
+ date
73
109
  stringio
74
- public_suffix (4.0.7)
75
- racc (1.6.2)
76
- rack (3.0.4.1)
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.0.6)
79
- rchardet (1.8.0)
80
- rdoc (6.4.0)
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.0)
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.0)
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.1)
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.0)
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.2)
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.3.12
158
+ 2.4.22
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.4
1
+ 0.4.6
@@ -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
- class BITMAPFILEHEADER < ReadableStruct.new 'VvvV', #a2VvvV',
5
- #:bfType,
6
- :bfSize, # the size of the BMP file in bytes
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 # imagedata offset
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 < ReadableStruct.new 'V3v2V6',
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.color == ZPNG.const_get(k)
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 || '?'} bytes"
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] || (2**@depth-1)
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 || (2**@depth-1) # NULL alpha means fully opaque
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
- max = 2**depth-1
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 == 2**depth-1
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)/(2**@depth-1), 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)/(2**depth-1)*self.send(part))
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
- # XXX what to do with alpha?
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) & max,
252
- @g.send(op, c.g) & max,
253
- @b.send(op, c.b) & max,
254
- :depth => self.depth
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) & max,
259
- @g.send(op) & max,
260
- @b.send(op) & max,
261
- :depth => self.depth
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, :imagedata, :extradata, :format, :verbose
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".force_encoding('binary')
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(BMP_HDR.size)
158
- if hdr == BMP_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 - BMP_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 crop params
491
- decode_all_scanlines
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
- height.times do |y|
512
- width.times do |x|
513
- yield(self[x,y], x, y)
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