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