xref: /haiku/src/tools/generate_boot_screen.cpp (revision 2600324b57fa31cdea1627d584d314f2a579c4a8)
1 /*
2  *	Copyright (c) 2008, Haiku, Inc. All rights reserved.
3  *	Distributed under the terms of the MIT license.
4  *
5  *	Authors:
6  *		Artur Wyszynski <harakash@gmail.com>
7  *		Stephan Aßmus <superstippi@gmx.de>
8  *		Philippe Saint-Pierre <stpere@gmail.com>
9  *		David Powell <david@mad.scientist.com>
10  */
11 
12 //! Haiku boot splash image generator/converter
13 
14 #include <iostream>
15 #include <png.h>
16 #include <string>
17 #include <stdarg.h>
18 #include <stdint.h>
19 
20 #include <ColorQuantizer.h>
21 
22 // TODO: Create 4 bit palette version of these
23 // images as well, so that they are ready to be
24 // used during boot in case we need to run in
25 // palette 4 bit VGA mode.
26 
27 
28 FILE* sOutput = NULL;
29 int sOffset = 0;
30 
31 static void
32 error(const char *s, ...)
33 {
34 	va_list args;
35 	va_start(args, s);
36 	vfprintf(stderr, s, args);
37 	fprintf(stderr, "\n");
38 	va_end(args);
39 	exit(-1);
40 }
41 
42 
43 class AutoFileCloser {
44 public:
45 	AutoFileCloser(FILE* file)
46 		: fFile(file)
47 	{}
48 	~AutoFileCloser()
49 	{
50 		fclose(fFile);
51 	}
52 private:
53 	FILE* fFile;
54 };
55 
56 
57 static void
58 read_png(const char* filename, int& width, int& height, png_bytep*& rowPtrs,
59 	png_structp& pngPtr, png_infop& infoPtr)
60 {
61 	char header[8];
62 	FILE* input = fopen(filename, "rb");
63 	if (!input)
64 		error("[read_png] File %s could not be opened for reading", filename);
65 
66 	AutoFileCloser _(input);
67 
68 	fread(header, 1, 8, input);
69 	if (png_sig_cmp((png_byte *)header, 0, 8 ))
70 		error("[read_png] File %s is not recognized as a PNG file", filename);
71 
72 	pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
73 		NULL, NULL, NULL);
74 	if (!pngPtr)
75 		error("[read_png] png_create_read_struct failed");
76 
77 	infoPtr = png_create_info_struct(pngPtr);
78 	if (!infoPtr)
79 		error("[read_png] png_create_info_struct failed");
80 
81 // TODO: I don't know which version of libpng introduced this feature:
82 #if PNG_LIBPNG_VER > 10005
83 	if (setjmp(png_jmpbuf(pngPtr)))
84 		error("[read_png] Error during init_io");
85 #endif
86 
87 	png_init_io(pngPtr, input);
88 	png_set_sig_bytes(pngPtr, 8);
89 
90 	// make sure we automatically get RGB data with 8 bits per channel
91 	// also make sure the alpha channel is stripped, in the end, we
92 	// expect 24 bits BGR data
93 	png_set_expand(pngPtr);
94 	png_set_gray_1_2_4_to_8(pngPtr);
95 	png_set_palette_to_rgb(pngPtr);
96 	png_set_gray_to_rgb(pngPtr);
97 	png_set_strip_alpha(pngPtr);
98 	png_set_bgr(pngPtr);
99 
100 	png_read_info(pngPtr, infoPtr);
101 	width = infoPtr->width;
102 	height = infoPtr->height;
103 	if (infoPtr->bit_depth != 8)
104 		error("[read_png] File %s has wrong bit depth\n", filename);
105 	if ((int)infoPtr->rowbytes < width * 3) {
106 		error("[read_png] File %s has wrong color type (RGB required)\n",
107 			filename);
108 	}
109 
110 
111 	png_set_interlace_handling(pngPtr);
112 	png_read_update_info(pngPtr, infoPtr);
113 
114 #if PNG_LIBPNG_VER > 10005
115 	if (setjmp(png_jmpbuf(pngPtr)))
116 		error("[read_png] Error during read_image");
117 #endif
118 
119 	rowPtrs = (png_bytep*)malloc(sizeof(png_bytep) * height);
120 	for (int y = 0; y < height; y++)
121 		rowPtrs[y] = (png_byte*)malloc(infoPtr->rowbytes);
122 
123 	png_read_image(pngPtr, rowPtrs);
124 }
125 
126 
127 static void
128 new_line_if_required()
129 {
130 	sOffset++;
131 	if (sOffset % 12 == 0)
132 		fprintf(sOutput, "\n\t");
133 }
134 
135 
136 static void
137 write_24bit_image(const char* baseName, int width, int height, png_bytep* rowPtrs)
138 {
139 	fprintf(sOutput, "static const uint16 %sWidth = %d;\n", baseName, width);
140 	fprintf(sOutput, "static const uint16 %sHeight = %d;\n", baseName, height);
141 	fprintf(sOutput, "#ifndef __BOOTSPLASH_KERNEL__\n");
142 
143 	int buffer[128];
144 	// buffer[0] stores count, buffer[1..127] holds the actual values
145 
146 	fprintf(sOutput, "static uint8 %s24BitCompressedImage[] = {\n\t",
147 		baseName);
148 
149 	for (int c = 0; c < 3; c++) {
150 		// for each component i.e. R, G, B ...
151 		// NOTE : I don't care much about performance at this step,
152 		// decoding however...
153 		int currentValue = rowPtrs[0][c];
154 		int count = 0;
155 
156 		// When bufferActive == true, we store the number rather than writing
157 		// them directly; we use this to store numbers until we find a pair..
158 		bool bufferActive = false;
159 
160 		sOffset = 0;
161 
162 		for (int y = 0; y < height; y++) {
163 			png_byte* row = rowPtrs[y];
164 			for (int x = c; x < width * 3; x += 3) {
165 				if (row[x] == currentValue) {
166 					if (bufferActive) {
167 						bufferActive = false;
168 						count = 2;
169 						if (buffer[0] > 1) {
170 							fprintf(sOutput, "%d, ",
171 								128 + buffer[0] - 1);
172 							new_line_if_required();
173 							for (int i = 1; i < buffer[0] ; i++) {
174 								fprintf(sOutput, "%d, ",
175 									buffer[i]);
176 								new_line_if_required();
177 							}
178 						}
179 					} else {
180 						count++;
181 						if (count == 127) {
182 							fprintf(sOutput, "127, ");
183 							new_line_if_required();
184 							fprintf(sOutput, "%d, ", currentValue);
185 							new_line_if_required();
186 							count = 0;
187 						}
188 					}
189 				} else {
190 					if (bufferActive) {
191 						if (buffer[0] == 127) {
192 							// we don't have enough room,
193 							// flush the buffer
194 							fprintf(sOutput, "%d, ",
195 								128 + buffer[0] - 1);
196 							new_line_if_required();
197 							for (int i = 1; i < buffer[0]; i++) {
198 								fprintf(sOutput, "%d, ", buffer[i]);
199 								new_line_if_required();
200 							}
201 							buffer[0] = 0;
202 						}
203 						buffer[0]++;
204 						buffer[buffer[0]] = row[x];
205 					} else if (count > 0) {
206 						buffer[0] = 1;
207 						buffer[1] = row[x];
208 						bufferActive = true;
209 						if (count > 1) {
210 							fprintf(sOutput, "%d, ", count);
211 							new_line_if_required();
212 							fprintf(sOutput, "%d, ", currentValue);
213 							new_line_if_required();
214 						}
215 					}
216 					currentValue = row[x];
217 				}
218 			}
219 		}
220 		if (bufferActive) {
221 			// I could have written 127 + buffer[0],
222 			// but I think this is more readable...
223 			fprintf(sOutput, "%d, ", 128 + buffer[0] - 1);
224 			new_line_if_required();
225 			for (int i = 1; i < buffer[0] ; i++) {
226 				fprintf(sOutput, "%d, ", buffer[i]);
227 				new_line_if_required();
228 			}
229 		} else {
230 			fprintf(sOutput, "%d, %d, ", count, currentValue);
231 			new_line_if_required();
232 		}
233 		// we put a terminating zero for the next byte that indicates
234 		// a "count", just to indicate the end of the channel
235 		fprintf(sOutput, "0");
236 		if (c != 2)
237 			fprintf(sOutput, ",");
238 
239 		fprintf(sOutput, "\n\t");
240 	}
241 	fprintf(sOutput, "};\n");
242 	fprintf(sOutput, "#endif\n\n");
243 }
244 
245 
246 static void
247 write_8bit_image(const char* baseName, int width, int height, unsigned char** rowPtrs)
248 {
249 	int buffer[128];
250 	// buffer[0] stores count, buffer[1..127] holds the actual values
251 
252 	fprintf(sOutput, "static uint8 %s8BitCompressedImage[] = {\n\t",
253 		baseName);
254 
255 	// NOTE: I don't care much about performance at this step,
256 	// decoding however...
257 	unsigned char currentValue = rowPtrs[0][0];
258 	int count = 0;
259 
260 	// When bufferActive == true, we store the number rather than writing
261 	// them directly; we use this to store numbers until we find a pair..
262 	bool bufferActive = false;
263 
264 	sOffset = 0;
265 	for (int y = 0; y < height; y++) {
266 		unsigned char* row = rowPtrs[y];
267 		for (int x = 0; x < width; x++) {
268 			if (row[x] == currentValue) {
269 				if (bufferActive) {
270 					bufferActive = false;
271 					count = 2;
272 					if (buffer[0] > 1) {
273 						fprintf(sOutput, "%d, ",
274 							128 + buffer[0] - 1);
275 						new_line_if_required();
276 						for (int i = 1; i < buffer[0] ; i++) {
277 							fprintf(sOutput, "%d, ",
278 								buffer[i]);
279 							new_line_if_required();
280 						}
281 					}
282 				} else {
283 					count++;
284 					if (count == 127) {
285 						fprintf(sOutput, "127, ");
286 						new_line_if_required();
287 						fprintf(sOutput, "%d, ", currentValue);
288 						new_line_if_required();
289 						count = 0;
290 					}
291 				}
292 			} else {
293 				if (bufferActive) {
294 					if (buffer[0] == 127) {
295 						// we don't have enough room,
296 						// flush the buffer
297 						fprintf(sOutput, "%d, ",
298 							128 + buffer[0] - 1);
299 						new_line_if_required();
300 						for (int i = 1; i < buffer[0]; i++) {
301 							fprintf(sOutput, "%d, ", buffer[i]);
302 							new_line_if_required();
303 						}
304 						buffer[0] = 0;
305 					}
306 					buffer[0]++;
307 					buffer[buffer[0]] = row[x];
308 				} else if (count > 0) {
309 					buffer[0] = 1;
310 					buffer[1] = row[x];
311 					bufferActive = true;
312 					if (count > 1) {
313 						fprintf(sOutput, "%d, ", count);
314 						new_line_if_required();
315 						fprintf(sOutput, "%d, ", currentValue);
316 						new_line_if_required();
317 					}
318 				}
319 				currentValue = row[x];
320 			}
321 		}
322 	}
323 	if (bufferActive) {
324 		// I could have written 127 + buffer[0],
325 		// but I think this is more readable...
326 		fprintf(sOutput, "%d, ", 128 + buffer[0] - 1);
327 		new_line_if_required();
328 		for (int i = 1; i < buffer[0] ; i++) {
329 			fprintf(sOutput, "%d, ", buffer[i]);
330 			new_line_if_required();
331 		}
332 	} else {
333 		fprintf(sOutput, "%d, %d, ", count, currentValue);
334 		new_line_if_required();
335 	}
336 	// we put a terminating zero for the next byte that indicates
337 	// a "count", to indicate the end
338 	fprintf(sOutput, "0");
339 
340 	fprintf(sOutput, "\n\t");
341 	fprintf(sOutput, "};\n\n");
342 }
343 
344 
345 unsigned char
346 nearest_color(unsigned char* color, RGBA palette[256])
347 {
348 	int i, dist, minDist, index = 0;
349 	minDist = 255 * 255 + 255 * 255 + 255 * 255 + 1;
350 	for (i = 0; i < 256; i++) {
351 		int dr = ((int)color[2]) - palette[i].r;
352 		int dg = ((int)color[1]) - palette[i].g;
353 		int db = ((int)color[0]) - palette[i].b;
354 		dist = dr * dr + dg * dg + db * db;
355 		if (dist < minDist) {
356 			minDist = dist;
357 			index = i;
358 		}
359 	}
360 	return index;
361 }
362 
363 
364 static void
365 create_8bit_images(const char* logoBaseName, int logoWidth, int logoHeight,
366 	png_bytep* logoRowPtrs, const char* iconsBaseName, int iconsWidth,
367 	int iconsHeight, png_bytep* iconsRowPtrs)
368 {
369 	// Generate 8-bit palette
370 	BColorQuantizer quantizer(256, 8);
371 	quantizer.ProcessImage(logoRowPtrs, logoWidth, logoHeight);
372 	quantizer.ProcessImage(iconsRowPtrs, iconsWidth, iconsHeight);
373 
374 	RGBA palette[256];
375 	quantizer.GetColorTable(palette);
376 
377 	// convert 24-bit logo image to 8-bit indexed color
378 	uint8* logoIndexedImageRows[logoHeight];
379 	for (int y = 0; y < logoHeight; y++) {
380 		logoIndexedImageRows[y] = new uint8[logoWidth];
381 		for (int x = 0; x < logoWidth; x++)	{
382 			logoIndexedImageRows[y][x] = nearest_color(&logoRowPtrs[y][x*3],
383 				palette);
384 		}
385 	}
386 
387 	// convert 24-bit icons image to 8-bit indexed color
388 	uint8* iconsIndexedImageRows[iconsHeight];
389 	for (int y = 0; y < iconsHeight; y++) {
390 		iconsIndexedImageRows[y] = new uint8[iconsWidth];
391 		for (int x = 0; x < iconsWidth; x++)	{
392 			iconsIndexedImageRows[y][x] = nearest_color(&iconsRowPtrs[y][x*3],
393 				palette);
394 		}
395 	}
396 
397 
398 	fprintf(sOutput, "#ifndef __BOOTSPLASH_KERNEL__\n");
399 
400 	// write the 8-bit color palette
401 	fprintf(sOutput, "static const uint8 k8BitPalette[] = {\n");
402 	for (int c = 0; c < 256; c++) {
403 		fprintf(sOutput, "\t0x%x, 0x%x, 0x%x,\n",
404 			palette[c].r, palette[c].g, palette[c].b);
405 	}
406 	fprintf(sOutput, "\t};\n\n");
407 
408 	// write the 8-bit images
409 	write_8bit_image(logoBaseName, logoWidth, logoHeight, logoIndexedImageRows);
410 	write_8bit_image(iconsBaseName, iconsWidth, iconsHeight,
411 		iconsIndexedImageRows);
412 
413 	fprintf(sOutput, "#endif\n\n");
414 
415 	// free memory
416 	for (int y = 0; y < logoHeight; y++)
417 		delete[] logoIndexedImageRows[y];
418 
419 	for (int y = 0; y < iconsHeight; y++)
420 		delete[] iconsIndexedImageRows[y];
421 }
422 
423 
424 static void
425 parse_images(const char* logoFilename, const char* logoBaseName,
426 	const char* iconsFilename, const char* iconsBaseName)
427 {
428 	int logoWidth;
429 	int logoHeight;
430 	png_bytep* logoRowPtrs = NULL;
431 	png_structp logoPngPtr;
432 	png_infop logoInfoPtr;
433 
434 	int iconsWidth;
435 	int iconsHeight;
436 	png_bytep* iconsRowPtrs = NULL;
437 	png_structp iconsPngPtr;
438 	png_infop iconsInfoPtr;
439 
440 	read_png(logoFilename, logoWidth, logoHeight, logoRowPtrs, logoPngPtr,
441 		logoInfoPtr);
442 	read_png(iconsFilename, iconsWidth, iconsHeight, iconsRowPtrs, iconsPngPtr,
443 		iconsInfoPtr);
444 
445 	// write 24-bit images
446 	write_24bit_image(logoBaseName, logoWidth, logoHeight, logoRowPtrs);
447 	write_24bit_image(iconsBaseName, iconsWidth, iconsHeight, iconsRowPtrs);
448 
449 	// write 8-bit index color images
450 	create_8bit_images(logoBaseName, logoWidth, logoHeight, logoRowPtrs,
451 		iconsBaseName, iconsWidth, iconsHeight, iconsRowPtrs);
452 
453 	// free resources
454 	png_destroy_read_struct(&logoPngPtr, &logoInfoPtr, NULL);
455 	for (int y = 0; y < logoHeight; y++)
456 		free(logoRowPtrs[y]);
457 	free(logoRowPtrs);
458 
459 	png_destroy_read_struct(&iconsPngPtr, &iconsInfoPtr, NULL);
460 	for (int y = 0; y < iconsHeight; y++)
461 		free(iconsRowPtrs[y]);
462 	free(iconsRowPtrs);
463 }
464 
465 
466 int
467 main(int argc, char* argv[])
468 {
469 	if (argc < 8) {
470 		printf("Usage:\n");
471 		printf("\t%s <splash.png> <x placement in %%> <y placement in %%> "
472 			"<icons.png> <x placement in %%> <y placement in %%> <images.h>\n",
473 			argv[0]);
474 		return 0;
475 	}
476 
477 	int logoPlacementX = atoi(argv[2]);
478 	int logoPlacementY = atoi(argv[3]);
479 	int iconPlacementX = atoi(argv[5]);
480 	int iconPlacementY = atoi(argv[6]);
481 	if (logoPlacementX < 0 || logoPlacementX > 100) {
482 		printf("Bad X placement for logo: %d%%\n", logoPlacementX);
483 		return 1;
484 	}
485 	if (logoPlacementY < 0 || logoPlacementY > 100) {
486 		printf("Bad Y placement for logo: %d%%\n\n", logoPlacementY);
487 		return 1;
488 	}
489 	if (iconPlacementX < 0 || iconPlacementX > 100) {
490 		printf("Bad X placement for icons: %d%%\n", iconPlacementX);
491 		return 1;
492 	}
493 	if (iconPlacementY < 0 || iconPlacementY > 100) {
494 		printf("Bad Y placement for icons: %d%%\n", iconPlacementY);
495 		return 1;
496 	}
497 
498 	const char* headerFileName = argv[7];
499 	sOutput = fopen(headerFileName, "wb");
500 	if (!sOutput)
501 		error("Could not open file \"%s\" for writing", headerFileName);
502 
503 	fputs("// This file was generated by the generate_boot_screen Haiku build "
504 		"tool.\n\n", sOutput);
505 
506 	fprintf(sOutput, "static const int32 kSplashLogoPlacementX = %d;\n",
507 		logoPlacementX);
508 	fprintf(sOutput, "static const int32 kSplashLogoPlacementY = %d;\n",
509 		logoPlacementY);
510 	fprintf(sOutput, "static const int32 kSplashIconsPlacementX = %d;\n",
511 		iconPlacementX);
512 	fprintf(sOutput, "static const int32 kSplashIconsPlacementY = %d;\n\n",
513 		iconPlacementY);
514 
515 	parse_images(argv[1], "kSplashLogo", argv[4], "kSplashIcons");
516 
517 	fclose(sOutput);
518 	return 0;
519 }
520