Rays - A 2D drawing engine on OpenGL
⚠️ Notice
This repository is a read-only mirror of our monorepo. We do not accept pull requests or direct contributions here.
🔄 Where to Contribute?
All development happens in our xord/all monorepo, which contains all our main libraries. If you'd like to contribute, please submit your changes there.
For more details, check out our Contribution Guidelines.
Thanks for your support! 🙌
🚀 About
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) through a Painter.
It is the rendering layer used by Reflex, Processing, RubySketch, and 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.
📋 Requirements
- Ruby 3.0.0 or later
- A C++ compiler with C++20 support
- Xot and Rucy (declared as runtime dependencies)
- Platform graphics backend:
- macOS — AppKit, OpenGL, AVFoundation (bundled with the OS)
- iOS — UIKit, OpenGL ES, AVFoundation (bundled with the OS)
- Windows — GDI32, OpenGL32, GLEW (
MINGW_PACKAGE_PREFIX-glew) - Linux —
libsdl2-dev,libsdl2-ttf-dev,libglew-dev
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:
| Library | Role |
|---|---|
| GLM | Vector / matrix math used internally |
| Clipper | Polygon Boolean operations (+, -, &, `\ |
| earcut.hpp | Polygon triangulation |
| splines-lib | Curve / spline math |
| stb (Windows / Linux only) | Image file loading |
📦 Installation
Add this line to your Gemfile:
gem 'rays'
Then install:
$ bundle install
Or install it directly:
$ gem install rays
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.
Rays needs a current OpenGL context. When used through Reflex, the window creates and binds a context for you. To use Rays standalone for off-screen rendering, it allocates a hidden context automatically.
📚 What's Included
Geometry and color types
| Class | Purpose |
|---|---|
Rays::Point |
2D / 3D point with arithmetic operators |
Rays::Bounds |
Axis-aligned rectangle (position + size) |
Rays::Color |
RGBA color in floating-point components |
Rays::ColorSpace |
Pixel format / color space descriptor (RGBA, ARGB, GRAY, ...) |
Rays::Matrix |
4×4 transformation matrix |
Drawing primitives
| Class | Purpose |
|---|---|
Rays::Polyline |
A single open or closed polyline; expandable into a stroked polygon |
Rays::Polygon |
One or more polylines forming a closed shape; supports Boolean ops via +, -, &, `\ |
Rays::Image |
A renderable texture with an associated Painter for off-screen drawing |
Rays::Bitmap |
CPU-side pixel buffer that can be uploaded to / downloaded from an Image |
Rays::Font |
Text rendering — created from a system font name and a size |
Rays::Shader |
GLSL fragment / vertex shader with set_uniform / uniform for parameters |
Rays::Camera |
Live camera capture (per platform) rendered into an Image |
The Painter
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:
- Shapes —
point,line,rect,ellipse,curve,bezier,triangle,quad,polygon - Bitmaps & text —
image,text - State —
fill,stroke,background,stroke_width,stroke_cap,stroke_join,blend_mode,clip,font,texture,shader - Transforms —
translate,scale,rotate,set_matrix - Push / pop —
push(:state, :matrix)/pop, or the block formpush(fill: ..., stroke: ...) { ... }
stroke_cap, stroke_join, blend_mode, texcoord_mode, texcoord_wrap accept symbols (e.g. :round, :miter, :multiply).
Top-level helpers
Rays.init!/Rays.fin!— explicit lifecycle (called automatically onrequire)Rays::Image.load(path)/Rays::Image#save(path)— image file I/ORays.renderer_info— debug info about the current OpenGL context
💡 Usage
Draw to an off-screen image and save it
require 'rays'
image = Rays::Image.new(200, 200)
image.paint do |p|
p.background 0, 0, 0
p.fill 1, 0.4, 0.1 # orange-ish
p.stroke 1, 1, 1
p.stroke_width 4
p.rect 20, 20, 160, 160, round: 16
p.ellipse 100, 100, 60, 60
end
image.save 'out.png'
Compose a polygon from Boolean operations
require 'rays'
a = Rays::Polygon.rect(0, 0, 100, 100)
b = Rays::Polygon.ellipse(50, 50, 80, 80)
union = a | b # outer outline of both
difference = a - b # rectangle with circular bite
intersection = a & b # lens-shaped overlap
xor = a ^ b # everything except the overlap
Rays::Image.new(140, 140).paint {|p| p.fill 1; p.polygon difference }.save 'diff.png'
(The C++ operator names map to Ruby's |, -, &, ^.)
Use a fragment shader
require 'rays'
invert = Rays::Shader.new <<~GLSL
uniform sampler2D texture;
varying vec4 vTexCoord;
void main() {
vec4 c = texture2D(texture, vTexCoord.xy);
gl_FragColor = vec4(1.0 - c.rgb, c.a);
}
GLSL
src = Rays::Image.load('photo.png')
out = Rays::Image.new(src.width, src.height)
out.paint do |p|
p.shader = invert
p.image src
end
out.save 'photo-invert.png'
Drive push / pop with attributes
image.paint do |p|
p.push(:state, :matrix, fill: [1, 0, 0]) do
p.translate 50, 50
p.rotate 30
p.rect -20, -20, 40, 40
end
# fill, state, and matrix are restored here
end
🛠️ Development
$ rake vendor # clone the external libraries into vendor/
$ rake lib # build the native C++ library (librays)
$ rake ext # build the Ruby C extension
$ rake test # run the test suite
$ rake doc # generate RDoc from C++ sources
$ rake # default: builds the extension
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.
In the xord/all monorepo you can scope by module, e.g. rake rays test.
📜 License
Rays is licensed under the MIT License. See the LICENSE file for details.
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).