Class: Sabrina::Palette
- Inherits:
-
Bytestream
- Object
- Bytestream
- Sabrina::Palette
- Defined in:
- lib/sabrina/palette.rb
Overview
A class dedicated to handling color palette data inside a ROM file. This must be used alongside sprites to display the correct colors in game or when exported to files.
While a palette will function in this and some other programs even if smaller than 16 colors, it must have exactly 16 colors to work in-game. To ensure this, use the #pad method to fill the remaining slots with a default color. This will, however, make it impossible to add further colors.
Parts adapted from Gen III Hacking Suite by thekaratekid552.
Instance Attribute Summary
Attributes inherited from Bytestream
#filename, #index, #last_write, #rom, #table, #work_dir
Class Method Summary collapse
-
.create_synced_palettes(rgb1, rgb2, h1 = {}, h2 = {}) ⇒ Array
Generates an array of two palettes from two
0xRRGGBB-format streams: One containing every color fromrgb1, and another where each color is replaced with its spatial equivalent fromrgb2. -
.empty(h = {}) ⇒ Palette
Returns an object representing an empty palette.
-
.from_array(a = [], h = {}) ⇒ Palette
Returns a palette object represented by the given array of [R,G,B] values.
-
.from_rgb(rgb, h = {}) ⇒ Sprite
Generates a palette from a stream of bytes following the
0xRRGGBBformat, failing if the total number of colors in the palette exceeds 16. - .from_table(rom, table, index, h = {}) ⇒ Palette
Instance Method Summary collapse
-
#add_color(color, h = {}) ⇒ self
(also: #add)
Adds a color to the array.
-
#generate_bytes ⇒ String
Converts the internal representation to a GBA-compatible stream of bytes.
-
#index_of(color) ⇒ Integer
Returns the index of the [R, G, B] color in the palette, or
nilif absent. -
#initialize(h = {}) ⇒ Palette
constructor
Same as Bytestream#initialize, but with
:lz77and:pointer_modeset to true by default. -
#pad(l = 16, c = [16, 16, 16]) ⇒ self
Pads the palette until it has the specified number of colors.
-
#present ⇒ Array
(also: #to_a)
Returns the palette as an array of [R, G, B] values.
-
#to_s ⇒ String
A blurb showing the color count of the palette.
Methods inherited from Bytestream
#lz77_mode, #offset, #offset=, parse_offset, #pointer, #string_mode
Methods included from Bytestream::ByteInput
#from_bytes, #from_hex, #from_rom, #from_rom_as_lz77, #from_table, #from_table_as_pointer
Methods included from Bytestream::RomOperations
#calculate_length, #clear_cache, #reload_from_rom, #write_to_rom
Methods included from Bytestream::ByteOutput
#to_b, #to_bytes, #to_hex, #to_hex_reverse, #to_i, #to_lz77
Constructor Details
#initialize(h = {}) ⇒ Palette
Same as Bytestream#initialize, but with :lz77 and :pointer_mode set to true by default.
101 102 103 104 105 106 |
# File 'lib/sabrina/palette.rb', line 101 def initialize(h = {}) @lz77 = true @pointer_mode = true super end |
Class Method Details
.create_synced_palettes(rgb1, rgb2, h1 = {}, h2 = {}) ⇒ Array
Generates an array of two palettes from two 0xRRGGBB-format streams: One containing every color from rgb1, and another where each color is replaced with its spatial equivalent from rgb2. This assumes palette 1 does not contain duplicate entries, but palette 2 might.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/sabrina/palette.rb', line 29 def create_synced_palettes(rgb1, rgb2, h1 = {}, h2 = {}) unless rgb1.length % 3 == 0 && rgb2.length % 3 == 0 fail 'RGB stream length must divide by 3.' end a1, a2 = rgb1.scan(/.../), rgb2.scan(/.../) pal1, pal2 = Palette.empty(h1), Palette.empty(h2) a1.each_index do |i| pix1 = a1[i].unpack('CCC') next if pal1.index_of(pix1) pix2 = a2[i].unpack('CCC') pal1.add_color(pix1) pal2.add_color(pix2, force: true) end [pal1.pad, pal2.pad] end |
.empty(h = {}) ⇒ Palette
Returns an object representing an empty palette.
80 81 82 83 |
# File 'lib/sabrina/palette.rb', line 80 def empty(h = {}) h.merge!(representation: []) new(h) end |
.from_array(a = [], h = {}) ⇒ Palette
Returns a palette object represented by the given array of [R,G,B] values. Caution is advised as there is no validation.
91 92 93 94 |
# File 'lib/sabrina/palette.rb', line 91 def from_array(a = [], h = {}) h.merge!(representation: a) new(h) end |
.from_rgb(rgb, h = {}) ⇒ Sprite
Generates a palette from a stream of bytes following the 0xRRGGBB format, failing if the total number of colors in the palette exceeds 16.
64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/sabrina/palette.rb', line 64 def from_rgb(rgb, h = {}) fail 'RGB stream length must divide by 3.' unless rgb.length % 3 == 0 out_pal = empty(h) until rgb.empty? pixel = rgb.slice!(0, 3).unpack('CCC') out_pal.add(pixel) unless out_pal.index_of(pixel) end out_pal end |
.from_table(rom, table, index, h = {}) ⇒ Palette
54 55 56 |
# File 'lib/sabrina/palette.rb', line 54 def from_table(rom, table, index, h = {}) from_table_as_pointer(rom, table, index, h) end |
Instance Method Details
#add_color(color, h = {}) ⇒ self Also known as: add
Adds a color to the array. The color must be a [R, G, B] array. Will fail on malformed color or 16 coors exceeded.
This will clear the internal cache.
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/sabrina/palette.rb', line 129 def add_color(color, h = {}) unless color.is_a?(Array) && color.length == 3 fail "Color must be [R, G, B]. (#{color})" end color.each do |i| next if i.between?(0, 255) fail "Color component out of bounds. (#{color})" end @representation ||= [] if @representation.index(color) && !h.fetch(:force, false) return clear_cache end @representation << color if present.length > 16 fail "Palette must be smaller than 16. (#{present.length}, #{color})" end clear_cache end |
#generate_bytes ⇒ String
Converts the internal representation to a GBA-compatible stream of bytes.
195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/sabrina/palette.rb', line 195 def generate_bytes pal = '' @representation.each do |c| red = c[0] >> 3 green = c[1] >> 3 << 5 blue = c[2] >> 3 << 10 pal << Bytestream.from_hex(format('%04X', (blue | green | red))).to_b.reverse end pal.rjust(2 * @representation.length, "\x00") end |
#index_of(color) ⇒ Integer
Returns the index of the [R, G, B] color in the palette, or nil if absent.
160 161 162 |
# File 'lib/sabrina/palette.rb', line 160 def index_of(color) present.index(color) end |
#pad(l = 16, c = [16, 16, 16]) ⇒ self
Pads the palette until it has the specified number of colors. This is a mandatory step for the palette to actually work in-game.
114 115 116 117 |
# File 'lib/sabrina/palette.rb', line 114 def pad(l = 16, c = [16, 16, 16]) add_color(c, force: true) until present.length >= l clear_cache end |
#present ⇒ Array Also known as: to_a
Returns the palette as an array of [R, G, B] values.
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/sabrina/palette.rb', line 167 def present return @representation if @representation red_mask, green_mask, blue_mask = 0x1f, 0x3e0, 0x7c00 in_bytes = to_bytes.dup out_array = [] until in_bytes.empty? color = Bytestream.from_bytes(in_bytes.slice!(0, 2).reverse).to_i out_array << [ (color & red_mask) << 3, (color & green_mask) >> 5 << 3, (color & blue_mask) >> 10 << 3 ] end @representation = out_array end |
#to_s ⇒ String
A blurb showing the color count of the palette.
212 213 214 |
# File 'lib/sabrina/palette.rb', line 212 def to_s "Palette (#{present.length})" end |