xref: /haiku/src/tools/generate_boot_screen.cpp (revision 1deede7388b04dbeec5af85cae7164735ea9e70d)
1 /*
2  *	Copyright (c) 2008-2011, 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  *		Philippe Houdoin
11  */
12 
13 //! Haiku boot splash image generator/converter
14 
15 #include <iostream>
16 #include <png.h>
17 #include <string>
18 #include <stdarg.h>
19 #include <stdint.h>
20 #include <stdlib.h>
21 
22 #include <zlib.h>
23 
24 #include <ColorQuantizer.h>
25 
26 // TODO: Create 4 bit palette version of these
27 // images as well, so that they are ready to be
28 // used during boot in case we need to run in
29 // palette 4 bit VGA mode.
30 
31 
32 FILE* sOutput = NULL;
33 int sOffset = 0;
34 
35 static void
36 error(const char *s, ...)
37 {
38 	va_list args;
39 	va_start(args, s);
40 	vfprintf(stderr, s, args);
41 	fprintf(stderr, "\n");
42 	va_end(args);
43 	exit(-1);
44 }
45 
46 
47 static void
48 new_line_if_required()
49 {
50 	sOffset++;
51 	if (sOffset % 12 == 0)
52 		fprintf(sOutput, "\n\t");
53 }
54 
55 
56 // #pragma mark -
57 
58 
59 class AutoFileCloser {
60 public:
61 	AutoFileCloser(FILE* file)
62 		: fFile(file)
63 	{}
64 	~AutoFileCloser()
65 	{
66 		fclose(fFile);
67 	}
68 private:
69 	FILE* fFile;
70 };
71 
72 
73 // #pragma mark -
74 
75 
76 class ZlibCompressor {
77 public:
78 	ZlibCompressor(FILE* output);
79 
80 	int Compress(const void* data, int dataSize, int flush = Z_NO_FLUSH);
81 	int Finish();
82 
83 private:
84 	FILE* 		fOutput;
85 	z_stream 	fStream;
86 };
87 
88 
89 ZlibCompressor::ZlibCompressor(FILE* output)
90 	: fOutput(output)
91 {
92 	// prepare zlib stream
93 
94 	fStream.next_in = NULL;
95 	fStream.avail_in = 0;
96 	fStream.total_in = 0;
97 	fStream.next_out = NULL;
98 	fStream.avail_out = 0;
99 	fStream.total_out = 0;
100 	fStream.msg = 0;
101 	fStream.state = 0;
102 	fStream.zalloc = Z_NULL;
103 	fStream.zfree = Z_NULL;
104 	fStream.opaque = Z_NULL;
105 	fStream.data_type = 0;
106 	fStream.adler = 0;
107 	fStream.reserved = 0;
108 
109 	int zlibError = deflateInit(&fStream, Z_BEST_COMPRESSION);
110 	if (zlibError != Z_OK)
111 		return;	// TODO: translate zlibError
112 }
113 
114 
115 int
116 ZlibCompressor::Compress(const void* data, int dataSize, int flush)
117 {
118 	uint8 buffer[1024];
119 
120 	fStream.next_in = (Bytef*)data;
121 	fStream.avail_in = dataSize;
122 
123 	int zlibError = Z_OK;
124 
125 	do {
126 		fStream.next_out = (Bytef*)buffer;
127 		fStream.avail_out = sizeof(buffer);
128 
129 		zlibError = deflate(&fStream, flush);
130 		if (zlibError != Z_OK &&
131 			(flush == Z_FINISH && zlibError != Z_STREAM_END))
132 			return zlibError;
133 
134 		unsigned int outputSize = sizeof(buffer) - fStream.avail_out;
135 		for (unsigned int i = 0; i < outputSize; i++) {
136 			fprintf(fOutput, "%d, ", buffer[i]);
137 			new_line_if_required();
138 		}
139 
140 		if (zlibError == Z_STREAM_END)
141 			break;
142 
143 	} while (fStream.avail_in > 0 || flush == Z_FINISH);
144 
145 	return zlibError;
146 }
147 
148 
149 int
150 ZlibCompressor::Finish()
151 {
152 	Compress(NULL, 0, Z_FINISH);
153 	return deflateEnd(&fStream);
154 }
155 
156 
157 // #pragma mark -
158 
159 
160 static void
161 read_png(const char* filename, int& width, int& height, png_bytep*& rowPtrs,
162 	png_structp& pngPtr, png_infop& infoPtr)
163 {
164 	char header[8];
165 	FILE* input = fopen(filename, "rb");
166 	if (!input)
167 		error("[read_png] File %s could not be opened for reading", filename);
168 
169 	AutoFileCloser _(input);
170 
171 	fread(header, 1, 8, input);
172 	if (png_sig_cmp((png_byte *)header, 0, 8 ))
173 		error("[read_png] File %s is not recognized as a PNG file", filename);
174 
175 	pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
176 		NULL, NULL, NULL);
177 	if (!pngPtr)
178 		error("[read_png] png_create_read_struct failed");
179 
180 	infoPtr = png_create_info_struct(pngPtr);
181 	if (!infoPtr)
182 		error("[read_png] png_create_info_struct failed");
183 
184 // TODO: I don't know which version of libpng introduced this feature:
185 #if PNG_LIBPNG_VER > 10005
186 	if (setjmp(png_jmpbuf(pngPtr)))
187 		error("[read_png] Error during init_io");
188 #endif
189 
190 	png_init_io(pngPtr, input);
191 	png_set_sig_bytes(pngPtr, 8);
192 
193 	// make sure we automatically get RGB data with 8 bits per channel
194 	// also make sure the alpha channel is stripped, in the end, we
195 	// expect 24 bits BGR data
196 	png_set_expand(pngPtr);
197 	png_set_expand_gray_1_2_4_to_8(pngPtr);
198 	png_set_palette_to_rgb(pngPtr);
199 	png_set_gray_to_rgb(pngPtr);
200 	png_set_strip_alpha(pngPtr);
201 	png_set_bgr(pngPtr);
202 
203 	png_read_info(pngPtr, infoPtr);
204 	width = (int)png_get_image_width(pngPtr, infoPtr);
205 	height = (int)png_get_image_height(pngPtr, infoPtr);
206 	if (png_get_bit_depth(pngPtr, infoPtr) != 8)
207 		error("[read_png] File %s has wrong bit depth\n", filename);
208 	if ((int)png_get_rowbytes(pngPtr, infoPtr) < width * 3) {
209 		error("[read_png] File %s has wrong color type (RGB required)\n",
210 			filename);
211 	}
212 
213 
214 	png_set_interlace_handling(pngPtr);
215 	png_read_update_info(pngPtr, infoPtr);
216 
217 #if PNG_LIBPNG_VER > 10005
218 	if (setjmp(png_jmpbuf(pngPtr)))
219 		error("[read_png] Error during read_image");
220 #endif
221 
222 	rowPtrs = (png_bytep*)malloc(sizeof(png_bytep) * height);
223 	for (int y = 0; y < height; y++)
224 		rowPtrs[y] = (png_byte*)malloc(png_get_rowbytes(pngPtr, infoPtr));
225 
226 	png_read_image(pngPtr, rowPtrs);
227 }
228 
229 
230 static void
231 write_24bit_image(const char* baseName, int width, int height, png_bytep* rowPtrs)
232 {
233 	fprintf(sOutput, "static const uint16 %sWidth = %d;\n", baseName, width);
234 	fprintf(sOutput, "static const uint16 %sHeight = %d;\n", baseName, height);
235 	fprintf(sOutput, "#ifndef __BOOTSPLASH_KERNEL__\n");
236 
237 	fprintf(sOutput, "static uint8 %s24BitCompressedImage[] = {\n\t",
238 		baseName);
239 
240 	ZlibCompressor zlib(sOutput);
241 
242 	for (int y = 0; y < height; y++)
243 		zlib.Compress(rowPtrs[y], width * 3);
244 
245 	zlib.Finish();
246 
247 	fprintf(sOutput, "\n};\n");
248 	fprintf(sOutput, "#endif\n\n");
249 }
250 
251 
252 static void
253 write_8bit_image(const char* baseName, int width, int height, unsigned char** rowPtrs)
254 {
255 	//int buffer[128];
256 	// buffer[0] stores count, buffer[1..127] holds the actual values
257 
258 	fprintf(sOutput, "static uint8 %s8BitCompressedImage[] = {\n\t",
259 		baseName);
260 
261 
262 	ZlibCompressor zlib(sOutput);
263 
264 	for (int y = 0; y < height; y++)
265 		zlib.Compress(rowPtrs[y], width);
266 
267 	zlib.Finish();
268 
269 	fprintf(sOutput, "\n};\n\n");
270 }
271 
272 
273 unsigned char
274 nearest_color(unsigned char* color, RGBA palette[256])
275 {
276 	int i, dist, minDist, index = 0;
277 	minDist = 255 * 255 + 255 * 255 + 255 * 255 + 1;
278 	for (i = 0; i < 256; i++) {
279 		int dr = ((int)color[2]) - palette[i].r;
280 		int dg = ((int)color[1]) - palette[i].g;
281 		int db = ((int)color[0]) - palette[i].b;
282 		dist = dr * dr + dg * dg + db * db;
283 		if (dist < minDist) {
284 			minDist = dist;
285 			index = i;
286 		}
287 	}
288 	return index;
289 }
290 
291 
292 static void
293 create_8bit_images(const char* logoBaseName, int logoWidth, int logoHeight,
294 	png_bytep* logoRowPtrs, const char* iconsBaseName, int iconsWidth,
295 	int iconsHeight, png_bytep* iconsRowPtrs)
296 {
297 	// Generate 8-bit palette
298 	BColorQuantizer quantizer(256, 8);
299 	quantizer.ProcessImage(logoRowPtrs, logoWidth, logoHeight);
300 	quantizer.ProcessImage(iconsRowPtrs, iconsWidth, iconsHeight);
301 
302 	RGBA palette[256];
303 	quantizer.GetColorTable(palette);
304 
305 	// convert 24-bit logo image to 8-bit indexed color
306 	uint8* logoIndexedImageRows[logoHeight];
307 	for (int y = 0; y < logoHeight; y++) {
308 		logoIndexedImageRows[y] = new uint8[logoWidth];
309 		for (int x = 0; x < logoWidth; x++)	{
310 			logoIndexedImageRows[y][x] = nearest_color(&logoRowPtrs[y][x*3],
311 				palette);
312 		}
313 	}
314 
315 	// convert 24-bit icons image to 8-bit indexed color
316 	uint8* iconsIndexedImageRows[iconsHeight];
317 	for (int y = 0; y < iconsHeight; y++) {
318 		iconsIndexedImageRows[y] = new uint8[iconsWidth];
319 		for (int x = 0; x < iconsWidth; x++)	{
320 			iconsIndexedImageRows[y][x] = nearest_color(&iconsRowPtrs[y][x*3],
321 				palette);
322 		}
323 	}
324 
325 
326 	fprintf(sOutput, "#ifndef __BOOTSPLASH_KERNEL__\n");
327 
328 	// write the 8-bit color palette
329 	fprintf(sOutput, "static const uint8 k8BitPalette[] = {\n");
330 	for (int c = 0; c < 256; c++) {
331 		fprintf(sOutput, "\t0x%x, 0x%x, 0x%x,\n",
332 			palette[c].r, palette[c].g, palette[c].b);
333 	}
334 	fprintf(sOutput, "\t};\n\n");
335 
336 	// write the 8-bit images
337 	write_8bit_image(logoBaseName, logoWidth, logoHeight, logoIndexedImageRows);
338 	write_8bit_image(iconsBaseName, iconsWidth, iconsHeight,
339 		iconsIndexedImageRows);
340 
341 	fprintf(sOutput, "#endif\n\n");
342 
343 	// free memory
344 	for (int y = 0; y < logoHeight; y++)
345 		delete[] logoIndexedImageRows[y];
346 
347 	for (int y = 0; y < iconsHeight; y++)
348 		delete[] iconsIndexedImageRows[y];
349 }
350 
351 
352 static void
353 parse_images(const char* logoFilename, const char* logoBaseName,
354 	const char* iconsFilename, const char* iconsBaseName)
355 {
356 	int logoWidth;
357 	int logoHeight;
358 	png_bytep* logoRowPtrs = NULL;
359 	png_structp logoPngPtr;
360 	png_infop logoInfoPtr;
361 
362 	int iconsWidth;
363 	int iconsHeight;
364 	png_bytep* iconsRowPtrs = NULL;
365 	png_structp iconsPngPtr;
366 	png_infop iconsInfoPtr;
367 
368 	read_png(logoFilename, logoWidth, logoHeight, logoRowPtrs, logoPngPtr,
369 		logoInfoPtr);
370 	read_png(iconsFilename, iconsWidth, iconsHeight, iconsRowPtrs, iconsPngPtr,
371 		iconsInfoPtr);
372 
373 	// write 24-bit images
374 	write_24bit_image(logoBaseName, logoWidth, logoHeight, logoRowPtrs);
375 	write_24bit_image(iconsBaseName, iconsWidth, iconsHeight, iconsRowPtrs);
376 
377 	// write 8-bit index color images
378 	create_8bit_images(logoBaseName, logoWidth, logoHeight, logoRowPtrs,
379 		iconsBaseName, iconsWidth, iconsHeight, iconsRowPtrs);
380 
381 	// free resources
382 	png_destroy_read_struct(&logoPngPtr, &logoInfoPtr, NULL);
383 	for (int y = 0; y < logoHeight; y++)
384 		free(logoRowPtrs[y]);
385 	free(logoRowPtrs);
386 
387 	png_destroy_read_struct(&iconsPngPtr, &iconsInfoPtr, NULL);
388 	for (int y = 0; y < iconsHeight; y++)
389 		free(iconsRowPtrs[y]);
390 	free(iconsRowPtrs);
391 }
392 
393 
394 int
395 main(int argc, char* argv[])
396 {
397 	if (argc < 8) {
398 		printf("Usage:\n");
399 		printf("\t%s <splash.png> <x placement in %%> <y placement in %%> "
400 			"<icons.png> <x placement in %%> <y placement in %%> <images.h>\n",
401 			argv[0]);
402 		return 0;
403 	}
404 
405 	int logoPlacementX = atoi(argv[2]);
406 	int logoPlacementY = atoi(argv[3]);
407 	int iconPlacementX = atoi(argv[5]);
408 	int iconPlacementY = atoi(argv[6]);
409 	if (logoPlacementX < 0 || logoPlacementX > 100) {
410 		printf("Bad X placement for logo: %d%%\n", logoPlacementX);
411 		return 1;
412 	}
413 	if (logoPlacementY < 0 || logoPlacementY > 100) {
414 		printf("Bad Y placement for logo: %d%%\n\n", logoPlacementY);
415 		return 1;
416 	}
417 	if (iconPlacementX < 0 || iconPlacementX > 100) {
418 		printf("Bad X placement for icons: %d%%\n", iconPlacementX);
419 		return 1;
420 	}
421 	if (iconPlacementY < 0 || iconPlacementY > 100) {
422 		printf("Bad Y placement for icons: %d%%\n", iconPlacementY);
423 		return 1;
424 	}
425 
426 	const char* headerFileName = argv[7];
427 	sOutput = fopen(headerFileName, "wb");
428 	if (!sOutput)
429 		error("Could not open file \"%s\" for writing", headerFileName);
430 
431 	fputs("// This file was generated by the generate_boot_screen Haiku build "
432 		"tool.\n\n", sOutput);
433 
434 	fprintf(sOutput, "static const int32 kSplashLogoPlacementX = %d;\n",
435 		logoPlacementX);
436 	fprintf(sOutput, "static const int32 kSplashLogoPlacementY = %d;\n",
437 		logoPlacementY);
438 	fprintf(sOutput, "static const int32 kSplashIconsPlacementX = %d;\n",
439 		iconPlacementX);
440 	fprintf(sOutput, "static const int32 kSplashIconsPlacementY = %d;\n\n",
441 		iconPlacementY);
442 
443 	parse_images(argv[1], "kSplashLogo", argv[4], "kSplashIcons");
444 
445 	fclose(sOutput);
446 	return 0;
447 }
448