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