rays 0.3.11 → 0.3.13

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: 9ccc3b9eda8d2da41df31850e7ab6a6e480936ad80dfff07adaf35e5d12c8eee
4
- data.tar.gz: 46785a8507b11be71d877a87fb8f7a42478d19190086701e748978e040a16ad3
3
+ metadata.gz: be106158650d37c835a8396c0ce079a3bc829f6fdd10fed78153f5bba6e0246d
4
+ data.tar.gz: 833c9b8cd83d1e484d8424991cffd89af1a4af0a31eead4cfe8ea52db0dacead
5
5
  SHA512:
6
- metadata.gz: 505a1f2e9a2e3a1aea29b4e69528c4741ce0ad956f9c2e7fafa67bf528b6b25c51e6d7371624d52f3a4a8e0e8fa8aab1388d76775b03445f4882af49dd3b18d0
7
- data.tar.gz: 51d70c57b927a3b8564718170962d41d36ca7f9a5b209f71c6d72e23bcd6278b8bbf73181909084bb973d2fb72aa4d7172e8ee218407ebf7236c74bb46530e6f
6
+ metadata.gz: ff626e5099d975fa64f4990d3c4ee12265612dde05b56103354f1cbbbbba6653770c183b262b757d103bf3e1c31824813d1afe66b3445c33090448587c81441a
7
+ data.tar.gz: 590f20a70dc9505ab609482ce98ff7fbcc9eddbdf03271597869dfadbec55a307f775327c41ffa7f630eef5d7dc7c0c6344d78070e9c5888f2c3bdef0f1de7cf
@@ -122,6 +122,15 @@ VALUE get_smooth(VALUE self)
122
122
  return value(THIS->smooth());
123
123
  }
124
124
 
125
+ static
126
+ VALUE compare(VALUE self, VALUE other)
127
+ {
128
+ CHECK;
129
+ auto* a = THIS->self.get();
130
+ auto* b = to<const Rays::Image*>(other)->self.get();
131
+ return value(a < b ? -1 : a > b ? 1 : 0);
132
+ }
133
+
125
134
  static
126
135
  VALUE load(VALUE self, VALUE path)
127
136
  {
@@ -149,6 +158,7 @@ Init_rays_image ()
149
158
  rb_define_private_method(cImage, "get_bitmap", RUBY_METHOD_FUNC(get_bitmap), 1);
150
159
  rb_define_method(cImage, "smooth=", RUBY_METHOD_FUNC(set_smooth), 1);
151
160
  rb_define_method(cImage, "smooth", RUBY_METHOD_FUNC(get_smooth), 0);
161
+ cImage.define_method("<=>", compare);
152
162
  cImage.define_module_function("load!", load);
153
163
  }
154
164
 
@@ -250,7 +250,17 @@ VALUE image(VALUE self)
250
250
  CHECK;
251
251
  check_arg_count(__FILE__, __LINE__, "Painter#image", argc, 1, 3, 5, 7, 9);
252
252
 
253
- const Rays::Image* image = to<Rays::Image*>(argv[0]);
253
+ RUCY_SYM(to_image);
254
+ std::unique_ptr<Rays::Image> pimage;
255
+
256
+ const Rays::Image* image = NULL;
257
+ if (argv[0].is_a(Rays::image_class()))
258
+ image = to<Rays::Image*>(argv[0]);
259
+ else if (argv[0].respond_to(to_image))
260
+ {
261
+ pimage.reset(new Rays::Image(to<const Rays::Image&>(argv[0].call(to_image))));
262
+ image = pimage.get();
263
+ }
254
264
  if (!image)
255
265
  argument_error(__FILE__, __LINE__, "%s is not an image.", argv[0].inspect().c_str());
256
266
 
@@ -742,6 +752,38 @@ VALUE pop_matrix(VALUE self)
742
752
  }
743
753
 
744
754
 
755
+ static
756
+ VALUE set_debug(VALUE self, VALUE debug)
757
+ {
758
+ CHECK;
759
+ if (debug)
760
+ THIS->remove_flag(Rays::Painter::FLAG_BATCHING);
761
+ else
762
+ THIS-> add_flag(Rays::Painter::FLAG_BATCHING);
763
+ return debug;
764
+ }
765
+
766
+ static
767
+ VALUE get_debug(VALUE self)
768
+ {
769
+ CHECK;
770
+ return value(!THIS->has_flag(Rays::Painter::FLAG_BATCHING));
771
+ }
772
+
773
+ static
774
+ VALUE set_global_debug(VALUE self, VALUE debug)
775
+ {
776
+ Rays::Painter::set_debug(debug);
777
+ return debug;
778
+ }
779
+
780
+ static
781
+ VALUE get_global_debug(VALUE self)
782
+ {
783
+ return value(Rays::Painter::debug());
784
+ }
785
+
786
+
745
787
  static Class cPainter;
