processing 1.1.17 → 1.2.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: e50909c9512c824caba22c77bcca692a84174e3d09e227080980589e616b643c
4
- data.tar.gz: 2511f64137c36a7464ad063f7df7045f0993899bed1a9b2207cf87f3d3b9b5f4
3
+ metadata.gz: 854a85bdbae535597d51821ec8a2b8cc0c948ad355a889bfdd714dbcf2f2ee38
4
+ data.tar.gz: 5ed6cb2ae766ad26658a85826a660be0fbe92575c1b221a8440a38c722d8ec63
5
5
  SHA512:
6
- metadata.gz: a7b194cba66b6fe6933aaa584405a58d7d3675d232116d87e5cf713b5bd004a507c209843da5cd6bfd8a411e2153a9fed015f29ddcb35d5102a7ecc3131ea47c
7
- data.tar.gz: f33a5d8216523f976840a065394e73ce535809d51381ee5ed6488194c0f2c3a80e526be3613ae517684ed018ec3d7e9436f31991e9c60e178305fd594788a582
6
+ metadata.gz: 49a769fbfd6e72f538b007b6da83dae50b873b2f15f1e4240bb4639f90ec459a56b0b1266a8e262aee994ed06e2f01be8e212c1215cbe815f54f188caefdef01
7
+ data.tar.gz: d5cd70ca39f8bd953d322efa398a4325863937c9d28338bd29d7f0004b8882c59d0004e49ca9b8468c89920e0980272f5fd8ad764ff754ebeb137f0b81c02169
@@ -4,6 +4,9 @@ on:
4
4
  push:
5
5
  tags: ['v[0-9]*']
6
6
 
7
+ permissions:
8
+ contents: write
9
+
7
10
  jobs:
8
11
  release:
9
12
  runs-on: macos-latest
@@ -33,23 +36,9 @@ jobs:
33
36
  echo path=$(ruby -e 'print Dir.glob("*.gem").first') >> $GITHUB_OUTPUT
34
37
 
35
38
  - name: create github release
36
- id: release
37
- uses: actions/create-release@v1
38
39
  env:
39
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40
- with:
41
- tag_name: ${{ github.ref }}
42
- release_name: ${{ github.ref }}
43
-
44
- - name: upload to github release
45
- uses: actions/upload-release-asset@v1
46
- env:
47
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48
- with:
49
- upload_url: ${{ steps.release.outputs.upload_url }}
50
- asset_path: ./${{ steps.gem.outputs.path }}
51
- asset_name: ${{ steps.gem.outputs.path }}
52
- asset_content_type: application/zip
40
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41
+ run: ruby -I.github/workflows -rutils -e 'release(*ARGV)' ./${{ steps.gem.outputs.path }}
53
42
 
54
43
  - name: upload to rubygems
55
44
  env:
@@ -16,7 +16,7 @@ jobs:
16
16
  ruby-version: 3.2
17
17
 
18
18
  - name: checkout
19
- uses: actions/checkout@v2
19
+ uses: actions/checkout@v4
20
20
 
21
21
  - name: setup gems
22
22
  run: bundle install
@@ -1,3 +1,9 @@
1
+ require 'shellwords'
2
+
3
+ ALL_REPO = 'xord/all'
4
+ ALL_DIR = '../all'
5
+ ALL_FETCH_DEPTH = 100
6
+
1
7
  RENAMES = {reflex: 'reflexion'}
2
8
 
3
9
  def sh(cmd)
@@ -5,7 +11,7 @@ def sh(cmd)
5
11
  system cmd
6
12
  end
7
13
 
8
- def setup_dependencies(build: true, only: nil)
14
+ def setup_dependencies(only: nil)
9
15
  gemspec_path = `git ls-files`.lines(chomp: true).find {|l| l =~ /\.gemspec$/}
10
16
  return unless gemspec_path
11
17
 
@@ -13,44 +19,109 @@ def setup_dependencies(build: true, only: nil)
13
19
  name = File.basename gemspec_path, '.gemspec'
14
20
 
15
21
  exts = File.readlines('Rakefile')
16
- .map {|l| l[%r|^\s*require\W+(\w+)/extension\W+$|, 1]}
22
+ .map {|l| l[%r|^\s*require\W+([\w\-\_]+)/extension\W+$|, 1]}
17
23
  .compact
