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