Skip to content

feat: add SDL2::Renderer#read_pixels#29

Closed
takaokouji wants to merge 2 commits intoohai:masterfrom
smalruby:smalruby/add-read-pixels
Closed

feat: add SDL2::Renderer#read_pixels#29
takaokouji wants to merge 2 commits intoohai:masterfrom
smalruby:smalruby/add-read-pixels

Conversation

@takaokouji
Copy link
Copy Markdown
Contributor

Summary

Add SDL2::Renderer#read_pixels method that wraps SDL_RenderReadPixels.

This enables reading pixel data from the current rendering target, which is essential for taking screenshots of the rendered screen.

Usage

# Read the entire screen (ARGB8888 format)
pixels = renderer.read_pixels(nil, 0)

# Read a specific area
rect = SDL2::Rect.new(0, 0, 100, 100)
pixels = renderer.read_pixels(rect, 0)

Parameters

  • rectSDL2::Rect or nil (nil reads the entire target)
  • format — pixel format as Integer (0 defaults to SDL_PIXELFORMAT_ARGB8888)

Returns

Raw pixel data as a binary String.

Motivation

As noted in #28, there is currently no way to capture screen contents for screenshots. This method provides the missing functionality by wrapping SDL_RenderReadPixels.

Closes #28

@takaokouji takaokouji force-pushed the smalruby/add-read-pixels branch from ea83589 to 984ba4f Compare March 27, 2026 12:10
takaokouji and others added 2 commits March 27, 2026 21:52
Replace all deprecated Data_Wrap_Struct/Data_Get_Struct with
TypedData_Wrap_Struct/TypedData_Get_Struct across all source files.

Add DEFINE_DATA_TYPE helper macro to rubysdl2_internal.h for easy
rb_data_type_t definition. Eliminates all deprecation warnings on
Ruby 3.4.x while maintaining backward compatibility with Ruby 3.3.x.

Files changed:
- rubysdl2_internal.h: DEFINE_DATA_TYPE macro, DEFINE_GETTER uses TypedData
- video.c.m4: Window, Renderer, Texture, Surface, DisplayMode, Rect, Point
- event.c: SDL_Event (EVENT_READER/EVENT_WRITER macros + all event types)
- mixer.c.m4: Chunk, Music
- ttf.c.m4: TTF
- joystick.c.m4: Joystick
- gamecontroller.c.m4: GameController
- gl.c.m4: GLContext

Closes #1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wraps SDL_RenderReadPixels to read pixel data from the current
rendering target. Returns raw pixel data as a binary string.
Default format is ARGB8888. Pass nil for rect to read the entire target.

Refs: ohai#28

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@takaokouji takaokouji force-pushed the smalruby/add-read-pixels branch from 984ba4f to e3b7a77 Compare March 27, 2026 14:05
@ohai
Copy link
Copy Markdown
Owner

ohai commented Mar 29, 2026

Thank you for your PR.
Would you give me a sample Ruby program to demonstrate the new method?

@takaokouji
Copy link
Copy Markdown
Contributor Author

Thank you for reviewing!

Here is a real-world usage from Smalruby. We use read_pixels to capture the SDL2 window contents for debugging screenshots:

https://github.com/smalruby/smalruby3-editor/blob/77c82085940532d7b7097b3afe8686134c131261/ruby/smalruby3/lib/smalruby3/render/renderer.rb#L229

Here is a standalone sample program that saves the rendered screen as a BMP file (no extra image library needed):

require "sdl2"

SDL2.init(SDL2::INIT_VIDEO)
window = SDL2::Window.create("read_pixels demo", 100, 100, 320, 240, 0)
renderer = window.create_renderer(-1, 0)

# Draw something
renderer.draw_color = [0, 128, 255]
renderer.fill_rect(SDL2::Rect.new(60, 40, 200, 160))
renderer.draw_color = [255, 255, 0]
renderer.fill_rect(SDL2::Rect.new(110, 80, 100, 80))
renderer.present

