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
32FILE* sOutput = NULL;
33int sOffset = 0;
34
35static void
36error(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
47static void
48new_line_if_required()
49{
50	sOffset++;
51	if (sOffset % 12 == 0)
52		fprintf(sOutput, "\n\t");
53}
54
55
56// #pragma mark -
57
58
59class AutoFileCloser {
60public:
61	AutoFileCloser(FILE* file)
62		: fFile(file)
63	{}
64	~AutoFileCloser()
65	{
66		fclose(fFile);
67	}
68private:
69	FILE* fFile;
70};
71
72
73// #pragma mark -
74
75
76class ZlibCompressor {
77public:
78	ZlibCompressor(FILE* output);
79
80	int Compress(const void* data, int dataSize, int flush = Z_NO_FLUSH);
81	int Finish();
82
83private:
84	FILE* 		fOutput;
85	z_stream 	fStream;
86};
87
88
89ZlibCompressor::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
115int
116ZlibCompressor::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
149int
150ZlibCompressor::Finish()
151{
152	Compress(NULL, 0, Z_FINISH);
153	return deflateEnd(&fStream);
154}
155
156
157// #pragma mark -
158
159
160static void
161read_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
230static void
231write_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
252static void
253write_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
273unsigned char
274nearest_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
292static void
293create_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
352static void
353parse_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
394int
395main(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