root/src/add-ons/translators/gif/GIFSave.cpp
////////////////////////////////////////////////////////////////////////////////
//
//      File: GIFSave.cpp
//
//      Date: December 1999
//
//      Author: Daniel Switkin
//
//      Copyright 2003 (c) by Daniel Switkin. This file is made publically available
//      under the BSD license, with the stipulations that this complete header must
//      remain at the top of the file indefinitely, and credit must be given to the
//      original author in any about box using this software.
//
////////////////////////////////////////////////////////////////////////////////

// Additional authors:  Stephan Aßmus, <superstippi@gmx.de>
//                                              Philippe Saint-Pierre, <stpere@gmail.com>
//                                              John Scipione, <jscipione@gmail.com>


#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];
                        // Allow index of -1 too

                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];
                        // Allow index of -1 too

                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];
                        // Allow index of -1 too

                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;

        // Terminating character
        char t = TERMINATOR_INTRODUCER;
        output->Write(&t, 1);
}


GIFSave::~GIFSave()
{
        delete palette;
        fSettings->Release();
}


status_t
GIFSave::WriteGIFHeader()
{
        // Standard header
        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;

        // global palette
        int size = (1 << palette->SizeInBits()) * 3;
                // can't be bigger than this
        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;
                                                // this is different
                                }
                                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;
                // Don't carry error on to next line when interlaced because
                // that line won't be adjacent, hence error is meaningless
                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;
                // don't carry error on to next line when interlaced because
                // that line won't be adjacent, hence error is meaningless
                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();
}