Skip to content
Newer
Older
100644 472 lines (394 sloc) 12.7 KB
23bd782 @johnezang First commit of the local iPhone optimized changes.
johnezang authored
1 /*
2 * This file is part of the Advance project.
3 *
4 * Copyright (C) 2002, 2003, 2005 Andrea Mazzoleni
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21 #include "portable.h"
22
23 #include "pngex.h"
24 #include "file.h"
25 #include "compress.h"
26 #include "siglock.h"
27
28 #include "lib/endianrw.h"
29
30 #include <iostream>
31 #include <iomanip>
32
33 using namespace std;
34
35 shrink_t opt_level;
36 bool opt_quiet;
37 bool opt_force;
38 bool opt_crc;
39
40 adv_error recompress_png_idat(adv_fz* f_in, adv_fz *f_out) {
41 unsigned char *data = NULL;
42 unsigned int type = 0, size = 0, noZlibHeader = 0;
21f85ed @johnezang Fixed a memory leak.
johnezang authored
43 unsigned int pixel = 1, width = 1, width_align = 1, height = 1, depth = 1, depth_bytes = 1, color_type = 0;
23bd782 @johnezang First commit of the local iPhone optimized changes.
johnezang authored
44
45 unsigned char *decompressed_IDAT = NULL;
46
47 if (adv_png_read_signature(f_in) != 0) { goto err; }
48 if (adv_png_write_signature(f_out, NULL) != 0) { goto err; }
49
50 do {
51 if (adv_png_read_chunk(f_in, &data, &size, &type) != 0) { goto err; }
52
21f85ed @johnezang Fixed a memory leak.
johnezang authored
53 reloopWithChunk:
23bd782 @johnezang First commit of the local iPhone optimized changes.
johnezang authored
54 switch (type) {
55 case ADV_PNG_CN_IDAT:
56 {
57 unsigned int decompressed_IDAT_size = (height * (width_align * pixel + 1));
58 if((decompressed_IDAT = (unsigned char *)malloc(decompressed_IDAT_size)) == NULL) { goto err_data; }
59
60 int r;
61 z_stream z;
62 memset(&z, 0, sizeof(z_stream));
63
64 z.zalloc = NULL;
65 z.zfree = NULL;
66 z.next_out = (Bytef *)decompressed_IDAT;
67 z.avail_out = decompressed_IDAT_size;
68
69 r = inflateInit2(&z, noZlibHeader ? -15 : 15); // A negative value is used to specify a "raw" (no header, footer, CRC info) zlib stream which is used by the iPhone .png format.
70
71 while (r == Z_OK && type == ADV_PNG_CN_IDAT) {
72 z.next_in = data;
73 z.avail_in = size;
74 r = inflate(&z, Z_NO_FLUSH);
75 free(data); data = NULL;
76 if (adv_png_read_chunk(f_in, &data, &size, &type) != 0) { inflateEnd(&z); goto err_data; }
77 }
78
79 inflateEnd(&z);
80
21f85ed @johnezang Fixed a memory leak.
johnezang authored
81 if (r != Z_STREAM_END) { error_set("Unable to decompress data"); goto err_data; }
23bd782 @johnezang First commit of the local iPhone optimized changes.
johnezang authored
82
21f85ed @johnezang Fixed a memory leak.
johnezang authored
83 unsigned int compressed_IDAT_size = oversize_zlib(z.total_out) + 1024u; // For extremely small file corner cases.
84 unsigned char *compressed_IDAT = NULL;
23bd782 @johnezang First commit of the local iPhone optimized changes.
johnezang authored
85
21f85ed @johnezang Fixed a memory leak.
johnezang authored
86 if((compressed_IDAT = (unsigned char *)malloc(compressed_IDAT_size)) == NULL) { error_set("Unable to allocate memory"); goto err_data; }
23bd782 @johnezang First commit of the local iPhone optimized changes.
johnezang authored
87
21f85ed @johnezang Fixed a memory leak.
johnezang authored
88 if(!compress_zlib(shrink_extreme, compressed_IDAT, compressed_IDAT_size, decompressed_IDAT, decompressed_IDAT_size, noZlibHeader ? 0 : 1)) { error_set("Recompression failed"); goto err_data; }
89 if (adv_png_write_chunk(f_out, ADV_PNG_CN_IDAT, compressed_IDAT, compressed_IDAT_size, NULL) != 0) { goto err_data; }
90 free(compressed_IDAT); compressed_IDAT = NULL;
91 goto reloopWithChunk;
23bd782 @johnezang First commit of the local iPhone optimized changes.
johnezang authored
92 }
93 break;
94
95
96 case ADV_PNG_CN_CgBI:
97 noZlibHeader = 1;
98 goto writeChunk;
99 break;
100
101 case ADV_PNG_CN_IHDR:
21f85ed @johnezang Fixed a memory leak.
johnezang authored
102 width = be_uint32_read(data + 0);
103 height = be_uint32_read(data + 4);
104 depth = data[8];
105 depth_bytes = (depth < 8) ? 1 : depth / 8;
106 color_type = data[9];
107
108 switch(color_type) {
109 case 0: /* Grey */ pixel = 1 * depth_bytes; if(!((depth == 1) || (depth == 2) || (depth == 4) || (depth == 8) || (depth == 16))) { error_unsupported_set("Unsupported bit depth/color type, %d/%d", depth, color_type); goto err_data; } break;
110 case 2: /* RGB */ pixel = 3 * depth_bytes; if(!( (depth == 8) || (depth == 16))) { error_unsupported_set("Unsupported bit depth/color type, %d/%d", depth, color_type); goto err_data; } break;
111 case 3: /* Index */ pixel = 1 * depth_bytes; if(!((depth == 1) || (depth == 2) || (depth == 4) || (depth == 8) )) { error_unsupported_set("Unsupported bit depth/color type, %d/%d", depth, color_type); goto err_data; } break;
112 case 4: /* GreyA */ pixel = 2 * depth_bytes; if(!( (depth == 8) || (depth == 16))) { error_unsupported_set("Unsupported bit depth/color type, %d/%d", depth, color_type); goto err_data; } break;
113 case 6: /* RGBA */ pixel = 4 * depth_bytes; if(!( (depth == 8) || (depth == 16))) { error_unsupported_set("Unsupported bit depth/color type, %d/%d", depth, color_type); goto err_data; } break;
114 default: error_unsupported_set("Unsupported color type, %d", color_type); goto err_data; break;
115 }
116
117 width_align = (width + 7) & ~7; // worst case.
23bd782 @johnezang First commit of the local iPhone optimized changes.
johnezang authored
118
119 goto writeChunk;
120 break;
121
122
123 default :
124 writeChunk:
125 if (adv_png_write_chunk(f_out, type, data, size, NULL) != 0) { goto err_data; }
126 free(data); data = NULL;
127 if(type == ADV_PNG_CN_IEND) {
128 if(decompressed_IDAT != NULL) { free(decompressed_IDAT); decompressed_IDAT = NULL; }
129 return(0);
130 }
131 break;
132 }
133
134 free(data); data = NULL;
21f85ed @johnezang Fixed a memory leak.
johnezang authored
135 } while (type != ADV_PNG_CN_IEND);
23bd782 @johnezang First commit of the local iPhone optimized changes.
johnezang authored
136
137 error_set("Invalid PNG file");
138 return -1;
139
140 err_data:
141 if(data != NULL) { free(data); }
142 err:
143 if(decompressed_IDAT != NULL) { free(decompressed_IDAT); decompressed_IDAT = NULL; }
144 return -1;
145 }
146
147 void convert_inplace(const string& path)
148 {
149 adv_fz* f_in;
150 adv_fz* f_out;
151
152 // temp name of the saved file
153 string path_dst = file_basepath(path) + ".tmp";
154
155 f_in = fzopen(path.c_str(), "rb");
156 if (!f_in) {
157 throw error() << "Failed open for reading " << path;
158 }
159
160 f_out = fzopen(path_dst.c_str(), "wb");
161 if (!f_out) {
162 fzclose(f_in);
163 throw error() << "Failed open for writing " << path_dst;
164 }
165
166 try {
167 if(recompress_png_idat(f_in, f_out) != 0) { throw_png_error(); }
168 } catch (...) {
169 fzclose(f_in);
170 fzclose(f_out);
171 remove(path_dst.c_str());
172 throw;
173 }
174
175 fzclose(f_in);
176 fzclose(f_out);
177
178 unsigned dst_size = file_size(path_dst);
179 if (!opt_force && file_size(path) < dst_size) {
180 // delete the new file
181 remove(path_dst.c_str());
182 throw error_unsupported() << "Bigger " << dst_size;
183 } else {
184 // prevent external signal
185 sig_auto_lock sal;
186
187 // delete the old file
188 if (remove(path.c_str()) != 0) {
189 remove(path_dst.c_str());
190 throw error() << "Failed delete of " << path;
191 }
192
193 // rename the new version with the correct name
194 if (::rename(path_dst.c_str(), path.c_str()) != 0) {
195 throw error() << "Failed rename of " << path_dst << " to " << path;
196 }
197 }
198 }
199
200 void png_print(const string& path)
201 {
202 unsigned type;
203 unsigned size;
204 adv_fz* f_in;
205
206 f_in = fzopen(path.c_str(), "rb");
207 if (!f_in) {
208 throw error() << "Failed open for reading " << path;
209 }
210
211 try {
212 if (adv_png_read_signature(f_in) != 0) {
213 throw_png_error();
214 }
215
216 do {
217 unsigned char* data;
218
219 if (adv_png_read_chunk(f_in, &data, &size, &type) != 0) {
220 throw_png_error();
221 }
222
223 if (opt_crc) {
224 cout << hex << setw(8) << setfill('0') << crc32(0, data, size);
225 cout << " ";
226 cout << dec << setw(0) << setfill(' ') << size;
227 cout << "\n";
228 } else {
229 png_print_chunk(type, data, size);
230 }
231
232 free(data);
233
234 } while (type != ADV_PNG_CN_IEND);
235 } catch (...) {
236 fzclose(f_in);
237 throw;
238 }
239
240 fzclose(f_in);
241 }
242
243 void rezip_single(const string& file, unsigned long long& total_0, unsigned long long& total_1)
244 {
245 unsigned size_0;
246 unsigned size_1;
247 string desc;
248
249 if (!file_exists(file)) {
250 throw error() << "File " << file << " doesn't exist";
251 }
252
253 try {
254 size_0 = file_size(file);
255
256 try {
257 convert_inplace(file);
258 } catch (error_unsupported& e) {
259 desc = e.desc_get();
260 }
261
262 size_1 = file_size(file);
263 } catch (error& e) {
264 throw e << " on " << file;
265 }
266
267 if (!opt_quiet) {
268 cout << setw(12) << size_0 << setw(12) << size_1 << " ";
269 if (size_0) {
270 unsigned perc = size_1 * 100LL / size_0;
271 cout << setw(3) << perc;
272 } else {
273 cout << " 0";
274 }
275 cout << "% " << file;
276 if (desc.length())
277 cout << " (" << desc << ")";
278 cout << endl;
279 }
280
281 total_0 += size_0;
282 total_1 += size_1;
283 }
284
285 void rezip_all(int argc, char* argv[])
286 {
287 unsigned long long total_0 = 0;
288 unsigned long long total_1 = 0;
289
290 for(int i=0;i<argc;++i)
291 rezip_single(argv[i], total_0, total_1);
292
293 if (!opt_quiet) {
294 cout << setw(12) << total_0 << setw(12) << total_1 << " ";
295 if (total_0) {
296 unsigned perc = total_1 * 100LL / total_0;
297 cout << setw(3) << perc;
298 } else {
299 cout << " 0";
300 }
301 cout << "%" << endl;
302 }
303 }
304
305 void list_all(int argc, char* argv[])
306 {
307 for(int i=0;i<argc;++i) {
308 if (argc > 1 && !opt_crc)
309 cout << "File: " << argv[i] << endl;
310 png_print(argv[i]);
311 }
312 }
313
314 #if HAVE_GETOPT_LONG
315 struct option long_options[] = {
316 {"recompress", 0, 0, 'z'},
317 {"list", 0, 0, 'l'},
318 {"list-crc", 0, 0, 'L'},
319
320 {"shrink-store", 0, 0, '0'},
321 {"shrink-fast", 0, 0, '1'},
322 {"shrink-normal", 0, 0, '2'},
323 {"shrink-extra", 0, 0, '3'},
324 {"shrink-insane", 0, 0, '4'},
325
326 {"quiet", 0, 0, 'q'},
327 {"help", 0, 0, 'h'},
328 {"version", 0, 0, 'V'},
329 {0, 0, 0, 0}
330 };
331 #endif
332
333 #define OPTIONS "zlL01234fqhV"
334
335 void version()
336 {
337 cout << PACKAGE " v" VERSION " by Andrea Mazzoleni" << endl;
338 }
339
340 void usage()
341 {
342 version();
343
0b67de1 @johnezang Changed the not very well named 'adviphone' / etc to 'advpngidat'.
johnezang authored
344 cout << "Usage: advpngidat [options] [FILES...]" << endl;
23bd782 @johnezang First commit of the local iPhone optimized changes.
johnezang authored
345 cout << endl;
346 cout << "Modes:" << endl;
347 cout << " " SWITCH_GETOPT_LONG("-l, --list ", "-l") " List the content of the files" << endl;
348 cout << " " SWITCH_GETOPT_LONG("-z, --recompress ", "-z") " Recompress the specified files" << endl;
349 cout << "Options:" << endl;
350 cout << " " SWITCH_GETOPT_LONG("-0, --shrink-store ", "-0") " Don't compress" << endl;
351 cout << " " SWITCH_GETOPT_LONG("-1, --shrink-fast ", "-1") " Compress fast" << endl;
352 cout << " " SWITCH_GETOPT_LONG("-2, --shrink-normal ", "-2") " Compress normal" << endl;
353 cout << " " SWITCH_GETOPT_LONG("-3, --shrink-extra ", "-3") " Compress extra" << endl;
354 cout << " " SWITCH_GETOPT_LONG("-4, --shrink-insane ", "-4") " Compress extreme" << endl;
355 cout << " " SWITCH_GETOPT_LONG("-f, --force ", "-f") " Force the new file also if it's bigger" << endl;
356 cout << " " SWITCH_GETOPT_LONG("-q, --quiet ", "-q") " Don't print on the console" << endl;
357 cout << " " SWITCH_GETOPT_LONG("-h, --help ", "-h") " Help of the program" << endl;
358 cout << " " SWITCH_GETOPT_LONG("-V, --version ", "-V") " Version of the program" << endl;
359 }
360
361 void process(int argc, char* argv[])
362 {
363 enum cmd_t {
364 cmd_unset, cmd_recompress, cmd_list
365 } cmd = cmd_unset;
366
367 opt_quiet = false;
368 opt_level = shrink_normal;
369 opt_force = false;
370 opt_crc = false;
371
372 if (argc <= 1) {
373 usage();
374 return;
375 }
376
377 int c = 0;
378
379 opterr = 0; // don't print errors
380
381 while ((c =
382 #if HAVE_GETOPT_LONG
383 getopt_long(argc, argv, OPTIONS, long_options, 0))
384 #else
385 getopt(argc, argv, OPTIONS))
386 #endif
387 != EOF) {
388 switch (c) {
389 case 'z' :
390 if (cmd != cmd_unset)
391 throw error() << "Too many commands";
392 cmd = cmd_recompress;
393 break;
394 case 'l' :
395 if (cmd != cmd_unset)
396 throw error() << "Too many commands";
397 cmd = cmd_list;
398 break;
399 case 'L' :
400 if (cmd != cmd_unset)
401 throw error() << "Too many commands";
402 cmd = cmd_list;
403 opt_crc = true;
404 break;
405 case '0' :
406 opt_level = shrink_none;
407 opt_force = true;
408 break;
409 case '1' :
410 opt_level = shrink_fast;
411 break;
412 case '2' :
413 opt_level = shrink_normal;
414 break;
415 case '3' :
416 opt_level = shrink_extra;
417 break;
418 case '4' :
419 opt_level = shrink_extreme;
420 break;
421 case 'f' :
422 opt_force = true;
423 break;
424 case 'q' :
425 opt_quiet = true;
426 break;
427 case 'h' :
428 usage();
429 return;
430 case 'V' :
431 version();
432 return;
433 default: {
434 // not optimal code for g++ 2.95.3
435 string opt;
436 opt = (char)optopt;
437 throw error() << "Unknown option `" << opt << "'";
438 }
439 }
440 }
441
442 switch (cmd) {
443 case cmd_recompress :
444 rezip_all(argc - optind, argv + optind);
445 break;
446 case cmd_list :
447 list_all(argc - optind, argv + optind);
448 break;
449 case cmd_unset :
450 throw error() << "No command specified";
451 }
452 }
453
454 int main(int argc, char* argv[])
455 {
456 try {
457 process(argc, argv);
458 } catch (error& e) {
459 cerr << e << endl;
460 exit(EXIT_FAILURE);
461 } catch (std::bad_alloc) {
462 cerr << "Low memory" << endl;
463 exit(EXIT_FAILURE);
464 } catch (...) {
465 cerr << "Unknown error" << endl;
466 exit(EXIT_FAILURE);
467 }
468
469 return EXIT_SUCCESS;
470 }
471
Something went wrong with that request. Please try again.