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