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