processing 1.4.0 → 1.5.0

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: fa64127f24d18aa12a47207a90d248ef826b9bfc13e4ad26db9234dc3c53c9bd
4
- data.tar.gz: 2304f4c5aa57e846fdc74ad7871864368a9f461d32e4ee51db6c946ca23f6a95
3
+ metadata.gz: 76374671234065ea70aed78db67df170da655638e1e10dc3cd392d12a8f8c9bf
4
+ data.tar.gz: c8387066f1cfa2f276c557cd882639663bc17b947aef89e4b68b701b276ca1f2
5
5
  SHA512:
6
- metadata.gz: 98ef96fb9029a6e4075e496edbb2e64352f5cc25f2b8c8c50e466b93a70a52fa29618881b394e5d7ec5f94a8d639061d7f3e4ced8ee8a7653d5cf0adb5ca70fb
7
- data.tar.gz: c85f64570deb0378bac44dcf81a190d5ec4467636ed78794d5e3c057d37e219e3e81e7ffe57261b0b5c5cd9bda368518d660dcd3e4509e427418eddf26ae655e
6
+ metadata.gz: f4c1b08e0f857eed89b7c07c004445270ec6cb24da946571d8c088dfd38ee5d68baab706a4d4d67c4374a8d7b60a5cbd3aa8de919a286714b3ab25a0500fbd34
7
+ data.tar.gz: d98a30c04ba898afc44852fc08f0d4b2b74e3f894df3a4f31c38d21442cc4ace3e74dd3eafff90a61f301fd1eca7adc4793622d533d46a1966b5671cd3a78956
@@ -9,52 +9,5 @@ permissions:
9
9
 
10
10
  jobs:
11
11
  release:
12
- runs-on: macos-latest
13
-
14
- steps:
15
- - name: ruby 3.2
16
- uses: ruby/setup-ruby@v1
17
- with:
18
- ruby-version: 3.2
19
-
20
- - name: checkout
21
- uses: actions/checkout@v4
22
-
23
- - name: setup gems
24
- run: bundle install
25
-
26
- - name: setup dependencies
27
- run: "ruby -I.github/workflows -rutils -e 'setup_dependencies'"
28
-
29
- - name: packages
30
- run: bundle exec rake packages
31
-
32
- - name: setup zig
33
- if: github.event.repository.name == 'reflex-terminal'
34
- uses: mlugg/setup-zig@v2
35
- with:
36
- version: 0.16.0
37
-
38
- - name: test
39
- run: bundle exec rake test
40
-
41
- - name: create gem
42
- id: gem
43
- run: |
44
- bundle exec rake gem
45
- echo path=$(ruby -e 'print Dir.glob("*.gem").first') >> $GITHUB_OUTPUT
46
-
47
- - name: create github release
48
- env:
49
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
50
- run: ruby -I.github/workflows -rutils -e 'release(*ARGV)' ./${{ steps.gem.outputs.path }}
51
-
52
- - name: upload to rubygems
53
- env:
54
- GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_AUTH_TOKEN }}
55
- run: |
56
- mkdir -p $HOME/.gem
57
- touch $HOME/.gem/credentials
58
- chmod 0600 $HOME/.gem/credentials
59
- printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
60
- bundle exec rake upload
12
+ uses: xord/xot/.github/workflows/release-gem.yml@master
13
+ secrets: inherit
@@ -1,35 +1,12 @@
1
1
  name: Tag
2
2
 
3
3
  on:
4
- push:
4
+ workflow_run:
5
+ workflows: [Test]
6
+ types: [completed]
5
7
  branches: [master]
6
8
 
7
9
  jobs:
8
10
  tag:
9
- runs-on: ubuntu-latest
10
-
11
- steps:
12
- - name: ruby 3.2
13
- uses: ruby/setup-ruby@v1
14
- with:
15
- ruby-version: 3.2
16
-
17
- - name: checkout
18
- uses: actions/checkout@v4
19
- with:
20
- fetch-depth: 0
21
- token: ${{ secrets.PAT }}
22
-
23
- - name: setup dependencies
24
- run: "ruby -I.github/workflows -rutils -e 'setup_dependencies only: :xot'"
25
-
26
- - name: setup user name and email
27
- run: |
28
- git config --global user.email "xordog@gmail.com"
29
- git config --global user.name "xord"
30
-
31
- - name: tag versions
32
- run: "ruby -I.github/workflows -rutils -e 'tag_versions'"
33
-
34
- - name: push tags
35
- run: git push origin --tags
11
+ uses: xord/xot/.github/workflows/tag.yml@master
12
+ secrets: inherit
@@ -10,19 +10,24 @@ jobs:
10
10
  runs-on: macos-latest
