Class: Gifenc::ColorTable
- Inherits:
-
Object
- Object
- Gifenc::ColorTable
- Defined in:
- lib/color_table.rb
Overview
The color table is the palette of the GIF, it contains all the colors that may appear in any of its images. The color table can be global (GCT), in which case it applies to all images, and local (LCT), in which case it applies only to the subsequent image, overriding the global one, if present. Technically, both are optional according to the standard, but this breaks many decoders, so by default we enforce having an LCT if no GCT is present.
A color table can have a size of at most 8 bits, i.e., at most 256 colors, and it's always a power of 2, even if there's leftover space or empty slots. The color of each pixel in the image is then determined by specifying its index in the corresponding color table (local, if present, or global). Regardless of the bit depth, each color component still takes up a byte (and each pixel thus 3 bytes) in the encoded GIF file.
This class handles all the logic dealing with color indexes (in the table) internally, so that the user can work purely with colors directly.
Notes:
- Many of the methods that manipulate the color table return the table back, so that they may be chained properly.
- Several methods may change the color indexes, thus potentially corrupting images already made with this table. These methods are indicated with a note, and should probably only be used when building the desired palette, before actually starting to use it to craft images.
Constant Summary collapse
- MAX_SIZE =
The maximum size of a GIF color table. The encoder will always choose the smallest size possible that fits all colors, so this is only a hard limit.
256
Instance Attribute Summary collapse
-
#colors ⇒ Array<Integer>
readonly
The raw list of colors in the table.
-
#depth ⇒ Integer
The color resolution, in bits per channel, of the original image, NOT of the GIF.
-
#sorted ⇒ Boolean
Whether the colors of the table are sorted in decreasing order of importance.
Instance Method Summary collapse
-
#add(*colors) ⇒ ColorTable
Insert new colors into the color table.
-
#clear ⇒ ColorTable
(also: #reset)
Empties the whole color table, bringing its size down to 0.
-
#compact ⇒ ColorTable
Rearrange all colors to remove empty intermediate slots.
-
#count ⇒ Integer
Count of actual colors present in the table.
-
#cycle(*colors, step: 1) ⇒ ColorTable
Rearrange a subset of colors in the table according to a cycle.
-
#delete(*colors) ⇒ ColorTable
Delete colors from the color table.
-
#distinct ⇒ Integer
Count of distinct colors present in the table.
-
#dup ⇒ ColorTable
Create a duplicate copy of this color table.
-
#encode(stream) ⇒ Object
Encode the color table as it will appear in the GIF.
-
#initialize(colors = [], depth: 8, sorted: false) ⇒ ColorTable
constructor
Creates a new color table.
-
#invert ⇒ ColorTable
Inverts all the colors in the table.
-
#permute(*colors, order: []) ⇒ ColorTable
Rearrange a subset of colors in the table according to a permutation.
-
#replace(old_color, new_color) ⇒ ColorTable
Changes one color in the table to another one.
-
#set(colors) ⇒ ColorTable
Change all colors in this table with a different list of colors.
-
#simplify ⇒ ColorTable
Simplifies the color table by removing color duplicates and empty slots.
-
#size ⇒ Integer
(also: #length)
Real size of the table that will be used by the encoder.
-
#swap(col_a, col_b) ⇒ ColorTable
Swap 2 colors of the color table.
-
#uniq ⇒ ColorTable
Eliminates duplicate colors from the color table.
Constructor Details
#initialize(colors = [], depth: 8, sorted: false) ⇒ ColorTable
Creates a new color table. This color table can then be used as a GCT, as an LCT for as many images as desired, or both.
69 70 71 72 73 74 |
# File 'lib/color_table.rb', line 69 def initialize(colors = [], depth: 8, sorted: false) clear @depth = depth.clamp(1, 8) @sorted = sorted set(colors) end |
Instance Attribute Details
#colors ⇒ Array<Integer> (readonly)
55 56 57 |
# File 'lib/color_table.rb', line 55 def colors @colors end |
#depth ⇒ Integer
This attribute is essentially meaningless nowadays, and ignored by most decoders.
The color resolution, in bits per channel, of the original image, NOT of the GIF. The GIF's color depth is always 8 bits per channel.
39 40 41 |
# File 'lib/color_table.rb', line 39 def depth @depth end |
#sorted ⇒ Boolean
This attribute is essentially meaningless nowadays, and ignored by most decoders.
Whether the colors of the table are sorted in decreasing order of importance. As per the specification, this would typically be decreasing order of frequency, in order to assist older systems and decoders with fewer available colors in choosing the best subset of colors to represent the image.
48 49 50 |
# File 'lib/color_table.rb', line 48 def sorted @sorted end |
Instance Method Details
#add(*colors) ⇒ ColorTable
Insert new colors into the color table.
226 227 228 229 230 231 232 233 234 |
# File 'lib/color_table.rb', line 226 def add(*colors) colors = (colors - @colors).compact.uniq if count + colors.size > MAX_SIZE raise Exception::ColorTableError, "Cannot add colors to the color table:\ Table over size limit (#{MAX_SIZE})." end colors.each{ |c| @colors[find_slot] = c & 0xFFFFFF } self end |
#clear ⇒ ColorTable Also known as: reset
This method may change color indexes.
Empties the whole color table, bringing its size down to 0.
163 164 165 166 |
# File 'lib/color_table.rb', line 163 def clear @colors = [nil] * MAX_SIZE self end |
#compact ⇒ ColorTable
This method may change color indexes.
Rearrange all colors to remove empty intermediate slots. This is accomplished by laying out all colors subsequently from the start. The order of the actual colors is preserved.
143 144 145 146 147 |
# File 'lib/color_table.rb', line 143 def compact @colors.compact! @colors += [nil] * (MAX_SIZE - @colors.size) self end |
#count ⇒ Integer
Count of actual colors present in the table.
282 283 284 |
# File 'lib/color_table.rb', line 282 def count @colors.count{ |c| !!c } end |
#cycle(*colors, step: 1) ⇒ ColorTable
Rearrange a subset of colors in the table according to a cycle. For
example, if we cycle the colors A, B, C, D
with a step of -2, the colors
become C, D, A, B
.
206 207 208 209 |
# File 'lib/color_table.rb', line 206 def cycle(*colors, step: 1) permutation = colors.times.map{ |i| (i - step) % colors.size } permute(*colors, order: permutation) end |
#delete(*colors) ⇒ ColorTable
Delete colors from the color table.
239 240 241 242 |
# File 'lib/color_table.rb', line 239 def delete(*colors) colors.each{ |c| replace(c, nil) } self end |
#distinct ⇒ Integer
Count of distinct colors present in the table. See #count.
289 290 291 |
# File 'lib/color_table.rb', line 289 def distinct @colors.uniq.count{ |c| !!c } end |
#dup ⇒ ColorTable
Create a duplicate copy of this color table.
87 88 89 |
# File 'lib/color_table.rb', line 87 def dup ColorTable.new(@colors.dup, depth: @depth, sorted: @sorted) end |
#encode(stream) ⇒ Object
Encode the color table as it will appear in the GIF.
78 79 80 81 82 83 |
# File 'lib/color_table.rb', line 78 def encode(stream) @colors.take(size).each{ |c| c = 0 if !c stream << [c >> 16 & 0xFF, c >> 8 & 0xFF, c & 0xFF].pack('C3') } end |
#invert ⇒ ColorTable
Inverts all the colors in the table.
256 257 258 259 |
# File 'lib/color_table.rb', line 256 def invert @colors.each{ |c| replace(c, c ^ 0xFFFFFF) } self end |
#permute(*colors, order: []) ⇒ ColorTable
Rearrange a subset of colors in the table according to a permutation.
The permutation must match the length of the provided colors. For example,
if we pass the colors A, B, C, D
and the permutation [2, 0, 3, 1]
, the
colors will now be sorted like C, A, D, B
.
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/color_table.rb', line 181 def permute(*colors, order: []) # Ensure permutation makes sense for the provided colors if order.sort != colors.size.times.to_a || order.uniq.size != order.size raise Exception::ColorTableError, "Cannot permute colors: Permutation is invalid." end # Ensure provided colors are in the table if !(colors - @colors).empty? raise Exception::ColorTableError, "Cannot permute colors: Color not found." end mapping = colors.each_with_index.map{ |c, i| [c, colors[order[i]]] }.to_h @colors.map!{ |c| mapping[c] || c } self end |
#replace(old_color, new_color) ⇒ ColorTable
Changes one color in the table to another one.
248 249 250 251 252 |
# File 'lib/color_table.rb', line 248 def replace(old_color, new_color) new_color = !!new_color ? new_color & 0xFFFFFF : nil @colors.map!{ |c| c == old_color ? new_color : c } self end |
#set(colors) ⇒ ColorTable
This method may change color indexes.
Change all colors in this table with a different list of colors. The list
may contain nil
s, indicating empty slots.
109 110 111 112 113 114 115 116 |
# File 'lib/color_table.rb', line 109 def set(colors) if colors.size > MAX_SIZE raise Exception::ColorTableError, "Cannot build color table, the supplied color list\ has more than #{MAX_SIZE} entries." end colors.each_with_index{ |c, i| @colors[i] = !!c ? c & 0xFFFFFF : nil } self end |
#simplify ⇒ ColorTable
This method may change color indexes.
Simplifies the color table by removing color duplicates and empty slots.
It's equivalent to uniq
+ compact
.
155 156 157 158 |
# File 'lib/color_table.rb', line 155 def simplify uniq compact end |
#size ⇒ Integer Also known as: length
Real size of the table that will be used by the encoder. The size is the smallest power of 2 capable of holding all colors currently in the list. It must be at least 2, even if there's a single color in the table.
272 273 274 |
# File 'lib/color_table.rb', line 272 def size 2 ** bit_size end |
#swap(col_a, col_b) ⇒ ColorTable
Swap 2 colors of the color table. This can be used to change the theme of a GIF in a trivial way.
217 218 219 |
# File 'lib/color_table.rb', line 217 def swap(col_a, col_b) permute(col_a, col_b, order: [1, 0]) end |
#uniq ⇒ ColorTable
This method may change color indexes.
Eliminates duplicate colors from the color table. This will keep the first
instance of each color untouched (i.e., its index will remain valid), and
set subsequent duplicate entries to nil
.
124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/color_table.rb', line 124 def uniq unique_colors = Set.new for i in (0 ... MAX_SIZE) next if !@colors[i] if unique_colors.include?(@colors[i]) @colors[i] = nil else unique_colors.add(@colors[i]) end end self end |