xref: /haiku/src/tools/hvif2png/hvif2png.cpp (revision a5a3b2d9a3d95cbae71eaf371708c73a1780ac0d)
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 
34 static const uint8 kHvifMagic[] = { 'n', 'c', 'i', 'f' };
35 
36 
37 typedef struct h2p_hvif_buffer {
38 	uint8*	buffer;
39 	size_t	used;
40 	size_t	allocated;
41 } h2p_hvif_buffer;
42 
43 
44 typedef struct h2p_parameters {
45 	int		size;
46 	char*	in_filename;
47 	char*	out_filename;
48 } h2p_parameters;
49 
50 
51 typedef 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 
60 static int
61 h2p_fprintsyntax(FILE* stream)
62 {
63 	return fprintf(stream, "syntax: hvif2png -s <size> [-i <input-file>]"
64 		" [-o <output-file>]\n");
65 }
66 
67 
68 static void
69 h2p_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 */
92 static bool
93 h2p_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 
125 static bool
126 h2p_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 */
180 static size_t
181 h2p_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 */
240 static bool
241 h2p_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 
316 int
317 main(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