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
usage(char * message,...)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
changeExtension(char * pPath,size_t pathCapacity,const char * pExtension)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
user_error_fn(png_structp png_ptr,png_const_charp message)100 void PNGAPI user_error_fn(png_structp png_ptr, png_const_charp message) {
101 fprintf(stderr, "PNG error: %s\n", message);
102 }
103
user_warning_fn(png_structp png_ptr,png_const_charp message)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
fwrite_big_endian_uint16(png_uint_32 data,FILE * pOut)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
fread_big_endian_uint16(png_uint_32 * data,FILE * pIn)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
read_PNG_File(const char * pInput,etc1_byte ** ppImageData,etc1_uint32 * pWidth,etc1_uint32 * pHeight)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.
readPKMFile(const char * pInput,etc1_byte ** ppImageData,etc1_uint32 * pWidth,etc1_uint32 * pHeight)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
encode(const char * pInput,const char * pOutput,bool bEmitHeader,const char * pDiffFile)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
writePNGFile(const char * pOutput,png_uint_32 width,png_uint_32 height,const png_bytep pImageData,png_uint_32 imageStride)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
decode(const char * pInput,const char * pOutput)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
multipleEncodeDecodeCheck(bool * pbEncodeDecodeSeen)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
main(int argc,char ** argv)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