11
11
 
12
12
  steps:
13
- - name: ruby 3.2
14
- uses: ruby/setup-ruby@v1
15
- with:
16
- ruby-version: 3.2
17
-
18
13
  - name: checkout
19
14
  uses: actions/checkout@v4
20
15
 
16
+ - name: setup ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: 3.3
20
+
21
21
  - name: setup gems
22
22
  run: bundle install
23
23
 
24
+ - name: setup xot
25
+ run: git clone -q --depth 1 https://github.com/xord/xot.git "${{ runner.temp }}/xot"
26
+
24
27
  - name: setup dependencies
25
- run: "ruby -I.github/workflows -rutils -e 'setup_dependencies'"
28
+ env:
29
+ RUBYLIB: ${{ runner.temp }}/xot/lib
30
+ run: ruby -rxot/rake -e 'setup_dependencies'
26
31
 
27
32
  - name: setup chrome
28
33
  uses: browser-actions/setup-chrome@latest
@@ -7,40 +7,4 @@ on:
7
7
 
8
8
  jobs:
9
9
  test:
10
- runs-on: macos-latest
11
-
12
- steps:
13
- - name: ruby 3.2
14
- uses: ruby/setup-ruby@v1
15
- with:
16
- ruby-version: 3.2
17
-
18
- - name: checkout
19
- uses: actions/checkout@v4
20
-
21
- - name: setup gems
22
- run: bundle install
23
-
24
- - name: setup dependencies
25
- run: "ruby -I.github/workflows -rutils -e 'setup_dependencies'"
26
-
27
- - name: packages
28
- run: bundle exec rake verbose packages
29
-
30
- - name: setup zig
31
- if: github.event.repository.name == 'reflex-terminal'
32
- uses: mlugg/setup-zig@v2
33
- with:
34
- version: 0.16.0
35
-
36
- - name: vendor
37
- run: bundle exec rake vendor
38
-
39
- - name: lib
40
- run: bundle exec rake verbose lib
41
-
42
- - name: ext
43
- run: bundle exec rake verbose ext
44
-
45
- - name: test
46
- run: bundle exec rake verbose test
10
+ uses: xord/xot/.github/workflows/test.yml@master
data/ChangeLog.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # processing ChangeLog
2
2
 
3
3
 
4
+ ## [v1.5.0] - 2026-11-04
5
+
6
+ - [BREAKING] loadPixels and updatePixels exchange straight alpha, not premultiplied
7
+ - [BREAKING] A shader from createShader sees premultiplied samples from texMap and must emit premultiplied color
8
+ - [BREAKING] Report wheel deltas in pixels, in the same direction, on every platform (from reflex)
9
+ - Add TextBounds#to_a
10
+
11
+ - Fix half-transparent images, tinted images and translucent shapes on a Graphics coming out darker than in p5.js
12
+ - Fix GRAY, INVERT, THRESHOLD and BLUR breaking the alpha of a Graphics with transparent pixels
13
+ - Fix mouseOver and mouseOut defined by def in a sketch never being called
14
+ - Fix a canceled press, as by a menu popping up, becoming a click
15
+
16
+
4
17
  ## [v1.4.0] - 2026-08-04
5
18
 
6
19
  - Add mouseOver/mouseOut blocks to the sketch
data/Gemfile.lock CHANGED
@@ -4,31 +4,26 @@ GEM
4
4
  addressable (2.9.0)
5
5
  public_suffix (>= 2.0.2, < 8.0)
6
6
  base64 (0.3.0)
7
- concurrent-ruby (1.3.6)
8
- ferrum (0.17.2)
7
+ concurrent-ruby (1.3.8)
8
+ ferrum (0.18.0)
9
9
  addressable (~> 2.5)
10
10
  base64 (~> 0.2)
11
11
  concurrent-ruby (~> 1.1)
12
- webrick (~> 1.7)
13
12
  websocket-driver (~> 0.7)
14
- power_assert (3.0.1)
15
- public_suffix (6.0.2)
16
- rake (13.3.1)
13
+ power_assert (3.1.0)
14
+ public_suffix (7.0.5)
15
+ rake (13.4.2)
17
16
  rexml (3.4.4)
18
- test-unit (3.7.7)
17
+ test-unit (3.7.8)
19
18
  power_assert
20
- webrick (1.9.2)
21
- websocket-driver (0.8.0)
19
+ websocket-driver (0.8.2)
22
20
  base64
23
21
  websocket-extensions (>= 0.1.0)
24
22
  websocket-extensions (0.1.5)
25
- yard (0.9.39)
23
+ yard (0.9.45)
26
24
 
27
25
  PLATFORMS
