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, void, &h2p_close_state> stateCloser(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 > 8192) { 267 fprintf(stderr,"bad size specified; '%s'\n", argv[i + 1]); 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