12constexpr size_t MAX_PALETTE_SIZE = 256;
14constexpr unsigned MIN_PALETTE_BITS = 2;
15constexpr unsigned MAX_CODE_BITS = 12;
16constexpr uint32_t MAX_CODES = 1u << MAX_CODE_BITS;
18constexpr size_t DICTIONARY_SIZE = 2 * MAX_CODES;
19constexpr uint32_t EMPTY_ENTRY = 0xFFFFFFFF;
20constexpr size_t MAX_SUB_BLOCK_SIZE = 255;
22constexpr size_t COLOR_KEY_COUNT = 1u << 16;
24constexpr uint16_t color_key(uint8_t red, uint8_t green, uint8_t blue) {
25 return static_cast<uint16_t
>(((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3));
30constexpr unsigned key_channel(uint16_t key,
unsigned channel) {
33 return (key >> 11) << 3;
35 return ((key >> 5) & 0x3F) << 2;
37 return (key & 0x1F) << 3;
42void put_le16(std::vector<uint8_t> &
out,
unsigned value) {
43 out.push_back(
static_cast<uint8_t
>(value & 0xFF));
44 out.push_back(
static_cast<uint8_t
>(value >> 8));
49 std::vector<uint8_t> &
out;
53 void put(
uint32_t value,
unsigned bits) {
56 while (this->count >= 8) {
57 this->out.push_back(
static_cast<uint8_t
>(this->buffer & 0xFF));
65 this->out.push_back(
static_cast<uint8_t
>(this->buffer & 0xFF));
72void lzw_compress(
const uint8_t *data,
size_t length,
unsigned min_code_size, std::vector<uint8_t> &
out) {
77 const uint32_t clear_code = 1u << min_code_size;
78 const uint32_t end_code = clear_code + 1;
79 std::vector<Entry> dictionary(DICTIONARY_SIZE);
82 unsigned code_size = 0;
85 std::fill(dictionary.begin(), dictionary.end(), Entry{EMPTY_ENTRY, 0});
86 next_code = end_code + 1;
87 code_size = min_code_size + 1;
91 bits.put(clear_code, code_size);
93 for (
size_t i = 1; i !=
length; i++) {
95 size_t slot = (key * 2654435761u >> 16) & (DICTIONARY_SIZE - 1);
96 while (dictionary[slot].key != EMPTY_ENTRY && dictionary[slot].key != key)
97 slot = (slot + 1) & (DICTIONARY_SIZE - 1);
98 if (dictionary[slot].key == key) {
99 prefix = dictionary[slot].code;
102 bits.put(prefix, code_size);
103 if (next_code < MAX_CODES) {
104 dictionary[slot] = Entry{key,
static_cast<uint16_t
>(next_code++)};
106 if (next_code > (1u << code_size))
109 bits.put(clear_code, code_size);
114 bits.put(prefix, code_size);
116 if (next_code == (1u << code_size) && code_size < MAX_CODE_BITS)
118 bits.put(end_code, code_size);
128 bins_(COLOR_KEY_COUNT),
129 palette_index_(COLOR_KEY_COUNT),
130 indices_(static_cast<size_t>(width) * height) {}
134 static constexpr char SIGNATURE[] =
"GIF89a";
135 this->
output_.insert(this->
output_.end(), SIGNATURE, SIGNATURE + 6);
142 static constexpr char LOOP_EXTENSION[] =
"\x21\xFF\x0BNETSCAPE2.0\x03\x01";
143 this->
output_.insert(this->
output_.end(), LOOP_EXTENSION, LOOP_EXTENSION +
sizeof(LOOP_EXTENSION) - 1);
146 return fwrite(this->
output_.data(), 1, this->output_.size(), this->file_) == this->
output_.size();
158 for (
size_t key = 0; key != COLOR_KEY_COUNT; key++) {
160 this->
used_colors_.push_back(
static_cast<uint16_t
>(key));
161 total += this->
bins_[key].count;
167 std::vector<Box> boxes;
169 while (boxes.size() < MAX_PALETTE_SIZE) {
170 size_t chosen = boxes.size();
171 for (
size_t i = 0; i != boxes.size(); i++) {
172 if (boxes[i].
end - boxes[i].begin >= 2 && (chosen == boxes.size() || boxes[i].pixels > boxes[chosen].pixels))
175 if (chosen == boxes.size())
177 const Box box = boxes[chosen];
179 unsigned low[3] = {255, 255, 255};
180 unsigned high[3] = {0, 0, 0};
181 for (
size_t i = box.begin; i != box.end; i++) {
182 for (
unsigned channel = 0; channel != 3; channel++) {
183 const unsigned value = key_channel(this->
used_colors_[i], channel);
184 low[channel] = std::min(low[channel], value);
185 high[channel] = std::max(high[channel], value);
189 for (
unsigned channel = 1; channel != 3; channel++) {
190 if (high[channel] - low[channel] > high[widest] - low[widest])
193 std::sort(this->
used_colors_.begin() + box.begin, this->used_colors_.begin() + box.end,
194 [widest](uint16_t a, uint16_t b) {
195 const unsigned value_a = key_channel(a, widest);
196 const unsigned value_b = key_channel(b, widest);
197 return value_a != value_b ? value_a < value_b : a < b;
201 size_t cut = box.begin;
203 while (cut < box.end - 1 && below * 2 < box.pixels)
205 boxes[chosen] = {box.begin, cut, below};
206 boxes.push_back({cut, box.end, box.pixels - below});
209 for (
size_t i = 0; i != boxes.size(); i++) {
210 uint64_t sum[3] = {0, 0, 0};
211 for (
size_t j = boxes[i].begin; j != boxes[i].end; j++) {
213 for (
unsigned channel = 0; channel != 3; channel++)
214 sum[channel] += bin.
sum[channel];
215 this->
palette_index_[this->used_colors_[j]] =
static_cast<uint8_t
>(i);
218 for (
unsigned channel = 0; channel != 3; channel++)
219 palette[i * 3 + channel] =
static_cast<uint8_t
>((sum[channel] + boxes[i].pixels / 2) / boxes[i].pixels);
225 std::fill(this->
bins_.begin(), this->bins_.end(),
ColorBin{});
227 const uint8_t *in = bgr +
y * row_stride;
228 for (
int x = 0;
x != this->
width_;
x++, in += 3) {
237 uint8_t palette[MAX_PALETTE_SIZE * 3] = {};
239 unsigned palette_bits = MIN_PALETTE_BITS;
240 while ((
size_t{1} << palette_bits) < colors)
243 uint8_t *out_index = this->
indices_.data();
245 const uint8_t *in = bgr +
y * row_stride;
246 for (
int x = 0;
x != this->
width_;
x++, in += 3)
247 *out_index++ = this->
palette_index_[color_key(in[2], in[1], in[0])];
252 static constexpr uint8_t GRAPHIC_CONTROL[] = {0x21, 0xF9, 0x04, 0x04};
253 this->
output_.insert(this->
output_.end(), std::begin(GRAPHIC_CONTROL), std::end(GRAPHIC_CONTROL));
254 put_le16(this->
output_, delay_centiseconds);
262 this->
output_.push_back(
static_cast<uint8_t
>(0x80 | (palette_bits - 1)));
263 this->
output_.insert(this->
output_.end(), palette, palette + (
size_t{3} << palette_bits));
264 this->
output_.push_back(
static_cast<uint8_t
>(palette_bits));
266 std::vector<uint8_t> compressed;
267 lzw_compress(this->
indices_.data(), this->indices_.size(), palette_bits, compressed);
268 for (
size_t pos = 0;
pos < compressed.size();
pos += MAX_SUB_BLOCK_SIZE) {
269 const size_t length = std::min(MAX_SUB_BLOCK_SIZE, compressed.size() -
pos);
274 return fwrite(this->
output_.data(), 1, this->output_.size(), this->file_) == this->
output_.size();
std::vector< ColorBin > bins_
std::vector< uint8_t > palette_index_
std::vector< uint16_t > used_colors_
std::vector< uint8_t > output_
GifWriter(FILE *file, int width, int height)
bool write_header()
Write the start of the file. This must come first. The animation repeats forever.
bool write_frame(const uint8_t *bgr, size_t row_stride, unsigned delay_centiseconds)
Add a frame from three bytes per pixel in blue, green, red order, topmost row first,...
std::vector< uint8_t > indices_
bool write_trailer()
Write the end of the file. This must come last.
size_t build_palette_(uint8_t *palette)
Choose up to 256 colours for the frame in bins_, storing them as red, green, blue triples in palette ...
std::vector< uint8_t > & out
std::span< const uint8_t > data
How many pixels of one colour there are in the frame, and the sum of their red, green and blue.