class Sabrina::Sprite

A class tailored towards dealing with graphical (sprite) data.

Note that a sprite generated from ROM data does not contain color data by default. Pass a {Palette} as :palette in the option hash, or to {#palette=}, to specify a default palette for the RGB output methods.

Attributes

palette[RW]

Gets or sets the default palette for RGB output. Note that this will not cause the palette to also be automatically written to ROM on sprite {RomOperations#write_to_rom}.

Public Class Methods

from_canvas(c, palette = Palette.empty, h = {}) click to toggle source

Generates a sprite from a {rdoc.info/gems/chunky_png/1.2.0/ChunkyPNG/Canvas Canvas} and optionally attempts to match the colors to the provided {Palette}.

@param [Canvas] c @param [Palette] palette @param [Hash] h see {Bytestream#initialize} @return [Sprite]

# File lib/sabrina/sprite.rb, line 66
def from_canvas(c, palette = Palette.empty, h = {})
  from_rgb(c.to_rgb_stream, c.width, palette, h)
end
from_png(file, palette = Palette.empty, h = {}) click to toggle source

Generates a sprite from a PNG file and optionally attempts to match the colors to the provided {Palette}.

Internally, this creates a {rdoc.info/gems/chunky_png/1.2.0/ChunkyPNG/Canvas Canvas} object from the provided file and then passes the extracted RGB stream and width to {from_rgb} along with the palette.

@param [String] file @param [Palette] palette @param [Hash] h see {Bytestream#initialize} @return [Sprite]

# File lib/sabrina/sprite.rb, line 51
def from_png(file, palette = Palette.empty, h = {})
  c = ChunkyPNG::Canvas.from_file(file)

  from_canvas(c, palette, h)
end
from_rgb(rgb, width = 64, palette = Palette.empty, h = {}) click to toggle source

Generates a sprite from a stream of bytes following the 0xRRGGBB format with the provided width, optionally matching the colors to the supplied {Palette} (and failing if the sprite dimensions are not multiples of 8 or the total number of colors in the image and the palette exceeds 16).

It is important to remember that while the resulting image will be ready for saving to PNG, writing it to a ROM will not save the color data by itself. The generated palette (accessible via {#palette}) should be written separately. The {Plugins::Spritesheet Spritesheet} plugin should take care of that for you.

@param [String] rgb @param [Integer] width @param [Palette] palette @param [Hash] h see {Bytestream#initialize} @return [Sprite]

# File lib/sabrina/sprite.rb, line 87
def from_rgb(rgb, width = 64, palette = Palette.empty, h = {})
  fail 'RGB stream length must divide by 3.' unless rgb.length % 3 == 0

  unless width % 8 == 0 && (rgb.length / 3 / width) % 8 == 0
    fail 'Sprite dimensions must be divisible by 8.'
  end

  out_array = []

  until rgb.empty?
    pixel = rgb.slice!(0, 3).unpack('CCC')
    palette.add(pixel) unless palette.index_of(pixel)
    out_array << palette.index_of(pixel).to_s(16).upcase
  end

  h.merge!(
    representation: out_array,
    width: width,
    palette: palette
  )
  new(h)
end
from_rom(rom, offset, width = 64, h = {}) click to toggle source

Same as {ByteInput#from_rom}, but supplies no length (due to implicit {Lz77} mode) and supports a width parameter for the resulting picture.

@param [Integer] width @param [Hash] h see {Bytestream#initialize} @return [Sprite] @see ByteInput#from_rom

Calls superclass method
# File lib/sabrina/sprite.rb, line 34
def from_rom(rom, offset, width = 64, h = {})
  h.merge!(width: width)
  super(rom, offset, nil, h)
end
from_table(rom, table, index, width = 64, h = {}) click to toggle source

Same as {ByteInput#from_table_as_pointer}, but also supports a width parameter for the resulting picture.

@param [Integer] width @param [Hash] h see {Bytestream#initialize} @return [Sprite] @see ByteInput#from_table_as_pointer

# File lib/sabrina/sprite.rb, line 21
def from_table(rom, table, index, width = 64, h = {})
  h.merge!(width: width)
  from_table_as_pointer(rom, table, index, h)
end
new(h = {}) click to toggle source

Same as {Bytestream#initialize}, but with :lz77 and :pointer_mode set to true by default and support for the following extra options.

@param [Hash] h

@option h [Integer] :width The width of the picture.
@option h [Palette] :palette The default palette to use
  with the RGB and Canvas output methods.

@see Bytestream#initialize

Calls superclass method Sabrina::Bytestream::new
# File lib/sabrina/sprite.rb, line 120
def initialize(h = {})
  @lz77 = true
  @pointer_mode = true

  @width = 64
  @palette = nil

  super
end

Public Instance Methods

generate_bytes() click to toggle source

@todo Some breakage with number 360, is this the culprit? Converts the internal representation to a GBA-compatible stream of bytes.

@return [String] @see ByteOutput#generate_bytes

# File lib/sabrina/sprite.rb, line 168
def generate_bytes
  in_array = []
  present.join('').scan(/(.)(.)/) { |x, y| in_array += [y, x] }

  column_num = @width / 8
  out_array = []

  loop do
    break if in_array.empty?
    columns = [[]] * column_num

    until columns[0].length == 8 * 8
      column_num.times { |i| columns[i] += in_array.slice!(0, 8) }
    end # Filled one block

    out_array += columns.slice!(0) until columns.empty?
  end

  Bytestream.from_hex(out_array.join('')).to_b
end
justify() click to toggle source

Crops or repeats the sprite vertically until it meets the current ROM’s frame count, assuming 64x64 pixels per frame.

@return [self]

# File lib/sabrina/sprite.rb, line 212
def justify
  frame_count =
    if @table.to_sym == :front_table
      @rom.special_frames.fetch(@index, @rom.frames).first
    else
      @rom.special_frames.fetch(@index, @rom.frames).last
    end

  h = { lz77: false }
  target = frame_count * 64 * 64

  old_rep = @representation.dup
  if @representation.length < target
    @representation += old_rep until @representation.length >= target
    h = { lz77: true }
  elsif @representation.length > target
    @representation.slice!(0, target)
    h = { lz77: true }
  end

  clear_cache(h)
end
present() click to toggle source

Returns an array of characters that represent the palette index of each pixel in hexadecimal format.

@return [Array] an array of characters from 0 through F.

# File lib/sabrina/sprite.rb, line 134
def present
  return @representation if @representation
  # return nil if to_bytes.empty?

  in_array = []
  to_hex.scan(/(.)(.)/) { |x, y| in_array += [y, x] }

  column_num = @width / 8

  blocks = []
  blocks << in_array.slice!(0, 64) until in_array.empty?

  out_array = []
  i = 0
  loop do
    loop do
      break if blocks[i % column_num].empty?
      out_array += blocks[i % column_num].slice!(0, 8)
      i += 1
    end
    blocks.slice!(0, column_num)
    break if blocks.empty?
  end

  @representation = out_array
end
Also aliased as: to_a
rom=(p_rom) click to toggle source

@see Bytestream#rom=

# File lib/sabrina/sprite.rb, line 236
def rom=(p_rom)
  @rom = p_rom
  justify
  @rom
end
to_a()
Alias for: present
to_ascii(width_multiplier = 2) click to toggle source

Outputs the sprite as ASCII art with each pixel represented by the hexadecimal value of its palette index.

@param [Integer] width_multiplier each pixel will be repeated this

many times horizontally. Defaults to 2 for better proportions
with typical monospace fonts.

@return [String] @see palette=

# File lib/sabrina/sprite.rb, line 287
def to_ascii(width_multiplier = 2)
  output = ''
  present.each_index do |i|
    output << present[i] * width_multiplier
    output << "\n" if (i + 1) % @width == 0
  end
  output
end
to_canvas(pal = @palette) click to toggle source

Converts the internal representation to a {rdoc.info/gems/chunky_png/1.2.0/ChunkyPNG/Canvas Canvas} object using the default palette or the provided one (and failing if neither is present.)

@param [Palette] pal @return [Canvas] a

{http://rdoc.info/gems/chunky_png/1.2.0/ChunkyPNG/Canvas Canvas}
object.

@see palette=

# File lib/sabrina/sprite.rb, line 252
def to_canvas(pal = @palette)
  ChunkyPNG::Canvas.from_rgb_stream(
    @width,
    present.length / @width,
    to_rgb(pal)
  )
end
to_file(file, dir = '', pal = @palette)
Alias for: to_png
to_png(file, dir = '', pal = @palette) click to toggle source

Saves the internal representation to a PNG file (appending the file extension if absent) using the default palette or the provided one (and failing if neither is present.)

@param [String] file the file to save to. A .png extension is optional. @param [Palette] pal @see palette=

# File lib/sabrina/sprite.rb, line 267
def to_png(file, dir = '', pal = @palette)
  dir << '/' unless dir.empty? || dir.end_with?('/')
  FileUtils.mkpath(dir) unless Dir.exist?(dir)

  path = dir << file
  path << '.png' unless path.downcase.end_with?('.png')

  to_canvas(pal).save(path)
end
Also aliased as: to_file
to_rgb(pal = @palette) click to toggle source

Converts the internal representation to a stream of 0xRRGGBB bytes using the default palette or the provided one (and failing if neither is present.)

@param [Palette] pal @return [String] @see palette=

# File lib/sabrina/sprite.rb, line 196
def to_rgb(pal = @palette)
  fail 'A palette must be specified for conversion to RGB.' unless pal

  rgb = present.map do |x|
    color = pal.present[x.hex]
    fail "No such entry in the palette: #{x.hex}" unless color
    color.map(&:chr).join('')
  end

  rgb.join('')
end
to_s() click to toggle source

A blurb showing the sprite dimensions.

@return [String]

# File lib/sabrina/sprite.rb, line 299
def to_s
  "Sprite (#{ @width }x#{ present.length / @width })"
end

Private Instance Methods

load_settings(h) click to toggle source

@see {Bytestream#load_settings}

Calls superclass method Sabrina::Bytestream#load_settings
# File lib/sabrina/sprite.rb, line 306
def load_settings(h)
  @width = h.fetch(:width, @width)
  @palette = h.fetch(:palette, @palette)
  super
end