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.
Original code license
The MIT License (MIT)
Copyright (c) 2014 karatekid552
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
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
0xRRGGBB
format, 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
nil
if absent. -
#initialize(h = {}) ⇒ Palette
constructor
Same as Bytestream#initialize, but with
:lz77
and:pointer_mode
set 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.
125 126 127 128 129 130 |
# File 'lib/sabrina/palette.rb', line 125 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.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/sabrina/palette.rb', line 53 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.
104 105 106 107 |
# File 'lib/sabrina/palette.rb', line 104 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.
115 116 117 118 |
# File 'lib/sabrina/palette.rb', line 115 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.
88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/sabrina/palette.rb', line 88 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
78 79 80 |
# File 'lib/sabrina/palette.rb', line 78 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.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/sabrina/palette.rb', line 153 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.
219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/sabrina/palette.rb', line 219 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.
184 185 186 |
# File 'lib/sabrina/palette.rb', line 184 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.
138 139 140 141 |
# File 'lib/sabrina/palette.rb', line 138 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.
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/sabrina/palette.rb', line 191 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.
236 237 238 |
# File 'lib/sabrina/palette.rb', line 236 def to_s "Palette (#{present.length})" end |