Rays - A 2D drawing engine on OpenGL

May 15, 2026 ยท View on GitHub

Ask DeepWiki License Build Status Gem Version

โš ๏ธ 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:

LibraryRole
GLMVector / matrix math used internally
ClipperPolygon Boolean operations (+, -, &, |, ^)
earcut.hppPolygon triangulation
splines-libCurve / 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

ClassPurpose
Rays::Point2D / 3D point with arithmetic operators
Rays::BoundsAxis-aligned rectangle (position + size)
Rays::ColorRGBA color in floating-point components
Rays::ColorSpacePixel format / color space descriptor (RGBA, ARGB, GRAY, ...)
`Rays::Matrix$4 \times 4 \text{transformation} \text{matrix}

\text{Drawing} \text{primitives}

\text{Class}\text{Purpose}
$Rays::Polyline`A single open or closed polyline; expandable into a stroked polygon
Rays::PolygonOne or more polylines forming a closed shape; supports Boolean ops via +, -, &, |, ^
Rays::ImageA renderable texture with an associated Painter for off-screen drawing
Rays::BitmapCPU-side pixel buffer that can be uploaded to / downloaded from an Image
Rays::FontText rendering โ€” created from a system font name and a size
Rays::ShaderGLSL fragment / vertex shader with set_uniform / uniform for parameters
Rays::CameraLive 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 form push(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 on require)
  • Rays::Image.load(path) / Rays::Image#save(path) โ€” image file I/O
  • Rays.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).