# Capture the rendered pixels (ARGB8888, little-endian → BGRA bytes)
pixels = renderer.read_pixels(nil, 0)

# Save as BMP
width, height = 320, 240
row_size = width * 4
file_size = 54 + row_size * height
bmp = String.new(encoding: Encoding::BINARY)
bmp << ["BM", file_size, 0, 0, 54].pack("A2Vv2V")           # file header
bmp << [40, width, height, 1, 32, 0, 0, 0, 0, 0, 0].pack("VV2v2V6") # DIB header
# BMP stores rows bottom-to-top; pixel data is already BGRA which BMP expects
(height - 1).downto(0) do |y|
  bmp << pixels.byteslice(y * row_size, row_size)
end
File.binwrite("screenshot.bmp", bmp)
puts "Saved screenshot.bmp (#{File.size("screenshot.bmp")} bytes)"

PNG conversion requires an image utility library, so I used BMP here to keep the example dependency-free.

@ohai
Copy link
Copy Markdown
Owner

ohai commented Apr 13, 2026

After the assessment of the patch, I have concluded that the quality is not satisfactory:

  • The new method does not accept SDL2::PixelFormat
  • The default pixel format is not ARGB8888. See https://wiki.libsdl.org/SDL2/SDL_RenderReadPixels
  • The attached sample code does not work as intended in some environments, such as Wayland on Linux. See the above official document.

@ohai ohai closed this Apr 13, 2026
takaokouji added a commit to smalruby/ruby-sdl2 that referenced this pull request Apr 14, 2026
- Accept SDL2::PixelFormat in addition to Integer by using
  uint32_for_format() instead of NUM2UINT()
- Remove incorrect ARGB8888 default override; pass format=0 through
  to SDL_RenderReadPixels as-is (SDL2 uses the rendering target's
  format when 0 is specified)
- Query renderer info to determine correct pitch when format is 0
- Improve RDoc with @overload, usage examples, and caveats

Addresses feedback from ohai#29.
Fixes #9

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
takaokouji added a commit to smalruby/smalruby3-editor that referenced this pull request Apr 14, 2026
…xels

- Update screenshot.rb to explicitly specify SDL2::PixelFormat::ARGB8888
  instead of relying on format=0 default, ensuring consistent byte order
  across all platforms (including Wayland/Linux)
- Update ruby-sdl2 submodule to include read_pixels fixes:
  - Accept SDL2::PixelFormat objects (not just Integer)
  - Remove incorrect ARGB8888 default override
  - Query renderer info for correct pitch when format=0

Addresses feedback from ohai/ruby-sdl2#29.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
takaokouji added a commit to smalruby/ruby-sdl2 that referenced this pull request Apr 14, 2026
- Accept SDL2::PixelFormat in addition to Integer by using
  uint32_for_format() instead of NUM2UINT()
- Remove incorrect ARGB8888 default override; pass format=0 through
  to SDL_RenderReadPixels as-is (SDL2 uses the rendering target's
  format when 0 is specified)
- Query renderer info to determine correct pitch when format is 0
- Improve RDoc with @overload, usage examples, and caveats

Addresses feedback from ohai#29.
Fixes #9

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@takaokouji
Copy link
Copy Markdown
Contributor Author

Thank you for the detailed review. I have addressed all three points and resubmitted as #31:

  1. SDL2::PixelFormat support — The format parameter now accepts both SDL2::PixelFormat and Integer, using uint32_for_format() (consistent with create_texture etc.)
  2. Default pixel format — Removed the ARGB8888 override. Passing 0 now correctly uses the rendering target's native format as defined by SDL2.
  3. Sample program — Included sample/test_read_pixels.rb which explicitly specifies SDL2::PixelFormat::ARGB8888 to ensure consistent behavior across platforms.

Would you please review #31 when you have a chance?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

How to take a screenshot of the screen?

2 participants