xref: /haiku/src/add-ons/translators/gif/GIFSave.cpp (revision 5a7193d55818af7419e95ede22e678d45a0786bb)
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