28
- arm64-darwin-20
29
- arm64-darwin-21
30
- arm64-darwin-23
31
- arm64-darwin-24
26
+ ruby
32
27
 
33
28
  DEPENDENCIES
34
29
  ferrum
@@ -37,5 +32,20 @@ DEPENDENCIES
37
32
  test-unit
38
33
  yard
39
34
 
35
+ CHECKSUMS
36
+ addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
37
+ base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
38
+ bundler (4.0.20) sha256=7978a8ac648767f5e635bc522445b79e80a52b907a39a36c2d8085ed6bc762ae
39
+ concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
40
+ ferrum (0.18.0) sha256=4cb8be16e352fc1d75f087e9214b34ec1b93ba932410c730a4724909ca89d7c6
41
+ power_assert (3.1.0) sha256=fefe98ed5d3140628138f3f9f9194d6ece01f1320e69700d3c928adca7518251
42
+ public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
43
+ rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
44
+ rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
45
+ test-unit (3.7.8) sha256=689d1ca53f4d46f678b4e5d68d8e4294f87fc5e8b9238cc4de7c5727e8d65943
46
+ websocket-driver (0.8.2) sha256=97c556b019bf3410b4961002ac501621e9322d3f8a7bc02161a09301cc4c4146
47
+ websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241
48
+ yard (0.9.45) sha256=52e211493f7cb8a3ebf7e104a25a1e73937a3103092545d34cb88fafebb3dc51
49
+
40
50
  BUNDLED WITH
41
- 2.4.19
51
+ 4.0.20
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.4.0
1
+ 1.5.0
@@ -17,7 +17,7 @@ module Processing
17
17
  setup draw
18
18
  keyPressed keyReleased keyTyped
19
19
  mousePressed mouseReleased mouseMoved mouseDragged
20
- mouseClicked doubleClicked mouseWheel
20
+ mouseClicked doubleClicked mouseOver mouseOut mouseWheel
21
21
  touchStarted touchEnded touchMoved
22
22
  windowMoved windowResized motion
23
23
  ]
@@ -35,12 +35,12 @@ module Processing
35
35
  end
36
36
 
37
37
  # @private
38
- def self.setup__(window_class, context_class)
38
+ def self.setup__(window_class, context_class, *args, **kwargs)
39
39
  tmpdir__.tap {|dir| FileUtils.rm_r dir.to_s if dir.directory?} unless Xot.wasm?
40
40
 
41
41
  w = (ENV['WIDTH'] || 500).to_i
42
42
  h = (ENV['HEIGHT'] || 500).to_i
43
- window_class.new w, h, context_class: context_class
43
+ window_class.new w, h, *args, context_class: context_class, **kwargs
44
44
  end
45
45
 
46
46
  # @private
@@ -56,19 +56,23 @@ module Processing
56
56
  end
57
57
 
58
58
  # @private
59
- def self.alias_snake_case_methods__(klass, recursive = 1)
60
- to_snake_case__(klass.instance_methods false)
61
- .reject {|camel, _| camel =~ SUFFIX_PRIVATE}
62
- .reject {|camel, snake| camel == snake}
63
- .each do |camel, snake|
64
- klass.remove_method snake if klass.method_defined?(snake, false)
65
- klass.alias_method snake, camel
59
+ def self.alias_snake_case_methods__(*modules, recursive: 1)
60
+ modules.each do |mod|
61
+ to_snake_case__(mod.instance_methods false)
62
+ .reject {|camel, _| camel =~ SUFFIX_PRIVATE}
63
+ .reject {|camel, snake| camel == snake}
64
+ .each do |camel, snake|
65
+ mod.remove_method snake if mod.method_defined?(snake, false)
66
+ mod.alias_method snake, camel
67
+ end
68
+ if recursive > 0
69
+ mod.constants.map {mod.const_get _1}
70
+ .flatten
71
+ .select {_1.class == Module || _1.class == Class}
72
+ .each {|inner_class|
73
+ alias_snake_case_methods__ inner_class, recursive: recursive - 1
74
+ }
66
75
  end
67
- if recursive > 0
68
- klass.constants.map {klass.const_get _1}
69
- .flatten
70
- .select {_1.class == Module || _1.class == Class}
71
- .each {|inner_class| alias_snake_case_methods__ inner_class, recursive - 1}
72
76
  end
73
77
  end
74
78
 
@@ -127,17 +127,20 @@ module Processing
127
127
  @touchStartedBlock__&.call
128
128
  end
129
129
 
