public
Description: Automatic approximation of typical Photoshop actions
Homepage: http://rcrowley.org/2007/11/08/introducing-curvr/
Clone URL: git://github.com/rcrowley/curvr.git
curvr / curvr.cc
100644 221 lines (191 sloc) 5.877 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/*
* curvr
* Richard Crowley <r@rcrowley.org>
*/
 
// GraphicsMagick
#include "Magick++.h"
 
// Exiv2
#include "image.hpp"
#include "exif.hpp"
#include "iptc.hpp"
 
// Regular ass C++
#include <stdio.h>
#include <string>
#include <map>
 
/* Color maps
* Loosely based on Magick::PixelPacket structure.
* Notice that neither of these are #define'd anywhere in this file.
* You have to pass the -DQUANTUM_DEPTH_? flag to g++, but the Makefile
* does this for you using the depth.cc program.
*/
#ifdef QUANTUM_DEPTH_8
typedef unsigned char pixel_t;
typedef short cmap_channel_t;
typedef unsigned char range_t;
#elif QUANTUM_DEPTH_16
typedef unsigned short pixel_t;
typedef int cmap_channel_t;
typedef unsigned short range_t;
#endif
typedef struct _cmap_t {
cmap_channel_t red;
cmap_channel_t green;
cmap_channel_t blue;
} cmap_t;
 
/* Build a color map, returning a pointer to the map
* Taking edge and middle, this creates a bowed shape across the entire
* color range, being == edge at both ends and approaching middle in
* the middle.
*/
cmap_t * build_cmap(int edge, int middle) {
range_t range = ~0 >> 2;
cmap_t * cmap = new cmap_t[range];
 
// Map each color level in this quantum size
for (range_t i = 0; i < range; ++i) {
double halfway = (double)range / 2;
double percent;
if (i > halfway) {
percent = 1.0 - ((double)i - (double)halfway) / halfway;
} else {
percent = (double)i / halfway;
}
 
// Could round here instead of truncating
cmap_channel_t offset = (cmap_channel_t)((double)edge + percent *
(double)middle);
 
cmap[i].red = offset;
cmap[i].green = offset;
cmap[i].blue = offset;
}
return cmap;
}
 
/* Apply a color map to an image
* Uses the assumption that the red channel of an image is a good
* indicator of color value, so the map is of red values to a modifier
* structure. Thus far, this assumption has been quite good.
*/
void apply_cmap(Magick::Image & img, cmap_t * cmap) {
unsigned int columns = img.columns(), rows = img.rows();
range_t range = ~0;
Magick::Pixels view(img);
 
// Map each pixel
Magick::PixelPacket * px = view.get(0, 0, columns, rows);
for (unsigned int c = 0; c < columns; ++c) {
for (unsigned int r = 0; r < rows; ++r) {
pixel_t red = px->red, green = px->green, blue = px->blue;
 
/* Each color has bounds checking to prevent a negative offset
* from wrapping it down through zero and a positive offset
* from wrapping it over the top. Empirically, favoring "on"
* for red seems to prevent the "aqua distortion" seen at
* http://flickr.com/photos/rcrowley/2233623053/.
*/
 
/* WTF?
*/
 
if (0 > cmap[red].red && red < -cmap[red].red) {
// px->red = 0;
} else if (0 < cmap[red].red && range - red < cmap[red].red) {
// px->red = range;
} else {
px->red += cmap[red].red;
}
 
if (0 > cmap[red].green && green < -cmap[red].green) {
// px->green = 0;
} else if (0 < cmap[red].green && range - green < cmap[red].green) {
// px->green = range;
} else {
px->green += cmap[red].green;
}
 
if (0 > cmap[red].blue && blue < -cmap[red].blue) {
// px->blue = 0;
} else if (0 < cmap[red].blue && range - blue < cmap[red].blue) {
// px->blue = range;
} else {
px->blue += cmap[red].blue;
}
 
++px;
}
}
 
view.sync();
}
 
/* Curve, bigcurve and anticurve processes
* Normalize the image and then color map it to be slightly darker.
* TODO: Make these parameterizable
*/
int _curve(Magick::Image & img, const char * process, int edge, int middle) {
 
// Tuck white and black points
printf("[%s] Normalizing\n", process);
img.normalize();
 
// A little bit darker now
printf("[%s] Applying curve over [%d, %d)\n", process, edge, middle);
cmap_t * cmap = build_cmap(edge, middle);
apply_cmap(img, cmap);
delete [] cmap;
 
return 0;
}
int curve(Magick::Image & img) {
return _curve(img, "curve", 0, -50);
}
int bigcurve(Magick::Image & img) {
return _curve(img, "bigcurve", 0, -80);
}
int anticurve(Magick::Image & img) {
return _curve(img, "anticurve", 0, 50);
}
 
int main(int argc, char * * argv) {
 
// Gotta have two arguments, an input file and an output file
std::string input = "", process = "curve", output = "";
if (4 == argc) {
input = *(argv + 1);
process = *(argv + 2);
output = *(argv + 3);
} else if (3 == argc) {
printf("[status] No process found, using default (%s)\n",
process.c_str());
input = *(argv + 1);
output = *(argv + 2);
} else {
printf("Usage: %s <input> [<process>] <output>\n", *argv);
printf("\tcurve\n\tbigcurve\n\tanticurve\n");
return 1;
}
printf("[status] Input: %s, output: %s\n", input.c_str(), output.c_str());
 
// Open the victim
Magick::Image img;
try {
img = Magick::Image(input);
} catch (Magick::Exception & e) {
printf("[error] %s: %s\n", input.c_str(), e.what());
return 2;
}
 
// Pull out EXIF and IPTC data for later
Exiv2::ExifData exif;
Exiv2::IptcData iptc;
bool meta_found = true;
try {
Exiv2::Image::AutoPtr meta_r = Exiv2::ImageFactory::open(input);
meta_r->readMetadata();
exif = meta_r->exifData();
iptc = meta_r->iptcData();
} catch (Exiv2::Error &) {
meta_found = false;
}
 
// Run the transform
std::map<std::string, int (*)(Magick::Image &)> processes;
processes["curve"] = &curve;
processes["bigcurve"] = &bigcurve;
processes["anticurve"] = &anticurve;
printf("[status] Process: %s\n", process.c_str());
(*processes[process])(img);
 
// Write back to the output file
img.compressType(Magick::NoCompression);
img.write(output);
 
// It's later, put back the EXIF and IPTC data
try {
Exiv2::Image::AutoPtr meta_w = Exiv2::ImageFactory::open(output);
meta_w->setExifData(exif);
meta_w->setIptcData(iptc);
meta_w->writeMetadata();
} catch (Exiv2::Error & e) {
if (meta_found) printf("[error] exiv2: %s", e.what());
}
 
return 0;
}