apple_png 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,265 @@
1
+ #include <ruby.h>
2
+ #include <zlib.h>
3
+ #include <arpa/inet.h>
4
+ #include "dyn_arr.h"
5
+
6
+ #define PNG_HEADER "\x89PNG\r\n\x1a\n"
7
+ #define PNG_BYTES2UINT(char_ptr) (ntohl(*(uint32_t *)(char_ptr)))
8
+
9
+ #define APPLE_PNG_OK 0
10
+ #define APPLE_PNG_STREAM_ERROR Z_STREAM_ERROR
11
+ #define APPLE_PNG_DATA_ERROR Z_DATA_ERROR
12
+ #define APPLE_PNG_ZLIB_VERSION_ERROR Z_VERSION_ERROR
13
+ #define APPLE_PNG_NO_MEM_ERROR Z_MEM_ERROR
14
+
15
+ /* inflate from apple png file, don't expect headers */
16
+ static int png_inflate(unsigned char *data, uint32_t length, uint32_t width, uint32_t height, unsigned char **out_buff, uint32_t *out_uncompressed_size) {
17
+ int error;
18
+ z_stream inflate_strm = {0};
19
+
20
+ *out_uncompressed_size = height + width * height * 4;
21
+ *out_buff = malloc(sizeof(char) * (*out_uncompressed_size));
22
+ if (*out_buff == 0) {
23
+ return APPLE_PNG_NO_MEM_ERROR;
24
+ }
25
+
26
+ inflate_strm.data_type = Z_BINARY;
27
+ inflate_strm.avail_in = length;
28
+ inflate_strm.next_in = (Bytef *)data;
29
+ inflate_strm.avail_out = *out_uncompressed_size;
30
+ inflate_strm.next_out = (Bytef *)*out_buff;
31
+
32
+ error = inflateInit2(&inflate_strm, -15);
33
+ if (error != Z_OK) {
34
+ free(*out_buff);
35
+ return error;
36
+ }
37
+ error = inflate(&inflate_strm, Z_FINISH);
38
+ if (error != Z_OK && error != Z_STREAM_END) {
39
+ free (*out_buff);
40
+ return error;
41
+ }
42
+ error = inflateEnd(&inflate_strm);
43
+ if (error != Z_OK) {
44
+ free(*out_buff);
45
+ return error;
46
+ }
47
+
48
+ return APPLE_PNG_OK;
49
+ }
50
+
51
+ /* deflate for standard png file */
52
+ static int png_deflate(unsigned char *data, uint32_t length, unsigned char **out_buff, uint32_t *out_compressed_size) {
53
+ int error;
54
+ uint32_t sizeEstimate;
55
+ z_stream deflate_strm = {0};
56
+ deflate_strm.avail_in = length;
57
+ deflate_strm.next_in = (Bytef *)data;
58
+
59
+ error = deflateInit(&deflate_strm, Z_DEFAULT_COMPRESSION);
60
+ if (error != Z_OK) {
61
+ return error;
62
+ }
63
+
64
+ sizeEstimate = (uint32_t)deflateBound(&deflate_strm, length);
65
+ *out_buff = malloc(sizeof(unsigned char) * sizeEstimate);
66
+ if (*out_buff == 0) {
67
+ free(*out_buff);
68
+ return APPLE_PNG_NO_MEM_ERROR;
69
+ }
70
+
71
+ deflate_strm.avail_out = (uint32_t)sizeEstimate;
72
+ deflate_strm.next_out = *out_buff;
73
+
74
+ error = deflate(&deflate_strm, Z_FINISH);
75
+ if (error != Z_OK && error != Z_STREAM_END) {
76
+ free(*out_buff);
77
+ return error;
78
+ }
79
+ error = deflateEnd(&deflate_strm);
80
+ if (error != Z_OK) {
81
+ free(*out_buff);
82
+ return error;
83
+ }
84
+
85
+ *out_compressed_size = (uint32_t)deflate_strm.total_out;
86
+
87
+ return APPLE_PNG_OK;
88
+ }
89
+
90
+ /* flip first and third color in uncompressed png pixel data */
91
+ static void flip_color_bytes(unsigned char *pixelData, uint32_t width, uint32_t height) {
92
+ uint32_t x, y;
93
+ size_t i = 0;
94
+
95
+ for (y = 0; y < height; y++) {
96
+ i += 1;
97
+ for (x = 0; x < width; x++) {
98
+ char tmp = pixelData[i];
99
+ pixelData[i] = pixelData[i + 2];
100
+ pixelData[i + 2] = tmp;
101
+ i += 4;
102
+ }
103
+ }
104
+ }
105
+
106
+ /* calculate chunk checksum out of chunk type and chunk data */
107
+ static uint32_t png_crc32(const char *chunkType, const char *chunkData, uint32_t chunkLength) {
108
+ unsigned long running_crc = crc32(0, 0, 0);
109
+ running_crc = crc32(running_crc, (Bytef *)chunkType, 4);
110
+ running_crc = crc32(running_crc, (Bytef *)chunkData, chunkLength);
111
+ return (uint32_t)((running_crc + 0x100000000) % 0x100000000);
112
+ }
113
+
114
+ /* extract chunks from PNG data */
115
+ static int readPngChunks(VALUE self, const char *oldPNG, size_t oldPngLength, dyn_arr *newPNG) {
116
+ uint32_t width = 0, height = 0;
117
+ size_t cursor = 8;
118
+ dyn_arr *applePngCompressedPixelData = dyn_arr_create(oldPngLength);
119
+ if (applePngCompressedPixelData == 0) {
120
+ return APPLE_PNG_NO_MEM_ERROR;
121
+ }
122
+
123
+ while (cursor < oldPngLength) {
124
+ int breakLoop = 0;
125
+ const char *chunkLength_raw = &oldPNG[cursor];
126
+ uint32_t chunkLength = PNG_BYTES2UINT(chunkLength_raw);
127
+ const char *chunkType = &oldPNG[cursor + 4];
128
+ const char *chunkData = &oldPNG[cursor + 8];
129
+ const char *chunkCRC_raw = &oldPNG[cursor + 8 + chunkLength];
130
+ cursor += chunkLength + 12;
131
+
132
+ if (strncmp(chunkType, "IHDR", 4) == 0) {
133
+ /* extract dimensions from header */
134
+ width = PNG_BYTES2UINT(&chunkData[0]);
135
+ height = PNG_BYTES2UINT(&chunkData[4]);
136
+ rb_funcall(self, rb_intern("width="), 1, INT2NUM(width));
137
+ rb_funcall(self, rb_intern("height="), 1, INT2NUM(height));
138
+ } else if (strncmp(chunkType, "IDAT", 4) == 0) {
139
+ /* collect pixel data to process it once an IEND chunk appears */
140
+ dyn_arr_append(applePngCompressedPixelData, chunkData, chunkLength);
141
+ continue;
142
+ } else if (strncmp(chunkType, "CgBI", 4) == 0) {
143
+ /* don't write CgBI chunks to the output png */
144
+ continue;
145
+ } else if (strncmp(chunkType, "IEND", 4) == 0) {
146
+ /* all png data has been procssed, now flip the color bytes */
147
+ unsigned char *decompressedPixelData, *standardPngCompressedPixelData;
148
+ uint32_t compressed_size, uncompressed_size;
149
+ uint32_t chunkCRC;
150
+ uint32_t tmp_chunkLength, tmp_chunkCRC;
151
+ int error;
152
+
153
+ /* decompress, flip color bytes, then compress again */
154
+ error = png_inflate((unsigned char *)applePngCompressedPixelData->arr, (uint32_t)applePngCompressedPixelData->used, width, height, &decompressedPixelData, &uncompressed_size);
155
+ if (error != APPLE_PNG_OK) {
156
+ dyn_arr_free(applePngCompressedPixelData);
157
+ return error;
158
+ }
159
+ flip_color_bytes(decompressedPixelData, width, height);
160
+ error = png_deflate(decompressedPixelData, uncompressed_size, &standardPngCompressedPixelData, &compressed_size);
161
+ if (error != APPLE_PNG_OK) {
162
+ dyn_arr_free(applePngCompressedPixelData);
163
+ return error;
164
+ }
165
+
166
+ /* clean up temporary data structures */
167
+ dyn_arr_free(applePngCompressedPixelData);
168
+ free(decompressedPixelData);
169
+
170
+ /* write the pixel data to the output PNG as IDAT chunk */
171
+ chunkType = "IDAT";
172
+ chunkLength = compressed_size;
173
+ tmp_chunkLength = htonl(chunkLength);
174
+ chunkLength_raw = (char*)(&tmp_chunkLength);
175
+ chunkData = (char*)standardPngCompressedPixelData;
176
+ chunkCRC = png_crc32(chunkType, chunkData, chunkLength);
177
+ tmp_chunkCRC = htonl(chunkCRC);
178
+ chunkCRC_raw = (char *)(&tmp_chunkCRC);
179
+
180
+ /* we're done */
181
+ breakLoop = 1;
182
+ }
183
+
184
+ /* write the chunk to the output png */
185
+ dyn_arr_append(newPNG, chunkLength_raw, 4);
186
+ dyn_arr_append(newPNG, chunkType, 4);
187
+ dyn_arr_append(newPNG, chunkData, chunkLength);
188
+ dyn_arr_append(newPNG, chunkCRC_raw, 4);
189
+
190
+ if (breakLoop) {
191
+ break;
192
+ }
193
+ }
194
+
195
+ return APPLE_PNG_OK;
196
+ }
197
+
198
+ /* convert an apple png data string to a standard png data string */
199
+ static VALUE ApplePng_convert_apple_png(VALUE self, VALUE data) {
200
+ int error;
201
+ VALUE ret;
202
+ const char *oldPNG = StringValuePtr(data);
203
+ size_t oldPNG_length = RSTRING_LEN(data);
204
+
205
+ dyn_arr *newPNG = dyn_arr_create(oldPNG_length);
206
+ if (newPNG == 0) {
207
+ rb_raise(rb_eNoMemError, "There was not enough memory to uncompress the PNG data.");
208
+ }
209
+
210
+ dyn_arr_append(newPNG, PNG_HEADER, 8);
211
+ error = readPngChunks(self, oldPNG, oldPNG_length, newPNG);
212
+ if (error != APPLE_PNG_OK) {
213
+ dyn_arr_free(newPNG);
214
+ switch (error) {
215
+ case APPLE_PNG_STREAM_ERROR:
216
+ case APPLE_PNG_DATA_ERROR:
217
+ rb_raise(rb_eArgError, "Could not process the input data. Please make sure this is valid Apple PNG format data.");
218
+ case APPLE_PNG_ZLIB_VERSION_ERROR:
219
+ rb_raise(rb_eArgError, "Unexpected Zlib version encountered. The caller was expecting Zlib " ZLIB_VERSION ".");
220
+ case APPLE_PNG_NO_MEM_ERROR:
221
+ rb_raise(rb_eArgError, "Ran out of memory while processing the PNG data.");
222
+ default:
223
+ rb_raise(rb_eStandardError, "An unexpected error was encountered while processing the PNG data. Please make sure the input is valid Apple PNG format data.");
224
+ }
225
+ }
226
+
227
+ ret = rb_str_new(newPNG->arr, newPNG->used);
228
+ dyn_arr_free(newPNG);
229
+ return ret;
230
+ }
231
+
232
+ /* this is a quick way to get the width and height from png data without converting the file */
233
+ static VALUE ApplePng_get_dimensions(VALUE self, VALUE data) {
234
+ const char *oldPNG = StringValuePtr(data);
235
+ size_t oldPNG_length = RSTRING_LEN(data);
236
+ size_t cursor = 8;
237
+
238
+ /* check whether this is actually a png file */
239
+ if (strncmp(PNG_HEADER, oldPNG, 8) != 0) {
240
+ rb_raise(rb_eArgError, "Input data is not a valid PNG file (missing the PNG magic bytes).");
241
+ }
242
+
243
+ while (cursor < oldPNG_length) {
244
+ uint32_t chunkLength = ntohl(*(uint32_t *)(&oldPNG[cursor]));
245
+ const char *chunkType = &oldPNG[cursor + 4];
246
+ const char *chunkData = &oldPNG[cursor + 8];
247
+ cursor += chunkLength + 12;
248
+
249
+ if (strncmp(chunkType, "IHDR", 4) == 0) {
250
+ uint32_t width = PNG_BYTES2UINT(&chunkData[0]);
251
+ uint32_t height = PNG_BYTES2UINT(&chunkData[4]);
252
+ rb_funcall(self, rb_intern("width="), 1, INT2NUM(width));
253
+ rb_funcall(self, rb_intern("height="), 1, INT2NUM(height));
254
+ return Qnil;
255
+ }
256
+ }
257
+
258
+ rb_raise(rb_eArgError, "Input data is not a valid PNG file (missing IHDR chunk).");
259
+ }
260
+
261
+ void Init_apple_png(void) {
262
+ VALUE klass = rb_define_class("ApplePng", rb_cObject);
263
+ rb_define_method(klass, "convert_apple_png", ApplePng_convert_apple_png, 1);
264
+ rb_define_method(klass, "get_dimensions", ApplePng_get_dimensions, 1);
265
+ }
@@ -0,0 +1,41 @@
1
+ #ifndef DYN_ARR_H
2
+ #define DYN_ARR_H
3
+
4
+ /* dynamic array implementation */
5
+
6
+ typedef struct dyn_arr_t {
7
+ size_t size;
8
+ size_t used;
9
+ char *arr;
10
+ } dyn_arr;
11
+
12
+ dyn_arr *dyn_arr_create(size_t size) {
13
+ dyn_arr *ret = calloc(1, sizeof(dyn_arr));
14
+ if (!ret) {
15
+ return 0;
16
+ }
17
+ ret->size = size;
18
+ ret->arr = malloc(size * sizeof(char));
19
+ if (!ret->arr) {
20
+ free(ret);
21
+ return 0;
22
+ }
23
+ return ret;
24
+ }
25
+
26
+ void dyn_arr_free(dyn_arr *arr) {
27
+ free(arr->arr);
28
+ memset(arr, 0, sizeof(dyn_arr));
29
+ free(arr);
30
+ }
31
+
32
+ void dyn_arr_append(dyn_arr *arr, const char *elements, size_t el_count) {
33
+ while (arr->used + el_count > arr->size) {
34
+ arr->size *= 2;
35
+ arr->arr = realloc(arr->arr, arr->size * sizeof(char));
36
+ }
37
+ memcpy(&arr->arr[arr->used], elements, el_count);
38
+ arr->used += el_count;
39
+ }
40
+
41
+ #endif
@@ -0,0 +1,7 @@
1
+ require 'mkmf'
2
+
3
+ if have_library('z', 'inflate')
4
+ create_makefile('apple_png/apple_png')
5
+ else
6
+ puts "Could not find zlib library"
7
+ end
data/lib/apple_png.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'apple_png/apple_png'
2
+
3
+ class ApplePng
4
+ attr_accessor :width, :height
5
+
6
+ def initialize(apple_png_data)
7
+ self.get_dimensions(apple_png_data)
8
+ @raw_data = apple_png_data
9
+ end
10
+
11
+ def data
12
+ if @data.nil?
13
+ @data = self.convert_apple_png(@raw_data)
14
+ end
15
+ return @data
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apple_png
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Marvin Killing
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-17 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Converts the Apple PNG format used in iOS packages to standard PNG
15
+ email: marvinkilling@gmail.com
16
+ executables: []
17
+ extensions:
18
+ - ext/apple_png/extconf.rb
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/apple_png.rb
22
+ - ext/apple_png/apple_png.c
23
+ - ext/apple_png/dyn_arr.h
24
+ - ext/apple_png/extconf.rb
25
+ homepage: http://rubygems.org/gems/apple_png
26
+ licenses: []
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ none: false
33
+ requirements:
34
+ - - ! '>='
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 1.8.25
46
+ signing_key:
47
+ specification_version: 3
48
+ summary: Converts the Apple PNG format to standard PNG
49
+ test_files: []