Class: RGame::Core::Image

Inherits:
Object
  • Object
show all
Defined in:
lib/rgame/core/image.rb,
ext/rgame_core/ruby/image_ext.c

Overview

A picture on the GPU, and the sprites cut out of it.

img = RGame::Core::Image.new(app, 'hero.png')
img.width                       # => 64
frame = img.subimage(0, 0, 16, 16)
walk  = RGame::Core::Image.load_tiles(app, 'hero.png', 16, 16)

Everything below the constructor is a view: subimage, tile and load_tiles share the one texture the file was decoded into, so slicing a sheet into a hundred frames costs a hundred small objects and no extra video memory. The texture is released when the last view of it is collected, in whatever order that happens.

Images are always sampled nearest-neighbour: this engine draws pixel art, and there is no setting to blur it.

The class itself is defined in C (ext/rgame_core/ruby/image_ext.c); what is added here is the sheet-slicing convenience, which is a loop and belongs in Ruby.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, path) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'ext/rgame_core/ruby/image_ext.c', line 105

static VALUE image_initialize(VALUE self, VALUE app, VALUE path) {
    rgame_image_ref *ref;
    TypedData_Get_Struct(self, rgame_image_ref, &image_data_type, ref);

    /* Unwrapping raises TypeError on anything that is not an App, so there is
     * no separate type check to keep in step with this one. */
    rgame_app *engine_app = rgame_app_unwrap(app);
    const char *path_str = StringValueCStr(path);

    char error[256] = {0};
    rgame_image *image = rgame_image_load(engine_app, path_str, error, sizeof(error));
    if (!image) {
        rb_raise(rb_const_get(cImage, rb_intern("LoadError")), "%s", error);
    }

    ref->image = image;
    ref->app = app;
    return self;
}

Class Method Details

.debug_live_texturesObject



208
209
210
211
# File 'ext/rgame_core/ruby/image_ext.c', line 208

static VALUE image_s_debug_live_textures(VALUE klass) {
    (void)klass;
    return LONG2NUM(rgame_texture_live_sheets());
}

.load_tiles(app, path, tile_width, tile_height) ⇒ Object

Every whole tile_width x tile_height tile of an image file, in reading order: left to right, then top to bottom. A partial tile along the right or bottom edge is padding and is skipped.

The file is decoded and uploaded exactly once however many tiles come out of it — the returned images are views of that single texture.



33
34
35
# File 'lib/rgame/core/image.rb', line 33

def self.load_tiles(app, path, tile_width, tile_height)
  new(app, path).tiles(tile_width, tile_height)
end

Instance Method Details

#each_tile(tile_width, tile_height) ⇒ Object

Yields each tile in turn, without building the Array.



46
47
48
49
50
51
52
# File 'lib/rgame/core/image.rb', line 46

def each_tile(tile_width, tile_height)
  return enum_for(:each_tile, tile_width, tile_height) unless block_given?

  tile_count(tile_width, tile_height).times do |index|
    yield tile(tile_width, tile_height, index)
  end
end

#heightObject



137
138
139
# File 'ext/rgame_core/ruby/image_ext.c', line 137

static VALUE image_height(VALUE self) {
    return INT2NUM(rgame_image_height(image_ref_unwrap(self)->image));
}

#inspectObject



188
189
190
191
192
193
194
195
196
197
# File 'ext/rgame_core/ruby/image_ext.c', line 188

static VALUE image_inspect(VALUE self) {
    rgame_image_ref *ref;
    TypedData_Get_Struct(self, rgame_image_ref, &image_data_type, ref);
    if (!ref->image) {
        return rb_sprintf("#<%" PRIsVALUE " (uninitialized)>", rb_obj_class(self));
    }

    return rb_sprintf("#<%" PRIsVALUE " %dx%d>", rb_obj_class(self),
                      rgame_image_width(ref->image), rgame_image_height(ref->image));
}

#subimage(x, y, width, height) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'ext/rgame_core/ruby/image_ext.c', line 149

static VALUE image_subimage(VALUE self, VALUE x, VALUE y, VALUE width, VALUE height) {
    rgame_image_ref *ref = image_ref_unwrap(self);

    rgame_image *sub = rgame_image_subimage(ref->image, NUM2INT(x), NUM2INT(y), NUM2INT(width),
                                            NUM2INT(height));
    if (!sub) {
        rb_raise(rb_eArgError,
                 "subimage %" PRIsVALUE ",%" PRIsVALUE " %" PRIsVALUE "x%" PRIsVALUE
                 " does not fit in a %dx%d image",
                 x, y, width, height, rgame_image_width(ref->image),
                 rgame_image_height(ref->image));
    }

    return image_wrap(sub, ref->app);
}

#tile(tile_width, tile_height, index) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'ext/rgame_core/ruby/image_ext.c', line 172

static VALUE image_tile(VALUE self, VALUE tile_width, VALUE tile_height, VALUE index) {
    rgame_image_ref *ref = image_ref_unwrap(self);

    rgame_image *tile = rgame_image_tile(ref->image, NUM2INT(tile_width), NUM2INT(tile_height),
                                         NUM2INT(index));
    if (!tile) {
        rb_raise(rb_eIndexError, "tile %" PRIsVALUE " is out of range (%d tiles of %" PRIsVALUE
                                 "x%" PRIsVALUE ")",
                 index,
                 rgame_image_tile_count(ref->image, NUM2INT(tile_width), NUM2INT(tile_height)),
                 tile_width, tile_height);
    }

    return image_wrap(tile, ref->app);
}

#tile_count(tile_width, tile_height) ⇒ Object



166
167
168
169
# File 'ext/rgame_core/ruby/image_ext.c', line 166

static VALUE image_tile_count(VALUE self, VALUE tile_width, VALUE tile_height) {
    return INT2NUM(rgame_image_tile_count(image_ref_unwrap(self)->image, NUM2INT(tile_width),
                                          NUM2INT(tile_height)));
}

#tiles(tile_width, tile_height) ⇒ Object

This image sliced into whole tiles, as an Array. load_tiles is the same thing starting from a path.



39
40
41
42
43
# File 'lib/rgame/core/image.rb', line 39

def tiles(tile_width, tile_height)
  Array.new(tile_count(tile_width, tile_height)) do |index|
    tile(tile_width, tile_height, index)
  end
end

#widthObject



133
134
135
# File 'ext/rgame_core/ruby/image_ext.c', line 133

static VALUE image_width(VALUE self) {
    return INT2NUM(rgame_image_width(image_ref_unwrap(self)->image));
}