#include "GIFSave.h"
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <new>
#include "GIFPrivate.h"
const int gs_pass_starts_at[] = {0, 4, 2, 1, 0};
const int gs_increment_pass_by[] = {8, 8, 4, 2, 0};
const int32 one_sixteenth = (int32)((1.0 / 16.0) * 32768);
const int32 three_sixteenth = (int32)((3.0 / 16.0) * 32768);
const int32 five_sixteenth = (int32)((5.0 / 16.0) * 32768);
const int32 seven_sixteenth = (int32)((7.0 / 16.0) * 32768);
extern bool debug;
class ColorCache : public HashItem {
public:
unsigned char index;
};
GIFSave::GIFSave(BBitmap* bitmap, BPositionIO* output,
TranslatorSettings* settings)
{
fSettings = settings;
color_space colorSpace = bitmap->ColorSpace();
if (colorSpace != B_RGB32 && colorSpace != B_RGBA32
&& colorSpace != B_RGB32_BIG && colorSpace != B_RGBA32_BIG) {
if (debug)
syslog(LOG_ERR, "GIFSave::GIFSave() - Unknown color space\n");
fatalerror = true;
return;
}
fatalerror = false;
if (fSettings->SetGetInt32(GIF_SETTING_PALETTE_MODE) == OPTIMAL_PALETTE) {
palette = new(std::nothrow) SavePalette(bitmap,
fSettings->SetGetInt32(GIF_SETTING_PALETTE_SIZE));
} else {
palette = new(std::nothrow) SavePalette(
fSettings->SetGetInt32(GIF_SETTING_PALETTE_MODE));
}
if (palette == NULL) {
fatalerror = true;
return;
}
if (!palette->IsValid()) {
delete palette;
fatalerror = true;
return;
}
width = bitmap->Bounds().IntegerWidth() + 1;
height = bitmap->Bounds().IntegerHeight() + 1;
if (debug) {
syslog(LOG_INFO, "GIFSave::GIFSave() - "
"Image dimensions are %d by %d\n", width, height);
}
if (fSettings->SetGetBool(GIF_SETTING_USE_DITHERING)) {
if (debug)
syslog(LOG_INFO, "GIFSave::GIFSave() - Using dithering\n");
red_error = new(std::nothrow) int32[width + 2];
if (red_error == NULL) {
delete palette;
fatalerror = true;
return;
}
red_error = &red_error[1];
green_error = new(std::nothrow) int32[width + 2];
if (green_error == NULL) {
delete palette;
delete[] &red_error[-1];
fatalerror = true;
return;
}
green_error = &green_error[1];
blue_error = new(std::nothrow) int32[width + 2];
if (blue_error == NULL) {
delete palette;
delete[] &red_error[-1];
delete[] &green_error[-1];
fatalerror = true;
return;
}
blue_error = &blue_error[1];
red_side_error = green_side_error = blue_side_error = 0;
for (int32 x = -1; x < width + 1; x++) {
red_error[x] = 0;
green_error[x] = 0;
blue_error[x] = 0;
}
} else if (debug)
syslog(LOG_INFO, "GIFSave::GIFSave() - Not using dithering\n");
if (debug) {
if (fSettings->SetGetBool(GIF_SETTING_INTERLACED))
syslog(LOG_INFO, "GIFSave::GIFSave() - Interlaced, ");
else
syslog(LOG_INFO, "GIFSave::GIFSave() - Not interlaced, ");
switch (fSettings->SetGetInt32(GIF_SETTING_PALETTE_MODE)) {
case WEB_SAFE_PALETTE:
syslog(LOG_INFO, "web safe palette\n");
break;
case BEOS_SYSTEM_PALETTE:
syslog(LOG_INFO, "BeOS system palette\n");
break;
case GREYSCALE_PALETTE:
syslog(LOG_INFO, "greyscale palette\n");
break;
case OPTIMAL_PALETTE:
default:
syslog(LOG_INFO, "optimal palette\n");
}
}
if (fSettings->SetGetBool(GIF_SETTING_USE_TRANSPARENT)) {
if (fSettings->SetGetBool(GIF_SETTING_USE_TRANSPARENT_AUTO)) {
palette->PrepareForAutoTransparency();
if (debug) {
syslog(LOG_INFO, "GIFSave::GIFSave() - "
"Using transparent index %d\n",
palette->TransparentIndex());
}
} else {
palette->SetTransparentColor(
(uint8)fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_RED),
(uint8)fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_GREEN),
(uint8)fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_BLUE));
if (debug) {
syslog(LOG_INFO, "GIFSave::GIFSave() - "
"Found transparent color %d,%d,%d at index %d\n",
fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_RED),
fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_GREEN),
fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_BLUE),
palette->TransparentIndex());
}
}
} else {
if (debug)
syslog(LOG_INFO, "GIFSave::GIFSave() - Not using transparency\n");
}
this->output = output;
this->bitmap = bitmap;
if (WriteGIFHeader() != B_OK) {
delete palette;
delete[] &red_error[-1];
delete[] &green_error[-1];
delete[] &blue_error[-1];
fatalerror = true;
return;
}
if (debug)
syslog(LOG_INFO, "GIFSave::GIFSave() - Wrote gif header\n");
hash = new(std::nothrow) SFHash(1 << 16);
if (hash == NULL) {
delete palette;
delete[] &red_error[-1];
delete[] &green_error[-1];
delete[] &blue_error[-1];
fatalerror = true;
return;
}
WriteGIFControlBlock();
if (debug)
syslog(LOG_INFO, "GIFSave::GIFSave() - Wrote gif control block\n");
WriteGIFImageHeader();
if (debug)
syslog(LOG_INFO, "GIFSave::GIFSave() - Wrote gif image header\n");
WriteGIFImageData();
if (debug)
syslog(LOG_INFO, "GIFSave::GIFSave() - Wrote gif image data\n");
if (fSettings->SetGetBool(GIF_SETTING_USE_DITHERING)) {
delete[] &red_error[-1];
delete[] &green_error[-1];
delete[] &blue_error[-1];
}
delete hash;
char t = TERMINATOR_INTRODUCER;
output->Write(&t, 1);
}
GIFSave::~GIFSave()
{
delete palette;
fSettings->Release();
}
status_t
GIFSave::WriteGIFHeader()
{
unsigned char header[]
= { 'G', 'I', 'F', '8', '9', 'a', 0, 0, 0, 0, 0, 0, 0 };
header[6] = width & 0xff;
header[7] = (width & 0xff00) >> 8;
header[8] = height & 0xff;
header[9] = (height & 0xff00) >> 8;
header[10] = 0xf0 | (palette->SizeInBits() - 1);
header[11] = palette->BackgroundIndex();
if (output->Write(header, 13) < 13)
return B_IO_ERROR;
int size = (1 << palette->SizeInBits()) * 3;
uint8* buffer = new(std::nothrow) uint8[size];
if (buffer == NULL)
return B_NO_MEMORY;
palette->GetColors(buffer, size);
if (output->Write(buffer, size) < size) {
delete[] buffer;
return B_IO_ERROR;
}
delete[] buffer;
return B_OK;
}
status_t
GIFSave::WriteGIFControlBlock()
{
unsigned char b[8] = {
EXTENSION_INTRODUCER, GRAPHIC_CONTROL_LABEL, 0x04, 0x00, 0x00, 0x00,
0x00, BLOCK_TERMINATOR
};
if (palette->UseTransparent()) {
b[3] = b[3] | 1;
b[6] = palette->TransparentIndex();
}
return output->Write(b, 8) < 8 ? B_IO_ERROR : B_OK;
}
status_t
GIFSave::WriteGIFImageHeader()
{
unsigned char header[10];
header[0] = DESCRIPTOR_INTRODUCER;
header[1] = header[2] = 0;
header[3] = header[4] = 0;
header[5] = width & 0xff;
header[6] = (width & 0xff00) >> 8;
header[7] = height & 0xff;
header[8] = (height & 0xff00) >> 8;
if (fSettings->SetGetBool(GIF_SETTING_INTERLACED))
header[9] = 0x40;
else
header[9] = BLOCK_TERMINATOR;
return output->Write(header, 10) < 10 ? B_IO_ERROR : B_OK;
}
status_t
GIFSave::WriteGIFImageData()
{
InitFrame();
status_t result = B_OK;
code_value = (short*)malloc(HASHSIZE * 2);
if (code_value == NULL)
return B_NO_MEMORY;
prefix_code = (short*)malloc(HASHSIZE * 2);
if (prefix_code == NULL) {
free(code_value);
return B_NO_MEMORY;
}
append_char = (unsigned char*)malloc(HASHSIZE);
if (append_char == NULL) {
free(code_value);
free(prefix_code);
return B_NO_MEMORY;
}
ResetHashtable();
if (output->Write(&code_size, 1) < 1) {
free(code_value);
free(prefix_code);
free(append_char);
return B_IO_ERROR;
}
result = OutputCode(clear_code, BITS);
if (result != B_OK) {
free(code_value);
free(prefix_code);
free(append_char);
return B_IO_ERROR;
}
string_code = NextPixel(0);
int area = height * width;
for (int x = 1; x < area; x++) {
character = NextPixel(x);
int y = 0;
if ((y = CheckHashtable(string_code, character)) != -1)
string_code = y;
else {
AddToHashtable(string_code, character);
result = OutputCode(string_code, BITS);
if (result != B_OK) {
free(code_value);
free(prefix_code);
free(append_char);
return B_IO_ERROR;
}
if (next_code > max_code) {
BITS++;
if (BITS > LZ_MAX_BITS) {
result = OutputCode(clear_code, LZ_MAX_BITS);
if (result != B_OK) {
free(code_value);
free(prefix_code);
free(append_char);
return B_IO_ERROR;
}
BITS = code_size + 1;
ResetHashtable();
next_code = clear_code + 1;
}
max_code = (1 << BITS) - 1;
}
string_code = character;
next_code++;
}
}
result = OutputCode(string_code, BITS);
if (result != B_OK) {
free(code_value);
free(prefix_code);
free(append_char);
return B_IO_ERROR;
}
result = OutputCode(end_code, BITS);
if (result != B_OK) {
free(code_value);
free(prefix_code);
free(append_char);
return B_IO_ERROR;
}
result = OutputCode(0, BITS, true);
if (result != B_OK) {
free(code_value);
free(prefix_code);
free(append_char);
return B_IO_ERROR;
}
char t = BLOCK_TERMINATOR;
if (output->Write(&t, 1) < 1) {
free(code_value);
free(prefix_code);
free(append_char);
return B_IO_ERROR;
}
free(code_value);
free(prefix_code);
free(append_char);
return result;
}
status_t
GIFSave::OutputCode(short code, int BITS, bool flush)
{
if (!flush) {
bit_buffer |= (unsigned int)code << bit_count;
bit_count += BITS;
while (bit_count >= 8) {
byte_buffer[byte_count + 1] = (unsigned char)(bit_buffer & 0xff);
byte_count++;
bit_buffer >>= 8;
bit_count -= 8;
}
if (byte_count >= 255) {
byte_buffer[0] = 255;
if (output->Write(byte_buffer, 256) < 256)
return B_IO_ERROR;
if (byte_count == 256) {
byte_buffer[1] = byte_buffer[256];
byte_count = 1;
} else
byte_count = 0;
}
} else {
bit_buffer |= (unsigned int) code << bit_count;
bit_count += BITS;
while (bit_count > 0) {
byte_buffer[byte_count + 1] = (unsigned char)(bit_buffer & 0xff);
byte_count++;
bit_buffer >>= 8;
bit_count -= 8;
}
if (byte_count > 0) {
byte_buffer[0] = (unsigned char)byte_count;
if (output->Write(byte_buffer, byte_count + 1) < byte_count + 1)
return B_IO_ERROR;
}
}
return B_OK;
}
void
GIFSave::ResetHashtable()
{
for (int q = 0; q < HASHSIZE; q++) {
code_value[q] = -1;
prefix_code[q] = 0;
append_char[q] = 0;
}
}
int
GIFSave::CheckHashtable(int s, unsigned char c)
{
if (s == -1)
return c;
int hashindex = HASH(s, c);
int nextindex;
while ((nextindex = code_value[hashindex]) != -1) {
if (prefix_code[nextindex] == s && append_char[nextindex] == c)
return nextindex;
hashindex = (hashindex + HASHSTEP) % HASHSIZE;
}
return -1;
}
void
GIFSave::AddToHashtable(int s, unsigned char c)
{
int hashindex = HASH(s, c);
while (code_value[hashindex] != -1)
hashindex = (hashindex + HASHSTEP) % HASHSIZE;
code_value[hashindex] = next_code;
prefix_code[next_code] = s;
append_char[next_code] = c;
}
unsigned char
GIFSave::NextPixel(int pixel)
{
int bpr = bitmap->BytesPerRow();
color_space colorSpace = bitmap->ColorSpace();
bool useAlphaForTransparency = colorSpace == B_RGBA32_BIG
|| (fSettings->SetGetBool(GIF_SETTING_USE_TRANSPARENT_AUTO)
&& colorSpace == B_RGBA32);
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char a;
if (colorSpace == B_RGB32 || colorSpace == B_RGBA32) {
b = gifbits[0];
g = gifbits[1];
r = gifbits[2];
a = gifbits[3];
} else {
a = gifbits[0];
r = gifbits[1];
g = gifbits[2];
b = gifbits[3];
}
gifbits += 4;
pos += 4;
if (!fSettings->SetGetBool(GIF_SETTING_USE_TRANSPARENT)
|| fSettings->SetGetBool(GIF_SETTING_USE_TRANSPARENT_AUTO)
|| r != fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_RED)
|| g != fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_GREEN)
|| b != fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_BLUE)) {
if (fSettings->SetGetBool(GIF_SETTING_USE_DITHERING)) {
if (pixel % width == 0)
red_side_error = green_side_error = blue_side_error = 0;
b = min_c(255, max_c(0, b - blue_side_error));
g = min_c(255, max_c(0, g - green_side_error));
r = min_c(255, max_c(0, r - red_side_error));
}
}
if (fSettings->SetGetBool(GIF_SETTING_INTERLACED)) {
if (pos >= bpr) {
pos = 0;
row += gs_increment_pass_by[pass];
while (row >= height) {
pass++;
row = gs_pass_starts_at[pass];
}
gifbits = (unsigned char*)bitmap->Bits() + (bpr * row);
}
}
#if 0
unsigned int key = (r << 16) + (g << 8) + b;
ColorCache* cc = (ColorCache*)hash->GetItem(key);
if (cc == NULL) {
cc = new ColorCache();
cc->key = key;
cc->index = palette->IndexForColor(r, g, b);
hash->AddItem((HashItem*)cc);
}
if (prefs->usedithering) {
int x = pixel % width;
if (prefs->interlaced && x == width - 1) {
for (int32 y = -1; y < width + 1; y++) {
red_error[y] = 0;
green_error[y] = 0;
blue_error[y] = 0;
}
}
int32 red_total_error = palette->pal[cc->index].red - r;
int32 green_total_error = palette->pal[cc->index].green - g;
int32 blue_total_error = palette->pal[cc->index].blue - b;
red_side_error = (red_error[x + 1]
+ (red_total_error * seven_sixteenth)) >> 15;
blue_side_error = (blue_error[x + 1]
+ (blue_total_error * seven_sixteenth)) >> 15;
green_side_error = (green_error[x + 1]
+ (green_total_error * seven_sixteenth)) >> 15;
red_error[x - 1] += (red_total_error * three_sixteenth);
green_error[x - 1] += (green_total_error * three_sixteenth);
blue_error[x - 1] += (blue_total_error * three_sixteenth);
red_error[x] += (red_total_error * five_sixteenth);
green_error[x] += (green_total_error * five_sixteenth);
blue_error[x] += (blue_total_error * five_sixteenth);
red_error[x + 1] = (red_total_error * one_sixteenth);
green_error[x + 1] = (green_total_error * one_sixteenth);
blue_error[x + 1] = (blue_total_error * one_sixteenth);
}
return cc->index;
#endif
int index = palette->IndexForColor(r, g, b, useAlphaForTransparency
? a : 255);
if (index != palette->TransparentIndex()
&& fSettings->SetGetBool(GIF_SETTING_USE_DITHERING)) {
int x = pixel % width;
if (fSettings->SetGetBool(GIF_SETTING_INTERLACED) && x == width - 1) {
for (int32 y = -1; y < width + 1; y++) {
red_error[y] = 0;
green_error[y] = 0;
blue_error[y] = 0;
}
}
int32 red_total_error = palette->pal[index].red - r;
int32 green_total_error = palette->pal[index].green - g;
int32 blue_total_error = palette->pal[index].blue - b;
red_side_error = (red_error[x + 1]
+ (red_total_error * seven_sixteenth)) >> 15;
blue_side_error = (blue_error[x + 1]
+ (blue_total_error * seven_sixteenth)) >> 15;
green_side_error = (green_error[x + 1]
+ (green_total_error * seven_sixteenth)) >> 15;
red_error[x - 1] += (red_total_error * three_sixteenth);
green_error[x - 1] += (green_total_error * three_sixteenth);
blue_error[x - 1] += (blue_total_error * three_sixteenth);
red_error[x] += (red_total_error * five_sixteenth);
green_error[x] += (green_total_error * five_sixteenth);
blue_error[x] += (blue_total_error * five_sixteenth);
red_error[x + 1] = (red_total_error * one_sixteenth);
green_error[x + 1] = (green_total_error * one_sixteenth);
blue_error[x + 1] = (blue_total_error * one_sixteenth);
}
return index;
}
void
GIFSave::InitFrame()
{
code_size = palette->SizeInBits();
if (code_size == 1)
code_size++;
BITS = code_size + 1;
clear_code = 1 << code_size;
end_code = clear_code + 1;
next_code = clear_code + 2;
max_code = (1 << BITS) - 1;
string_code = 0;
character = 0;
table_size = 1 << LZ_MAX_BITS;
bit_count = 0;
bit_buffer = 0;
byte_count = 0;
pass = pos = 0;
row = gs_pass_starts_at[0];
gifbits = (unsigned char*)bitmap->Bits();
}