18
24
  .reject {|ext| ext == name}
19
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
20
76
 
77
+ def setup_dependencies_via_each_repo_by_version(gemspec, exts)
21
78
  exts.each do |ext|
22
79
  gem = RENAMES[ext.to_sym].then {|s| s || ext}
23
- ver = gemspec[/add_dependency.*['"]#{gem}['"].*['"]\s*>=\s*([\d\.]+)\s*['"]/, 1]
80
+ ver = gemspec[/add_dependency.*['"]#{gem}['"].*['"]\s*~>\s*([\d\.]+)\s*['"]/, 1]
24
81
  opts = '-c advice.detachedHead=false --depth 1'
25
82
  clone = "git clone #{opts} https://github.com/xord/#{ext}.git ../#{ext}"
26
83
 
27
84
  # 'rake subtree:push' pushes all subrepos, so cloning by new tag
28
85
  # often fails before tagging each new tag
29
86
  sh %( #{clone} --branch v#{ver} || #{clone} )
30
- sh %( cd ../#{ext} && rake ext )
31
87
  end
32
88
  end
33
89
 
34
90
  def tag_versions()
35
- tags = `git tag`.lines chomp: true
36
- vers = `git log --oneline ./VERSION`
91
+ changes = changelogs
92
+ tags = `git tag`.lines chomp: true
93
+ vers = `git log --oneline ./VERSION`
37
94
  .lines(chomp: true)
38
95
  .map {|line| line.split.first[/^\w+$/]}
39
- .map {|hash| [`git cat-file -p #{hash}:./VERSION 2>/dev/null`[/[\d\.]+/], hash]}
40
- .select {|ver, hash| ver && hash}
96
+ .map {|sha| [`git cat-file -p #{sha}:./VERSION 2>/dev/null`[/[\d\.]+/], sha]}
97
+ .select {|ver, sha| ver && sha}
41
98
  .reverse
42
99
  .to_h
43
100
 
44
- changes = File.read('ChangeLog.md')
45
- .split(/^\s*##\s*\[\s*v([\d\.]+)\s*\].*$/)
46
- .slice(1..-1)
47
- .each_slice(2)
48
- .to_h
49
- .transform_values(&:strip!)
50
-
51
- vers.to_a.reverse.each do |ver, hash|
101
+ vers.to_a.reverse.each do |ver, sha|
52
102
  tag = "v#{ver}"
53
103
  break if tags.include?(tag)
54
- sh %( git tag -a -m \"#{changes[ver]&.gsub '"', '\\"'}\" #{tag} #{hash} )
104
+ sh %( git tag -a -m \"#{changes[tag]&.gsub '"', '\\"'}\" #{tag} #{sha} )
55
105
  end
56
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
data/ChangeLog.md CHANGED
@@ -1,6 +1,23 @@
1
1
  # processing ChangeLog
2
2
 
3
3
 
4
+ ## [v1.2.0] - 2026-05-17
5
+
6
+ - [BREAKING] Wheel Y delta now follows top-left origin convention (from reflex)
7
+
8
+ - Avoid painter.push overhead in image/blend draws
9
+ - Rewrite README.md
10
+ - CI: Migrate release-gem.yml from actions/create-release to gh release create
11
+
12
+
13
+ ## [v1.1.18] - 2026-05-10
14
+
15
+ - Support WebAssembly
16
+ - Disable auto-GC on WASM to avoid mid-draw Asyncify yield
17
+ - Lazy-load net/http and disable HTTP fetch on WASM
18
+ - Skip tmpdir cleanup on WASM
19
+
20
+
4
21
  ## [v1.1.17] - 2026-04-17
5
22
 
6
23
  - Update dependencies
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Processing for CRuby - Processing compatible Creative Coding Framework
1
+ # Processing for CRuby - A Processing-compatible creative coding framework
2
2
 
3
3
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/xord/processing)
4
4
  ![License](https://img.shields.io/github/license/xord/processing)
@@ -21,18 +21,28 @@ Thanks for your support! 🙌
21
21
 
22
22
  ## 🚀 About
23
23
 
24
- **Processing for CRuby** is a creative coding framework compatible with the Processing language, designed specifically for CRuby.
24
+ **Processing for CRuby** is an independent, OpenGL-based Ruby implementation of the [Processing](https://processing.org/) API. It is **not** affiliated with the original Processing project, and it is not the JRuby / JOGL-based [`ruby-processing`](https://github.com/jashkenas/ruby-processing) either — this gem runs on CRuby (MRI), and all rendering goes through the `xord/*` family's own OpenGL 2D drawing engine ([Rays](https://github.com/xord/rays), via [Reflex](https://github.com/xord/reflex)).
25
25
 
26
- It allows artists and developers to create visual art and interactive graphics using the familiar Processing syntax and concepts, leveraging the power and flexibility of Ruby.
26
+ Write a `setup do ... end` and a `draw do ... end` at the top of a Ruby file, run it, and a window appears — just like a `.pde` sketch. This gem is the thin, pure-Ruby layer that maps Processing's vocabulary onto the underlying engine (windowing, drawing, input, MIDI, camera, physics).
27
+
28
+ > Looking for the same API on mobile / with a game-engine flavor? See [RubySketch](https://github.com/xord/rubysketch).
29
+
30
+ ## 📋 Requirements
31
+
32
+ - Ruby **3.0.0** or later
33
+ - All the runtime requirements of [Reflex](https://github.com/xord/reflex) (which in turn brings Rays, Rucy, Xot, plus the platform GUI backend — AppKit / UIKit / Win32 / SDL2 — and OpenGL)
34
+ - The dependent gems are installed automatically: `rexml`, `xot`, `rucy`, `rays`, `reflexion`
35
+
36
+ There is no native C/C++ extension in this gem; the heavy lifting is done by the dependencies' extensions.
27
37
 
28
38
  ## 📦 Installation
29
39
 
30
40
  Add this line to your Gemfile:
31
41
  ```ruby
32
- $ gem 'processing'
42
+ gem 'processing'
33
43
  ```
34
44
 
35
- Then, install gem:
45
+ Then install:
36
46
  ```bash
37
47
  $ bundle install
38
48
  ```
@@ -42,7 +52,178 @@ Or install it directly:
42
52
  $ gem install processing
43
53
  ```
44
54
 
55
+ ## 📚 What's Provided
56
+
57
+ `require 'processing'` exposes a `Processing` module that, when activated, makes the Processing API available as top-level methods inside the current file. There are two ways to activate it.
58
+
59
+ ### Two ways to use the gem
60
+
61
+ | Style | How |
62
+ | ------------------------------ | ----------------------------------------------------------------------------------------- |
63
+ | **Refinement, camelCase** | `require 'processing'` + `using Processing` — the standard, Processing-compatible style |
64
+ | **Refinement, snake_case too** | `require 'processing'` + `using Processing(snake_case: true)` — adds Ruby-idiomatic aliases (`color_mode`, `ellipse_mode`, ...) alongside the camelCase originals |
65
+
66
+ In both cases, the framework opens a window for you and runs the sketch automatically when the file exits. You do not need an explicit `start` call.
67
+
68
+ ### Core API
69
+
70
+ | Area | Examples |
71
+ | ----------------- | --------------------------------------------------------------------------------------------------- |
72
+ | Sketch lifecycle | `setup`, `draw`, `windowResized`, `windowMoved` |
73
+ | State | `size`, `frameRate`, `frameCount`, `width`, `height`, `pixelDensity`, `noLoop` / `loop_` / `redraw` |
74
+ | Color & state | `background`, `fill`, `stroke`, `noFill`, `noStroke`, `strokeWeight`, `colorMode`, `blendMode` |
75
+ | Shapes | `point`, `line`, `rect`, `square`, `triangle`, `quad`, `ellipse`, `circle`, `arc`, `curve`, `bezier`, `beginShape` / `endShape` / `vertex` |
76
+ | Transforms | `push` / `pop`, `pushMatrix` / `popMatrix`, `translate`, `rotate`, `scale`, `shearX`, `shearY` |
77
+ | Text | `text`, `textSize`, `textFont`, `textAlign`, `textWidth`, `loadFont` |
78
+ | Images | `image`, `loadImage` (file path *or* URL), `createImage`, `tint`, `noTint` |
79
+ | Vectors & math | `PVector`, `createVector`, `random`, `noise`, `map`, `lerp`, `dist`, `radians`, `degrees` |
80
+ | Input | `mousePressed`, `mouseReleased`, `mouseMoved`, `mouseDragged`, `mouseClicked`, `doubleClicked`, `mouseWheel`, `keyPressed`, `keyReleased`, `keyTyped`, `touchStarted` / `touchEnded` / `touchMoved`, `motion` |
81
+ | Live state | `mouseX`, `mouseY`, `pmouseX`, `pmouseY`, `key`, `keyCode`, `touches` |
82
+ | Off-screen buffer | `createGraphics` → a `Graphics` object that shares the same drawing API |
83
+ | Shapes / SVG | `createShape`, `loadShape` (SVG) |
84
+ | Shaders | `loadShader`, `shader`, `resetShader` — GLSL shaders go straight to the OpenGL pipeline |
85
+ | Camera | `createCapture` — live camera capture via `Rays::Camera` |
86
+
87
+ ### Top-level constants
88
+
89
+ The familiar Processing constants are defined: `RGB`, `HSB`, `RADIANS`, `DEGREES`, `CORNER`, `CORNERS`, `CENTER`, `RADIUS`, `LEFT`, `RIGHT`, `TOP`, `BOTTOM`, `BASELINE`, `BLEND`, `ADD`, `MULTIPLY`, `SCREEN`, ...
90
+
91
+ ### Internal API convention
92
+
93
+ Methods ending in `__` (e.g. `init__`, `beginDraw__`, `@context__`) are framework internals and are deliberately excluded from the refinement-exposed top-level method set, so they will not collide with names in your sketch.
94
+
95
+ ## 💡 Usage
96
+
97
+ ### Hello, world
98
+
99
+ ```ruby
100
+ require 'processing'
101
+ using Processing
102
+
103
+ draw do
104
+ background 0, 10 # alpha trail
105
+ textSize 50
106
+ text 'hello, world!', mouseX, mouseY
107
+ end
108
+ ```
109
+
110
+ Save as `hello.rb`, then `ruby hello.rb`. A 500 × 500 window opens automatically.
111
+
112
+ ### `setup` and `draw`
113
+
114
+ ```ruby
115
+ require 'processing'
116
+ using Processing
117
+
118
+ setup do
119
+ size 600, 400
120
+ colorMode RGB, 1
121
+ angleMode DEGREES
122
+ end
123
+
124
+ draw do
125
+ background 0
126
+ fill 1, 0.4, 0.1
127
+ stroke 1
128
+ strokeWeight 2
129
+
130
+ rectMode CENTER
131
+ push
132
+ translate width / 2, height / 2
133
+ rotate frameCount # one degree per frame
134
+ rect 0, 0, 200, 120, 12
135
+ pop
136
+ end
137
+ ```
138
+
139
+ ### Loading an image from a URL
140
+
141
+ ```ruby
142
+ require 'processing'
143
+ using Processing
144
+
145
+ icon = loadImage 'https://xord.org/rubysketch/images/rubysketch128.png'
146
+
147
+ draw do
148
+ background 0, 10
149
+ image icon, mouseX, mouseY, icon.width / 10, icon.height / 10
150
+ end
151
+ ```
152
+
153
+ ### Mouse and keyboard input
154
+
155
+ ```ruby
156
+ require 'processing'
157
+ using Processing
158
+
159
+ setup { background 0 }
160
+
161
+ draw do
162
+ fill 1
163
+ ellipse mouseX, mouseY, 20, 20
164
+ end
165
+
166
+ mousePressed { background 0 }
167
+ keyPressed { puts "pressed: #{key.inspect} (#{keyCode})" }
168
+ ```
169
+
170
+ ### Snake_case aliases
171
+
172
+ ```ruby
173
+ require 'processing'
174
+ using Processing(snake_case: true)
175
+
176
+ draw do
177
+ background 0
178
+ fill 1
179
+ stroke_weight 4 # alias of strokeWeight
180
+ no_fill # alias of noFill
181
+ ellipse mouse_x, mouse_y, 50, 50
182
+ end
183
+ ```
184
+
185
+ ### Off-screen rendering with `createGraphics`
186
+
187
+ ```ruby
188
+ require 'processing'
189
+ using Processing
190
+
191
+ g = nil
192
+
193
+ setup do
194
+ size 400, 400
195
+ g = createGraphics 200, 200
196
+ g.beginDraw
197
+ g.background 0, 100, 200
198
+ g.fill 255
199
+ g.ellipse 100, 100, 120, 120
200
+ g.endDraw
201
+ end
202
+
203
+ draw do
204
+ background 0
205
+ image g, mouseX - 100, mouseY - 100
206
+ end
207
+ ```
208
+
209
+ More examples live in [`examples/`](./examples) — `breakout.rb`, `camera.rb`, `clock.rb`, `delay_camera.rb`, `filter.rb`, `image.rb`, `shake.rb`, `shapes.rb`.
210
+
211
+ ## 🛠️ Development
212
+
213
+ ```bash
214
+ $ rake test # run the test suite (drawing tests run headless by default)
215
+ $ rake test:draw # run drawing tests only
216
+ $ rake doc # generate YARD docs
217
+ $ rake # default tasks
218
+ ```
219
+
220
+ Visual regression tests can be enabled with `TEST_WITH_BROWSER=1` — the suite then drives [Ferrum](https://github.com/rubycdp/ferrum) to capture browser-rendered screenshots and compare them against the gem's output.
221
+
222
+ In the [`xord/all`](https://github.com/xord/all) monorepo you can scope by module, e.g. `rake processing test`.
223
+
45
224
  ## 📜 License
46
225
 
47
226
  **Processing for CRuby** is licensed under the MIT License.
48
227
  See the [LICENSE](./LICENSE) file for details.
228
+
229
+ This project is not affiliated with [Processing.org](https://processing.org/); it is an independent reimplementation of their API.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.17
1
+ 1.2.0
@@ -5,8 +5,8 @@ require 'pathname'
5
5
  require 'tmpdir'
6
6
  require 'uri'
7
7
  require 'rexml'
8
- require 'net/http'
9
8
  require 'xot/inspectable'
9
+ require 'xot/util'
10
10
  require 'reflex'
11
11
 
12
12
 
@@ -108,11 +108,15 @@ module Processing
108
108
  end
109
109
 
110
110
  # @private
111
- def drawImage__(painter, *args, **states)
112
- shader = painter.shader || @filter&.getInternal__
113
- painter.push shader: shader, **states do |_|
114
- painter.image getInternal__, *args
111
+ def drawImage__(painter, *args)
112
+ if @filter
113
+ shader = painter.shader
114
+ painter.shader = @filter.getInternal__
115
115
  end
116
+ painter.image getInternal__, *args
117
+ nil
118
+ ensure
119
+ painter.shader = shader if @filter
116
120
  end
117
121
 
118
122
  end# Capture
@@ -45,7 +45,7 @@ module Processing
45
45
  def initialize(window)
46
46
  @@rootContext__ = self
47
47
 
48
- tmpdir__.tap {|dir| FileUtils.rm_r dir.to_s if dir.directory?}
48
+ tmpdir__.tap {|dir| FileUtils.rm_r dir.to_s if dir.directory?} unless Xot.wasm?
49
49
 
50
50
  @window__ = window
51
51
  init__(
@@ -6,8 +6,10 @@ module Processing
6
6
 
7
7
  module_function
8
8
 
9
- def name()
10
- super.split('::')[-2]
9
+ def name(downcase = false)
10
+ super().split('::')[-2].then {|s|
11
+ downcase ? s.gsub(/([a-z])([A-Z])/) {"#{$1}-#{$2}"}.downcase : s
12
+ }
11
13
  end
12
14
 
13
15
  def version()
@@ -1835,8 +1835,14 @@ module Processing
1835
1835
  def image(img, a, b, c = nil, d = nil)
1836
1836
  assertDrawing__
1837
1837
  x, y, w, h = toXYWH__ @imageMode__, a, b, c || img.width, d || img.height
1838
- img.drawImage__ @painter__, x, y, w, h, fill: getTint__, stroke: :none
1839
- nil
1838
+ fill = @painter__.fill
1839
+ begin
1840
+ @painter__.fill = getTint__
1841
+ img.drawImage__ @painter__, x, y, w, h
1842
+ nil
1843
+ ensure
1844
+ @painter__.fill = fill
1845
+ end
1840
1846
  end
1841
1847
 
1842
1848
  alias drawImage image
@@ -2074,9 +2080,17 @@ module Processing
2074
2080
  #
2075
2081
  def blend(img = nil, sx, sy, sw, sh, dx, dy, dw, dh, mode)
2076
2082
  assertDrawing__
2077
- (img || self).drawImage__(
2078
- @painter__, sx, sy, sw, sh, dx, dy, dw, dh,
2079
- fill: getTint__, stroke: :none, blend_mode: mode)
2083
+ fill = @painter__.fill
2084
+ blend_mode = @painter__.blend_mode
2085
+ begin
2086
+ @painter__.fill = getTint__
2087
+ @painter__.blend_mode = mode
2088
+ (img || self).drawImage__(@painter__, sx, sy, sw, sh, dx, dy, dw, dh)
2089
+ nil
2090
+ ensure
2091
+ @painter__.fill = fill
2092
+ @painter__.blend_mode = blend_mode
2093
+ end
2080
2094
  end
2081
2095
 
2082
2096
  # Loads all pixels to the 'pixels' array.
@@ -2535,11 +2549,15 @@ module Processing
2535
2549
  end
2536
2550
 
2537
2551
  # @private
2538
- def drawImage__(painter, *args, image__: getInternal__, **states)
2539
- shader = painter.shader || @filter__&.getInternal__
2540
- painter.push shader: shader, **states do |_|
2541
- painter.image image__, *args
2552
+ def drawImage__(painter, *args, image__: getInternal__)
2553
+ if @filter__
2554
+ shader = painter.shader
2555
+ painter.shader = @filter__.getInternal__
2542
2556
  end
2557
+ painter.image image__, *args
2558
+ nil
2559
+ ensure
2560
+ painter.shader = shader if @filter__
2543
2561
  end
2544
2562
 
2545
2563
  # @private
@@ -3449,6 +3467,8 @@ module Processing
3449
3467
  path = path.sub_ext ext
3450
3468
 
3451
3469
  unless path.file?
3470
+ raise NotImplementedError, 'HTTP request is not supported on WASM' if Xot.wasm?
3471
+ require 'net/http'
3452
3472
  Net::HTTP.get_response URI.parse(uri) do |res|
3453
3473
  res.value # raise an error unless successful
3454
3474
  tmpdir.mkdir unless tmpdir.directory?
@@ -200,7 +200,13 @@ module Processing
200
200
  def blend(img = nil, sx, sy, sw, sh, dx, dy, dw, dh, mode)
201
201
  img ||= self
202
202
  getInternal__.paint do |painter|
203
- img.drawImage__ painter, sx, sy, sw, sh, dx, dy, dw, dh, blend_mode: mode
203
+ blend_mode = painter.blend_mode
204
+ begin
205
+ painter.blend_mode = mode
206
+ img.drawImage__ painter, sx, sy, sw, sh, dx, dy, dw, dh
207
+ ensure
208
+ painter.blend_mode = blend_mode
209
+ end
204
210
  end
205
211
  nil
206
212
  end
@@ -270,11 +276,15 @@ module Processing
270
276
  end
271
277
 
272
278
  # @private
273
- def drawImage__(painter, *args, **states)
274
- shader = painter.shader || @filter&.getInternal__
275
- painter.push shader: shader, **states do |_|
276
- painter.image getInternal__, *args
279
+ def drawImage__(painter, *args)
280
+ if @filter
281
+ shader = painter.shader
282
+ painter.shader = @filter.getInternal__
277
283
  end
284
+ painter.image getInternal__, *args
285
+ nil
286
+ ensure
287
+ painter.shader = shader if @filter
278
288
  end
279
289
 
280
290
  # @private
@@ -71,15 +71,17 @@ module Processing
71
71
  ENV__ = {
72
72
  attribute_position: [:vertex, :position],
73
73
  attribute_texcoord: :texCoord,
74
+ attribute_texcoord_min: :texCoordMin,
75
+ attribute_texcoord_max: :texCoordMax,
74
76
  attribute_color: :color,
75
77
  varying_position: :vertPosition,
76
78
  varying_texcoord: :vertTexCoord,
79
+ varying_texcoord_min: :vertTexCoordMin,
80
+ varying_texcoord_max: :vertTexCoordMax,
77
81
  varying_color: :vertColor,
78
82
  uniform_position_matrix: [:transform, :transformMatrix],
79
83
  uniform_texcoord_matrix: :texMatrix,
80
- uniform_texcoord_min: :texMin,
81
- uniform_texcoord_max: :texMax,
82
- uniform_texcoord_offset: :texOffset,
84
+ uniform_texcoord_pixel: :texOffset,
83
85
  uniform_texture: [:texMap, :texture]
84
86
  }.freeze
85
87
 
@@ -144,10 +146,10 @@ module Processing
144
146
  #define PI 3.1415926538
145
147
  uniform float radius;
146
148
  uniform sampler2D texMap;
147
- uniform vec3 texMin;
148
- uniform vec3 texMax;
149
149
  uniform vec3 texOffset;
150
150
  varying vec4 vertTexCoord;
151
+ varying vec3 vertTexCoordMin;
152
+ varying vec3 vertTexCoordMax;
151
153
  varying vec4 vertColor;
152
154
  float gaussian(vec2 pos, float sigma) {
153
155
  float s2 = sigma * sigma;
@@ -163,8 +165,8 @@ module Processing
163
165
  float weight = gaussian(offset, sigma);
164
166
  vec2 texcoord = vertTexCoord.xy + offset * texOffset.xy;
165
167
  if (
166
- texcoord.x < texMin.x || texMax.x < texcoord.x ||
167
- texcoord.y < texMin.y || texMax.y < texcoord.y
168
+ texcoord.x < vertTexCoordMin.x || vertTexCoordMax.x < texcoord.x ||
169
+ texcoord.y < vertTexCoordMin.y || vertTexCoordMax.y < texcoord.y
168
170
  ) continue;
169
171
  color += texture2D(texMap, texcoord).rgb * weight;
170
172
  total_weight += weight;
@@ -17,6 +17,11 @@ module Processing
17
17
  @canvas_view = add CanvasView.new name: :canvas
18
18
  @overlay_view = @canvas_view.add Reflex::View.new name: :overlay
19
19
 
20
+ # On WASM, auto-GC may yield mid-draw via Asyncify and cause partial
21
+ # frames to be presented. Disable auto-GC and run GC.start manually
22
+ # at a safe point (on_canvas_update) to avoid this.
23
+ GC.disable if Xot.wasm?
24
+
20
25
  super(*args, size: [width, height], **kwargs, &block)
21
26
 
22
27
  self.center = screen.center
@@ -101,6 +106,7 @@ module Processing
101
106
  def on_canvas_update(e)
102
107
  call_block @update, e
103
108
  @canvas_view.redraw
109
+ GC.start if Xot.wasm? # explicit GC at a safe point (no draw in progress)
104
110
  end
105
111
 
106
112
  def on_canvas_draw(e)
data/processing.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  end
11
11
 
12
12
  ext = Processing::Extension
13
- name = ext.name.downcase
13
+ name = ext.name true
14
14
  rdocs = glob.call *%w[README]
15
15
 
16
16
  s.name = name
@@ -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.3.11'
30
- s.add_dependency 'rucy', '~> 0.3.11'
31
- s.add_dependency 'rays', '~> 0.3.11'
32
- s.add_dependency 'reflexion', '~> 0.3.14'
29
+ s.add_dependency 'xot', '~> 0.3.13'
30
+ s.add_dependency 'rucy', '~> 0.3.13'
31
+ s.add_dependency 'rays', '~> 0.3.13'
32
+ s.add_dependency 'reflexion', '~> 0.4.0'
33
33
 
34
34
  s.files = `git ls-files`.split $/
35
35
  s.test_files = s.files.grep %r{^(test|spec|features)/}
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.1.17
4
+ version: 1.2.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-04-17 00:00:00.000000000 Z
11
+ date: 2026-05-16 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.3.11
33
+ version: 0.3.13
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.3.11
40
+ version: 0.3.13
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.3.11
47
+ version: 0.3.13
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.3.11
54
+ version: 0.3.13
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.3.11
61
+ version: 0.3.13
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.3.11
68
+ version: 0.3.13
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.3.14
75
+ version: 0.4.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.3.14
82
+ version: 0.4.0
83
83
  description: Creative Coding Framework has API compatible to Processing or p5.js.
84
84
  email: xordog@gmail.com
85
85
  executables: []