teek-sdl2 0.1.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 +7 -0
- data/ext/teek_sdl2/extconf.rb +112 -0
- data/ext/teek_sdl2/sdl2bridge.c +142 -0
- data/ext/teek_sdl2/sdl2image.c +83 -0
- data/ext/teek_sdl2/sdl2pixels.c +166 -0
- data/ext/teek_sdl2/sdl2surface.c +534 -0
- data/ext/teek_sdl2/sdl2text.c +234 -0
- data/ext/teek_sdl2/teek_sdl2.c +65 -0
- data/ext/teek_sdl2/teek_sdl2.h +63 -0
- data/lib/teek/sdl2/font.rb +94 -0
- data/lib/teek/sdl2/renderer.rb +227 -0
- data/lib/teek/sdl2/texture.rb +91 -0
- data/lib/teek/sdl2/version.rb +7 -0
- data/lib/teek/sdl2/viewport.rb +230 -0
- data/lib/teek/sdl2.rb +69 -0
- data/teek-sdl2.gemspec +28 -0
- data/test/test_callback_teardown.rb +35 -0
- data/test/test_helper.rb +19 -0
- data/test/test_image.rb +87 -0
- data/test/test_input.rb +185 -0
- data/test/test_renderer.rb +138 -0
- data/test/test_sdl2.rb +24 -0
- data/test/test_viewport.rb +89 -0
- metadata +120 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3d084a6f681a0b04a3b68ad4bc0b905ff12a50e8cf08060982752601f1d33cad
|
|
4
|
+
data.tar.gz: 1ecc0e2befda331f9f427e9885d86f2a0261d609ca2cb6600c7a8f6ab3d92add
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 7f847401b7b73c85de8d153ca1275d5756b6d5fa258b1dfd7f7a3d4ff451775fa2d47fc541e0234e405c1b01cf49ee49f7cfa7bb32c042b7a1934aad8fcab246
|
|
7
|
+
data.tar.gz: 819be6909a74692e4de3a6e0c00b79305c7f899ba7175e0fe01ae815b9c549dfe5360a827af04988317e3a77806e953021cffa452285f506664b3d298167dd1c
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mkmf'
|
|
4
|
+
|
|
5
|
+
def find_sdl2
|
|
6
|
+
# 1. Try SDL2_DIR env var first (explicit override)
|
|
7
|
+
if ENV['SDL2_DIR']
|
|
8
|
+
dir = ENV['SDL2_DIR']
|
|
9
|
+
$INCFLAGS << " -I#{dir}/include"
|
|
10
|
+
$LDFLAGS << " -L#{dir}/lib"
|
|
11
|
+
if have_header('SDL2/SDL.h') && have_library('SDL2')
|
|
12
|
+
return true
|
|
13
|
+
end
|
|
14
|
+
# Also try flat include layout (SDL.h directly in include/)
|
|
15
|
+
if have_header('SDL.h') && have_library('SDL2')
|
|
16
|
+
return true
|
|
17
|
+
end
|
|
18
|
+
abort "SDL2_DIR=#{dir} set but SDL2 not found there"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# 2. Try pkg-config
|
|
22
|
+
if pkg_config('sdl2')
|
|
23
|
+
return true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# 3. Try Homebrew paths (macOS)
|
|
27
|
+
homebrew_dirs = [
|
|
28
|
+
'/opt/homebrew/opt/sdl2', # Apple Silicon
|
|
29
|
+
'/usr/local/opt/sdl2' # Intel
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
homebrew_dirs.each do |dir|
|
|
33
|
+
next unless File.directory?(dir)
|
|
34
|
+
inc = "#{dir}/include"
|
|
35
|
+
lib = "#{dir}/lib"
|
|
36
|
+
|
|
37
|
+
# Homebrew SDL2 puts headers in include/SDL2/
|
|
38
|
+
if File.exist?("#{inc}/SDL2/SDL.h")
|
|
39
|
+
$INCFLAGS << " -I#{inc}"
|
|
40
|
+
$LDFLAGS << " -L#{lib}"
|
|
41
|
+
if have_header('SDL2/SDL.h') && have_library('SDL2')
|
|
42
|
+
return true
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# 4. Try standard system paths
|
|
48
|
+
system_dirs = ['/usr/local', '/usr']
|
|
49
|
+
system_dirs.each do |dir|
|
|
50
|
+
inc = "#{dir}/include"
|
|
51
|
+
lib = "#{dir}/lib"
|
|
52
|
+
if File.exist?("#{inc}/SDL2/SDL.h")
|
|
53
|
+
$INCFLAGS << " -I#{inc}"
|
|
54
|
+
$LDFLAGS << " -L#{lib}"
|
|
55
|
+
if have_header('SDL2/SDL.h') && have_library('SDL2')
|
|
56
|
+
return true
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# 5. MSYS2/MinGW (Windows)
|
|
62
|
+
if RbConfig::CONFIG['host_os'] =~ /mingw|mswin/
|
|
63
|
+
# MSYS2 installs to the mingw prefix
|
|
64
|
+
mingw_prefix = RbConfig::CONFIG['prefix'] # e.g. C:/msys64/mingw64
|
|
65
|
+
inc = "#{mingw_prefix}/include"
|
|
66
|
+
lib = "#{mingw_prefix}/lib"
|
|
67
|
+
if File.exist?("#{inc}/SDL2/SDL.h")
|
|
68
|
+
$INCFLAGS << " -I#{inc}"
|
|
69
|
+
$LDFLAGS << " -L#{lib}"
|
|
70
|
+
# MinGW uses -lSDL2 (same name)
|
|
71
|
+
if have_header('SDL2/SDL.h') && have_library('SDL2')
|
|
72
|
+
return true
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
false
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
unless find_sdl2
|
|
81
|
+
abort <<~MSG
|
|
82
|
+
SDL2 not found. Install it:
|
|
83
|
+
macOS: brew install sdl2
|
|
84
|
+
Debian: sudo apt-get install libsdl2-dev
|
|
85
|
+
Windows: pacman -S mingw-w64-x86_64-SDL2 (MSYS2)
|
|
86
|
+
Or set SDL2_DIR=/path/to/sdl2
|
|
87
|
+
MSG
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# SDL2_ttf for text rendering
|
|
91
|
+
unless pkg_config('SDL2_ttf') || have_library('SDL2_ttf', 'TTF_Init', 'SDL2/SDL_ttf.h')
|
|
92
|
+
abort <<~MSG
|
|
93
|
+
SDL2_ttf not found. Install it:
|
|
94
|
+
macOS: brew install sdl2_ttf
|
|
95
|
+
Debian: sudo apt-get install libsdl2-ttf-dev
|
|
96
|
+
Windows: pacman -S mingw-w64-x86_64-SDL2_ttf (MSYS2)
|
|
97
|
+
MSG
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# SDL2_image for loading PNG, JPG, WebP, etc.
|
|
101
|
+
unless pkg_config('SDL2_image') || have_library('SDL2_image', 'IMG_Init', 'SDL2/SDL_image.h')
|
|
102
|
+
abort <<~MSG
|
|
103
|
+
SDL2_image not found. Install it:
|
|
104
|
+
macOS: brew install sdl2_image
|
|
105
|
+
Debian: sudo apt-get install libsdl2-image-dev
|
|
106
|
+
Windows: pacman -S mingw-w64-x86_64-SDL2_image (MSYS2)
|
|
107
|
+
MSG
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
$srcs = ['teek_sdl2.c', 'sdl2surface.c', 'sdl2bridge.c', 'sdl2text.c', 'sdl2pixels.c', 'sdl2image.c']
|
|
111
|
+
|
|
112
|
+
create_makefile('teek_sdl2')
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#include "teek_sdl2.h"
|
|
2
|
+
|
|
3
|
+
/* ---------------------------------------------------------
|
|
4
|
+
* Layer 2: Tk bridge
|
|
5
|
+
*
|
|
6
|
+
* Embeds an SDL2 window into a Tk frame using
|
|
7
|
+
* SDL_CreateWindowFrom(). This is the only file that knows
|
|
8
|
+
* about Tk's winfo id. It produces a Layer 1 Renderer.
|
|
9
|
+
* --------------------------------------------------------- */
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* Teek::SDL2.create_renderer_from_handle(native_handle) -> Renderer
|
|
13
|
+
*
|
|
14
|
+
* Creates an SDL2 window embedded in the native window identified by
|
|
15
|
+
* native_handle (from Tk's 'winfo id'), then creates a GPU-accelerated
|
|
16
|
+
* renderer on it.
|
|
17
|
+
*
|
|
18
|
+
* The Ruby Viewport class is responsible for getting the handle and
|
|
19
|
+
* calling this. This C function just does the SDL2 work.
|
|
20
|
+
*/
|
|
21
|
+
static VALUE
|
|
22
|
+
bridge_create_renderer_from_handle(VALUE self, VALUE handle_val)
|
|
23
|
+
{
|
|
24
|
+
ensure_sdl2_init();
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* Handle comes from Teek::Interp#native_window_handle as an Integer:
|
|
28
|
+
* macOS: NSWindow* pointer
|
|
29
|
+
* X11: X Window ID
|
|
30
|
+
* Win: HWND
|
|
31
|
+
*/
|
|
32
|
+
void *native_handle = (void *)(uintptr_t)NUM2ULL(handle_val);
|
|
33
|
+
|
|
34
|
+
if (!native_handle) {
|
|
35
|
+
rb_raise(rb_eArgError, "invalid native handle (NULL)");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
SDL_Window *window = SDL_CreateWindowFrom(native_handle);
|
|
39
|
+
if (!window) {
|
|
40
|
+
rb_raise(rb_eRuntimeError, "SDL_CreateWindowFrom failed: %s", SDL_GetError());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
SDL_Renderer *sdl_ren = SDL_CreateRenderer(window, -1,
|
|
44
|
+
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
|
45
|
+
if (!sdl_ren) {
|
|
46
|
+
/* Fall back to software if GPU not available */
|
|
47
|
+
sdl_ren = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
|
|
48
|
+
}
|
|
49
|
+
if (!sdl_ren) {
|
|
50
|
+
SDL_DestroyWindow(window);
|
|
51
|
+
rb_raise(rb_eRuntimeError, "SDL_CreateRenderer failed: %s", SDL_GetError());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Wrap in a Renderer object (Layer 1) */
|
|
55
|
+
VALUE klass = rb_const_get(mTeekSDL2, rb_intern("Renderer"));
|
|
56
|
+
VALUE obj = rb_obj_alloc(klass);
|
|
57
|
+
|
|
58
|
+
struct sdl2_renderer *r;
|
|
59
|
+
TypedData_Get_Struct(obj, struct sdl2_renderer, &renderer_type, r);
|
|
60
|
+
r->window = window;
|
|
61
|
+
r->renderer = sdl_ren;
|
|
62
|
+
r->owned_window = 0; /* Tk owns the parent window */
|
|
63
|
+
r->destroyed = 0;
|
|
64
|
+
|
|
65
|
+
return obj;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* C-level poll function — called directly from teek's event source
|
|
70
|
+
* check proc via function pointer. No Ruby overhead.
|
|
71
|
+
*
|
|
72
|
+
* Signature: void (*)(void *client_data)
|
|
73
|
+
*/
|
|
74
|
+
static void
|
|
75
|
+
sdl2_event_check(void *client_data)
|
|
76
|
+
{
|
|
77
|
+
(void)client_data;
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
* Intentionally a no-op for now. SDL_PollEvent() on macOS pumps
|
|
81
|
+
* the Cocoa run loop, which steals events from Tk and can freeze
|
|
82
|
+
* other windows (e.g. the debug inspector).
|
|
83
|
+
*
|
|
84
|
+
* When we need SDL events (mouse/kb in the viewport), this will
|
|
85
|
+
* be wired up carefully to avoid Cocoa event conflicts.
|
|
86
|
+
*/
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/*
|
|
90
|
+
* Teek::SDL2.poll_events -> Integer
|
|
91
|
+
*
|
|
92
|
+
* Manual pump for use outside the event source (e.g. testing).
|
|
93
|
+
*/
|
|
94
|
+
static VALUE
|
|
95
|
+
bridge_poll_events(VALUE self)
|
|
96
|
+
{
|
|
97
|
+
SDL_Event event;
|
|
98
|
+
int count = 0;
|
|
99
|
+
|
|
100
|
+
while (SDL_PollEvent(&event)) {
|
|
101
|
+
count++;
|
|
102
|
+
}
|
|
103
|
+
return INT2NUM(count);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/*
|
|
107
|
+
* Teek::SDL2._event_check_fn_ptr -> Integer
|
|
108
|
+
*
|
|
109
|
+
* Returns the address of the C-level SDL2 event check function.
|
|
110
|
+
* Passed to Teek._register_event_source for hot-path polling.
|
|
111
|
+
*/
|
|
112
|
+
static VALUE
|
|
113
|
+
bridge_event_check_fn_ptr(VALUE self)
|
|
114
|
+
{
|
|
115
|
+
return ULL2NUM((uintptr_t)sdl2_event_check);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
* Teek::SDL2.sdl_quit
|
|
120
|
+
*
|
|
121
|
+
* Shuts down SDL2 subsystems. Called at process exit.
|
|
122
|
+
*/
|
|
123
|
+
static VALUE
|
|
124
|
+
bridge_sdl_quit(VALUE self)
|
|
125
|
+
{
|
|
126
|
+
SDL_Quit();
|
|
127
|
+
return Qnil;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* ---------------------------------------------------------
|
|
131
|
+
* Init
|
|
132
|
+
* --------------------------------------------------------- */
|
|
133
|
+
|
|
134
|
+
void
|
|
135
|
+
Init_sdl2bridge(VALUE mTeekSDL2)
|
|
136
|
+
{
|
|
137
|
+
rb_define_module_function(mTeekSDL2, "create_renderer_from_handle",
|
|
138
|
+
bridge_create_renderer_from_handle, 1);
|
|
139
|
+
rb_define_module_function(mTeekSDL2, "poll_events", bridge_poll_events, 0);
|
|
140
|
+
rb_define_module_function(mTeekSDL2, "_event_check_fn_ptr", bridge_event_check_fn_ptr, 0);
|
|
141
|
+
rb_define_module_function(mTeekSDL2, "sdl_quit", bridge_sdl_quit, 0);
|
|
142
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#include "teek_sdl2.h"
|
|
2
|
+
#include <SDL2/SDL_image.h>
|
|
3
|
+
|
|
4
|
+
/* ---------------------------------------------------------
|
|
5
|
+
* SDL2_image wrapper
|
|
6
|
+
*
|
|
7
|
+
* Loads image files (PNG, JPG, WebP, BMP, etc.) directly
|
|
8
|
+
* into SDL2 textures via IMG_LoadTexture.
|
|
9
|
+
* --------------------------------------------------------- */
|
|
10
|
+
|
|
11
|
+
static int img_initialized = 0;
|
|
12
|
+
|
|
13
|
+
static void
|
|
14
|
+
ensure_img_init(void)
|
|
15
|
+
{
|
|
16
|
+
if (img_initialized) return;
|
|
17
|
+
|
|
18
|
+
int flags = IMG_INIT_PNG | IMG_INIT_JPG;
|
|
19
|
+
int initted = IMG_Init(flags);
|
|
20
|
+
|
|
21
|
+
/* Not fatal if a specific codec is missing — IMG_LoadTexture
|
|
22
|
+
* will fail at load time with a clear error message. We just
|
|
23
|
+
* want to preload the common ones. */
|
|
24
|
+
(void)initted;
|
|
25
|
+
img_initialized = 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* Teek::SDL2::Renderer#load_image(path) -> Texture
|
|
30
|
+
*
|
|
31
|
+
* Load an image file into a GPU texture. Supports PNG, JPG, BMP,
|
|
32
|
+
* GIF, WebP, TGA, and other formats via SDL2_image.
|
|
33
|
+
*
|
|
34
|
+
* The returned texture has alpha blending enabled and its width/height
|
|
35
|
+
* set from the image dimensions.
|
|
36
|
+
*/
|
|
37
|
+
static VALUE
|
|
38
|
+
renderer_load_image(VALUE self, VALUE path)
|
|
39
|
+
{
|
|
40
|
+
struct sdl2_renderer *ren = get_renderer(self);
|
|
41
|
+
|
|
42
|
+
ensure_sdl2_init();
|
|
43
|
+
ensure_img_init();
|
|
44
|
+
|
|
45
|
+
StringValue(path);
|
|
46
|
+
const char *cpath = StringValueCStr(path);
|
|
47
|
+
|
|
48
|
+
SDL_Texture *texture = IMG_LoadTexture(ren->renderer, cpath);
|
|
49
|
+
if (!texture) {
|
|
50
|
+
rb_raise(rb_eRuntimeError, "IMG_LoadTexture failed: %s", IMG_GetError());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Query dimensions */
|
|
54
|
+
int w, h;
|
|
55
|
+
SDL_QueryTexture(texture, NULL, NULL, &w, &h);
|
|
56
|
+
|
|
57
|
+
/* Enable alpha blending (common for PNGs with transparency) */
|
|
58
|
+
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
|
|
59
|
+
|
|
60
|
+
/* Wrap as a Texture object */
|
|
61
|
+
VALUE klass = rb_const_get(mTeekSDL2, rb_intern("Texture"));
|
|
62
|
+
VALUE obj = rb_obj_alloc(klass);
|
|
63
|
+
|
|
64
|
+
struct sdl2_texture *t;
|
|
65
|
+
TypedData_Get_Struct(obj, struct sdl2_texture, &texture_type, t);
|
|
66
|
+
t->texture = texture;
|
|
67
|
+
t->w = w;
|
|
68
|
+
t->h = h;
|
|
69
|
+
t->renderer_obj = self;
|
|
70
|
+
|
|
71
|
+
return obj;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* ---------------------------------------------------------
|
|
75
|
+
* Init
|
|
76
|
+
* --------------------------------------------------------- */
|
|
77
|
+
|
|
78
|
+
void
|
|
79
|
+
Init_sdl2image(VALUE mTeekSDL2)
|
|
80
|
+
{
|
|
81
|
+
VALUE cRenderer = rb_const_get(mTeekSDL2, rb_intern("Renderer"));
|
|
82
|
+
rb_define_method(cRenderer, "load_image", renderer_load_image, 1);
|
|
83
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#include "teek_sdl2.h"
|
|
2
|
+
|
|
3
|
+
/* ---------------------------------------------------------
|
|
4
|
+
* Pixel format conversion helpers
|
|
5
|
+
*
|
|
6
|
+
* Fast C-level conversion from various pixel formats to
|
|
7
|
+
* ARGB8888 (our native SDL2 texture format). Designed for
|
|
8
|
+
* emulators and games that output pixels in different formats.
|
|
9
|
+
* --------------------------------------------------------- */
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* Teek::SDL2::Pixels.pack_uint32(array, width, height) -> String
|
|
13
|
+
*
|
|
14
|
+
* Packs an Array of uint32 integers into an ARGB8888 byte string
|
|
15
|
+
* suitable for Texture#update. Each integer is treated as a
|
|
16
|
+
* native-endian 32-bit pixel value.
|
|
17
|
+
*
|
|
18
|
+
* This is the fast path for optcarrot and similar emulators that
|
|
19
|
+
* output pre-palette-mapped uint32 pixel arrays.
|
|
20
|
+
*/
|
|
21
|
+
static VALUE
|
|
22
|
+
pixels_pack_uint32(VALUE self, VALUE ary, VALUE vw, VALUE vh)
|
|
23
|
+
{
|
|
24
|
+
int w = NUM2INT(vw);
|
|
25
|
+
int h = NUM2INT(vh);
|
|
26
|
+
long expected = (long)w * h;
|
|
27
|
+
long len;
|
|
28
|
+
VALUE result;
|
|
29
|
+
uint32_t *dst;
|
|
30
|
+
long i;
|
|
31
|
+
|
|
32
|
+
Check_Type(ary, T_ARRAY);
|
|
33
|
+
len = RARRAY_LEN(ary);
|
|
34
|
+
|
|
35
|
+
if (len < expected) {
|
|
36
|
+
rb_raise(rb_eArgError,
|
|
37
|
+
"array too short: need %ld pixels, got %ld", expected, len);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
result = rb_str_new(NULL, expected * 4);
|
|
41
|
+
dst = (uint32_t *)RSTRING_PTR(result);
|
|
42
|
+
|
|
43
|
+
for (i = 0; i < expected; i++) {
|
|
44
|
+
dst[i] = (uint32_t)NUM2UINT(rb_ary_entry(ary, i));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/*
|
|
51
|
+
* Teek::SDL2::Pixels.convert(source, width, height, from_format) -> String
|
|
52
|
+
*
|
|
53
|
+
* Converts a pixel byte string from one format to ARGB8888.
|
|
54
|
+
*
|
|
55
|
+
* Supported from_format values:
|
|
56
|
+
* :argb8888 - passthrough (no conversion)
|
|
57
|
+
* :rgba8888 - RGBA -> ARGB byte shuffle
|
|
58
|
+
* :bgra8888 - BGRA -> ARGB byte shuffle
|
|
59
|
+
* :abgr8888 - ABGR -> ARGB byte shuffle
|
|
60
|
+
* :rgb888 - 3-byte RGB -> 4-byte ARGB (adds 0xFF alpha)
|
|
61
|
+
*/
|
|
62
|
+
static VALUE
|
|
63
|
+
pixels_convert(VALUE self, VALUE source, VALUE vw, VALUE vh, VALUE format)
|
|
64
|
+
{
|
|
65
|
+
int w = NUM2INT(vw);
|
|
66
|
+
int h = NUM2INT(vh);
|
|
67
|
+
long npixels = (long)w * h;
|
|
68
|
+
const uint8_t *src;
|
|
69
|
+
uint8_t *dst;
|
|
70
|
+
VALUE result;
|
|
71
|
+
ID fmt;
|
|
72
|
+
long i;
|
|
73
|
+
|
|
74
|
+
Check_Type(source, T_STRING);
|
|
75
|
+
fmt = SYM2ID(format);
|
|
76
|
+
src = (const uint8_t *)RSTRING_PTR(source);
|
|
77
|
+
|
|
78
|
+
/* :argb8888 — passthrough */
|
|
79
|
+
if (fmt == rb_intern("argb8888")) {
|
|
80
|
+
if (RSTRING_LEN(source) < npixels * 4) {
|
|
81
|
+
rb_raise(rb_eArgError, "source too short for %ldx%d ARGB8888", (long)w, h);
|
|
82
|
+
}
|
|
83
|
+
return rb_str_dup(source);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* :rgba8888 — RGBA -> ARGB */
|
|
87
|
+
if (fmt == rb_intern("rgba8888")) {
|
|
88
|
+
if (RSTRING_LEN(source) < npixels * 4) {
|
|
89
|
+
rb_raise(rb_eArgError, "source too short for %ldx%d RGBA8888", (long)w, h);
|
|
90
|
+
}
|
|
91
|
+
result = rb_str_new(NULL, npixels * 4);
|
|
92
|
+
dst = (uint8_t *)RSTRING_PTR(result);
|
|
93
|
+
for (i = 0; i < npixels; i++) {
|
|
94
|
+
long off = i * 4;
|
|
95
|
+
dst[off + 0] = src[off + 3]; /* A */
|
|
96
|
+
dst[off + 1] = src[off + 0]; /* R */
|
|
97
|
+
dst[off + 2] = src[off + 1]; /* G */
|
|
98
|
+
dst[off + 3] = src[off + 2]; /* B */
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* :bgra8888 — BGRA -> ARGB */
|
|
104
|
+
if (fmt == rb_intern("bgra8888")) {
|
|
105
|
+
if (RSTRING_LEN(source) < npixels * 4) {
|
|
106
|
+
rb_raise(rb_eArgError, "source too short for %ldx%d BGRA8888", (long)w, h);
|
|
107
|
+
}
|
|
108
|
+
result = rb_str_new(NULL, npixels * 4);
|
|
109
|
+
dst = (uint8_t *)RSTRING_PTR(result);
|
|
110
|
+
for (i = 0; i < npixels; i++) {
|
|
111
|
+
long off = i * 4;
|
|
112
|
+
dst[off + 0] = src[off + 3]; /* A */
|
|
113
|
+
dst[off + 1] = src[off + 2]; /* R */
|
|
114
|
+
dst[off + 2] = src[off + 1]; /* G */
|
|
115
|
+
dst[off + 3] = src[off + 0]; /* B */
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* :abgr8888 — ABGR -> ARGB */
|
|
121
|
+
if (fmt == rb_intern("abgr8888")) {
|
|
122
|
+
if (RSTRING_LEN(source) < npixels * 4) {
|
|
123
|
+
rb_raise(rb_eArgError, "source too short for %ldx%d ABGR8888", (long)w, h);
|
|
124
|
+
}
|
|
125
|
+
result = rb_str_new(NULL, npixels * 4);
|
|
126
|
+
dst = (uint8_t *)RSTRING_PTR(result);
|
|
127
|
+
for (i = 0; i < npixels; i++) {
|
|
128
|
+
long off = i * 4;
|
|
129
|
+
dst[off + 0] = src[off + 0]; /* A */
|
|
130
|
+
dst[off + 1] = src[off + 3]; /* R */
|
|
131
|
+
dst[off + 2] = src[off + 2]; /* G */
|
|
132
|
+
dst[off + 3] = src[off + 1]; /* B */
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* :rgb888 — 3-byte RGB -> 4-byte ARGB */
|
|
138
|
+
if (fmt == rb_intern("rgb888")) {
|
|
139
|
+
if (RSTRING_LEN(source) < npixels * 3) {
|
|
140
|
+
rb_raise(rb_eArgError, "source too short for %ldx%d RGB888", (long)w, h);
|
|
141
|
+
}
|
|
142
|
+
result = rb_str_new(NULL, npixels * 4);
|
|
143
|
+
dst = (uint8_t *)RSTRING_PTR(result);
|
|
144
|
+
for (i = 0; i < npixels; i++) {
|
|
145
|
+
long src_off = i * 3;
|
|
146
|
+
long dst_off = i * 4;
|
|
147
|
+
dst[dst_off + 0] = 0xFF; /* A */
|
|
148
|
+
dst[dst_off + 1] = src[src_off + 0]; /* R */
|
|
149
|
+
dst[dst_off + 2] = src[src_off + 1]; /* G */
|
|
150
|
+
dst[dst_off + 3] = src[src_off + 2]; /* B */
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
rb_raise(rb_eArgError, "unknown pixel format: %"PRIsVALUE, format);
|
|
156
|
+
return Qnil; /* unreachable */
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
void
|
|
160
|
+
Init_sdl2pixels(VALUE mTeekSDL2)
|
|
161
|
+
{
|
|
162
|
+
VALUE cPixels = rb_define_module_under(mTeekSDL2, "Pixels");
|
|
163
|
+
|
|
164
|
+
rb_define_module_function(cPixels, "pack_uint32", pixels_pack_uint32, 3);
|
|
165
|
+
rb_define_module_function(cPixels, "convert", pixels_convert, 4);
|
|
166
|
+
}
|