130
- @window__.pointer_up = proc do |e|
130
+ pointerEnded = -> e, click {
131
131
  updatePointerStates.call e
132
132
  updatePointersPressedAndReleased.call e, false
133
133
  if e.any? {|p| p.id == @pointer__.id}
134
134
  @mouseReleasedBlock__&.call
135
- @mouseClickedBlock__&.call if e.click_count > 0
136
- @doubleClickedBlock__&.call if e.click_count == 2
135
+ @mouseClickedBlock__&.call if click && e.click_count > 0
136
+ @doubleClickedBlock__&.call if click && e.click_count == 2
137
137
  end
138
138
  @touchEndedBlock__&.call
139
139
  @pointersReleased__.clear
140
- end
140
+ }
141
+
142
+ @window__.pointer_up = proc {|e| pointerEnded.call e, true}
143
+ @window__.pointer_cancel = proc {|e| pointerEnded.call e, false}
141
144
 
142
145
  @window__.pointer_move = proc do |e|
143
146
  updatePointerStates.call e
@@ -75,6 +75,11 @@ module Processing
75
75
  #
76
76
  class TextBounds
77
77
 
78
+ # @private
79
+ def initialize(x, y, w, h)
80
+ @x, @y, @w, @h = x, y, w, h
81
+ end
82
+
78
83
  # Horizontal position
79
84
  #
80
85
  attr_reader :x
@@ -91,9 +96,12 @@ module Processing
91
96
  #
92
97
  attr_reader :h
93
98
 
94
- # @private
95
- def initialize(x, y, w, h)
96
- @x, @y, @w, @h = x, y, w, h
99
+ # Returns the bounding box as an array.
100
+ #
101
+ # @return [Array<Numeric>] [x, y, w, h]
102
+ #
103
+ def to_a()
104
+ [x, y, w, h]
97
105
  end
98
106
 
99
107
  # Returns a string containing a human-readable representation of object.
@@ -1785,7 +1785,7 @@ module Processing
1785
1785
  else
1786
1786
  y -= font.ascent
1787
1787
  end
1788
- x, y = x.round, y.round unless font.smooth
1788
+ x, y = x.round, y.round unless font.smooth?
1789
1789
  @painter__.text str, x, y
1790
1790
  nil
1791
1791
  end
@@ -3204,7 +3204,8 @@ module Processing
3204
3204
  def createFont(name, size, smooth: true)
3205
3205
  size ||= FONT_SIZE_DEFAULT__
3206
3206
  size = FONT_SIZE_MAX__ if size > FONT_SIZE_MAX__
3207
- Font.new Rays::Font.new(name, size, smooth)
3207
+ Font.new Rays::Font.new(
3208
+ name, size, weight: :normal, italic: false, smooth: smooth)
3208
3209
  end
3209
3210
 
3210
3211
  # Creates a new image object.
@@ -3329,6 +3330,10 @@ module Processing
3329
3330
  # }
3330
3331
  # ```
3331
3332
  #
3333
+ # Colors sampled from the 'texMap' uniform are premultiplied by their
3334
+ # alpha, and gl_FragColor is blended as premultiplied too, so a fragment
3335
+ # shader should keep rgb scaled by alpha. 'vertColor' is straight alpha.
3336
+ #
3332
3337
  # @overload createShader(vertPath, fragPath)
3333
3338
  # @overload createShader(vertSource, fragSource)
3334
3339
  #
@@ -112,9 +112,12 @@ module Processing
112
112
  varying vec4 vertTexCoord;
113
113
  varying vec4 vertColor;
114
114
  void main() {
115
- vec4 col = texture2D(texMap, vertTexCoord.xy) * vertColor;
116
- float gray = col.r * 0.3 + col.g * 0.59 + col.b * 0.11;
117
- gl_FragColor = vec4(vec3(gray > threshold ? 1.0 : 0.0), 1.0);
115
+ vec4 tex = texture2D(texMap, vertTexCoord.xy);
116
+ vec4 col = vec4(vertColor.rgb * vertColor.a, vertColor.a);
117
+ vec3 rgb = tex.a > 0.0 ? tex.rgb / tex.a : tex.rgb;
118
+ float gray = rgb.r * 0.3 + rgb.g * 0.59 + rgb.b * 0.11;
119
+ float value = gray > threshold ? 1.0 : 0.0;
120
+ gl_FragColor = vec4(vec3(value * tex.a), tex.a) * col;
118
121
  }
119
122
  END
120
123
 
@@ -124,9 +127,10 @@ module Processing
124
127
  varying vec4 vertTexCoord;
125
128
  varying vec4 vertColor;