746
788
 
747
789
  void
@@ -822,6 +864,12 @@ Init_rays_painter ()
822
864
  rb_define_method(cPainter, "matrix", RUBY_METHOD_FUNC(get_matrix), 0);
823
865
  rb_define_method(cPainter, "push_matrix", RUBY_METHOD_FUNC(push_matrix), 0);
824
866
  rb_define_method(cPainter, "pop_matrix", RUBY_METHOD_FUNC(pop_matrix), 0);
867
+
868
+ rb_define_method(cPainter, "debug=", RUBY_METHOD_FUNC(set_debug), 1);
869
+ cPainter.define_method("debug?", get_debug);
870
+
871
+ rb_define_singleton_method(cPainter, "debug=", RUBY_METHOD_FUNC(set_global_debug), 1);
872
+ cPainter.define_singleton_method("debug?", get_global_debug);
825
873
  }
826
874
 
827
875
 
@@ -66,15 +66,17 @@ make_env (const Value& names, const Value& ignore_no_uniform_location_error)
66
66
  to_name_list(names, 0),
67
67
  to_name_list(names, 1),
68
68
  to_name_list(names, 2),
69
- to_name( names, 3),
70
- to_name( names, 4),
69
+ to_name_list(names, 3),
70
+ to_name_list(names, 4),
71
71
  to_name( names, 5),
72
- to_name_list(names, 6),
73
- to_name_list(names, 7),
74
- to_name_list(names, 8),
75
- to_name_list(names, 9),
72
+ to_name( names, 6),
73
+ to_name( names, 7),
74
+ to_name( names, 8),
75
+ to_name( names, 9),
76
76
  to_name_list(names, 10),
77
77
  to_name_list(names, 11),
78
+ to_name_list(names, 12),
79
+ to_name_list(names, 13),
78
80
  flags);
79
81
  }
80
82
 
@@ -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:
@@ -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,32 @@
1
1
  # rays ChangeLog
2
2
 
3
3
 
4
+ ## [v0.3.13] - 2026-05-17
5
+
6
+ - Improve batch rendering: defer flush until state actually changes
7
+ - Rewrite README.md
8
+ - CI: Migrate release-gem.yml from actions/create-release to gh release create
9
+
10
+ - Fix text rendering corruption when batching is enabled
11
+
12
+
13
+ ## [v0.3.12] - 2026-05-10
14
+
15
+ - Support WebAssembly
16
+ - Implement batch rendering
17
+ - Implement WASM font rendering via HTMLCanvasElement
18
+ - Implement SDL_ttf font rendering for SDL backend
19
+ - Use GL_DEPTH_COMPONENT16 on WASM builds
20
+ - Rename texcoord_offset to texcoord_pixel
21
+ - Pin external library versions
22
+ - Add Image and Texture comparison operators
23
+ - Add Painter::set_debug / debug singleton API
24
+ - Pin position attribute to location 0 on macOS
25
+ - Simplify Painter_draw_image to fill-only
26
+ - Add libsdl2-ttf-dev to CI apt packages
27
+ - Remove deprecated has_rdoc= from gemspecs
28
+
29
+
4
30
  ## [v0.3.11] - 2026-04-17
5
31
 
6
32
  - Move OpenGL code into src/opengl/ and introduce Renderer abstraction
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Rays - A Drawing Engine using OpenGL.
1
+ # Rays - A 2D drawing engine on OpenGL
2
2
 
