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