processing 0.5.30 → 0.5.32

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.
@@ -10,6 +10,7 @@ module Processing
10
10
  # @private
11
11
  def initialize(image)
12
12
  @image = image
13
+ @pixels, @error = nil, false
13
14
  end
14
15
 
15
16
  # Gets width of image.
@@ -17,7 +18,7 @@ module Processing
17
18
  # @return [Numeric] width of image
18
19
  #
19
20
  def width()
20
- @image.width
21
+ @image&.width || (@error ? -1 : 0)
21
22
  end
22
23
 
23
24
  # Gets height of image.
@@ -25,7 +26,7 @@ module Processing
25
26
  # @return [Numeric] height of image
26
27
  #
27
28
  def height()
28
- @image.height
29
+ @image&.height || (@error ? -1 : 0)
29
30
  end
30
31
 
31
32
  alias w width
@@ -36,7 +37,7 @@ module Processing
36
37
  # @return [Array<Numeric>] [width, height]
37
38
  #
38
39
  def size()
39
- @image.size
40
+ [width, height]
40
41
  end
41
42
 
42
43
  # Sets the color of the pixel.
@@ -48,21 +49,43 @@ module Processing
48
49
  # @return [nil] nil
49
50
  #
50
51
  def set(x, y, c)
51
- @image.bitmap[x, y] = self.class.fromColor__ c
52
+ getInternal__.bitmap[x, y] = self.class.fromColor__ c
52
53
  nil
53
54
  end
54
55
 
55
-
56
56
  # Returns the color of the pixel.
57
57
  #
58
58
  # @return [Integer] color value (0xAARRGGBB)
59
59
  #
60
60
  def get(x, y)
61
- @image.bitmap[x, y]
61
+ getInternal__.bitmap[x, y]
62
62
  .map {|n| (n * 255).to_i.clamp 0, 255}
63
63
  .then {|r, g, b, a| self.class.toColor__ r, g, b, a}
64
64
  end
65
65
 
66
+ # Loads all pixels to the 'pixels' array.
67
+ #
68
+ # @return [nil] nil
69
+ #
70
+ def loadPixels()
71
+ @pixels = getInternal__.pixels
72
+ end
73
+
74
+ # Update the image pixels with the 'pixels' array.
75
+ #
76
+ # @return [nil] nil
77
+ #
78
+ def updatePixels()
79
+ return unless @pixels
80
+ getInternal__.pixels = @pixels
81
+ @pixels = nil
82
+ end
83
+
84
+ # An array of all pixels.
85
+ # Call loadPixels() before accessing the array.
86
+ #
87
+ attr_reader :pixels
88
+
66
89
  # Applies an image filter.
67
90
  #
68
91
  # overload filter(shader)
@@ -86,7 +109,7 @@ module Processing
86
109
  #
87
110
  def resize(width, height)
88
111
  @image = Rays::Image.new(width, height).paint do |painter|
89
- painter.image @image, 0, 0, width, height
112
+ painter.image getInternal__, 0, 0, width, height
90
113
  end
91
114
  nil
92
115
  end
@@ -132,7 +155,7 @@ module Processing
132
155
  #
133
156
  def blend(img = nil, sx, sy, sw, sh, dx, dy, dw, dh, mode)
134
157
  img ||= self
135
- @image.paint do |painter|
158
+ getInternal__.paint do |painter|
136
159
  img.drawImage__ painter, sx, sy, sw, sh, dx, dy, dw, dh, blend_mode: mode
137
160
  end
138
161
  nil
@@ -145,13 +168,18 @@ module Processing
145
168
  # @return [nil] nil
146
169
  #
147
170
  def save(filename)
148
- @image.save filename
171
+ getInternal__.save filename
149
172
  nil
150
173
  end
151
174
 
152
175
  # @private
153
176
  def getInternal__()
154
- @image
177
+ @image or raise 'Invalid image object'
178
+ end
179
+
180
+ # @private
181
+ def setInternal__(image, error = false)
182
+ @image, @error = image, error
155
183
  end
156
184
 
157
185
  # @private