3
3
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/xord/rays)
4
4
  ![License](https://img.shields.io/github/license/xord/rays)
@@ -21,18 +21,39 @@ Thanks for your support! 🙌
21
21
 
22
22
  ## 🚀 About
23
23
 
24
- **Rays** is a drawing engine that utilizes OpenGL for 2D rendering.
24
+ **Rays** is a hardware-accelerated 2D drawing engine for Ruby. It is built on OpenGL and exposes a retained-mode-friendly API: build `Polygon`, `Polyline`, `Image`, `Shader`, and `Font` objects, then paint them into an off-screen `Image` (or a window provided by [Reflex](https://github.com/xord/reflex)) through a `Painter`.
25
25
 
26
- It is designed to provide efficient graphics capabilities, making it ideal for creating complex visualizations and graphics applications.
26
+ It is the rendering layer used by [Reflex](https://github.com/xord/reflex), [Processing](https://github.com/xord/processing), [RubySketch](https://github.com/xord/rubysketch), and [Reight](https://github.com/xord/reight). Like the rest of the `xord/*` family, it is primarily developed for our own use, but it also works as a standalone drawing gem.
27
+
28
+ ## 📋 Requirements
29
+
30
+ - Ruby **3.0.0** or later
31
+ - A C++ compiler with C++20 support
32
+ - [Xot](https://rubygems.org/gems/xot) and [Rucy](https://rubygems.org/gems/rucy) (declared as runtime dependencies)
33
+ - Platform graphics backend:
34
+ - **macOS** — AppKit, OpenGL, AVFoundation (bundled with the OS)
35
+ - **iOS** — UIKit, OpenGL ES, AVFoundation (bundled with the OS)
36
+ - **Windows** — GDI32, OpenGL32, GLEW (`MINGW_PACKAGE_PREFIX-glew`)
37
+ - **Linux** — `libsdl2-dev`, `libsdl2-ttf-dev`, `libglew-dev`
38
+
39
+ The following third-party libraries are cloned from GitHub and statically linked while the native extension is being built, so you do not need to install them separately:
40
+
41
+ | Library | Role |
42
+ | ------------------------------------------------------------- | ------------------------------------------------- |
43
+ | [GLM](https://github.com/g-truc/glm) | Vector / matrix math used internally |
44
+ | [Clipper](https://github.com/skyrpex/clipper) | Polygon Boolean operations (`+`, `-`, `&`, `\|`, `^`) |
45
+ | [earcut.hpp](https://github.com/mapbox/earcut.hpp) | Polygon triangulation |
46
+ | [splines-lib](https://github.com/andrewwillmott/splines-lib) | Curve / spline math |
47
+ | [stb](https://github.com/nothings/stb) (Windows / Linux only) | Image file loading |
27
48
 
28
49
  ## 📦 Installation
29
50
 
30
51
  Add this line to your Gemfile:
31
52
  ```ruby
32
- $ gem 'rays'
53
+ gem 'rays'
33
54
  ```
34
55
 
35
- Then, install gem:
56
+ Then install:
36
57
  ```bash
37
58
  $ bundle install
38
59
  ```
@@ -42,7 +63,145 @@ Or install it directly:
42
63
  $ gem install rays
43
64
  ```
44
65
 
66
+ `require 'rays'` automatically calls `Rays.init!` and registers `Rays.fin!` at exit. Set `$RAYS_NOAUTOINIT = true` before requiring if you want to manage the lifetime yourself.
67
+
68
+ Rays needs a current OpenGL context. When used through [Reflex](https://github.com/xord/reflex), the window creates and binds a context for you. To use Rays standalone for off-screen rendering, it allocates a hidden context automatically.
69
+
70
+ ## 📚 What's Included
71
+
72
+ ### Geometry and color types
73
+
74
+ | Class | Purpose |
75
+ | -------------------- | ---------------------------------------------------------------- |
76
+ | `Rays::Point` | 2D / 3D point with arithmetic operators |
77
+ | `Rays::Bounds` | Axis-aligned rectangle (position + size) |
78
+ | `Rays::Color` | RGBA color in floating-point components |
79
+ | `Rays::ColorSpace` | Pixel format / color space descriptor (RGBA, ARGB, GRAY, ...) |
80
+ | `Rays::Matrix` | 4×4 transformation matrix |
81
+
82
+ ### Drawing primitives
83
+
84
+ | Class | Purpose |
85
+ | -------------------- | -------------------------------------------------------------------------------------------- |
86
+ | `Rays::Polyline` | A single open or closed polyline; expandable into a stroked polygon |
87
+ | `Rays::Polygon` | One or more polylines forming a closed shape; supports Boolean ops via `+`, `-`, `&`, `\|`, `^` |
88
+ | `Rays::Image` | A renderable texture with an associated `Painter` for off-screen drawing |
89
+ | `Rays::Bitmap` | CPU-side pixel buffer that can be uploaded to / downloaded from an `Image` |
90
+ | `Rays::Font` | Text rendering — created from a system font name and a size |
91
+ | `Rays::Shader` | GLSL fragment / vertex shader with `set_uniform` / `uniform` for parameters |
92
+ | `Rays::Camera` | Live camera capture (per platform) rendered into an `Image` |
93
+
94
+ ### The `Painter`
95
+
96
+ `Rays::Painter` is the immediate-mode drawing surface. Obtain one through `Image#painter`, then between `painter.paint do |p| ... end` (or `begin_paint` / `end_paint`) issue draw calls:
97
+
98
+ - **Shapes** — `point`, `line`, `rect`, `ellipse`, `curve`, `bezier`, `triangle`, `quad`, `polygon`
99
+ - **Bitmaps & text** — `image`, `text`
100
+ - **State** — `fill`, `stroke`, `background`, `stroke_width`, `stroke_cap`, `stroke_join`, `blend_mode`, `clip`, `font`, `texture`, `shader`
101
+ - **Transforms** — `translate`, `scale`, `rotate`, `set_matrix`
102
+ - **Push / pop** — `push(:state, :matrix)` / `pop`, or the block form `push(fill: ..., stroke: ...) { ... }`
103
+
104
+ `stroke_cap`, `stroke_join`, `blend_mode`, `texcoord_mode`, `texcoord_wrap` accept symbols (e.g. `:round`, `:miter`, `:multiply`).
105
+
106
+ ### Top-level helpers
107
+
108
+ - `Rays.init!` / `Rays.fin!` — explicit lifecycle (called automatically on `require`)
109
+ - `Rays::Image.load(path)` / `Rays::Image#save(path)` — image file I/O
110
+ - `Rays.renderer_info` — debug info about the current OpenGL context
111
+
112
+ ## 💡 Usage
113
+
114
+ ### Draw to an off-screen image and save it
115
+
116
+ ```ruby
117
+ require 'rays'
118
+
119
+ image = Rays::Image.new(200, 200)
120
+ image.paint do |p|
121
+ p.background 0, 0, 0
122
+ p.fill 1, 0.4, 0.1 # orange-ish
123
+ p.stroke 1, 1, 1
124
+ p.stroke_width 4
125
+
126
+ p.rect 20, 20, 160, 160, round: 16
127
+ p.ellipse 100, 100, 60, 60
128
+ end
129
+
130
+ image.save 'out.png'
131
+ ```
132
+
133
+ ### Compose a polygon from Boolean operations
134
+
135
+ ```ruby
136
+ require 'rays'
137
+
138
+ a = Rays::Polygon.rect(0, 0, 100, 100)
139
+ b = Rays::Polygon.ellipse(50, 50, 80, 80)
140
+
141
+ union = a | b # outer outline of both
142
+ difference = a - b # rectangle with circular bite
143
+ intersection = a & b # lens-shaped overlap
144
+ xor = a ^ b # everything except the overlap
145
+
146
+ Rays::Image.new(140, 140).paint {|p| p.fill 1; p.polygon difference }.save 'diff.png'
147
+ ```
148
+
149
+ (The C++ operator names map to Ruby's `|`, `-`, `&`, `^`.)
150
+
151
+ ### Use a fragment shader
152
+
153
+ ```ruby
154
+ require 'rays'
155
+
156
+ invert = Rays::Shader.new <<~GLSL
157
+ uniform sampler2D texture;
158
+ varying vec4 vTexCoord;
159
+ void main() {
160
+ vec4 c = texture2D(texture, vTexCoord.xy);
161
+ gl_FragColor = vec4(1.0 - c.rgb, c.a);
162
+ }
163
+ GLSL
164
+
165
+ src = Rays::Image.load('photo.png')
166
+ out = Rays::Image.new(src.width, src.height)
167
+ out.paint do |p|
168
+ p.shader = invert
169
+ p.image src
170
+ end
171
+ out.save 'photo-invert.png'
172
+ ```
173
+
174
+ ### Drive `push` / `pop` with attributes
175
+
176
+ ```ruby
177
+ image.paint do |p|
178
+ p.push(:state, :matrix, fill: [1, 0, 0]) do
179
+ p.translate 50, 50
180
+ p.rotate 30
181
+ p.rect -20, -20, 40, 40
182
+ end
183
+ # fill, state, and matrix are restored here
184
+ end
185
+ ```
186
+
187
+ ## 🛠️ Development
188
+
189
+ ```bash
190
+ $ rake vendor # clone the external libraries into vendor/
191
+ $ rake lib # build the native C++ library (librays)
192
+ $ rake ext # build the Ruby C extension
193
+ $ rake test # run the test suite
194
+ $ rake doc # generate RDoc from C++ sources
195
+ $ rake # default: builds the extension
196
+ ```
197
+
198
+ The drawing tests render into off-screen images and compare pixels, so they require a working OpenGL context. The `test_rays_init.rb` test must run in its own process and is listed in `TESTS_ALONE`.
199
+
200
+ In the [`xord/all`](https://github.com/xord/all) monorepo you can scope by module, e.g. `rake rays test`.
201
+
45
202
  ## 📜 License
46
203
 
47
204
  **Rays** is licensed under the MIT License.
48
205
  See the [LICENSE](./LICENSE) file for details.
206
+
207
+ The third-party libraries listed above retain their own licenses (all MIT-compatible: MIT, ISC, Boost-1.0, dual-licensed MIT/Happy-Bunny for GLM, Unlicense for splines-lib).
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ TESTS_ALONE = ['test/test_rays_init.rb']
16
16
 
17
17
  install_packages(
18
18
  mingw: %w[MINGW_PACKAGE_PREFIX-glew],
19
- apt: %w[libglew-dev libsdl2-dev])
19
+ apt: %w[libglew-dev libsdl2-dev libsdl2-ttf-dev])
20
20
 
21
21
  use_external_library 'https://github.com/g-truc/glm',
22
22
  tag: '1.0.1',
@@ -29,7 +29,7 @@ use_external_library 'https://github.com/skyrpex/clipper',
29
29
  excludes: 'clipper/cpp/cpp_'
30
30
 
31
31
  use_external_library 'https://github.com/mapbox/earcut.hpp',
32
- tag: 'v2.2.4',
32
+ commit: '365be263bfed498f9d3d3bf065bcb39b0d0e4ba6',
33
33
  incdirs: 'include/mapbox',
34
34
  srcdirs: 'NOSRC'
35
35
 
@@ -44,7 +44,7 @@ use_external_library 'https://github.com/andrewwillmott/splines-lib',
44
44
  end
45
45
  }
46
46
 
47
- if win32? || linux?
47
+ if win32? || linux? || wasm?
48
48
  use_external_library 'https://github.com/nothings/stb',
49
49
  commit: 'ae721c50eaf761660b4f90cc590453cdb0c2acd0',
50
50
  srcdirs: 'NOSRC'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.11
1
+ 0.3.13
data/ext/rays/extconf.rb CHANGED
@@ -13,11 +13,10 @@ Xot::ExtConf.new Xot, Rucy, Rays do
13
13
  setup do
14
14
  headers << 'ruby.h'
15
15
  libs.unshift 'gdi32', 'opengl32', 'glew32' if win32?
16
- libs.unshift 'SDL2', 'GLEW', 'GL' if linux?
16
+ libs.unshift 'SDL2', 'SDL2_ttf', 'GLEW', 'GL' if linux? || wasm?
17
17
  frameworks << 'AppKit' << 'OpenGL' << 'AVFoundation' if osx?
18
-
19
- $CPPFLAGS << ' -DRAYS_32BIT_PIXELS_STRING' if RUBY_PLATFORM == 'x64-mingw-ucrt'
20
- $LDFLAGS << ' -Wl,--out-implib=rays_ext.dll.a' if mingw? || cygwin?
18
+ $CPPFLAGS << ' -DRAYS_32BIT_PIXELS_STRING' if RUBY_PLATFORM == 'x64-mingw-ucrt'
19
+ $LDFLAGS << ' -Wl,--out-implib=librays.dll.a' if mingw? || cygwin?
21
20
  end
22
21
 
23
22
  create_makefile 'rays_ext'
data/ext/rays/image.cpp CHANGED
@@ -134,6 +134,16 @@ RUCY_DEF0(get_smooth)
134
134
  }
135
135
  RUCY_END
136
136
 
137
+ static
138
+ RUCY_DEF1(compare, other)
139
+ {
140
+ CHECK;
141
+ auto* a = THIS->self.get();
142
+ auto* b = to<const Rays::Image*>(other)->self.get();
143
+ return value(a < b ? -1 : a > b ? 1 : 0);
144
+ }
145
+ RUCY_END
146
+
137
147
  static
138
148
  RUCY_DEF1(load, path)
139
149
  {
@@ -162,6 +172,7 @@ Init_rays_image ()
162
172
  cImage.define_private_method("get_bitmap", get_bitmap);
163
173
  cImage.define_method("smooth=", set_smooth);
164
174
  cImage.define_method("smooth", get_smooth);
175
+ cImage.define_method("<=>", compare);
165
176
  cImage.define_module_function("load!", load);
166
177
  }
167
178