126
129
  void main() {
127
- vec4 col = texture2D(texMap, vertTexCoord.xy);
128
- float gray = col.r * 0.3 + col.g * 0.59 + col.b * 0.11;
129
- gl_FragColor = vec4(vec3(gray), 1.0) * vertColor;
130
+ vec4 tex = texture2D(texMap, vertTexCoord.xy);
131
+ vec4 col = vec4(vertColor.rgb * vertColor.a, vertColor.a);
132
+ float gray = tex.r * 0.3 + tex.g * 0.59 + tex.b * 0.11;
133
+ gl_FragColor = vec4(vec3(gray), tex.a) * col;
130
134
  }
131
135
  END
132
136
 
@@ -136,8 +140,9 @@ module Processing
136
140
  varying vec4 vertTexCoord;
137
141
  varying vec4 vertColor;
138
142
  void main() {
139
- vec4 col = texture2D(texMap, vertTexCoord.xy);
140
- gl_FragColor = vec4(vec3(1.0 - col.rgb), 1.0) * vertColor;
143
+ vec4 tex = texture2D(texMap, vertTexCoord.xy);
144
+ vec4 col = vec4(vertColor.rgb * vertColor.a, vertColor.a);
145
+ gl_FragColor = vec4(tex.a - tex.rgb, tex.a) * col;
141
146
  }
142
147
  END
143
148
 
@@ -157,7 +162,7 @@ module Processing
157
162
  }
158
163
  void main() {
159
164
  float sigma = radius * 0.5;
160
- vec3 color = vec3(0.0);
165
+ vec4 sum = vec4(0.0);
161
166
  float total_weight = 0.0;
162
167
  for (float y = -radius; y < radius; y += 1.0)
163
168
  for (float x = -radius; x < radius; x += 1.0) {
@@ -168,10 +173,11 @@ module Processing
168
173
  texcoord.x < vertTexCoordMin.x || vertTexCoordMax.x < texcoord.x ||
169
174
  texcoord.y < vertTexCoordMin.y || vertTexCoordMax.y < texcoord.y
170
175
  ) continue;
171
- color += texture2D(texMap, texcoord).rgb * weight;
176
+ sum += texture2D(texMap, texcoord) * weight;
172
177
  total_weight += weight;
173
178
  }
174
- gl_FragColor = vec4(color / total_weight, 1.0) * vertColor;
179
+ vec4 col = vec4(vertColor.rgb * vertColor.a, vertColor.a);
180
+ gl_FragColor = sum / total_weight * col;
175
181
  }
176
182
  END
177
183
 
@@ -32,7 +32,8 @@ module Processing
32
32
 
33
33
  attr_accessor :setup, :update, :draw, :move, :resize, :motion,
34
34
  :key_down, :key_up,
35
- :pointer_down, :pointer_up, :pointer_move, :pointer_enter, :pointer_leave, :wheel,
35
+ :pointer_down, :pointer_up, :pointer_move, :pointer_cancel,
36
+ :pointer_enter, :pointer_leave, :wheel,
36
37
  :note_on, :note_off, :control_change,
37
38
  :before_draw, :after_draw, :update_window, :update_canvas
38
39
 
@@ -114,11 +115,12 @@ module Processing
114
115
 
115
116
  def on_canvas_pointer(e)
116
117
  block = case e.action
117
- when :down then @pointer_down
118
- when :up, :cancel then @pointer_up
119
- when :move then @pointer_move
120
- when :enter then @pointer_enter
121
- when :leave then @pointer_leave
118
+ when :down then @pointer_down
119
+ when :up then @pointer_up
120
+ when :move then @pointer_move
121
+ when :cancel then @pointer_cancel
122
+ when :enter then @pointer_enter
123
+ when :leave then @pointer_leave
122
124
  end
123
125
  draw_canvas {call_block block, e} if block
124
126
  end
data/processing.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
 
12
12
  ext = Processing::Extension
13
13
  name = ext.name true
14
- rdocs = glob.call *%w[README]
14
+ rdocs = glob.call(*%w[README])
15
15
 
16
16
  s.name = name
17
17
  s.version = ext.version
@@ -26,10 +26,10 @@ Gem::Specification.new do |s|
26
26
  s.required_ruby_version = '>= 3.0.0'
27
27
 
28
28
  s.add_dependency 'rexml'
29
- s.add_dependency 'xot', '~> 0.4.0'
30
- s.add_dependency 'rucy', '~> 0.4.0'
31
- s.add_dependency 'rays', '~> 0.4.0'
32
- s.add_dependency 'reflexion', '~> 0.6.0'
29
+ s.add_dependency 'xot', '~> 0.5.0'
30
+ s.add_dependency 'rucy', '~> 0.5.0'
31
+ s.add_dependency 'rays', '~> 0.5.0'
32
+ s.add_dependency 'reflexion', '~> 0.7.0'
33
33
 
34
34
  s.files = `git ls-files`.split $/
35
35
  s.test_files = s.files.grep %r{^(test|spec|features)/}
