55// Input can be any image type, output will be png
66//
77// CPSC 4040 | Owen Book | October 2022
8-
8+ # include " gloiioFuncs.h "
99#include < OpenImageIO/imageio.h>
1010#include < iostream>
1111#include < string>
12- #include < cmath >
12+ #include < exception >
1313
1414#ifdef __APPLE__
1515# pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -25,266 +25,13 @@ OIIO_NAMESPACE_USING
2525// default window dimensions
2626#define DEFAULT_WIDTH 600
2727#define DEFAULT_HEIGHT 600
28- // assumed maximum value of pixel data - change if modifying for int, float, etc.
29- #define MAX_VAL 255
30- // preprocess macros
31- #define maximum (x,y,z ) ((x) > (y)? ((x) > (z)? (x) : (z)) : ((y) > (z)? (y) : (z)))
32- #define minimum (x,y,z ) ((x) < (y)? ((x) < (z)? (x) : (z)) : ((y) < (z)? (y) : (z)))
33-
34- // utility struct that holds RGBA 4-tuple of chars
35- typedef struct pixel_rgba_t {
36- unsigned char red, green, blue, alpha;
37- } pxRGBA;
38- // utility struct that holds RGB 3-tuple of chars
39- typedef struct pixel_rgb_t {
40- unsigned char red, green, blue;
41- } pxRGB;
42- // utility struct that holds HSV 3-tuple of doubles
43- typedef struct pixel_hsv_t {
44- double hue, saturation, value;
45- } pxHSV;
46-
47- // struct to tie image spec and pixels together
48- typedef struct image_rgba_uint8_t {
49- ImageSpec spec;
50- pxRGBA* pixels;
51- } ImageRGBA;
52-
5328
5429/* * CONTROL & GLOBAL STATICS **/
5530// list of read images for multi-image viewing mode
5631static vector<ImageRGBA> imageCache;
5732// current index in vector to attempt to load (probably won't be touched in this program)
5833static int cacheIndex = 0 ;
5934
60- /* * UTILITY FUNCTIONS **/
61- /* clean up memory of unneeded ImageRGBA
62- don't forget to remove it from imageCache first! */
63- void discardImage (ImageRGBA image) {
64- delete image.pixels ;
65- }
66-
67- /* functions to quickly create pxRGB, pxRGBA, pxHSV,... from arbitrary values*/
68- pxRGB linkRGB (unsigned char r, unsigned char g, unsigned char b) {
69- pxRGB px;
70- px.red = r;
71- px.green = g;
72- px.blue = b;
73- return px;
74- }
75- pxRGBA linkRGBA (unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
76- pxRGBA px;
77- px.red = r;
78- px.green = g;
79- px.blue = b;
80- px.alpha = a;
81- return px;
82- }
83- pxHSV linkHSV (double h, double s, double v) {
84- pxHSV px;
85- px.hue = h;
86- px.saturation = s;
87- px.value = v;
88- return px;
89- }
90-
91- /* convert RGB values into HSV values*/
92- pxHSV RGBtoHSV (pxRGB rgb) {
93- pxHSV hsv;
94- double huer, hueg, hueb, max, min, delta;
95- /* convert from 0-255 to 0~1 */
96- huer = rgb.red / 255.0 ;
97- hueg = rgb.green / 255.0 ;
98- hueb = rgb.blue / 255.0 ;
99-
100- max = maximum (huer, hueg, hueb);
101- min = minimum (huer, hueg, hueb);
102- hsv.value = max;
103-
104- if (max==0 ) {
105- hsv.hue = 0 ;
106- hsv.value = 0 ;
107- }
108- else {
109- delta = max-min;
110- hsv.saturation = delta/max; // https://youtu.be/QYbG1AMSdiY
111- if (delta == 0 ) {
112- hsv.hue = 0 ;
113- }
114- else {
115- /* determine hue */
116- if (huer == max) {
117- hsv.hue = (hueg - hueb) / delta;
118- }
119- else if (hueg == max) {
120- hsv.hue = 2.0 + (hueb - huer) / delta;
121- }
122- else {
123- hsv.hue = 4.0 + (huer - hueg) / delta;
124- }
125- hsv.hue *= 60.0 ;
126- if (hsv.hue < 0 ) {
127- hsv.hue += 360.0 ;
128- }
129- }
130- }
131-
132- return hsv;
133- }
134-
135- /* shorthand function that double-converts RGBA to HSV, ignoring alpha channel */
136- pxHSV RGBAtoHSV (pxRGBA rgba) {
137- return RGBtoHSV (linkRGB (rgba.red , rgba.green , rgba.blue ));
138- }
139-
140- /* * PROCESSING FUNCTIONS **/
141- /* reads in image from specified filename as RGBA 8bit pixmap
142- puts a new ImageRGBA in the imageCache vector if successful */
143- void readImage (string filename) {
144- std::unique_ptr<ImageInput> in = ImageInput::open (filename);
145- if (!in) {
146- std::cerr << " could not open input file! " << geterror ();
147- // cancel routine
148- return ;
149- }
150-
151- // store spec and get metadata from it
152- ImageRGBA image;
153- image.spec = in->spec ();
154- int xr = image.spec .width ;
155- int yr = image.spec .height ;
156- int channels = image.spec .nchannels ;
157-
158- // declare temp memory to read raw image data
159- unsigned char temp_px[xr*yr*channels];
160-
161- // read the image into the temp_px from the input file, flipping it upside down using negative y-stride,
162- // since OpenGL pixmaps have the bottom scanline first, and
163- // oiio expects the top scanline first in the image file.
164- int scanlinesize = xr * channels * sizeof (unsigned char );
165- if (!in->read_image (TypeDesc::UINT8, temp_px+((yr-1 )*scanlinesize), AutoStride, -scanlinesize)){
166- cerr << " Could not read image from " << filename << " , error = " << geterror () << endl;
167- // cancel routine
168- return ;
169- }
170-
171- // allocate data for converted pxRGBA version
172- image.pixels = new pxRGBA[xr*yr];
173- // convert and store raw image data as pxRGBAs
174- for (int i=0 ; i<yr*xr; i++) {
175- switch (channels) {
176- // this could be more cleanly programmed but the less convoluted the better
177- case 1 : // grayscale
178- image.pixels [i].red = temp_px[i];
179- image.pixels [i].green = temp_px[i];
180- image.pixels [i].blue = temp_px[i];
181- image.pixels [i].alpha = 255 ;
182- break ;
183- case 2 : // weird grayscale with alpha (just covering my ass here)
184- image.pixels [i].red = temp_px[2 *i];
185- image.pixels [i].green = temp_px[2 *i];
186- image.pixels [i].blue = temp_px[2 *i];
187- image.pixels [i].alpha = temp_px[(2 *i)+1 ];
188- break ;
189- case 3 : // RGB
190- image.pixels [i].red = temp_px[(3 *i)];
191- image.pixels [i].green = temp_px[(3 *i)+1 ];
192- image.pixels [i].blue = temp_px[(3 *i)+2 ];
193- image.pixels [i].alpha = 255 ;
194- break ;
195- case 4 : // RGBA
196- image.pixels [i].red = temp_px[(4 *i)];
197- image.pixels [i].green = temp_px[(4 *i)+1 ];
198- image.pixels [i].blue = temp_px[(4 *i)+2 ];
199- image.pixels [i].alpha = temp_px[(4 *i)+3 ];
200- break ;
201- default : // something weird, just do nothing
202- break ;
203- }
204- }
205-
206- // close input
207- in->close ();
208- // store struct in cache and switch to display this image
209- imageCache.push_back (image);
210- cacheIndex = imageCache.size ()-1 ;
211- }
212-
213- /* writes currently dixplayed pixmap (as RGBA) to a file
214- (mostly the same as sample code) */
215- void writeImage (string filename){
216- int xr = imageCache[cacheIndex].spec .width ;
217- int yr = imageCache[cacheIndex].spec .height ;
218- int channels = 4 ;
219- // temporary 1d array to stick all the pxRGBA data into
220- // write_image does not like my structs >:(
221- unsigned char temp_px[xr*yr*channels];
222- for (int i=0 ; i<xr*yr; i++) {
223- temp_px[(4 *i)] = imageCache[cacheIndex].pixels [i].red ;
224- temp_px[(4 *i)+1 ] = imageCache[cacheIndex].pixels [i].green ;
225- temp_px[(4 *i)+2 ] = imageCache[cacheIndex].pixels [i].blue ;
226- temp_px[(4 *i)+3 ] = imageCache[cacheIndex].pixels [i].alpha ;
227- }
228-
229- // create the oiio file handler for the image
230- std::unique_ptr<ImageOutput> outfile = ImageOutput::create (filename);
231- if (!outfile){
232- cerr << " could not create output file! " << geterror () << endl;
233- // cancel routine
234- return ;
235- }
236-
237- // open a file for writing the image. The file header will indicate an image of
238- // width xr, height yr, and 4 channels per pixel (RGBA). All channels will be of
239- // type unsigned char
240- ImageSpec spec (xr, yr, channels, TypeDesc::UINT8);
241- if (!outfile->open (filename, spec)){
242- cerr << " could not open output file! " << geterror () << endl;
243- return ;
244- }
245-
246- // write the image to the file. All channel values in the pixmap are taken to be
247- // unsigned chars. flip using stride to undo same effort in readImage
248- int scanlinesize = xr * channels * sizeof (unsigned char );
249- if (!outfile->write_image (TypeDesc::UINT8, temp_px+((yr-1 )*scanlinesize), AutoStride, -scanlinesize)){
250- cerr << " could not write to file! " << geterror () << endl;
251- return ;
252- }
253- else cout << " successfully written image to " << filename << endl;
254-
255- // close the image file after the image is written
256- if (!outfile->close ()){
257- cerr << " could not close output file! " << geterror () << endl;
258- return ;
259- }
260- }
261-
262- /* chroma-key image to create alphamask using HSV differences
263- "fuzz" arguments determine max difference for each value to keep */
264- void chromaKey (pxHSV target, double huefuzz, double satfuzz, double valfuzz) {
265- ImageRGBA image = imageCache[cacheIndex];
266- int xr = image.spec .width ;
267- int yr = image.spec .height ;
268- for (int i=0 ; i<xr*yr; i++) {
269- pxHSV comp = RGBAtoHSV (image.pixels [i]);
270- // cut out based on absolute distance from target values
271- // if all three are in range, hide it!
272- double huediff = fabs (comp.hue - target.hue );
273- double satdiff = fabs (comp.saturation - target.saturation );
274- double valdiff = fabs (comp.value - target.value );
275- if (huediff < huefuzz && satdiff < satfuzz && valdiff < valfuzz) {
276- // some smoothing function for pixels way less close
277- double maskalpha = (0.2 *(huediff/huefuzz) + 0.4 *(satdiff/satfuzz) + 0.4 *(valdiff/valfuzz)) - 0.2 ;
278- // clamp to 0.0~1.0; if the result is like 0.012 don't bother with it
279- maskalpha = (maskalpha < 0.02 ? 0.0 : maskalpha);
280- maskalpha = (maskalpha > 1.0 ? 1.0 : maskalpha);
281-
282- // cout << "masking pixel: " << comp.hue << " " << comp.saturation << " " << comp.value << " to alpha value " << maskalpha << endl;
283- image.pixels [i].alpha = (unsigned char )255 *maskalpha;
284- }
285- }
286- }
287-
28835/* * OPENGL FUNCTIONS **/
28936/* main display callback: displays the image of current index from imageCache.
29037if no images are loaded, only draws a black background */
@@ -381,10 +128,10 @@ int main(int argc, char* argv[]){
381128 }
382129
383130 // do the things
384- readImage (instr);
385- chromaKey (target,fuzz.hue ,fuzz.saturation ,fuzz.value );
131+ imageCache. push_back ( readImage (instr) );
132+ chromaKey (imageCache[ 0 ], target,fuzz.hue ,fuzz.saturation ,fuzz.value );
386133 cout << " writing alphamask to file " << outstr << endl;
387- writeImage (outstr);
134+ writeImage (outstr, imageCache[ 0 ] );
388135 cout << " press ESC or Q to close" << endl;
389136 }
390137 else {
0 commit comments