@@ -0,0 +1,294 @@
1
+ module Processing
2
+
3
+
4
+ # Shape object.
5
+ #
6
+ class Shape
7
+
8
+ # @private
9
+ def initialize(polygon = nil, children = nil, context: nil)
10
+ @polygon, @children = polygon, children
11
+ @context = context || Context.context__
12
+ @visible, @fill, @matrix = true, nil, nil
13
+ @type = @points = @curvePoints = @colors = @texcoords = @close = nil
14
+ @contours = @contourPoints = @contourColors = @contourTexCoords = nil
15
+ end
16
+
17
+ # Gets width of shape.
18
+ #
19
+ # @return [Numeric] width of shape
20
+ #
21
+ def width()
22
+ polygon = getInternal__ or return 0
23
+ (@bounds ||= polygon.bounds).width
24
+ end
25
+
26
+ # Gets height of shape.
27
+ #
28
+ # @return [Numeric] height of shape
29
+ #
30
+ def height()
31
+ polygon = getInternal__ or return 0
32
+ (@bounds ||= polygon.bounds).height
33
+ end
34
+
35
+ alias w width
36
+ alias h height
37
+
38
+ # Returns whether the shape is visible or not.
39
+ #
40
+ # @return [Boolean] visible or not
41
+ #
42
+ def isVisible()
43
+ @visible
44
+ end
45
+
46
+ alias visible? isVisible
47
+
48
+ # Sets whether to display the shape or not.
49
+ #
50
+ # @return [nil] nil
51
+ #
52
+ def setVisible(visible)
53
+ @visible = !!visible
54
+ nil
55
+ end
56
+
57
+ def beginShape(type = nil)
58
+ raise "beginShape() cannot be called twice" if drawingShape__
59
+ @type = type
60
+ @points ||= []
61
+ @curvePoints = []
62
+ @colors ||= []
63
+ @texcoords ||= []
64
+ @close = nil
65
+ @contours ||= []
66
+ clearCache__
67
+ nil
68
+ end
69
+
70
+ def endShape(close = nil)
71
+ raise "endShape() must be called after beginShape()" unless drawingShape__
72
+ @close = close == GraphicsContext::CLOSE || @contours.size > 0
73
+ if @close && @curvePoints.size >= 8
74
+ x, y = @curvePoints[0, 2]
75
+ 2.times {curveVertex x, y}
76
+ end
77
+ @curvePoints = nil
78
+ nil
79
+ end
80
+
81
+ def beginContour()
82
+ raise "beginContour() must be called after beginShape()" unless drawingShape__
83
+ @contourPoints, @contourColors, @contourTexCoords = [], [], []
84
+ nil
85
+ end
86
+
87
+ def endContour()
88
+ raise "endContour() must be called after beginContour()" unless drawingContour__
89
+ @contours << Rays::Polyline.new(
90
+ *@contourPoints, colors: @contourColors, texcoords: @contourTexCoords,
91
+ loop: true, hole: true)
92
+ @contoursPoints = @contoursColors = @contoursTexCoords = nil
93
+ nil
94
+ end
95
+
96
+ def vertex(x, y, u = nil, v = nil)
97
+ raise "vertex() must be called after beginShape()" unless drawingShape__
98
+ raise "Either 'u' or 'v' is missing" if (u == nil) != (v == nil)
99
+ u ||= x
100
+ v ||= y
101
+ color = @fill || @context.getFill__
102
+ if drawingContour__
103
+ @contourPoints << x << y
104
+ @contourColors << color
105
+ @contourTexCoords << u << v
106
+ else
107
+ @points << x << y
108
+ @colors << color
109
+ @texcoords << u << v
110
+ end
111
+ end
112
+
113
+ def curveVertex(x, y)
114
+ raise "curveVertex() must be called after beginShape()" unless drawingShape__
115
+ @curvePoints << x << y
116
+ if @curvePoints.size >= 8
117
+ Rays::Polygon.curve(*@curvePoints[-8, 8])
118
+ .first.to_a.tap {|a| a.shift if @curvePoints.size > 8}
119
+ .each {|p| vertex p.x, p.y}
120
+ end
121
+ nil
122
+ end
123
+
124
+ def bezierVertex(x2, y2, x3, y3, x4, y4)
125
+ raise "bezierVertex() must be called after beginShape()" unless drawingShape__
126
+ x1, y1 = @points[-2, 2]
127
+ raise "vertex() is required before calling bezierVertex()" unless x1 && y1
128
+ Rays::Polygon.bezier(x1, y1, x2, y2, x3, y3, x4, y4)
129
+ .first.to_a.tap {|a| a.shift}
130
+ .each {|p| vertex p.x, p.y}
131
+ nil
132
+ end
133
+
134
+ def quadraticVertex(cx, cy, x3, y3)
135
+ x1, y1 = @points[-2, 2]
136
+ raise "vertex() is required before calling quadraticVertex()" unless x1 && y1
137
+ bezierVertex(
138
+ x1 + (cx - x1) * 2.0 / 3.0, y1 + (cy - y1) * 2.0 / 3.0,
139
+ x3 + (cx - x3) * 2.0 / 3.0, y3 + (cy - y3) * 2.0 / 3.0,
140
+ x3, y3)
141
+ nil
142
+ end
143
+
144
+ # @private
145
+ def drawingShape__()
146
+ @curvePoints
147
+ end
148
+
149
+ # @private
150
+ def drawingContour__()
151
+ @contourPoints
152
+ end
153
+
154
+ def fill(*args)
155
+ @fill = @context.toRaysColor__(*args)
156
+ end
157
+
158
+ def setVertex(index, point)
159
+ return nil unless @points && @points[index * 2, 2]&.size == 2
160
+ @points[index * 2, 2] = [point.x, point.y]
161
+ end
162
+
163
+ def getVertex(index)
164
+ return nil unless @points
165
+ point = @points[index * 2, 2]
166
+ return nil unless point&.size == 2
167
+ @context.createVector(*point)
168
+ end
169
+
170
+ def getVertexCount()
171
+ return 0 unless @points
172
+ @points.size / 2
173
+ end
174
+
175
+ def setFill(*args)
176
+ color = @context.toRaysColor__(*args)
177
+ count = getVertexCount
178
+ if count > 0
179
+ if @colors
180
+ @colors.fill color
181
+ else
182
+ @colors = [color] * count
183
+ end
184
+ clearCache__
185
+ elsif @polygon
186
+ @polygon = @polygon.transform do |polylines|
187
+ polylines.map {|pl| pl.with colors: pl.points.map {color}}
188
+ end
189
+ end
190
+ end
191
+
192
+ def addChild(child)
193
+ return unless @children
194
+ @children.push child
195
+ nil
196
+ end
197
+
198
+ def getChild(index)
199
+ @children&.[](index)
200
+ end
201
+
202
+ def getChildCount()
203
+ @children&.size || 0
204
+ end
205
+
206
+ def translate(x, y, z = 0)
207
+ matrix__.translate x, y, z
208
+ nil
209
+ end
210
+
211
+ def rotate(angle)
212
+ matrix__.rotate @context.toDegrees__(angle)
213
+ nil
214
+ end
215
+
216
+ def scale(x, y, z = 1)
217
+ matrix__.scale x, y, z
218
+ nil
219
+ end
220
+
221
+ def resetMatrix()
222
+ @matrix = nil
223
+ end
224
+
225
+ def rotateX = nil
226
+ def rotateY = nil
227
+ def rotateZ = nil
228
+
229
+ # @private
230
+ def clearCache__()
231
+ @polygon = nil# clear cache
232
+ end
233
+
234
+ # @private
235
+ def matrix__()
236
+ @matrix ||= Rays::Matrix.new
237
+ end
238
+
239
+ # @private
240
+ def getInternal__()
241
+ unless @polygon
242
+ return nil unless @points && @close != nil
243
+ @polygon = self.class.createPolygon__(
244
+ @type, @points, @close, @colors, @texcoords)
245
+ @polygon += @contours if @polygon
246
+ end
247
+ @polygon
248
+ end
249
+
250
+ # @private
251
+ def draw__(painter, x, y, w = nil, h = nil)
252
+ poly = getInternal__
253
+
254
+ backup = nil
255
+ if @matrix && (poly || @children)
256
+ backup = painter.matrix
257
+ painter.matrix = backup * @matrix
258
+ end
259
+
260
+ if poly
261
+ if w || h
262
+ painter.polygon poly, x, y, w,h
263
+ else
264
+ painter.polygon poly, x, y
265
+ end
266
+ end
267
+ @children&.each {|o| o.draw__ painter, x, y, w, h}
268
+
269
+ painter.matrix = backup if backup
270
+ end
271
+
272
+ # @private
273
+ def self.createPolygon__(
274
+ type, points, close = false, colors = nil, texcoords = nil)
275
+
276
+ kwargs = {colors: colors, texcoords: texcoords}
277
+ g, p = GraphicsContext, Rays::Polygon
278
+ case type
279
+ when g::POINTS then p.points( *points)
280
+ when g::LINES then p.lines( *points)
281
+ when g::TRIANGLES then p.triangles( *points, **kwargs)
282
+ when g::TRIANGLE_FAN then p.triangle_fan( *points, **kwargs)
283
+ when g::TRIANGLE_STRIP then p.triangle_strip(*points, **kwargs)
284
+ when g::QUADS then p.quads( *points, **kwargs)
285
+ when g::QUAD_STRIP then p.quad_strip( *points, **kwargs)
286
+ when g::TESS, nil then p.new( *points, **kwargs, loop: close)
287
+ else raise ArgumentError, "invalid polygon type '#{type}'"
288
+ end
289
+ end
290
+
291
+ end# Shape
292
+
293
+
294
+ end# Processing
@@ -463,9 +463,9 @@ module Processing
463
463
  # @return [Vector] rotated this object
