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