data/test/browser.rb CHANGED
@@ -57,6 +57,10 @@ def get_p5rb_html(width, height, draw_src, webgl: false)
57
57
  }
58
58
  </script>
59
59
  <script type="text/ruby">
60
+ class JS::Object
61
+ def beginDraw() = block_given? ? yield(self) : nil
62
+ def endDraw() = nil
63
+ end
60
64
  def setup()
61
65
  createCanvas #{width}, #{height}#{webgl ? ', WEBGL' : ''}
62
66
  end
@@ -34,6 +34,27 @@ class TestGraphicsContext < Test::Unit::TestCase
34
34
  assert_equal G::ADD, g.blendMode
35
35
 
36
36
  assert_raise {g.blendMode LEFT}
37
+
38
+ header = <<~END
39
+ blendMode BLEND
40
+ noStroke
41
+ background 100, 150, 200
42
+ fill 200, 100, 50
43
+ rect 100, 100, 400, 400
44
+ END
45
+ footer = <<~END
46
+ rect 300, 300, 400, 400
47
+ END
48
+ %w[BLEND ADD LIGHTEST DARKEST EXCLUSION MULTIPLY SCREEN].each do |mode|
49
+ assert_p5_draw header, "blendMode #{mode}; fill 50, 200, 100", footer
50
+ end
51
+
52
+ # LIGHTEST and DARKEST are left out: p5 lerps between the destination
53
+ # and max/min by the source alpha, which fixed-function GL_MAX/GL_MIN
54
+ # blending cannot express
55
+ %w[BLEND ADD EXCLUSION MULTIPLY SCREEN].each do |mode|
56
+ assert_p5_draw header, "blendMode #{mode}; fill 50, 200, 100, 128", footer
57
+ end
37
58
  end
38
59
 
39
60
  def test_strokeCap()
@@ -69,6 +90,27 @@ class TestGraphicsContext < Test::Unit::TestCase
69
90
  assert_p5_draw header, 'strokeJoin ROUND', footer
70
91
  end
71
92
 
93
+ def test_tint()
94
+ header = <<~END
95
+ noStroke
96
+ background 0, 255, 0
97
+ unless @img
98
+ @img = createGraphics 400, 400
99
+ @img.beginDraw
100
+ @img.noStroke
101
+ @img.background 255, 0, 0
102
+ @img.endDraw
103
+ end
104
+ END
105
+ footer = <<~END
106
+ image @img, 100, 100
107
+ END
108
+ assert_p5_draw header, '', footer
109
+ assert_p5_draw header, 'tint 255, 128', footer
110
+ assert_p5_draw header, 'tint 128, 255', footer
111
+ assert_p5_draw header, 'tint 0, 0, 255, 128', footer
112
+ end
113
+
72
114
  def test_textFont()
73
115
  graphics do |g|
74
116
  arial10 = g.createFont 'Arial', 10
@@ -698,6 +740,47 @@ class TestGraphicsContext < Test::Unit::TestCase
698
740
  assert_p5_fill_stroke src, 'endShape CLOSE'
699
741
  end
700
742
 
743
+ def test_image_with_alpha()
744
+ header = <<~END
745
+ noStroke
746
+ background 0, 255, 0
747
+ unless @img
748
+ @img = createGraphics 400, 400
749
+ @img.beginDraw
750
+ @img.noStroke
751
+ @img.fill 255, 255, 255, 128
752
+ @img.rect 0, 0, 400, 400
753
+ @img.endDraw
754
+ end
755
+ END
756
+ assert_p5_draw header, 'image @img, 100, 100'
757
+ end
758
+
759
+ def test_image_with_filter()
760
+ header = -> filter {<<~END}
761
+ noStroke
762
+ background 0, 255, 0
763
+ unless @img
764
+ @img = createGraphics 400, 400
765
+ @img.beginDraw
766
+ @img.noStroke
767
+ @img.fill 200, 100, 50, 128
768
+ @img.rect 0, 0, 400, 400
769
+ @img.endDraw
770
+ @img.filter #{filter}
771
+ end
772
+ END
773
+ footer = <<~END
774
+ image @img, 100, 100
775
+ END
776
+ # GRAY weighs rgb as Processing does (0.3, 0.59, 0.11) while p5 uses the
777
+ # CIE luminance (0.2126, 0.7152, 0.0722), which puts the two a few steps
778
+ # apart
779
+ assert_p5_draw header['GRAY'], footer, threshold: THRESHOLD_TO_BE_FIXED
780
+ assert_p5_draw header['INVERT'], footer
781
+ assert_p5_draw header['THRESHOLD'], footer
782
+ end
783
+
701
784
  def test_pixels()