464
464
  #
465
465
  def rotate(angle)
466
- angle = @context ?
467
- @context.toAngle__(angle) : angle * GraphicsContext::RAD2DEG__
468
- @point.rotate! angle
466
+ deg = @context ?
467
+ @context.toDegrees__(angle) : angle * GraphicsContext::RAD2DEG__
468
+ @point.rotate! deg
469
469
  self
470
470
  end
471
471
 
data/processing.gemspec CHANGED
@@ -25,10 +25,10 @@ Gem::Specification.new do |s|
25
25
  s.platform = Gem::Platform::RUBY
26
26
  s.required_ruby_version = '>= 3.0.0'
27
27
 
28
- s.add_runtime_dependency 'xot', '~> 0.1.40'
29
- s.add_runtime_dependency 'rucy', '~> 0.1.41'
30
- s.add_runtime_dependency 'rays', '~> 0.1.46'
31
- s.add_runtime_dependency 'reflexion', '~> 0.1.54'
28
+ s.add_runtime_dependency 'xot', '~> 0.1.41'
29
+ s.add_runtime_dependency 'rucy', '~> 0.1.43'
30
+ s.add_runtime_dependency 'rays', '~> 0.1.48'
31
+ s.add_runtime_dependency 'reflexion', '~> 0.1.56'
32
32
 
