1 //////////////////////////////////////////////////////////////////////////////// 2 // 3 // File: GIFSave.cpp 4 // 5 // Date: December 1999 6 // 7 // Author: Daniel Switkin 8 // 9 // Copyright 2003 (c) by Daniel Switkin. This file is made publically available 10 // under the BSD license, with the stipulations that this complete header must 11 // remain at the top of the file indefinitely, and credit must be given to the 12 // original author in any about box using this software. 13 // 14 //////////////////////////////////////////////////////////////////////////////// 15 16 // Additional authors: Stephan Aßmus, <superstippi@gmx.de> 17 // Philippe Saint-Pierre, <stpere@gmail.com> 18 19 #include "GIFSave.h" 20 #include <stdio.h> 21 #include <stdlib.h> 22 #include <syslog.h> 23 24 const int gs_pass_starts_at[] = {0, 4, 2, 1, 0}; 25 const int gs_increment_pass_by[] = {8, 8, 4, 2, 0}; 26 const int32 one_sixteenth = (int32)((1.0 / 16.0) * 32768); 27 const int32 three_sixteenth = (int32)((3.0 / 16.0) * 32768); 28 const int32 five_sixteenth = (int32)((5.0 / 16.0) * 32768); 29 const int32 seven_sixteenth = (int32)((7.0 / 16.0) * 32768); 30 31 extern bool debug; 32 33 class ColorCache : public HashItem { 34 public: 35 unsigned char index; 36 }; 37 38 // constructor 39 GIFSave::GIFSave(BBitmap* bitmap, BPositionIO* output, 40 TranslatorSettings* settings) 41 { 42 fSettings = settings; 43 color_space cs = bitmap->ColorSpace(); 44 if (cs != B_RGB32 && cs != B_RGBA32 && cs != B_RGB32_BIG 45 && cs != B_RGBA32_BIG) { 46 if (debug) 47 syslog(LOG_ERR, "GIFSave::GIFSave() - Unknown color space\n"); 48 fatalerror = true; 49 return; 50 } 51 52 fatalerror = false; 53 if (fSettings->SetGetInt32(GIF_SETTING_PALETTE_MODE) == OPTIMAL_PALETTE) 54 palette = new SavePalette(bitmap, 55 fSettings->SetGetInt32(GIF_SETTING_PALETTE_SIZE)); 56 else 57 palette = new SavePalette( 58 fSettings->SetGetInt32(GIF_SETTING_PALETTE_MODE)); 59 if (!palette->IsValid()) { 60 fatalerror = true; 61 return; 62 } 63 64 width = bitmap->Bounds().IntegerWidth() + 1; 65 height = bitmap->Bounds().IntegerHeight() + 1; 66 if (debug) 67 syslog(LOG_ERR, "GIFSave::GIFSave() - Image dimensions are %d by %d\n", 68 width, height); 69 70 if (fSettings->SetGetBool(GIF_SETTING_USE_DITHERING)) { 71 if (debug) 72 syslog(LOG_ERR, "GIFSave::GIFSave() - Using dithering\n"); 73 red_error = new int32[width + 2]; 74 red_error = &red_error[1]; // Allow index of -1 too 75 green_error = new int32[width + 2]; 76 green_error = &green_error[1]; // Allow index of -1 too 77 blue_error = new int32[width + 2]; 78 blue_error = &blue_error[1]; // Allow index of -1 too 79 80 red_side_error = green_side_error = blue_side_error = 0; 81 for (int32 x = -1; x < width + 1; x++) { 82 red_error[x] = 0; 83 green_error[x] = 0; 84 blue_error[x] = 0; 85 } 86 } else if (debug) 87 syslog(LOG_ERR, "GIFSave::GIFSave() - Not using dithering\n"); 88 89 if (debug) { 90 if (fSettings->SetGetBool(GIF_SETTING_INTERLACED)) 91 syslog(LOG_ERR, "GIFSave::GIFSave() - Interlaced, "); 92 else 93 syslog(LOG_ERR, "GIFSave::GIFSave() - Not interlaced, "); 94 switch (fSettings->SetGetInt32(GIF_SETTING_PALETTE_MODE)) { 95 case WEB_SAFE_PALETTE: 96 syslog(LOG_ERR, "web safe palette\n"); 97 break; 98 case BEOS_SYSTEM_PALETTE: 99 syslog(LOG_ERR, "BeOS system palette\n"); 100 break; 101 case GREYSCALE_PALETTE: 102 syslog(LOG_ERR, "greyscale palette\n"); 103 break; 104 case OPTIMAL_PALETTE: 105 default: 106 syslog(LOG_ERR, "optimal palette\n"); 107 break; 108 } 109 } 110 111 if (fSettings->SetGetBool(GIF_SETTING_USE_TRANSPARENT)) { 112 if (fSettings->SetGetBool(GIF_SETTING_USE_TRANSPARENT_AUTO)) { 113 palette->PrepareForAutoTransparency(); 114 if (debug) 115 syslog(LOG_ERR, "GIFSave::GIFSave() - Using transparent index %d\n", 116 palette->TransparentIndex()); 117 } else { 118 palette->SetTransparentColor( 119 (uint8)fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_RED), 120 (uint8)fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_GREEN), 121 (uint8)fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_BLUE)); 122 if (debug) { 123 syslog(LOG_ERR, "GIFSave::GIFSave() - Found transparent color %d,%d,%d " 124 "at index %d\n", 125 fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_RED), 126 fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_GREEN), 127 fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_BLUE), 128 palette->TransparentIndex()); 129 } 130 } 131 } else { 132 if (debug) 133 syslog(LOG_ERR, "GIFSave::GIFSave() - Not using transparency\n"); 134 } 135 136 this->output = output; 137 this->bitmap = bitmap; 138 WriteGIFHeader(); 139 if (debug) 140 syslog(LOG_ERR, "GIFSave::GIFSave() - Wrote gif header\n"); 141 142 hash = new SFHash(1 << 16); 143 WriteGIFControlBlock(); 144 if (debug) 145 syslog(LOG_ERR, "GIFSave::GIFSave() - Wrote gif control block\n"); 146 WriteGIFImageHeader(); 147 if (debug) 148 syslog(LOG_ERR, "GIFSave::GIFSave() - Wrote gif image header\n"); 149 WriteGIFImageData(); 150 if (debug) 151 syslog(LOG_ERR, "GIFSave::GIFSave() - Wrote gif image data\n"); 152 153 if (fSettings->SetGetBool(GIF_SETTING_USE_DITHERING)) { 154 delete [] &red_error[-1]; 155 delete [] &green_error[-1]; 156 delete [] &blue_error[-1]; 157 } 158 delete hash; 159 160 // Terminating character 161 char t = 0x3b; 162 output->Write(&t, 1); 163 } 164 165 // destructor 166 GIFSave::~GIFSave() 167 { 168 delete palette; 169 fSettings->Release(); 170 } 171 172 // WriteGIFHeader 173 void 174 GIFSave::WriteGIFHeader() 175 { 176 // Standard header 177 unsigned char header[] = {'G', 'I', 'F', '8', '9', 'a', 0, 0, 0, 0, 0, 0, 0}; 178 header[6] = width & 0xff; 179 header[7] = (width & 0xff00) >> 8; 180 header[8] = height & 0xff; 181 header[9] = (height & 0xff00) >> 8; 182 header[10] = 0xf0 | (palette->SizeInBits() - 1); 183 header[11] = palette->BackgroundIndex(); 184 output->Write(header, 13); 185 186 // Global palette 187 int size = (1 << palette->SizeInBits()) * 3; 188 uint8* buffer = new uint8[size]; // can't be bigger than this 189 palette->GetColors(buffer, size); 190 output->Write(buffer, size); 191 delete[] buffer; 192 } 193 194 // WriteGIFControlBlock 195 void 196 GIFSave::WriteGIFControlBlock() 197 { 198 unsigned char b[8] = {0x21, 0xf9, 0x04, 0, 0, 0, 0, 0x00}; 199 if (palette->UseTransparent()) { 200 b[3] = b[3] | 1; 201 b[6] = palette->TransparentIndex(); 202 } 203 output->Write(b, 8); 204 } 205 206 // WriteGIFImageHeader 207 void GIFSave::WriteGIFImageHeader() 208 { 209 unsigned char header[10]; 210 header[0] = 0x2c; 211 header[1] = header[2] = 0; 212 header[3] = header[4] = 0; 213 214 header[5] = width & 0xff; 215 header[6] = (width & 0xff00) >> 8; 216 header[7] = height & 0xff; 217 header[8] = (height & 0xff00) >> 8; 218 219 if (fSettings->SetGetBool(GIF_SETTING_INTERLACED)) 220 header[9] = 0x40; 221 else 222 header[9] = 0x00; 223 output->Write(header, 10); 224 } 225 226 // WriteGIFImageData 227 void GIFSave::WriteGIFImageData() 228 { 229 InitFrame(); 230 code_value = (short *)malloc(HASHSIZE * 2); 231 prefix_code = (short *)malloc(HASHSIZE * 2); 232 append_char = (unsigned char *)malloc(HASHSIZE); 233 ResetHashtable(); 234 235 output->Write(&code_size, 1); 236 OutputCode(clear_code, BITS); 237 string_code = NextPixel(0); 238 int area = height * width; 239 240 for (int x = 1; x < area; x++) { 241 character = NextPixel(x); 242 int y = 0; 243 if ((y = CheckHashtable(string_code, character)) != -1) { 244 string_code = y; 245 } else { 246 AddToHashtable(string_code, character); 247 OutputCode(string_code, BITS); 248 249 if (next_code > max_code) { 250 BITS++; 251 if (BITS > 12) { 252 OutputCode(clear_code, 12); 253 BITS = code_size + 1; 254 ResetHashtable(); 255 next_code = clear_code + 1; // this is different 256 } 257 max_code = (1 << BITS) - 1; 258 } 259 string_code = character; 260 next_code++; 261 } 262 } 263 OutputCode(string_code, BITS); 264 OutputCode(end_code, BITS); 265 OutputCode(0, BITS, true); 266 char t = 0x00; 267 output->Write(&t, 1); 268 free(code_value); 269 free(prefix_code); 270 free(append_char); 271 } 272 273 // OutputCode 274 void 275 GIFSave::OutputCode(short code, int BITS, bool flush) 276 { 277 if (!flush) { 278 bit_buffer |= (unsigned int) code << bit_count; 279 bit_count += BITS; 280 while (bit_count >= 8) { 281 byte_buffer[byte_count + 1] = (unsigned char)(bit_buffer & 0xff); 282 byte_count++; 283 bit_buffer >>= 8; 284 bit_count -= 8; 285 } 286 if (byte_count >= 255) { 287 byte_buffer[0] = 255; 288 output->Write(byte_buffer, 256); 289 if (byte_count == 256) { 290 byte_buffer[1] = byte_buffer[256]; 291 byte_count = 1; 292 } else byte_count = 0; 293 } 294 } else { 295 bit_buffer |= (unsigned int) code << bit_count; 296 bit_count += BITS; 297 while (bit_count > 0) { 298 byte_buffer[byte_count + 1] = (unsigned char)(bit_buffer & 0xff); 299 byte_count++; 300 bit_buffer >>= 8; 301 bit_count -= 8; 302 } 303 if (byte_count > 0) { 304 byte_buffer[0] = (unsigned char)byte_count; 305 output->Write(byte_buffer, byte_count + 1); 306 } 307 } 308 } 309 310 // ResetHashtable 311 void 312 GIFSave::ResetHashtable() 313 { 314 for (int q = 0; q < HASHSIZE; q++) { 315 code_value[q] = -1; 316 prefix_code[q] = 0; 317 append_char[q] = 0; 318 } 319 } 320 321 // CheckHashtable 322 int 323 GIFSave::CheckHashtable(int s, unsigned char c) 324 { 325 if (s == -1) return c; 326 int hashindex = HASH(s, c); 327 int nextindex; 328 while ((nextindex = code_value[hashindex]) != -1) { 329 if (prefix_code[nextindex] == s && append_char[nextindex] == c) 330 return nextindex; 331 hashindex = (hashindex + HASHSTEP) % HASHSIZE; 332 } 333 return -1; 334 } 335 336 // AddToHashtable 337 void 338 GIFSave::AddToHashtable(int s, unsigned char c) 339 { 340 int hashindex = HASH(s, c); 341 while (code_value[hashindex] != -1) hashindex = (hashindex + HASHSTEP) % HASHSIZE; 342 code_value[hashindex] = next_code; 343 prefix_code[next_code] = s; 344 append_char[next_code] = c; 345 } 346 347 // NextPixel 348 unsigned char 349 GIFSave::NextPixel(int pixel) 350 { 351 int bpr = bitmap->BytesPerRow(); 352 color_space cs = bitmap->ColorSpace(); 353 bool useAlphaForTransparency = 354 (fSettings->SetGetBool(GIF_SETTING_USE_TRANSPARENT_AUTO) 355 && cs == B_RGBA32) || cs == B_RGBA32_BIG; 356 unsigned char r, g, b, a; 357 358 if (cs == B_RGB32 || cs == B_RGBA32) { 359 b = gifbits[0]; 360 g = gifbits[1]; 361 r = gifbits[2]; 362 a = gifbits[3]; 363 } else { 364 a = gifbits[0]; 365 r = gifbits[1]; 366 g = gifbits[2]; 367 b = gifbits[3]; 368 } 369 gifbits += 4; 370 pos += 4; 371 372 if (!fSettings->SetGetBool(GIF_SETTING_USE_TRANSPARENT) 373 || fSettings->SetGetBool(GIF_SETTING_USE_TRANSPARENT_AUTO) 374 || r != fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_RED) 375 || g != fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_GREEN) 376 || b != fSettings->SetGetInt32(GIF_SETTING_TRANSPARENT_BLUE)) { 377 378 if (fSettings->SetGetBool(GIF_SETTING_USE_DITHERING)) { 379 if (pixel % width == 0) 380 red_side_error = green_side_error = blue_side_error = 0; 381 382 b = min_c(255, max_c(0, b - blue_side_error)); 383 g = min_c(255, max_c(0, g - green_side_error)); 384 r = min_c(255, max_c(0, r - red_side_error)); 385 } 386 } 387 388 if (fSettings->SetGetBool(GIF_SETTING_INTERLACED)) { 389 if (pos >= bpr) { 390 pos = 0; 391 row += gs_increment_pass_by[pass]; 392 while (row >= height) { 393 pass++; 394 row = gs_pass_starts_at[pass]; 395 } 396 gifbits = (unsigned char *)bitmap->Bits() + (bpr * row); 397 } 398 } 399 /* 400 unsigned int key = (r << 16) + (g << 8) + b; 401 ColorCache *cc = (ColorCache *)hash->GetItem(key); 402 if (cc == NULL) { 403 cc = new ColorCache(); 404 cc->key = key; 405 cc->index = palette->IndexForColor(r, g, b); 406 hash->AddItem((HashItem *)cc); 407 } 408 409 if (prefs->usedithering) { 410 int x = pixel % width; 411 // Don't carry error on to next line when interlaced because 412 // that line won't be adjacent, hence error is meaningless 413 if (prefs->interlaced && x == width - 1) { 414 for (int32 y = -1; y < width + 1; y++) { 415 red_error[y] = 0; 416 green_error[y] = 0; 417 blue_error[y] = 0; 418 } 419 } 420 421 int32 red_total_error = palette->pal[cc->index].red - r; 422 int32 green_total_error = palette->pal[cc->index].green - g; 423 int32 blue_total_error = palette->pal[cc->index].blue - b; 424 425 red_side_error = (red_error[x + 1] + (red_total_error * seven_sixteenth)) >> 15; 426 blue_side_error = (blue_error[x + 1] + (blue_total_error * seven_sixteenth)) >> 15; 427 green_side_error = (green_error[x + 1] + (green_total_error * seven_sixteenth)) >> 15; 428 429 red_error[x - 1] += (red_total_error * three_sixteenth); 430 green_error[x - 1] += (green_total_error * three_sixteenth); 431 blue_error[x - 1] += (blue_total_error * three_sixteenth); 432 433 red_error[x] += (red_total_error * five_sixteenth); 434 green_error[x] += (green_total_error * five_sixteenth); 435 blue_error[x] += (blue_total_error * five_sixteenth); 436 437 red_error[x + 1] = (red_total_error * one_sixteenth); 438 green_error[x + 1] = (green_total_error * one_sixteenth); 439 blue_error[x + 1] = (blue_total_error * one_sixteenth); 440 } 441 442 return cc->index;*/ 443 444 int index = palette->IndexForColor(r, g, b, useAlphaForTransparency ? a : 255); 445 446 if (index != palette->TransparentIndex() 447 && fSettings->SetGetBool(GIF_SETTING_USE_DITHERING)) { 448 int x = pixel % width; 449 // Don't carry error on to next line when interlaced because 450 // that line won't be adjacent, hence error is meaningless 451 if (fSettings->SetGetBool(GIF_SETTING_INTERLACED) && x == width - 1) { 452 for (int32 y = -1; y < width + 1; y++) { 453 red_error[y] = 0; 454 green_error[y] = 0; 455 blue_error[y] = 0; 456 } 457 } 458 459 int32 red_total_error = palette->pal[index].red - r; 460 int32 green_total_error = palette->pal[index].green - g; 461 int32 blue_total_error = palette->pal[index].blue - b; 462 463 red_side_error = (red_error[x + 1] + (red_total_error * seven_sixteenth)) >> 15; 464 blue_side_error = (blue_error[x + 1] + (blue_total_error * seven_sixteenth)) >> 15; 465 green_side_error = (green_error[x + 1] + (green_total_error * seven_sixteenth)) >> 15; 466 467 red_error[x - 1] += (red_total_error * three_sixteenth); 468 green_error[x - 1] += (green_total_error * three_sixteenth); 469 blue_error[x - 1] += (blue_total_error * three_sixteenth); 470 471 red_error[x] += (red_total_error * five_sixteenth); 472 green_error[x] += (green_total_error * five_sixteenth); 473 blue_error[x] += (blue_total_error * five_sixteenth); 474 475 red_error[x + 1] = (red_total_error * one_sixteenth); 476 green_error[x + 1] = (green_total_error * one_sixteenth); 477 blue_error[x + 1] = (blue_total_error * one_sixteenth); 478 } 479 480 return index; 481 } 482 483 // InitFrame 484 void 485 GIFSave::InitFrame() 486 { 487 code_size = palette->SizeInBits(); 488 if (code_size == 1) 489 code_size++; 490 BITS = code_size + 1; 491 clear_code = 1 << code_size; 492 end_code = clear_code + 1; 493 next_code = clear_code + 2; 494 max_code = (1 << BITS) - 1; 495 string_code = 0; 496 character = 0; 497 table_size = 1 << 12; 498 499 bit_count = 0; 500 bit_buffer = 0; 501 byte_count = 0; 502 503 pass = pos = 0; 504 row = gs_pass_starts_at[0]; 505 506 gifbits = (unsigned char *)bitmap->Bits(); 507 } 508