//////////////////////////////////////////////////////////////////////////////// // // 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, // Philippe Saint-Pierre, // John Scipione, #include "GIFSave.h" #include #include #include #include #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(); }