702
785
  g = graphics 2, 2
703
786
 
@@ -719,6 +802,22 @@ class TestGraphicsContext < Test::Unit::TestCase
719
802
  assert_raise(ArgumentError) {g.updatePixels}
720
803
  end
721
804
 
805
+ def test_pixels_with_alpha()
806
+ g = graphics 2, 2 do |g|
807
+ g.noStroke
808
+ g.fill 255, 255, 255, 128
809
+ g.rect 0, 0, 1, 1
810
+ end
811
+
812
+ g.loadPixels
813
+ assert_equal [0x80ffffff, 0, 0, 0], g.pixels
814
+
815
+ g.pixels.replace [0x80ffffff, 0x40ff0000, 0, 0]
816
+ g.updatePixels
817
+ g.loadPixels
818
+ assert_equal [0x80ffffff, 0x40ff0000, 0, 0], g.pixels
819
+ end
820
+
722
821
  def test_pixels_and_modified_flags()
723
822
  drawRect = -> g, x, *rgb do
724
823
  g.beginDraw do
data/test/test_image.rb CHANGED
@@ -37,6 +37,13 @@ class TestImage < Test::Unit::TestCase
37
37
  assert_equal g.color(0, 0, 255), i.get(0, 1)
38
38
  end
39
39
 
40
+ def test_get_color_with_alpha()
41
+ g = graphics
42
+ i = image(2, 2) {fill 1, 0, 0, 0.5; rect 0, 0, 1, 1}
43
+
44
+ assert_equal g.color(255, 0, 0, 128), i.get(0, 0)
45
+ end
46
+
40
47
  def test_pixels()
41
48
  i = image 2, 2
42
49
 
@@ -58,6 +65,18 @@ class TestImage < Test::Unit::TestCase
58
65
  assert_raise(ArgumentError) {i.updatePixels}
59
66
  end
60
67
 
68
+ def test_pixels_with_alpha()
69
+ i = image(2, 2) {fill 1, 1, 1, 0.5; rect 0, 0, 1, 1}
70
+
71
+ i.loadPixels
72
+ assert_equal [0x80ffffff, 0, 0, 0], i.pixels
73
+
74
+ i.pixels.replace [0x80ffffff, 0x40ff0000, 0, 0]
75
+ i.updatePixels
76
+ i.loadPixels
77
+ assert_equal [0x80ffffff, 0x40ff0000, 0, 0], i.pixels
78
+ end
79
+
61
80
  def test_inspect()
62
81
  assert_match %r|#<Processing::Image:0x\w{16}>|, image.inspect
63
82
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: processing
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - xordog
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-04 00:00:00.000000000 Z
11
+ date: 2026-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rexml
@@ -30,56 +30,56 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.4.0
33
+ version: 0.5.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.4.0
40
+ version: 0.5.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rucy
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.4.0
47
+ version: 0.5.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.4.0
54
+ version: 0.5.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rays
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.4.0
61
+ version: 0.5.0
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 0.4.0
68
+ version: 0.5.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: reflexion
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 0.6.0
75
+ version: 0.7.0
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 0.6.0
82
+ version: 0.7.0
83
83
  description: Creative Coding Framework has API compatible to Processing or p5.js.
84
84
  email: xordog@gmail.com
85
85
  executables: []
@@ -91,7 +91,6 @@ files:
91
91
  - ".github/workflows/tag.yml"
92
92
  - ".github/workflows/test-draw.yml"
93
93
  - ".github/workflows/test.yml"
94
- - ".github/workflows/utils.rb"
95
94
  - ".gitignore"
96
95
  - ".yardopts"
97
96
  - CLAUDE.md
@@ -164,7 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
163
  - !ruby/object:Gem::Version
165
164
  version: '0'
166
165
  requirements: []
167
- rubygems_version: 3.4.19
166
+ rubygems_version: 3.5.22
168
167
  signing_key:
169
168
  specification_version: 4
170
169
  summary: Processing compatible Creative Coding Framework.
