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(const char * message,...)31 void usage(const 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_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_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