33
33
  s.files = `git ls-files`.split $/
34
34
  s.test_files = s.files.grep %r{^(test|spec|features)/}
data/test/helper.rb CHANGED
@@ -5,28 +5,61 @@
5
5
  require 'xot/test'
6
6
  require 'processing/all'
7
7
 
8
+ require 'digest/md5'
9
+ require 'fileutils'
8
10
  require 'tempfile'
9
11
  require 'test/unit'
10
12
 
11
13
  include Xot::Test
12
14
 
13
15
 
14
- def temppath(ext: nil, &block)
16
+ DEFAULT_DRAW_HEADER = <<~END
17
+ background 100
18
+ fill 255, 0, 0
19
+ stroke 0, 255, 0
20
+ strokeWeight 50
21
+ END
22
+
23
+
24
+ def test_with_p5?()
25
+ ENV['TEST_WITH_P5'] == '1'
26
+ end
27
+
28
+ def md5(s)
29
+ Digest::MD5.hexdigest s
30
+ end
31
+
32
+ def mkdir(dir: nil, filename: nil)
33
+ path = dir || File.dirname(filename)
34
+ FileUtils.mkdir_p path unless File.exist? path
35
+ end
36
+
37
+ def test_label(index = 1)
38
+ caller_locations[index].then {|loc| "#{loc.label}_#{loc.lineno}"}
39
+ end
40
+
41
+ def temp_path(ext: nil, &block)
15
42
  f = Tempfile.new
16
43
  path = f.path