@@ -1,127 +0,0 @@
1
- require 'shellwords'
2
-
3
- ALL_REPO = 'xord/all'
4
- ALL_DIR = '../all'
5
- ALL_FETCH_DEPTH = 100
6
-
7
- RENAMES = {reflex: 'reflexion'}
8
-
9
- def sh(cmd)
10
- puts cmd
11
- system cmd
12
- end
13
-
14
- def setup_dependencies(only: nil)
15
- gemspec_path = `git ls-files`.lines(chomp: true).find {|l| l =~ /\.gemspec$/}
16
- return unless gemspec_path
17
-
18
- gemspec = File.read gemspec_path
19
- name = File.basename gemspec_path, '.gemspec'
20
-
21
- exts = File.readlines('Rakefile')
22
- .map {|l| l[%r|^\s*require\W+([\w\-\_]+)/extension\W+$|, 1]}
23
- .compact
24
- .reject {|ext| ext == name}
25
- exts = exts & [only].flatten.map(&:to_s) if only
26
- return if exts.empty?
27
-
28
- unless setup_dependencies_via_monorepo(exts)
29
- setup_dependencies_via_each_repo_by_version(gemspec, exts)
30
- end
31
-
32
- exts.each {|ext| sh %( cd ../#{ext} && rake ext )}
33
- end
34
-
35
- def setup_dependencies_via_monorepo(exts)
36
- return false unless checkout_monorepo
37
- exts.each {|ext| sh %( ln -snf all/#{ext} ../#{ext} )}
38
- true
39
- end
40
-
41
- def checkout_monorepo()
42
- uuid = `git log -1 --format=%B`[/^\[\[([0-9a-fA-F-]+)\]\]$/, 1]
43
- return false unless uuid
44
-
45
- commit = setup_monorepo uuid
46
- return false unless commit
47
-
48
- Dir.chdir(ALL_DIR) {sh %( git checkout -q #{commit} )}
49
- true
50
- end
51
-
52
- def setup_monorepo(uuid)
53
- unless File.directory? ALL_DIR
54
- url = "https://github.com/#{ALL_REPO}.git"
55
- sh %( git clone --no-tags --depth #{ALL_FETCH_DEPTH} #{url} #{ALL_DIR} )
56
- end
57
- loop do
58
- commit = find_monorepo_commit uuid
59
- return commit if commit
60
-
61
- deepened = Dir.chdir ALL_DIR do
62
- before = `git rev-list --count HEAD`.to_i
63
- sh %( git fetch --deepen #{ALL_FETCH_DEPTH} )
64
- `git rev-list --count HEAD`.to_i > before
65
- end
66
- return nil unless deepened
67
- end
68
- end
69
-
70
- def find_monorepo_commit(uuid)
71
- Dir.chdir ALL_DIR do
72
- out = `git log origin/HEAD -F --grep="[[#{uuid}]]" --format=%H -1`.strip
73
- out.empty? ? nil : out
74
- end
75
- end
76
-
77
- def setup_dependencies_via_each_repo_by_version(gemspec, exts)
78
- exts.each do |ext|
79
- gem = RENAMES[ext.to_sym].then {|s| s || ext}
80
- ver = gemspec[/add_dependency.*['"]#{gem}['"].*['"]\s*~>\s*([\d\.]+)\s*['"]/, 1]
81
- opts = '-c advice.detachedHead=false --depth 1'
82
- clone = "git clone #{opts} https://github.com/xord/#{ext}.git ../#{ext}"
83
-
84
- # 'rake subtree:push' pushes all subrepos, so cloning by new tag
85
- # often fails before tagging each new tag
86
- sh %( #{clone} --branch v#{ver} || #{clone} )
87
- end
88
- end
89
-
90
- def tag_versions()
91
- changes = changelogs
92
- tags = `git tag`.lines chomp: true
93
- vers = `git log --oneline ./VERSION`
94
- .lines(chomp: true)
95
- .map {|line| line.split.first[/^\w+$/]}
96
- .map {|sha| [`git cat-file -p #{sha}:./VERSION 2>/dev/null`[/[\d\.]+/], sha]}
97
- .select {|ver, sha| ver && sha}
98
- .reverse
99
- .to_h
100
-
101
- vers.to_a.reverse.each do |ver, sha|
102
- tag = "v#{ver}"
103
- break if tags.include?(tag)
104
- sh %( git tag -a -m \"#{changes[tag]&.gsub '"', '\\"'}\" #{tag} #{sha} )
105
- end
106
- end
107
-
108
- def release(*paths)
109
- tag = ENV['GITHUB_REF']&.sub(%r|^refs/tags/|, '') || raise('GITHUB_REF tag not set')
110
- notes = (changelogs[tag] || '').shellescape
111
- paths = paths.flatten.join ' '
112
-
113
- sh(%( gh release create #{tag} #{paths} --notes #{notes} )) ||
114
- sh(%( gh release upload #{tag} #{paths} --clobber )) ||
115
- raise('failed to upload to releases')
116
- end
117
-
118
- def changelogs()
119
- File.read('ChangeLog.md')
120
- .split(/^\s*##\s*\[\s*(v[\d\.]+)\s*\].*$/)
121
- .slice(1..)
122
- .each_slice(2)
123
- .to_h
124
- .transform_values(&:strip!)
125
- rescue Errno::ENOENT
126
- raise 'failed to get changelogs'
127
- end