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