17
- path += ".#{ext}" if ext
44
+ path += ext if ext
18
45
  f.close!
19
46
  block.call path
20
47
  File.delete path
21
48
  end
22
49
 
50
+ def draw_output_path(label, *sources, ext: '.png', dir: ext)
51
+ src = sources.compact.then {|ary| ary.empty? ? '' : "_#{md5 ary.join("\n")}"}
52
+ path = File.join __dir__, dir, label + src + ext
53
+ mkdir filename: path
54
+ path
55
+ end
56
+
23
57
  def get_pixels(image)
24
58
  %i[@image @image__]
25
59
  .map {image.instance_variable_get _1}
26
60
  .compact
27
61
  .first
28
- .bitmap
29
- .to_a
62
+ .pixels
30
63
  end
31
64
 
32
65
  def graphics(width = 10, height = 10, *args, &block)
@@ -35,6 +68,14 @@ def graphics(width = 10, height = 10, *args, &block)
35
68
  end
36
69
  end
37
70
 
71
+ def test_draw(*sources, width: 1000, height: 1000, pixelDensity: 1, label: nil)
72
+ graphics(width, height, pixelDensity).tap do |g|
73
+ g.beginDraw {g.instance_eval sources.compact.join("\n")}
74
+ g.save draw_output_path(label, *sources) if label
75
+ end
76
+ end
77
+
78
+
38
79
  def assert_equal_vector(v1, v2, delta = 0.000001)
39
80
  assert_in_delta v1.x, v2.x, delta
40
81
  assert_in_delta v1.y, v2.y, delta
@@ -52,3 +93,44 @@ def assert_equal_pixels(expected, actual, threshold: 1.0)
52
93
  The rate of the same pixel #{equal_rate} is below the threshold #{threshold}
53
94
  EOS
54
95
  end
96
+
97
+ def assert_equal_draw(
98
+ *shared_header, expected, actual, default_header: DEFAULT_DRAW_HEADER,
99
+ width: 1000, height: 1000, threshold: 1.0, label: test_label)
100
+
101
+ e = test_draw default_header, *shared_header, expected, label: "#{label}_expected"
102
+ a = test_draw default_header, *shared_header, actual, label: "#{label}_actual"
103
+
104
+ assert_equal_pixels e, a, threshold: threshold
105
+ end
106
+
107
+ def assert_p5_draw(
108
+ *sources, default_header: DEFAULT_DRAW_HEADER,
109
+ width: 1000, height: 1000, threshold: 0.99, label: test_label, **kwargs)
110
+
111
+ return unless test_with_p5?
112
+
113
+ source = [default_header, *sources].compact.join("\n")
114
+ path = draw_output_path "#{label}_expected", source
115
+
116
+ pd = draw_p5rb width, height, source, path, **kwargs
117
+ actual = test_draw source, width: width, height: height, pixelDensity: pd
118
+ actual.save path.sub('_expected', '_actual')
119
+
120
+ assert_equal_pixels actual.loadImage(path), actual, threshold: threshold
121
+ end
122
+
123
+ def assert_p5_fill(*sources, **kwargs)
124
+ assert_p5_draw 'noStroke', *sources, label: test_label, **kwargs
125
+ end
126
+
127
+ def assert_p5_stroke(*sources, **kwargs)
128
+ assert_p5_draw 'noFill; stroke 0, 255, 0', *sources, label: test_label, **kwargs
129
+ end
130
+
131
+ def assert_p5_fill_stroke(*sources, **kwargs)
132
+ assert_p5_draw 'stroke 0, 255, 0', *sources, label: test_label, **kwargs
133
+ end
134
+
135
+
136
+ require_relative 'p5' if test_with_p5?
@@ -13,7 +13,7 @@ def browser(width, height, headless: true)
13
13
  hash[key] ||= Ferrum::Browser.new headless: headless, window_size: [width, height]
14
14
  end
15
15
 
16
- def get_p5rb_html(width, height, draw_src)
16
+ def get_p5rb_html(width, height, draw_src, webgl: false)
17
17
  <<~END
18
18
  <html>
19
19
  <head>
@@ -34,12 +34,10 @@ def get_p5rb_html(width, height, draw_src)
34
34
  </script>
