axon 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/CHANGELOG.rdoc +3 -0
- data/README.rdoc +104 -0
- data/Rakefile +30 -0
- data/TODO.rdoc +12 -0
- data/ext/axon/axon.c +20 -0
- data/ext/axon/bilinear_interpolation.c +115 -0
- data/ext/axon/extconf.rb +21 -0
- data/ext/axon/iccjpeg.c +248 -0
- data/ext/axon/iccjpeg.h +73 -0
- data/ext/axon/interpolation.h +7 -0
- data/ext/axon/jpeg_common.c +118 -0
- data/ext/axon/jpeg_common.h +37 -0
- data/ext/axon/jpeg_native_writer.c +248 -0
- data/ext/axon/jpeg_reader.c +774 -0
- data/ext/axon/nearest_neighbor_interpolation.c +50 -0
- data/ext/axon/png_common.c +21 -0
- data/ext/axon/png_common.h +18 -0
- data/ext/axon/png_native_writer.c +166 -0
- data/ext/axon/png_reader.c +381 -0
- data/lib/axon/axon.so +0 -0
- data/lib/axon/bilinear_scaler.rb +60 -0
- data/lib/axon/cropper.rb +35 -0
- data/lib/axon/fit.rb +67 -0
- data/lib/axon/jpeg_writer.rb +41 -0
- data/lib/axon/nearest_neighbor_scaler.rb +39 -0
- data/lib/axon/png_writer.rb +35 -0
- data/lib/axon/scaler.rb +41 -0
- data/lib/axon/solid.rb +23 -0
- data/lib/axon.rb +45 -0
- data/test/_test_readme.rb +34 -0
- data/test/helper.rb +17 -0
- data/test/reader_tests.rb +115 -0
- data/test/stress_tests.rb +71 -0
- data/test/test_bilinear_scaler.rb +9 -0
- data/test/test_cropper.rb +9 -0
- data/test/test_exif.rb +39 -0
- data/test/test_generator.rb +10 -0
- data/test/test_icc.rb +18 -0
- data/test/test_jpeg.rb +9 -0
- data/test/test_jpeg_reader.rb +109 -0
- data/test/test_jpeg_writer.rb +26 -0
- data/test/test_nearest_neighbor_scaler.rb +13 -0
- data/test/test_png.rb +9 -0
- data/test/test_png_reader.rb +15 -0
- data/test/test_png_writer.rb +13 -0
- data/test/writer_tests.rb +179 -0
- metadata +148 -0
data/.gemtest
ADDED
File without changes
|
data/CHANGELOG.rdoc
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
= Axon
|
2
|
+
|
3
|
+
http://github.com/ender672/axon
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Axon is a library for streaming and manipulating JPEG and PNG images. It scales
|
8
|
+
and crops images along the way.
|
9
|
+
|
10
|
+
By limiting its functionality, Axon is able to depend on two ubiquitous
|
11
|
+
libraries: libjpeg and libpng. Axon can be installed anywhere those libraries
|
12
|
+
are available.
|
13
|
+
|
14
|
+
Axon never stores an entire image in memory. All images and operations are
|
15
|
+
streamed from an input to an output. As a result, memory requirements and
|
16
|
+
latency are low.
|
17
|
+
|
18
|
+
== FEATURES:
|
19
|
+
|
20
|
+
* Read and Write JPEG and PNG images.
|
21
|
+
* Scale images using bilinear (fast!) or nearest-neighbor (even faster!)
|
22
|
+
interpolation.
|
23
|
+
* Crop images.
|
24
|
+
|
25
|
+
== SYNOPSIS:
|
26
|
+
|
27
|
+
# Short, chained example. Reads a JPEG from io_in and writes scaled png to
|
28
|
+
# io_out.
|
29
|
+
Axon.JPEG(io_in).fit(100, 100).write_png(io_out)
|
30
|
+
|
31
|
+
# Longer example, reads the JPEG header, looks at properties and header
|
32
|
+
# values, sets decompression options, scales the image, sets compression
|
33
|
+
# options, and writes a JPEG.
|
34
|
+
image = Axon.JPEG(io, [:APP2])
|
35
|
+
|
36
|
+
puts image.width
|
37
|
+
puts image.height
|
38
|
+
puts image[:APP2]
|
39
|
+
image.scale_denom = 4
|
40
|
+
|
41
|
+
jpeg = image.fit(100, 100).to_jpeg
|
42
|
+
jpeg.quality = 88
|
43
|
+
jpeg.write(io)
|
44
|
+
|
45
|
+
== BASIC API:
|
46
|
+
|
47
|
+
There are three basic object types: Image, Reader and Writer.
|
48
|
+
|
49
|
+
Every Image object has the following methods:
|
50
|
+
|
51
|
+
* Image#height, Image#width
|
52
|
+
These are the output dimensions of the image.
|
53
|
+
* Image#color_model
|
54
|
+
Can be :GRAYSCALE or :RGB
|
55
|
+
* Image#components
|
56
|
+
An RGB image will have 3 components. A grayscale image with an alpha channel
|
57
|
+
(transparency) will have 2 components: grayscale and transparency, etc.
|
58
|
+
* Image#each(&block)
|
59
|
+
Yields every line in the image as a binary ruby string. Image properties
|
60
|
+
are not allowed to change between the first and last yield.
|
61
|
+
|
62
|
+
A Reader object has the same methods as an Image object, with added methods that
|
63
|
+
are specific to the decoding of the image format.
|
64
|
+
|
65
|
+
A Writer has two methods:
|
66
|
+
|
67
|
+
* Writer#write(io[, options])
|
68
|
+
* Writer#data([options]) # returns image data as a string
|
69
|
+
|
70
|
+
== REQUIREMENTS:
|
71
|
+
|
72
|
+
IJG JPEG Library Version 6b or later.
|
73
|
+
pnglib version 1.2.x or later.
|
74
|
+
|
75
|
+
== INSTALL:
|
76
|
+
|
77
|
+
gem install axon
|
78
|
+
|
79
|
+
== LICENSE:
|
80
|
+
|
81
|
+
(The MIT License)
|
82
|
+
|
83
|
+
Copyright (c) 2011
|
84
|
+
|
85
|
+
* {Timothy Elliott}[http://holymonkey.com]
|
86
|
+
|
87
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
88
|
+
a copy of this software and associated documentation files (the
|
89
|
+
'Software'), to deal in the Software without restriction, including
|
90
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
91
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
92
|
+
permit persons to whom the Software is furnished to do so, subject to
|
93
|
+
the following conditions:
|
94
|
+
|
95
|
+
The above copyright notice and this permission notice shall be
|
96
|
+
included in all copies or substantial portions of the Software.
|
97
|
+
|
98
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
99
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
100
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
101
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
102
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
103
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
104
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hoe'
|
3
|
+
|
4
|
+
HOE = Hoe.spec 'axon' do
|
5
|
+
developer('Timothy Elliott', 'tle@holymonkey.com')
|
6
|
+
self.readme_file = 'README.rdoc'
|
7
|
+
self.history_file = 'CHANGELOG.rdoc'
|
8
|
+
self.extra_dev_deps << ['rake-compiler', '>= 0']
|
9
|
+
self.spec_extras = { :extensions => ["ext/axon/extconf.rb"] }
|
10
|
+
end
|
11
|
+
|
12
|
+
require "rake/extensiontask"
|
13
|
+
|
14
|
+
Rake::ExtensionTask.new('axon', HOE.spec) do |ext|
|
15
|
+
ext.lib_dir = File.join('lib', 'axon')
|
16
|
+
end
|
17
|
+
|
18
|
+
Rake::Task[:test].prerequisites << :compile
|
19
|
+
|
20
|
+
desc 'Run a test in looped mode so that you can look for memory leaks.'
|
21
|
+
task 'test_loop' do
|
22
|
+
code = %Q[require '#{$*[1]}'; loop{ MiniTest::Unit.new.run }]
|
23
|
+
cmd = %Q[ruby -Ilib -Itest -e "#{ code }"]
|
24
|
+
system cmd
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Watch Memory use of a looping test'
|
28
|
+
task 'test_loop_mem' do
|
29
|
+
system 'watch "ps aux | grep Itest"'
|
30
|
+
end
|
data/TODO.rdoc
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
= TODO
|
2
|
+
* have a setting where decode / encode warnings will result in exceptions
|
3
|
+
|
4
|
+
* Test scalers with images that lie about width & height. Never trust
|
5
|
+
Image#width or Image#height -- instead always go by scanline size and how
|
6
|
+
many times #each_scanline calls yield
|
7
|
+
|
8
|
+
* add #initialize_copy
|
9
|
+
|
10
|
+
* Check frozen status on setters, etc.
|
11
|
+
* Check frozen status (and freeze) on all VALUEs that we stash for later use
|
12
|
+
* Check trusted status (1.9.x only)
|
data/ext/axon/axon.c
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
|
3
|
+
#include "jpeg_common.h"
|
4
|
+
#include "png_common.h"
|
5
|
+
#include "interpolation.h"
|
6
|
+
|
7
|
+
void
|
8
|
+
Init_axon()
|
9
|
+
{
|
10
|
+
Init_jpeg();
|
11
|
+
Init_jpeg_reader();
|
12
|
+
Init_jpeg_native_writer();
|
13
|
+
|
14
|
+
Init_png();
|
15
|
+
Init_png_reader();
|
16
|
+
Init_png_native_writer();
|
17
|
+
|
18
|
+
Init_bilinear_interpolation();
|
19
|
+
Init_nearest_neighbor_interpolation();
|
20
|
+
}
|
@@ -0,0 +1,115 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
|
3
|
+
static ID id_image, id_components, id_width_ratio, id_width;
|
4
|
+
|
5
|
+
|
6
|
+
/* c00 a c10
|
7
|
+
* --------------------------
|
8
|
+
* | | |
|
9
|
+
* | | |
|
10
|
+
* | ty| |
|
11
|
+
* | tx | |
|
12
|
+
* |------------------+-----|
|
13
|
+
* | /| |
|
14
|
+
* | sample| |
|
15
|
+
* | | |
|
16
|
+
* | | |
|
17
|
+
* | | |
|
18
|
+
* | | |
|
19
|
+
* --------------------------
|
20
|
+
* c01 b c11
|
21
|
+
*
|
22
|
+
* a = (1 - tx) * c00 + tx * c10
|
23
|
+
* b = (1 - tx) * c01 + tx * c11
|
24
|
+
* sample = (1 - ty) * a + ty * b
|
25
|
+
*
|
26
|
+
* sample = (1 - ty) * (1 - tx) * c00 +
|
27
|
+
* (1 - ty) * tx * c10 +
|
28
|
+
* ty * (1 - tx) * c01 +
|
29
|
+
ty * tx * c11
|
30
|
+
*/
|
31
|
+
static VALUE
|
32
|
+
interpolate_scanline2(size_t width, double width_ratio, size_t components,
|
33
|
+
double ty, char *scanline1, char *scanline2)
|
34
|
+
{
|
35
|
+
VALUE rb_dest_sl;
|
36
|
+
double sample_x, tx, _tx, p00, p10, p01, p11;
|
37
|
+
unsigned char *c00, *c10, *c01, *c11, *dest_sl;
|
38
|
+
size_t sample_x_i, i, j;
|
39
|
+
|
40
|
+
rb_dest_sl = rb_str_new(0, width * components);
|
41
|
+
dest_sl = RSTRING_PTR(rb_dest_sl);
|
42
|
+
|
43
|
+
for (i = 0; i < width; i++) {
|
44
|
+
sample_x = i / width_ratio;
|
45
|
+
sample_x_i = (int)sample_x;
|
46
|
+
|
47
|
+
tx = sample_x - sample_x_i;
|
48
|
+
_tx = 1 - tx;
|
49
|
+
|
50
|
+
p11 = tx * ty;
|
51
|
+
p01 = _tx * ty;
|
52
|
+
p10 = tx - p11;
|
53
|
+
p00 = _tx - p01;
|
54
|
+
|
55
|
+
c00 = scanline1 + sample_x_i * components;
|
56
|
+
c10 = c00 + components;
|
57
|
+
c01 = scanline2 + sample_x_i * components;
|
58
|
+
c11 = c01 + components;
|
59
|
+
|
60
|
+
for (j = 0; j < components; j++)
|
61
|
+
*dest_sl++ = p00 * c00[j] + p10 * c10[j] + p01 * c01[j] +
|
62
|
+
p11 * c11[j];
|
63
|
+
}
|
64
|
+
|
65
|
+
return rb_dest_sl;
|
66
|
+
}
|
67
|
+
|
68
|
+
/*
|
69
|
+
* call-seq:
|
70
|
+
* image.interpolate_scanline(original_scanlines, q) -> interpolated_scanline
|
71
|
+
* Expects the instance variable @image to respond to components, and
|
72
|
+
* expects the intance variable @width_ratio to respond to the ratio that
|
73
|
+
* we will resample our width to.
|
74
|
+
*/
|
75
|
+
static VALUE
|
76
|
+
interpolate_scanline(VALUE self, VALUE orig_scanline1, VALUE orig_scanline2,
|
77
|
+
VALUE rb_sample_y)
|
78
|
+
{
|
79
|
+
VALUE rb_components, rb_width_ratio, rb_image, rb_width;
|
80
|
+
double width_ratio, ty, sample_y;
|
81
|
+
unsigned char *scanline1, *scanline2;
|
82
|
+
size_t width, components;
|
83
|
+
|
84
|
+
rb_width = rb_ivar_get(self, id_width);
|
85
|
+
width = FIX2INT(rb_width);
|
86
|
+
|
87
|
+
rb_image = rb_ivar_get(self, id_image);
|
88
|
+
rb_components = rb_funcall(rb_image, id_components, 0);
|
89
|
+
components = FIX2INT(rb_components);
|
90
|
+
|
91
|
+
rb_width_ratio = rb_ivar_get(self, id_width_ratio);
|
92
|
+
width_ratio = NUM2DBL(rb_width_ratio);
|
93
|
+
|
94
|
+
sample_y = NUM2DBL(rb_sample_y);
|
95
|
+
ty = sample_y - (int)sample_y;
|
96
|
+
|
97
|
+
scanline1 = RSTRING_PTR(orig_scanline1);
|
98
|
+
scanline2 = RSTRING_PTR(orig_scanline2);
|
99
|
+
|
100
|
+
return interpolate_scanline2(width, width_ratio, components, ty, scanline1,
|
101
|
+
scanline2);
|
102
|
+
}
|
103
|
+
|
104
|
+
void
|
105
|
+
Init_bilinear_interpolation()
|
106
|
+
{
|
107
|
+
VALUE mAxon = rb_define_module("Axon");
|
108
|
+
VALUE mBilinearScaling = rb_define_module_under(mAxon, "BilinearScaling");
|
109
|
+
rb_define_method(mBilinearScaling, "interpolate_scanline", interpolate_scanline, 3);
|
110
|
+
|
111
|
+
id_width = rb_intern("@width");
|
112
|
+
id_width_ratio = rb_intern("@width_ratio");
|
113
|
+
id_image = rb_intern("@image");
|
114
|
+
id_components = rb_intern("components");
|
115
|
+
}
|
data/ext/axon/extconf.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
$CFLAGS += " -g -O0" if ENV['GCC_DEBUG']
|
4
|
+
|
5
|
+
unless find_header('jpeglib.h')
|
6
|
+
abort "libjpeg headers are missing. Please install libjpeg headers."
|
7
|
+
end
|
8
|
+
|
9
|
+
unless have_library('jpeg', nil)
|
10
|
+
abort "libjpeg is missing. Please install libjpeg."
|
11
|
+
end
|
12
|
+
|
13
|
+
unless find_header('png.h')
|
14
|
+
abort "libpng headers are missing. Please install libpng headers."
|
15
|
+
end
|
16
|
+
|
17
|
+
unless have_library('png', nil)
|
18
|
+
abort "libpng is missing. Please install libpng."
|
19
|
+
end
|
20
|
+
|
21
|
+
create_makefile('jpeg/axon')
|
data/ext/axon/iccjpeg.c
ADDED
@@ -0,0 +1,248 @@
|
|
1
|
+
/*
|
2
|
+
* iccprofile.c
|
3
|
+
*
|
4
|
+
* This file provides code to read and write International Color Consortium
|
5
|
+
* (ICC) device profiles embedded in JFIF JPEG image files. The ICC has
|
6
|
+
* defined a standard format for including such data in JPEG "APP2" markers.
|
7
|
+
* The code given here does not know anything about the internal structure
|
8
|
+
* of the ICC profile data; it just knows how to put the profile data into
|
9
|
+
* a JPEG file being written, or get it back out when reading.
|
10
|
+
*
|
11
|
+
* This code depends on new features added to the IJG JPEG library as of
|
12
|
+
* IJG release 6b; it will not compile or work with older IJG versions.
|
13
|
+
*
|
14
|
+
* NOTE: this code would need surgery to work on 16-bit-int machines
|
15
|
+
* with ICC profiles exceeding 64K bytes in size. If you need to do that,
|
16
|
+
* change all the "unsigned int" variables to "INT32". You'll also need
|
17
|
+
* to find a malloc() replacement that can allocate more than 64K.
|
18
|
+
*/
|
19
|
+
|
20
|
+
#include "iccjpeg.h"
|
21
|
+
#include <stdlib.h> /* define malloc() */
|
22
|
+
|
23
|
+
|
24
|
+
/*
|
25
|
+
* Since an ICC profile can be larger than the maximum size of a JPEG marker
|
26
|
+
* (64K), we need provisions to split it into multiple markers. The format
|
27
|
+
* defined by the ICC specifies one or more APP2 markers containing the
|
28
|
+
* following data:
|
29
|
+
* Identifying string ASCII "ICC_PROFILE\0" (12 bytes)
|
30
|
+
* Marker sequence number 1 for first APP2, 2 for next, etc (1 byte)
|
31
|
+
* Number of markers Total number of APP2's used (1 byte)
|
32
|
+
* Profile data (remainder of APP2 data)
|
33
|
+
* Decoders should use the marker sequence numbers to reassemble the profile,
|
34
|
+
* rather than assuming that the APP2 markers appear in the correct sequence.
|
35
|
+
*/
|
36
|
+
|
37
|
+
#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */
|
38
|
+
#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */
|
39
|
+
#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */
|
40
|
+
#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN)
|
41
|
+
|
42
|
+
|
43
|
+
/*
|
44
|
+
* This routine writes the given ICC profile data into a JPEG file.
|
45
|
+
* It *must* be called AFTER calling jpeg_start_compress() and BEFORE
|
46
|
+
* the first call to jpeg_write_scanlines().
|
47
|
+
* (This ordering ensures that the APP2 marker(s) will appear after the
|
48
|
+
* SOI and JFIF or Adobe markers, but before all else.)
|
49
|
+
*/
|
50
|
+
|
51
|
+
void
|
52
|
+
write_icc_profile (j_compress_ptr cinfo,
|
53
|
+
const JOCTET *icc_data_ptr,
|
54
|
+
unsigned int icc_data_len)
|
55
|
+
{
|
56
|
+
unsigned int num_markers; /* total number of markers we'll write */
|
57
|
+
int cur_marker = 1; /* per spec, counting starts at 1 */
|
58
|
+
unsigned int length; /* number of bytes to write in this marker */
|
59
|
+
|
60
|
+
/* Calculate the number of markers we'll need, rounding up of course */
|
61
|
+
num_markers = icc_data_len / MAX_DATA_BYTES_IN_MARKER;
|
62
|
+
if (num_markers * MAX_DATA_BYTES_IN_MARKER != icc_data_len)
|
63
|
+
num_markers++;
|
64
|
+
|
65
|
+
while (icc_data_len > 0) {
|
66
|
+
/* length of profile to put in this marker */
|
67
|
+
length = icc_data_len;
|
68
|
+
if (length > MAX_DATA_BYTES_IN_MARKER)
|
69
|
+
length = MAX_DATA_BYTES_IN_MARKER;
|
70
|
+
icc_data_len -= length;
|
71
|
+
|
72
|
+
/* Write the JPEG marker header (APP2 code and marker length) */
|
73
|
+
jpeg_write_m_header(cinfo, ICC_MARKER,
|
74
|
+
(unsigned int) (length + ICC_OVERHEAD_LEN));
|
75
|
+
|
76
|
+
/* Write the marker identifying string "ICC_PROFILE" (null-terminated).
|
77
|
+
* We code it in this less-than-transparent way so that the code works
|
78
|
+
* even if the local character set is not ASCII.
|
79
|
+
*/
|
80
|
+
jpeg_write_m_byte(cinfo, 0x49);
|
81
|
+
jpeg_write_m_byte(cinfo, 0x43);
|
82
|
+
jpeg_write_m_byte(cinfo, 0x43);
|
83
|
+
jpeg_write_m_byte(cinfo, 0x5F);
|
84
|
+
jpeg_write_m_byte(cinfo, 0x50);
|
85
|
+
jpeg_write_m_byte(cinfo, 0x52);
|
86
|
+
jpeg_write_m_byte(cinfo, 0x4F);
|
87
|
+
jpeg_write_m_byte(cinfo, 0x46);
|
88
|
+
jpeg_write_m_byte(cinfo, 0x49);
|
89
|
+
jpeg_write_m_byte(cinfo, 0x4C);
|
90
|
+
jpeg_write_m_byte(cinfo, 0x45);
|
91
|
+
jpeg_write_m_byte(cinfo, 0x0);
|
92
|
+
|
93
|
+
/* Add the sequencing info */
|
94
|
+
jpeg_write_m_byte(cinfo, cur_marker);
|
95
|
+
jpeg_write_m_byte(cinfo, (int) num_markers);
|
96
|
+
|
97
|
+
/* Add the profile data */
|
98
|
+
while (length--) {
|
99
|
+
jpeg_write_m_byte(cinfo, *icc_data_ptr);
|
100
|
+
icc_data_ptr++;
|
101
|
+
}
|
102
|
+
cur_marker++;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
|
107
|
+
/*
|
108
|
+
* Prepare for reading an ICC profile
|
109
|
+
*/
|
110
|
+
|
111
|
+
void
|
112
|
+
setup_read_icc_profile (j_decompress_ptr cinfo)
|
113
|
+
{
|
114
|
+
/* Tell the library to keep any APP2 data it may find */
|
115
|
+
jpeg_save_markers(cinfo, ICC_MARKER, 0xFFFF);
|
116
|
+
}
|
117
|
+
|
118
|
+
|
119
|
+
/*
|
120
|
+
* Handy subroutine to test whether a saved marker is an ICC profile marker.
|
121
|
+
*/
|
122
|
+
|
123
|
+
static boolean
|
124
|
+
marker_is_icc (jpeg_saved_marker_ptr marker)
|
125
|
+
{
|
126
|
+
return
|
127
|
+
marker->marker == ICC_MARKER &&
|
128
|
+
marker->data_length >= ICC_OVERHEAD_LEN &&
|
129
|
+
/* verify the identifying string */
|
130
|
+
GETJOCTET(marker->data[0]) == 0x49 &&
|
131
|
+
GETJOCTET(marker->data[1]) == 0x43 &&
|
132
|
+
GETJOCTET(marker->data[2]) == 0x43 &&
|
133
|
+
GETJOCTET(marker->data[3]) == 0x5F &&
|
134
|
+
GETJOCTET(marker->data[4]) == 0x50 &&
|
135
|
+
GETJOCTET(marker->data[5]) == 0x52 &&
|
136
|
+
GETJOCTET(marker->data[6]) == 0x4F &&
|
137
|
+
GETJOCTET(marker->data[7]) == 0x46 &&
|
138
|
+
GETJOCTET(marker->data[8]) == 0x49 &&
|
139
|
+
GETJOCTET(marker->data[9]) == 0x4C &&
|
140
|
+
GETJOCTET(marker->data[10]) == 0x45 &&
|
141
|
+
GETJOCTET(marker->data[11]) == 0x0;
|
142
|
+
}
|
143
|
+
|
144
|
+
|
145
|
+
/*
|
146
|
+
* See if there was an ICC profile in the JPEG file being read;
|
147
|
+
* if so, reassemble and return the profile data.
|
148
|
+
*
|
149
|
+
* TRUE is returned if an ICC profile was found, FALSE if not.
|
150
|
+
* If TRUE is returned, *icc_data_ptr is set to point to the
|
151
|
+
* returned data, and *icc_data_len is set to its length.
|
152
|
+
*
|
153
|
+
* IMPORTANT: the data at **icc_data_ptr has been allocated with malloc()
|
154
|
+
* and must be freed by the caller with free() when the caller no longer
|
155
|
+
* needs it. (Alternatively, we could write this routine to use the
|
156
|
+
* IJG library's memory allocator, so that the data would be freed implicitly
|
157
|
+
* at jpeg_finish_decompress() time. But it seems likely that many apps
|
158
|
+
* will prefer to have the data stick around after decompression finishes.)
|
159
|
+
*
|
160
|
+
* NOTE: if the file contains invalid ICC APP2 markers, we just silently
|
161
|
+
* return FALSE. You might want to issue an error message instead.
|
162
|
+
*/
|
163
|
+
|
164
|
+
boolean
|
165
|
+
read_icc_profile (j_decompress_ptr cinfo,
|
166
|
+
JOCTET **icc_data_ptr,
|
167
|
+
unsigned int *icc_data_len)
|
168
|
+
{
|
169
|
+
jpeg_saved_marker_ptr marker;
|
170
|
+
int num_markers = 0;
|
171
|
+
int seq_no;
|
172
|
+
JOCTET *icc_data;
|
173
|
+
unsigned int total_length;
|
174
|
+
#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */
|
175
|
+
char marker_present[MAX_SEQ_NO+1]; /* 1 if marker found */
|
176
|
+
unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */
|
177
|
+
unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */
|
178
|
+
|
179
|
+
*icc_data_ptr = NULL; /* avoid confusion if FALSE return */
|
180
|
+
*icc_data_len = 0;
|
181
|
+
|
182
|
+
/* This first pass over the saved markers discovers whether there are
|
183
|
+
* any ICC markers and verifies the consistency of the marker numbering.
|
184
|
+
*/
|
185
|
+
|
186
|
+
for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++)
|
187
|
+
marker_present[seq_no] = 0;
|
188
|
+
|
189
|
+
for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
|
190
|
+
if (marker_is_icc(marker)) {
|
191
|
+
if (num_markers == 0)
|
192
|
+
num_markers = GETJOCTET(marker->data[13]);
|
193
|
+
else if (num_markers != GETJOCTET(marker->data[13]))
|
194
|
+
return FALSE; /* inconsistent num_markers fields */
|
195
|
+
seq_no = GETJOCTET(marker->data[12]);
|
196
|
+
if (seq_no <= 0 || seq_no > num_markers)
|
197
|
+
return FALSE; /* bogus sequence number */
|
198
|
+
if (marker_present[seq_no])
|
199
|
+
return FALSE; /* duplicate sequence numbers */
|
200
|
+
marker_present[seq_no] = 1;
|
201
|
+
data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN;
|
202
|
+
}
|
203
|
+
}
|
204
|
+
|
205
|
+
if (num_markers == 0)
|
206
|
+
return FALSE;
|
207
|
+
|
208
|
+
/* Check for missing markers, count total space needed,
|
209
|
+
* compute offset of each marker's part of the data.
|
210
|
+
*/
|
211
|
+
|
212
|
+
total_length = 0;
|
213
|
+
for (seq_no = 1; seq_no <= num_markers; seq_no++) {
|
214
|
+
if (marker_present[seq_no] == 0)
|
215
|
+
return FALSE; /* missing sequence number */
|
216
|
+
data_offset[seq_no] = total_length;
|
217
|
+
total_length += data_length[seq_no];
|
218
|
+
}
|
219
|
+
|
220
|
+
if (total_length <= 0)
|
221
|
+
return FALSE; /* found only empty markers? */
|
222
|
+
|
223
|
+
/* Allocate space for assembled data */
|
224
|
+
icc_data = (JOCTET *) malloc(total_length * sizeof(JOCTET));
|
225
|
+
if (icc_data == NULL)
|
226
|
+
return FALSE; /* oops, out of memory */
|
227
|
+
|
228
|
+
/* and fill it in */
|
229
|
+
for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
|
230
|
+
if (marker_is_icc(marker)) {
|
231
|
+
JOCTET FAR *src_ptr;
|
232
|
+
JOCTET *dst_ptr;
|
233
|
+
unsigned int length;
|
234
|
+
seq_no = GETJOCTET(marker->data[12]);
|
235
|
+
dst_ptr = icc_data + data_offset[seq_no];
|
236
|
+
src_ptr = marker->data + ICC_OVERHEAD_LEN;
|
237
|
+
length = data_length[seq_no];
|
238
|
+
while (length--) {
|
239
|
+
*dst_ptr++ = *src_ptr++;
|
240
|
+
}
|
241
|
+
}
|
242
|
+
}
|
243
|
+
|
244
|
+
*icc_data_ptr = icc_data;
|
245
|
+
*icc_data_len = total_length;
|
246
|
+
|
247
|
+
return TRUE;
|
248
|
+
}
|
data/ext/axon/iccjpeg.h
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
/*
|
2
|
+
* iccprofile.h
|
3
|
+
*
|
4
|
+
* This file provides code to read and write International Color Consortium
|
5
|
+
* (ICC) device profiles embedded in JFIF JPEG image files. The ICC has
|
6
|
+
* defined a standard format for including such data in JPEG "APP2" markers.
|
7
|
+
* The code given here does not know anything about the internal structure
|
8
|
+
* of the ICC profile data; it just knows how to put the profile data into
|
9
|
+
* a JPEG file being written, or get it back out when reading.
|
10
|
+
*
|
11
|
+
* This code depends on new features added to the IJG JPEG library as of
|
12
|
+
* IJG release 6b; it will not compile or work with older IJG versions.
|
13
|
+
*
|
14
|
+
* NOTE: this code would need surgery to work on 16-bit-int machines
|
15
|
+
* with ICC profiles exceeding 64K bytes in size. See iccprofile.c
|
16
|
+
* for details.
|
17
|
+
*/
|
18
|
+
|
19
|
+
#include <stdio.h> /* needed to define "FILE", "NULL" */
|
20
|
+
#include "jpeglib.h"
|
21
|
+
|
22
|
+
|
23
|
+
/*
|
24
|
+
* This routine writes the given ICC profile data into a JPEG file.
|
25
|
+
* It *must* be called AFTER calling jpeg_start_compress() and BEFORE
|
26
|
+
* the first call to jpeg_write_scanlines().
|
27
|
+
* (This ordering ensures that the APP2 marker(s) will appear after the
|
28
|
+
* SOI and JFIF or Adobe markers, but before all else.)
|
29
|
+
*/
|
30
|
+
|
31
|
+
extern void write_icc_profile JPP((j_compress_ptr cinfo,
|
32
|
+
const JOCTET *icc_data_ptr,
|
33
|
+
unsigned int icc_data_len));
|
34
|
+
|
35
|
+
|
36
|
+
/*
|
37
|
+
* Reading a JPEG file that may contain an ICC profile requires two steps:
|
38
|
+
*
|
39
|
+
* 1. After jpeg_create_decompress() but before jpeg_read_header(),
|
40
|
+
* call setup_read_icc_profile(). This routine tells the IJG library
|
41
|
+
* to save in memory any APP2 markers it may find in the file.
|
42
|
+
*
|
43
|
+
* 2. After jpeg_read_header(), call read_icc_profile() to find out
|
44
|
+
* whether there was a profile and obtain it if so.
|
45
|
+
*/
|
46
|
+
|
47
|
+
|
48
|
+
/*
|
49
|
+
* Prepare for reading an ICC profile
|
50
|
+
*/
|
51
|
+
|
52
|
+
extern void setup_read_icc_profile JPP((j_decompress_ptr cinfo));
|
53
|
+
|
54
|
+
|
55
|
+
/*
|
56
|
+
* See if there was an ICC profile in the JPEG file being read;
|
57
|
+
* if so, reassemble and return the profile data.
|
58
|
+
*
|
59
|
+
* TRUE is returned if an ICC profile was found, FALSE if not.
|
60
|
+
* If TRUE is returned, *icc_data_ptr is set to point to the
|
61
|
+
* returned data, and *icc_data_len is set to its length.
|
62
|
+
*
|
63
|
+
* IMPORTANT: the data at **icc_data_ptr has been allocated with malloc()
|
64
|
+
* and must be freed by the caller with free() when the caller no longer
|
65
|
+
* needs it. (Alternatively, we could write this routine to use the
|
66
|
+
* IJG library's memory allocator, so that the data would be freed implicitly
|
67
|
+
* at jpeg_finish_decompress() time. But it seems likely that many apps
|
68
|
+
* will prefer to have the data stick around after decompression finishes.)
|
69
|
+
*/
|
70
|
+
|
71
|
+
extern boolean read_icc_profile JPP((j_decompress_ptr cinfo,
|
72
|
+
JOCTET **icc_data_ptr,
|
73
|
+
unsigned int *icc_data_len));
|