1/*
2 * Copyright 2015 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Andrew Lindesay
7 */
8
9
10// This command line program was created in order to be able to render
11// HVIF files and then save the resultant bitmap into a PNG image file.
12// The tool can be compiled for linux and was initially created for
13// use with the Haiku Depot Server application server so that it was
14// able to render HVIFs in the web page.
15
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19#include <unistd.h>
20
21#include <png.h>
22
23#include <Bitmap.h>
24#include <IconUtils.h>
25#include <InterfaceDefs.h>
26#include <SupportDefs.h>
27
28#include <AutoDeleter.h>
29
30
31#define SIZE_HVIF_BUFFER_STEP 1024
32
33
34static const uint8 kHvifMagic[] = { 'n', 'c', 'i', 'f' };
35
36
37typedef struct h2p_hvif_buffer {
38	uint8*	buffer;
39	size_t	used;
40	size_t	allocated;
41} h2p_hvif_buffer;
42
43
44typedef struct h2p_parameters {
45	int		size;
46	char*	in_filename;
47	char*	out_filename;
48} h2p_parameters;
49
50
51typedef struct h2p_state {
52	FILE*			in;
53	FILE*			out;
54	BBitmap*		bitmap;
55	h2p_hvif_buffer	hvif_buffer;
56	h2p_parameters	params;
57} h2p_state;
58
59
60static int
61h2p_fprintsyntax(FILE* stream)
62{
63	return fprintf(stream, "syntax: hvif2png -s <size> [-i <input-file>]"
64		" [-o <output-file>]\n");
65}
66
67
68static void
69h2p_close_state(h2p_state* state)
70{
71	if (state->hvif_buffer.buffer != NULL)
72		free(state->hvif_buffer.buffer);
73
74	if (state->in != NULL) {
75		if (state->in != stdin)
76			fclose(state->in);
77		state->in = NULL;
78	}
79
80	if (state->out != NULL) {
81		if (state->out != stdout)
82			fclose(state->out);
83		state->out = NULL;
84	}
85}
86
87
88/*! Opens the input and output streams for the conversion.
89    \return false if there was some problem in opening the streams; otherwise
90    	true.
91*/
92static bool
93h2p_open_streams(h2p_state* state)
94{
95	CObjectDeleter<h2p_state> stateCloser(state, &h2p_close_state);
96
97	if (state->params.in_filename != NULL)
98		state->in = fopen(state->params.in_filename, "rb");
99	else
100		state->in = stdin;
101
102	if (state->in == NULL) {
103		fprintf(stderr, "unable to open the input file; '%s'\n",
104			state->params.in_filename);
105		return false;
106	}
107
108	if (state->params.out_filename != NULL)
109		state->out = fopen(state->params.out_filename, "wb");
110	else
111		state->out = stdout;
112
113	if (state->out == NULL) {
114		fprintf(stderr, "unable to open the output file; '%s'\n",
115			state->params.out_filename);
116		return false;
117	}
118
119	stateCloser.Detach();
120
121	return true;
122}
123
124
125static bool
126h2p_write_png(BBitmap* bitmap, FILE* out)
127{
128	png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL,
129		NULL);
130
131	if (png == NULL) {
132		fprintf(stderr, "unable to setup png write data structures\n");
133		return false;
134	}
135
136	png_init_io(png, out);
137	png_infop info = png_create_info_struct(png);
138
139	bool result = false;
140
141	if (info != NULL) {
142		BRect rect = bitmap->Bounds();
143		png_uint_32 width = (png_uint_32)rect.Width() + 1;
144		png_uint_32 height = (png_uint_32)rect.Height() + 1;
145
146		png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGBA,
147			PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
148			PNG_FILTER_TYPE_BASE);
149
150		png_set_bgr(png);
151
152		png_write_info(png, info);
153
154		uint8 *bitmapData = (uint8*)bitmap->Bits();
155		int32 bitmapBytesPerRow = bitmap->BytesPerRow();
156
157		for (png_uint_32 i = 0; i < height; i++) {
158			png_write_row(png,
159				(png_bytep)&bitmapData[i * bitmapBytesPerRow]);
160		}
161
162		png_write_end(png, NULL);
163
164		png_free_data(png, info, PNG_FREE_ALL, -1);
165
166		result = true;
167	} else
168		fprintf(stderr, "unable to setup png info data structures\n");
169
170	png_destroy_write_struct(&png, (png_infopp)NULL);
171
172	return result;
173}
174
175
176/*! Reads the HVIF input data from the supplied input file.
177    \return the quantity of bytes that were read from the
178    	HVIF file or 0 if there was a problem reading the HVIF data.
179*/
180static size_t
181h2p_read_hvif_input(h2p_hvif_buffer* result, FILE* in)
182{
183	result->buffer = (uint8*)malloc(SIZE_HVIF_BUFFER_STEP);
184	result->allocated = SIZE_HVIF_BUFFER_STEP;
185	result->used = 0;
186
187	if (result->buffer == NULL) {
188		fprintf(stderr,"out of memory\n");
189		return 0;
190	}
191
192	while (!feof(in)) {
193		if (result->used == result->allocated) {
194			result->buffer = (uint8 *)realloc(result->buffer,
195				result->allocated + SIZE_HVIF_BUFFER_STEP);
196
197			if (result->buffer == NULL) {
198				fprintf(stderr,"out of memory\n");
199				return 0;
200			}
201
202			result->allocated += SIZE_HVIF_BUFFER_STEP;
203		}
204
205		result->used += fread(&result->buffer[result->used], sizeof(uint8),
206			result->allocated - result->used, in);
207
208		int err = ferror(in);
209
210		if (err != 0) {
211			fprintf(stderr, "error reading input; %s\n", strerror(err));
212			return 0;
213		}
214	}
215
216	if (result->used < 4) {
217		fprintf(stderr, "the hvif data is too small to visably be valid\n");
218		return 0;
219	}
220
221	// hvif files have a magic string of "ncif" so we should check for that as
222	// well.
223
224	if (memcmp(result->buffer, kHvifMagic, 4) != 0) {
225		fprintf(stderr, "the input data does not look like hvif because the"
226			" magic string is not 'ncif'; %d, %d, %d, %d\n",
227			result->buffer[0], result->buffer[1], result->buffer[2],
228			result->buffer[3]);
229		return 0;
230	}
231
232	return result->used;
233}
234
235
236/*! Parse the arguments to the conversion program from the command line.
237		\return false if there was a problem reading the parameters and true
238    	otherwise.
239*/
240static bool
241h2p_parse_args(h2p_parameters* result, int argc, char* argv[])
242{
243	for (int i = 1;i < argc;) {
244		if (argv[i][0] != '-') {
245			fprintf(stderr, "was expecting a switch; found '%s'\n",argv[i]);
246			h2p_fprintsyntax(stderr);
247			return false;
248		}
249
250		if (strlen(argv[i]) != 2) {
251			fprintf(stderr, "illegal switch; '%s'\n", argv[i]);
252			h2p_fprintsyntax(stderr);
253			return false;
254		}
255
256		switch (argv[i][1]) {
257			case 's':
258				if (i == argc - 1) {
259					fprintf(stderr,"the size has not been specified\n");
260					h2p_fprintsyntax(stderr);
261					return false;
262				}
263
264				result->size = atoi(argv[i + 1]);
265
266				if (result->size <= 0 || result->size > 1024) {
267					fprintf(stderr,"bad size specified; '%s'\n", argv[i]);
268					h2p_fprintsyntax(stderr);
269					return false;
270				}
271
272				i+=2;
273				break;
274
275			case 'i':
276				if (i == argc - 1) {
277					fprintf(stderr,
278						"the input filename has not been specified\n");
279					h2p_fprintsyntax(stderr);
280					return false;
281				}
282
283				result->in_filename = argv[i + 1];
284				i+=2;
285				break;
286
287			case 'o':
288				if (i == argc - 1) {
289					fprintf(stderr,
290						"the output filename has not been specified\n");
291					h2p_fprintsyntax(stderr);
292					return false;
293				}
294
295				result->out_filename = argv[i + 1];
296				i += 2;
297				break;
298
299			default:
300				fprintf(stderr, "unrecognized switch; '%s'\n", argv[i]);
301				h2p_fprintsyntax(stderr);
302				return false;
303		}
304	}
305
306	if (result->size == 0) {
307		fprintf(stderr, "size has not been specified\n");
308		h2p_fprintsyntax(stderr);
309		return false;
310	}
311
312	return true;
313}
314
315
316int
317main(int argc, char* argv[])
318{
319	if (argc == 1) {
320		h2p_fprintsyntax(stderr);
321		return 1;
322	}
323
324	h2p_state state;
325	bzero(&state, sizeof(state));
326
327	if (!h2p_parse_args(&state.params, argc, argv))
328		return 1;
329
330	if (!h2p_open_streams(&state))
331		return 1;
332
333	int exitResult = 1;
334
335	if (h2p_read_hvif_input(&state.hvif_buffer, state.in) > 0) {
336		// create the bitmap and then parse and render the HVIF icon
337		// data into the bitmap.
338
339		state.bitmap = new BBitmap(
340			BRect(0.0, 0.0, state.params.size - 1,
341				state.params.size - 1),
342			B_RGBA32); // actual storage is BGRA
343
344		status_t gviStatus = BIconUtils::GetVectorIcon(
345			state.hvif_buffer.buffer,
346			state.hvif_buffer.used,
347			state.bitmap);
348
349		if (gviStatus != B_OK) {
350			fprintf(stderr, "the hvif data (%zdB) was not able to "
351				"be parsed / rendered\n", state.hvif_buffer.used);
352		} else {
353			// write the bitmap data out again as a PNG.
354			if (h2p_write_png(state.bitmap, state.out))
355				exitResult = 0;
356		}
357	}
358
359	// clean up
360	h2p_close_state(&state);
361
362	return exitResult;
363}
364