35
35
  <script type="text/ruby">
36
36
  def setup()
37
- createCanvas #{width}, #{height}
37
+ createCanvas #{width}, #{height}#{webgl ? ', WEBGL' : ''}
38
38
  end
39
39
  def draw()
40
- background 0
41
- fill 255, 0, 0
42
- noStroke
40
+ #{webgl ? 'translate -width / 2, -height / 2' : ''}
43
41
  #{draw_src}
44
42
  JS.global.completed
45
43
  end
@@ -51,12 +49,28 @@ def get_p5rb_html(width, height, draw_src)
51
49
  END
52
50
  end
53
51
 
54
- def draw_p5rb(width, height, draw_src, path, headless: true)
52
+ def sleep_until (try: 3, timeout: 10, &block)
53
+ now = -> offset = 0 {Time.now.to_f + offset}
54
+ limit = now[timeout]
55
+ until block.call
56
+ if now[] > limit
57
+ limit = now[timeout]
58
+ try -= 1
59
+ next if try > 0
60
+ raise 'Drawing timed out in p5.rb'
61
+ end
62
+ sleep 0.1
63
+ end
64
+ end
65
+
66
+ def draw_p5rb(width, height, draw_src, path, headless: true, webgl: false)
55
67
  b = browser width, height, headless: headless
56
68
  unless File.exist? path
57
69
  b.reset
58
- b.main_frame.content = get_p5rb_html width, height, draw_src
59
- sleep 0.1 until b.evaluate 'document.querySelector("#completed") != null'
70
+ b.main_frame.content = get_p5rb_html width, height, draw_src, webgl: webgl
71
+ sleep_until do
72
+ b.evaluate 'document.querySelector("#completed") != null'
73
+ end
60
74
  b.screenshot path: path
61
75
  end
62
76
  b.device_pixel_ratio
data/test/test_font.rb CHANGED
@@ -5,12 +5,43 @@ class TestFont < Test::Unit::TestCase
5
5
 
6
6
  P = Processing
7
7
 
8
- def font()
9
- P::Font.new Rays::Font.new(nil, 10)
8
+ def font(*args)
9
+ P::Font.new(Rays::Font.new *args).tap {|font|
10
+ def font.intern()
11
+ getInternal__
12
+ end
13
+ }
14
+ end
15
+
16
+ def test_initialize()
17
+ assert_not_nil font(nil) .intern.name
18
+ assert_equal 'Arial', font('Arial').intern.name
19
+
20
+ assert_equal 12, font .intern.size
21
+ assert_equal 10, font(nil, 10).intern.size
22
+ end
23
+
24
+ def test_size()
25
+ f = font nil, 10
26
+ id = f.intern.object_id
27
+
28
+ assert_equal 10, f.intern.size
29
+
30
+ f.setSize__ 11
31
+ assert_equal 11, f.intern.size
32
+ assert_not_equal id, f.intern.object_id
33
+
34
+ f.setSize__ 10
35
+ assert_equal 10, f.intern.size
36
+ assert_equal id, f.intern.object_id
10
37
  end
11
38
 
12
39
  def test_inspect()
13
40
  assert_match %r|#<Processing::Font: name:'[\w\s]+' size:[\d\.]+>|, font.inspect
14
41
  end
15
42
 
43
+ def test_list()
44
+ assert_not P::Font.list.empty?
45
+ end
46
+
16
47
  end# TestFont
@@ -3,12 +3,6 @@ require_relative 'helper'
3
3
 
4
4
  class TestGraphics < Test::Unit::TestCase
5
5
 
6
- P = Processing
7
-
8
- def graphics(w = 10, h = 10)
9
- P::Graphics.new w, h
10
- end
11
-
12
6
  def test_beginDraw()
13
7
  g = graphics
14
8
  g.beginDraw
@@ -24,7 +18,7 @@ class TestGraphics < Test::Unit::TestCase
24
18
  g.ellipseMode :corner
25
19
  g.ellipse 0, 0, g.width, g.height
26
20
  end
27
- temppath(ext: 'png') do |path|
21
+ temp_path(ext: '.png') do |path|
28
22
  assert_nothing_raised {g.save path}
29
23
  assert_equal_pixels g, g.loadImage(path)
30
24
  end