1 // Copyright 2009 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include <errno.h> 16 #include <stdarg.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <string.h> 20 21 #include <png.h> 22 #include <ETC1/etc1.h> 23 24 25 int writePNGFile(const char* pOutput, png_uint_32 width, png_uint_32 height, 26 const png_bytep pImageData, png_uint_32 imageStride); 27 28 const char* gpExeName; 29 30 static 31 void usage(char* message, ...) { 32 if (message) { 33 va_list ap; 34 va_start(ap, message); 35 vfprintf(stderr, message, ap); 36 va_end(ap); 37 fprintf(stderr, "\n\n"); 38 fprintf(stderr, "usage:\n"); 39 } 40 fprintf( 41 stderr, 42 "%s infile [--help | --encode | --encodeNoHeader | --decode] [--showDifference difffile] [-o outfile]\n", 43 gpExeName); 44 fprintf(stderr, "\tDefault is --encode\n"); 45 fprintf(stderr, "\t\t--help print this usage information.\n"); 46 fprintf(stderr, 47 "\t\t--encode create an ETC1 file from a PNG file.\n"); 48 fprintf( 49 stderr, 50 "\t\t--encodeNoHeader create a raw ETC1 data file (without a header) from a PNG file.\n"); 51 fprintf(stderr, 52 "\t\t--decode create a PNG file from an ETC1 file.\n"); 53 fprintf(stderr, 54 "\t\t--showDifference difffile Write difference between original and encoded\n"); 55 fprintf(stderr, 56 "\t\t image to difffile. (Only valid when encoding).\n"); 57 fprintf(stderr, 58 "\tIf outfile is not specified, an outfile path is constructed from infile,\n"); 59 fprintf(stderr, "\twith the apropriate suffix (.pkm or .png).\n"); 60 exit(1); 61 } 62 63 // Returns non-zero if an error occured 64 65 static 66 int changeExtension(char* pPath, size_t pathCapacity, const char* pExtension) { 67 size_t pathLen = strlen(pPath); 68 size_t extensionLen = strlen(pExtension); 69 if (pathLen + extensionLen + 1 > pathCapacity) { 70 return -1; 71 } 72 73 // Check for '.' and '..' 74 if ((pathLen == 1 && pPath[0] == '.') || (pathLen == 2 && pPath[0] == '.' 75 && pPath[1] == '.') || (pathLen >= 2 && pPath[pathLen - 2] == '/' 76 && pPath[pathLen - 1] == '.') || (pathLen >= 3 77 && pPath[pathLen - 3] == '/' && pPath[pathLen - 2] == '.' 78 && pPath[pathLen - 1] == '.')) { 79 return -2; 80 } 81 82 int index; 83 for (index = pathLen - 1; index > 0; index--) { 84 char c = pPath[index]; 85 if (c == '/') { 86 // No extension found. Append our extension. 87 strcpy(pPath + pathLen, pExtension); 88 return 0; 89 } else if (c == '.') { 90 strcpy(pPath + index, pExtension); 91 return 0; 92 } 93 } 94 95 // No extension or directory found. Append our extension 96 strcpy(pPath + pathLen, pExtension); 97 return 0; 98 } 99 100 void PNGAPI user_error_fn(png_structp png_ptr, png_const_charp message) { 101 fprintf(stderr, "PNG error: %s\n", message); 102 } 103 104 void PNGAPI user_warning_fn(png_structp png_ptr, png_const_charp message) { 105 fprintf(stderr, "PNG warning: %s\n", message); 106 } 107 108 // Return non-zero on error 109 int fwrite_big_endian_uint16(png_uint_32 data, FILE* pOut) { 110 if (fputc(0xff & (data >> 8), pOut) == EOF) { 111 return -1; 112 } 113 if (fputc(0xff & data, pOut) == EOF) { 114 return -1; 115 } 116 return 0; 117 } 118 119 // Return non-zero on error 120 int fread_big_endian_uint16(png_uint_32* data, FILE* pIn) { 121 int a, b; 122 if ((a = fgetc(pIn)) == EOF) { 123 return -1; 124 } 125 if ((b = fgetc(pIn)) == EOF) { 126 return -1; 127 } 128 *data = ((0xff & a) << 8) | (0xff & b); 129 return 0; 130 } 131 132 // Read a PNG file into a contiguous buffer. 133 // Returns non-zero if an error occurred. 134 // caller has to delete[] *ppImageData when done with the image. 135 136 int read_PNG_File(const char* pInput, etc1_byte** ppImageData, 137 etc1_uint32* pWidth, etc1_uint32* pHeight) { 138 FILE* pIn = NULL; 139 png_structp png_ptr = NULL; 140 png_infop info_ptr = NULL; 141 png_infop end_info = NULL; 142 png_bytep* row_pointers = NULL; // Does not need to be deallocated. 143 png_uint_32 width = 0; 144 png_uint_32 height = 0; 145 png_uint_32 stride = 0; 146 int result = -1; 147 etc1_byte* pSourceImage = 0; 148 149 if ((pIn = fopen(pInput, "rb")) == NULL) { 150 fprintf(stderr, "Could not open input file %s for reading: %d\n", 151 pInput, errno); 152 goto exit; 153 } 154 155 static const size_t PNG_HEADER_SIZE = 8; 156 png_byte pngHeader[PNG_HEADER_SIZE]; 157 if (fread(pngHeader, 1, PNG_HEADER_SIZE, pIn) != PNG_HEADER_SIZE) { 158 fprintf(stderr, "Could not read PNG header from %s: %d\n", pInput, 159 errno); 160 goto exit; 161 } 162 163 if (png_sig_cmp(pngHeader, 0, PNG_HEADER_SIZE)) { 164 fprintf(stderr, "%s is not a PNG file.\n", pInput); 165 goto exit; 166 } 167 168 if (!(png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 169 (png_voidp) NULL, user_error_fn, user_warning_fn))) { 170 fprintf(stderr, "Could not initialize png read struct.\n"); 171 goto exit; 172 } 173 174 if (!(info_ptr = png_create_info_struct(png_ptr))) { 175 fprintf(stderr, "Could not create info struct.\n"); 176 goto exit; 177 } 178 if (!(end_info = png_create_info_struct(png_ptr))) { 179 fprintf(stderr, "Could not create end_info struct.\n"); 180 goto exit; 181 } 182 183 if (setjmp(png_jmpbuf(png_ptr))) { 184 goto exit; 185 } 186 187 png_init_io(png_ptr, pIn); 188 png_set_sig_bytes(png_ptr, PNG_HEADER_SIZE); 189 png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY 190 | PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_STRIP_ALPHA 191 | PNG_TRANSFORM_PACKING, NULL); 192 193 row_pointers = png_get_rows(png_ptr, info_ptr); 194 { 195 int bit_depth, color_type; 196 png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, 197 &color_type, NULL, NULL, NULL); 198 } 199 200 stride = 3 * width; 201 202 pSourceImage = new etc1_byte[stride * height]; 203 if (! pSourceImage) { 204 fprintf(stderr, "Out of memory.\n"); 205 goto exit; 206 } 207 208 for (etc1_uint32 y = 0; y < height; y++) { 209 memcpy(pSourceImage + y * stride, row_pointers[y], stride); 210 } 211 212 *pWidth = width; 213 *pHeight = height; 214 *ppImageData = pSourceImage; 215 216 result = 0; 217 exit: 218 if (result) { 219 delete[] pSourceImage; 220 } 221 if (png_ptr) { 222 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); 223 } 224 if (pIn) { 225 fclose(pIn); 226 } 227 228 return result; 229 } 230 231 // Read a PNG file into a contiguous buffer. 232 // Returns non-zero if an error occurred. 233 // caller has to delete[] *ppImageData when done with the image. 234 int readPKMFile(const char* pInput, etc1_byte** ppImageData, 235 etc1_uint32* pWidth, etc1_uint32* pHeight) { 236 int result = -1; 237 FILE* pIn = NULL; 238 etc1_byte header[ETC_PKM_HEADER_SIZE]; 239 png_bytep pEncodedData = NULL; 240 png_bytep pImageData = NULL; 241 242 png_uint_32 width = 0; 243 png_uint_32 height = 0; 244 png_uint_32 stride = 0; 245 png_uint_32 encodedSize = 0; 246 247 if ((pIn = fopen(pInput, "rb")) == NULL) { 248 fprintf(stderr, "Could not open input file %s for reading: %d\n", 249 pInput, errno); 250 goto exit; 251 } 252 253 if (fread(header, sizeof(header), 1, pIn) != 1) { 254 fprintf(stderr, "Could not read header from input file %s: %d\n", 255 pInput, errno); 256 goto exit; 257 } 258 259 if (! etc1_pkm_is_valid(header)) { 260 fprintf(stderr, "Bad header PKM header for input file %s\n", pInput); 261 goto exit; 262 } 263 264 width = etc1_pkm_get_width(header); 265 height = etc1_pkm_get_height(header); 266 encodedSize = etc1_get_encoded_data_size(width, height); 267 268 pEncodedData = new png_byte[encodedSize]; 269 if (!pEncodedData) { 270 fprintf(stderr, "Out of memory.\n"); 271 goto exit; 272 } 273 274 if (fread(pEncodedData, encodedSize, 1, pIn) != 1) { 275 fprintf(stderr, "Could not read encoded data from input file %s: %d\n", 276 pInput, errno); 277 goto exit; 278 } 279 280 fclose(pIn); 281 pIn = NULL; 282 283 stride = width * 3; 284 pImageData = new png_byte[stride * height]; 285 if (!pImageData) { 286 fprintf(stderr, "Out of memory.\n"); 287 goto exit; 288 } 289 290 etc1_decode_image(pEncodedData, pImageData, width, height, 3, stride); 291 292 // Success 293 result = 0; 294 *ppImageData = pImageData; 295 pImageData = 0; 296 *pWidth = width; 297 *pHeight = height; 298 299 exit: 300 delete[] pEncodedData; 301 delete[] pImageData; 302 if (pIn) { 303 fclose(pIn); 304 } 305 306 return result; 307 } 308 309 310 // Encode the file. 311 // Returns non-zero if an error occurred. 312 313 int encode(const char* pInput, const char* pOutput, bool bEmitHeader, const char* pDiffFile) { 314 FILE* pOut = NULL; 315 etc1_uint32 width = 0; 316 etc1_uint32 height = 0; 317 etc1_uint32 encodedSize = 0; 318 int result = -1; 319 etc1_byte* pSourceImage = 0; 320 etc1_byte* pEncodedData = 0; 321 etc1_byte* pDiffImage = 0; // Used for differencing 322 323 if (read_PNG_File(pInput, &pSourceImage, &width, &height)) { 324 goto exit; 325 } 326 327 encodedSize = etc1_get_encoded_data_size(width, height); 328 pEncodedData = new etc1_byte[encodedSize]; 329 if (!pEncodedData) { 330 fprintf(stderr, "Out of memory.\n"); 331 goto exit; 332 } 333 334 etc1_encode_image(pSourceImage, 335 width, height, 3, width * 3, pEncodedData); 336 337 if ((pOut = fopen(pOutput, "wb")) == NULL) { 338 fprintf(stderr, "Could not open output file %s: %d\n", pOutput, errno); 339 goto exit; 340 } 341 342 if (bEmitHeader) { 343 etc1_byte header[ETC_PKM_HEADER_SIZE]; 344 etc1_pkm_format_header(header, width, height); 345 if (fwrite(header, sizeof(header), 1, pOut) != 1) { 346 fprintf(stderr, 347 "Could not write header output file %s: %d\n", 348 pOutput, errno); 349 goto exit; 350 } 351 } 352 353 if (fwrite(pEncodedData, encodedSize, 1, pOut) != 1) { 354 fprintf(stderr, 355 "Could not write encoded data to output file %s: %d\n", 356 pOutput, errno); 357 goto exit; 358 } 359 360 fclose(pOut); 361 pOut = NULL; 362 363 if (pDiffFile) { 364 etc1_uint32 outWidth; 365 etc1_uint32 outHeight; 366 if (readPKMFile(pOutput, &pDiffImage, &outWidth, &outHeight)) { 367 goto exit; 368 } 369 if (outWidth != width || outHeight != height) { 370 fprintf(stderr, "Output file has incorrect bounds: %u, %u != %u, %u\n", 371 outWidth, outHeight, width, height); 372 goto exit; 373 } 374 const etc1_byte* pSrc = pSourceImage; 375 etc1_byte* pDest = pDiffImage; 376 etc1_uint32 size = width * height * 3; 377 for (etc1_uint32 i = 0; i < size; i++) { 378 int diff = *pSrc++ - *pDest; 379 diff *= diff; 380 diff <<= 3; 381 if (diff < 0) { 382 diff = 0; 383 } else if (diff > 255) { 384 diff = 255; 385 } 386 *pDest++ = (png_byte) diff; 387 } 388 writePNGFile(pDiffFile, outWidth, outHeight, pDiffImage, 3 * outWidth); 389 } 390 391 // Success 392 result = 0; 393 394 exit: 395 delete[] pSourceImage; 396 delete[] pEncodedData; 397 delete[] pDiffImage; 398 399 if (pOut) { 400 fclose(pOut); 401 } 402 return result; 403 } 404 405 int writePNGFile(const char* pOutput, png_uint_32 width, png_uint_32 height, 406 const png_bytep pImageData, png_uint_32 imageStride) { 407 int result = -1; 408 FILE* pOut = NULL; 409 png_structp png_ptr = NULL; 410 png_infop info_ptr = NULL; 411 412 if (!(png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 413 (png_voidp) NULL, user_error_fn, user_warning_fn)) || !(info_ptr 414 = png_create_info_struct(png_ptr))) { 415 fprintf(stderr, "Could not initialize PNG library for writing.\n"); 416 goto exit; 417 } 418 419 if (setjmp(png_jmpbuf(png_ptr))) { 420 goto exit; 421 } 422 423 if ((pOut = fopen(pOutput, "wb")) == NULL) { 424 fprintf(stderr, "Could not open output file %s: %d\n", pOutput, errno); 425 goto exit; 426 } 427 428 png_init_io(png_ptr, pOut); 429 430 png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, 431 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, 432 PNG_FILTER_TYPE_DEFAULT); 433 434 png_write_info(png_ptr, info_ptr); 435 436 for (png_uint_32 y = 0; y < height; y++) { 437 png_write_row(png_ptr, pImageData + y * imageStride); 438 } 439 png_write_end(png_ptr, info_ptr); 440 441 result = 0; 442 443 exit: if (png_ptr) { 444 png_destroy_write_struct(&png_ptr, &info_ptr); 445 } 446 447 if (pOut) { 448 fclose(pOut); 449 } 450 return result; 451 } 452 453 int decode(const char* pInput, const char* pOutput) { 454 int result = -1; 455 png_bytep pImageData = NULL; 456 etc1_uint32 width = 0; 457 etc1_uint32 height = 0; 458 459 if (readPKMFile(pInput, &pImageData, &width, &height)) { 460 goto exit; 461 } 462 463 if (writePNGFile(pOutput, width, height, pImageData, width * 3)) { 464 goto exit; 465 } 466 467 // Success 468 result = 0; 469 470 exit: delete[] pImageData; 471 472 return result; 473 } 474 475 void multipleEncodeDecodeCheck(bool* pbEncodeDecodeSeen) { 476 if (*pbEncodeDecodeSeen) { 477 usage("At most one occurrence of --encode --encodeNoHeader or --decode is allowed.\n"); 478 } 479 *pbEncodeDecodeSeen = true; 480 } 481 482 int main(int argc, char** argv) { 483 gpExeName = argv[0]; 484 const char* pInput = NULL; 485 const char* pOutput = NULL; 486 const char* pDiffFile = NULL; 487 char* pOutputFileBuff = NULL; 488 489 bool bEncodeDecodeSeen = false; 490 bool bEncode = false; 491 bool bEncodeHeader = false; 492 bool bDecode = false; 493 bool bShowDifference = false; 494 495 for (int i = 1; i < argc; i++) { 496 const char* pArg = argv[i]; 497 if (pArg[0] == '-') { 498 char c = pArg[1]; 499 switch (c) { 500 case 'o': 501 if (pOutput != NULL) { 502 usage("Only one -o flag allowed."); 503 } 504 if (i + 1 >= argc) { 505 usage("Expected outfile after -o"); 506 } 507 pOutput = argv[++i]; 508 break; 509 case '-': 510 if (strcmp(pArg, "--encode") == 0) { 511 multipleEncodeDecodeCheck(&bEncodeDecodeSeen); 512 bEncode = true; 513 bEncodeHeader = true; 514 } else if (strcmp(pArg, "--encodeNoHeader") == 0) { 515 multipleEncodeDecodeCheck(&bEncodeDecodeSeen); 516 bEncode = true; 517 bEncodeHeader = false; 518 } else if (strcmp(pArg, "--decode") == 0) { 519 multipleEncodeDecodeCheck(&bEncodeDecodeSeen); 520 bDecode = true; 521 } else if (strcmp(pArg, "--showDifference") == 0) { 522 if (bShowDifference) { 523 usage("Only one --showDifference option allowed.\n"); 524 } 525 bShowDifference = true; 526 if (i + 1 >= argc) { 527 usage("Expected difffile after --showDifference"); 528 } 529 pDiffFile = argv[++i]; 530 } else if (strcmp(pArg, "--help") == 0) { 531 usage( NULL); 532 } else { 533 usage("Unknown flag %s", pArg); 534 } 535 536 break; 537 default: 538 usage("Unknown flag %s", pArg); 539 break; 540 } 541 } else { 542 if (pInput != NULL) { 543 usage( 544 "Only one input file allowed. Already have %s, now see %s", 545 pInput, pArg); 546 } 547 pInput = pArg; 548 } 549 } 550 551 if (!bEncodeDecodeSeen) { 552 bEncode = true; 553 bEncodeHeader = true; 554 } 555 if ((! bEncode) && bShowDifference) { 556 usage("--showDifference is only valid when encoding."); 557 } 558 559 if (!pInput) { 560 usage("Expected an input file."); 561 } 562 563 if (!pOutput) { 564 const char* kDefaultExtension = bEncode ? ".pkm" : ".png"; 565 size_t buffSize = strlen(pInput) + strlen(kDefaultExtension) + 1; 566 pOutputFileBuff = new char[buffSize]; 567 strcpy(pOutputFileBuff, pInput); 568 if (changeExtension(pOutputFileBuff, buffSize, kDefaultExtension)) { 569 usage("Could not change extension of input file name: %s\n", pInput); 570 } 571 pOutput = pOutputFileBuff; 572 } 573 574 if (bEncode) { 575 encode(pInput, pOutput, bEncodeHeader, pDiffFile); 576 } else { 577 decode(pInput, pOutput); 578 } 579 580 delete[] pOutputFileBuff; 581 582 return 0; 583 } 584