xref: /haiku/src/tools/generate_boot_screen.cpp (revision 1345706a9ff6ad0dc041339a02d4259998b0765d)
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 						buffer[0]++;
205 						buffer[buffer[0]] = row[x];
206 					} else if (count > 0) {
207 						buffer[0] = 1;
208 						buffer[1] = row[x];
209 						bufferActive = true;
210 						if (count > 1) {
211 							fprintf(sOutput, "%d, ", count);
212 							new_line_if_required();
213 							fprintf(sOutput, "%d, ", currentValue);
214 							new_line_if_required();
215 						}
216 					}
217 					currentValue = row[x];
218 				}
219 			}
220 		}
221 		if (bufferActive) {
222 			// I could have written 127 + buffer[0],
223 			// but I think this is more readable...
224 			fprintf(sOutput, "%d, ", 128 + buffer[0] - 1);
225 			new_line_if_required();
226 			for (int i = 1; i < buffer[0] ; i++) {
227 				fprintf(sOutput, "%d, ", buffer[i]);
228 				new_line_if_required();
229 			}
230 		} else {
231 			fprintf(sOutput, "%d, %d, ", count, currentValue);
232 			new_line_if_required();
233 		}
234 		// we put a terminating zero for the next byte that indicates
235 		// a "count", just to indicate the end of the channel
236 		fprintf(sOutput, "0");
237 		if (c != 2)
238 			fprintf(sOutput, ",");
239 
240 		fprintf(sOutput, "\n\t");
241 	}
242 	fprintf(sOutput, "};\n");
243 	fprintf(sOutput, "#endif\n\n");
244 }
245 
246 
247 static void
248 write_8bit_image(const char* baseName, int width, int height, unsigned char** rowPtrs)
249 {
250 	int buffer[128];
251 	// buffer[0] stores count, buffer[1..127] holds the actual values
252 
253 	fprintf(sOutput, "static uint8 %s8BitCompressedImage[] = {\n\t",
254 		baseName);
255 
256 	// NOTE: I don't care much about performance at this step,
257 	// decoding however...
258 	unsigned char currentValue = rowPtrs[0][0];
259 	int count = 0;
260 
261 	// When bufferActive == true, we store the number rather than writing
262 	// them directly; we use this to store numbers until we find a pair..
263 	bool bufferActive = false;
264 
265 	sOffset = 0;
266 	for (int y = 0; y < height; y++) {
267 		unsigned char* row = rowPtrs[y];
268 		for (int x = 0; x < width; x++) {
269 			if (row[x] == currentValue) {
270 				if (bufferActive) {
271 					bufferActive = false;
272 					count = 2;
273 					if (buffer[0] > 1) {
274 						fprintf(sOutput, "%d, ",
275 							128 + buffer[0] - 1);
276 						new_line_if_required();
277 						for (int i = 1; i < buffer[0] ; i++) {
278 							fprintf(sOutput, "%d, ",
279 								buffer[i]);
280 							new_line_if_required();
281 						}
282 					}
283 				} else {
284 					count++;
285 					if (count == 127) {
286 						fprintf(sOutput, "127, ");
287 						new_line_if_required();
288 						fprintf(sOutput, "%d, ", currentValue);
289 						new_line_if_required();
290 						count = 0;
291 					}
292 				}
293 			} else {
294 				if (bufferActive) {
295 					if (buffer[0] == 127) {
296 						// we don't have enough room,
297 						// flush the buffer
298 						fprintf(sOutput, "%d, ",
299 							128 + buffer[0] - 1);
300 						new_line_if_required();
301 						for (int i = 1; i < buffer[0]; i++) {
302 							fprintf(sOutput, "%d, ", buffer[i]);
303 							new_line_if_required();
304 						}
305 						buffer[0] = 0;
306 					}
307 					buffer[0]++;
308 					buffer[buffer[0]] = row[x];
309 				} else if (count > 0) {
310 					buffer[0] = 1;
311 					buffer[1] = row[x];
312 					bufferActive = true;
313 					if (count > 1) {
314 						fprintf(sOutput, "%d, ", count);
315 						new_line_if_required();
316 						fprintf(sOutput, "%d, ", currentValue);
317 						new_line_if_required();
318 					}
319 				}
320 				currentValue = row[x];
321 			}
322 		}
323 	}
324 	if (bufferActive) {
325 		// I could have written 127 + buffer[0],
326 		// but I think this is more readable...
327 		fprintf(sOutput, "%d, ", 128 + buffer[0] - 1);
328 		new_line_if_required();
329 		for (int i = 1; i < buffer[0] ; i++) {
330 			fprintf(sOutput, "%d, ", buffer[i]);
331 			new_line_if_required();
332 		}
333 	} else {
334 		fprintf(sOutput, "%d, %d, ", count, currentValue);
335 		new_line_if_required();
336 	}
337 	// we put a terminating zero for the next byte that indicates
338 	// a "count", to indicate the end
339 	fprintf(sOutput, "0");
340 
341 	fprintf(sOutput, "\n\t");
342 	fprintf(sOutput, "};\n\n");
343 }
344 
345 
346 unsigned char
347 nearest_color(unsigned char* color, RGBA palette[256])
348 {
349 	int i, dist, minDist, index = 0;
350 	minDist = 255 * 255 + 255 * 255 + 255 * 255 + 1;
351 	for (i = 0; i < 256; i++) {
352 		int dr = ((int)color[2]) - palette[i].r;
353 		int dg = ((int)color[1]) - palette[i].g;
354 		int db = ((int)color[0]) - palette[i].b;
355 		dist = dr * dr + dg * dg + db * db;
356 		if (dist < minDist) {
357 			minDist = dist;
358 			index = i;
359 		}
360 	}
361 	return index;
362 }
363 
364 
365 static void
366 create_8bit_images(const char* logoBaseName, int logoWidth, int logoHeight,
367 	png_bytep* logoRowPtrs, const char* iconsBaseName, int iconsWidth,
368 	int iconsHeight, png_bytep* iconsRowPtrs)
369 {
370 	// Generate 8-bit palette
371 	BColorQuantizer quantizer(256, 8);
372 	quantizer.ProcessImage(logoRowPtrs, logoWidth, logoHeight);
373 	quantizer.ProcessImage(iconsRowPtrs, iconsWidth, iconsHeight);
374 
375 	RGBA palette[256];
376 	quantizer.GetColorTable(palette);
377 
378 	// convert 24-bit logo image to 8-bit indexed color
379 	uint8* logoIndexedImageRows[logoHeight];
380 	for (int y = 0; y < logoHeight; y++) {
381 		logoIndexedImageRows[y] = new uint8[logoWidth];
382 		for (int x = 0; x < logoWidth; x++)	{
383 			logoIndexedImageRows[y][x] = nearest_color(&logoRowPtrs[y][x*3],
384 				palette);
385 		}
386 	}
387 
388 	// convert 24-bit icons image to 8-bit indexed color
389 	uint8* iconsIndexedImageRows[iconsHeight];
390 	for (int y = 0; y < iconsHeight; y++) {
391 		iconsIndexedImageRows[y] = new uint8[iconsWidth];
392 		for (int x = 0; x < iconsWidth; x++)	{
393 			iconsIndexedImageRows[y][x] = nearest_color(&iconsRowPtrs[y][x*3],
394 				palette);
395 		}
396 	}
397 
398 
399 	fprintf(sOutput, "#ifndef __BOOTSPLASH_KERNEL__\n");
400 
401 	// write the 8-bit color palette
402 	fprintf(sOutput, "static const uint8 k8BitPalette[] = {\n");
403 	for (int c = 0; c < 256; c++) {
404 		fprintf(sOutput, "\t0x%x, 0x%x, 0x%x,\n",
405 			palette[c].r, palette[c].g, palette[c].b);
406 	}
407 	fprintf(sOutput, "\t};\n\n");
408 
409 	// write the 8-bit images
410 	write_8bit_image(logoBaseName, logoWidth, logoHeight, logoIndexedImageRows);
411 	write_8bit_image(iconsBaseName, iconsWidth, iconsHeight,
412 		iconsIndexedImageRows);
413 
414 	fprintf(sOutput, "#endif\n\n");
415 
416 	// free memory
417 	for (int y = 0; y < logoHeight; y++)
418 		delete[] logoIndexedImageRows[y];
419 
420 	for (int y = 0; y < iconsHeight; y++)
421 		delete[] iconsIndexedImageRows[y];
422 }
423 
424 
425 static void
426 parse_images(const char* logoFilename, const char* logoBaseName,
427 	const char* iconsFilename, const char* iconsBaseName)
428 {
429 	int logoWidth;
430 	int logoHeight;
431 	png_bytep* logoRowPtrs = NULL;
432 	png_structp logoPngPtr;
433 	png_infop logoInfoPtr;
434 
435 	int iconsWidth;
436 	int iconsHeight;
437 	png_bytep* iconsRowPtrs = NULL;
438 	png_structp iconsPngPtr;
439 	png_infop iconsInfoPtr;
440 
441 	read_png(logoFilename, logoWidth, logoHeight, logoRowPtrs, logoPngPtr,
442 		logoInfoPtr);
443 	read_png(iconsFilename, iconsWidth, iconsHeight, iconsRowPtrs, iconsPngPtr,
444 		iconsInfoPtr);
445 
446 	// write 24-bit images
447 	write_24bit_image(logoBaseName, logoWidth, logoHeight, logoRowPtrs);
448 	write_24bit_image(iconsBaseName, iconsWidth, iconsHeight, iconsRowPtrs);
449 
450 	// write 8-bit index color images
451 	create_8bit_images(logoBaseName, logoWidth, logoHeight, logoRowPtrs,
452 		iconsBaseName, iconsWidth, iconsHeight, iconsRowPtrs);
453 
454 	// free resources
455 	png_destroy_read_struct(&logoPngPtr, &logoInfoPtr, NULL);
456 	for (int y = 0; y < logoHeight; y++)
457 		free(logoRowPtrs[y]);
458 	free(logoRowPtrs);
459 
460 	png_destroy_read_struct(&iconsPngPtr, &iconsInfoPtr, NULL);
461 	for (int y = 0; y < iconsHeight; y++)
462 		free(iconsRowPtrs[y]);
463 	free(iconsRowPtrs);
464 }
465 
466 
467 int
468 main(int argc, char* argv[])
469 {
470 	if (argc < 8) {
471 		printf("Usage:\n");
472 		printf("\t%s <splash.png> <x placement in %%> <y placement in %%> "
473 			"<icons.png> <x placement in %%> <y placement in %%> <images.h>\n",
474 			argv[0]);
475 		return 0;
476 	}
477 
478 	int logoPlacementX = atoi(argv[2]);
479 	int logoPlacementY = atoi(argv[3]);
480 	int iconPlacementX = atoi(argv[5]);
481 	int iconPlacementY = atoi(argv[6]);
482 	if (logoPlacementX < 0 || logoPlacementX > 100) {
483 		printf("Bad X placement for logo: %d%%\n", logoPlacementX);
484 		return 1;
485 	}
486 	if (logoPlacementY < 0 || logoPlacementY > 100) {
487 		printf("Bad Y placement for logo: %d%%\n\n", logoPlacementY);
488 		return 1;
489 	}
490 	if (iconPlacementX < 0 || iconPlacementX > 100) {
491 		printf("Bad X placement for icons: %d%%\n", iconPlacementX);
492 		return 1;
493 	}
494 	if (iconPlacementY < 0 || iconPlacementY > 100) {
495 		printf("Bad Y placement for icons: %d%%\n", iconPlacementY);
496 		return 1;
497 	}
498 
499 	const char* headerFileName = argv[7];
500 	sOutput = fopen(headerFileName, "wb");
501 	if (!sOutput)
502 		error("Could not open file \"%s\" for writing", headerFileName);
503 
504 	fputs("// This file was generated by the generate_boot_screen Haiku build "
505 		"tool.\n\n", sOutput);
506 
507 	fprintf(sOutput, "static const int32 kSplashLogoPlacementX = %d;\n",
508 		logoPlacementX);
509 	fprintf(sOutput, "static const int32 kSplashLogoPlacementY = %d;\n",
510 		logoPlacementY);
511 	fprintf(sOutput, "static const int32 kSplashIconsPlacementX = %d;\n",
512 		iconPlacementX);
513 	fprintf(sOutput, "static const int32 kSplashIconsPlacementY = %d;\n\n",
514 		iconPlacementY);
515 
516 	parse_images(argv[1], "kSplashLogo", argv[4], "kSplashIcons");
517 
518 	fclose(sOutput);
519 	return 0;
520 }
521