1 /*
2  */
3 
4 /*
5 
6     Copyright (C) 2014 Ferrero Andrea
7 
8     This program is free software: you can redistribute it and/or modify
9     it under the terms of the GNU General Public License as published by
10     the Free Software Foundation, either version 3 of the License, or
11     (at your option) any later version.
12 
13     This program is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16     GNU General Public License for more details.
17 
18     You should have received a copy of the GNU General Public License
19     along with this program. If not, see <http://www.gnu.org/licenses/>.
20 
21 
22  */
23 
24 /*
25 
26     These files are distributed with PhotoFlow - http://aferrero2707.github.io/PhotoFlow/
27 
28  */
29 
30 #include "icc_transform.hh"
31 #include "../base/processor_imp.hh"
32 
33 
34 
ICCTransformPar()35 PF::ICCTransformPar::ICCTransformPar():
36   OpParBase(),
37   out_profile( NULL ),
38   intent( INTENT_RELATIVE_COLORIMETRIC ),
39   bpc( true ),
40   adaptation_state(-1),
41   input_cs_type( cmsSigRgbData ),
42   output_cs_type( cmsSigRgbData ),
43   clip_negative(false),
44   clip_overflow(false),
45   gamut_mapping(false),
46   saturation_intent(0)
47 {
48   do_Lab = true; do_LCh = do_LSh = false;
49   set_type("icc_transform" );
50 
51   set_default_name( _("ICC transform") );
52 }
53 
54 
build(std::vector<VipsImage * > & in,int first,VipsImage * imap,VipsImage * omap,unsigned int & level)55 VipsImage* PF::ICCTransformPar::build(std::vector<VipsImage*>& in, int first,
56 				     VipsImage* imap, VipsImage* omap,
57 				     unsigned int& level)
58 {
59   if( (int)in.size() < first+1 ) {
60     return NULL;
61   }
62 
63   VipsImage* image = in[first];
64   if( !image ) {
65     return NULL;
66   }
67 
68   void *data;
69   size_t data_length;
70 
71   in_profile = PF::get_icc_profile( in[first] );
72 
73   if( !in_profile || !out_profile ) {
74     PF_REF( in[first], "ICCTransformPar::build(): input image ref for missing input or output profiles" );
75     std::cout<<"ICCTransformPar::build(): missing input or output profiles, no transform needed"<<std::endl;
76     return in[first];
77   }
78 
79 
80   bool matching = false;
81   if( in_profile && out_profile && in_profile->equals_to(out_profile) ) {
82     matching = true;
83   }
84 
85   if( matching ) {
86     PF_REF( in[first], "ICCTransformPar::build(): input image ref for equal input and output profiles" );
87     //std::cout<<"ICCTransformPar::build(): matching input and output profiles, no transform needed"<<std::endl;
88     return in[first];
89   }
90 
91   if( gamut_mapping )
92     out_profile->init_gamut_mapping();
93 
94 
95   //std::cout<<"ICCTransformPar::build(): image="<<in[0]<<" data="<<data<<" data_length="<<data_length<<std::endl;
96 
97   bool in_changed = false;
98   if( in_profile && in_profile->get_profile() ) {
99     char tstr[1024];
100     cmsGetProfileInfoASCII(in_profile->get_profile(), cmsInfoDescription, "en", "US", tstr, 1024);
101 #ifndef NDEBUG
102     std::cout<<"icc_transform: embedded profile: "<<in_profile<<std::endl;
103     std::cout<<"icc_transform: embedded profile name: "<<tstr<<std::endl;
104 #endif
105 
106     if( in_profile_name != tstr ) {
107       in_changed = true;
108     }
109 
110     input_cs_type = cmsGetColorSpace( in_profile->get_profile() );
111   }
112 
113 #ifndef NDEBUG
114   if( out_profile )
115     std::cout<<"icc_transform: out_profile="<<out_profile<<" ("<<out_profile->get_profile()<<")"<<std::endl;
116 #endif
117 
118   if( in_profile && out_profile && out_profile->get_profile() ) {
119     transform.init( in_profile, out_profile, in[0]->BandFmt, intent, get_bpc(), adaptation_state );
120   }
121 
122   if( out_profile && out_profile->get_profile() ) {
123 #ifndef NDEBUG
124     std::cout<<"icc_transform: output profile: "<<out_profile<<std::endl;
125     char tstr[1024];
126     cmsGetProfileInfoASCII(out_profile->get_profile(), cmsInfoDescription, "en", "US", tstr, 1024);
127     std::cout<<"icc_transform: output profile: "<<tstr<<std::endl;
128 #endif
129     output_cs_type = cmsGetColorSpace( out_profile->get_profile() );
130     switch( output_cs_type ) {
131     case cmsSigGrayData:
132       grayscale_image( get_xsize(), get_ysize() );
133       break;
134     case cmsSigRgbData:
135       rgb_image( get_xsize(), get_ysize() );
136       break;
137     case cmsSigLabData:
138       lab_image( get_xsize(), get_ysize() );
139       break;
140     case cmsSigCmykData:
141       cmyk_image( get_xsize(), get_ysize() );
142       break;
143     default:
144       break;
145     }
146   }
147 
148   //if( in_profile )  cmsCloseProfile( in_profile );
149 
150   VipsImage* out = OpParBase::build( in, first, NULL, NULL, level );
151 
152   if( out && out_profile ) {
153     PF::set_icc_profile( out, out_profile );
154   }
155 
156   return out;
157 }
158 
159 
160 using namespace PF;
161 
162 
163 template < OP_TEMPLATE_DEF >
164 class ICCTransformProc
165 {
166 public:
render(VipsRegion ** ireg,int n,int in_first,VipsRegion * imap,VipsRegion * omap,VipsRegion * oreg,OpParBase * par)167   void render(VipsRegion** ireg, int n, int in_first,
168               VipsRegion* imap, VipsRegion* omap,
169               VipsRegion* oreg, OpParBase* par)
170   {
171     ICCTransformPar* opar = dynamic_cast<ICCTransformPar*>(par);
172     if( !opar ) return;
173     VipsRect *r = &oreg->valid;
174     int line_size = r->width * oreg->im->Bands; //layer->in_all[0]->Bands;
175     int width = r->width;
176     int height = r->height;
177 
178     T* p;
179     T* pin;
180     T* pout;
181     int x, y;
182 
183     for( y = 0; y < height; y++ ) {
184       p = (T*)VIPS_REGION_ADDR( ireg[in_first], r->left, r->top + y );
185       pout = (T*)VIPS_REGION_ADDR( oreg, r->left, r->top + y );
186 
187       pin = p;
188       if(opar->get_transform().valid())
189         //cmsDoTransform( opar->get_transform(), pin, pout, width );
190         opar->get_transform().apply(pin,pout,width);
191       else
192         memcpy( pout, pin, sizeof(T)*line_size );
193     }
194   }
195 };
196 
197 
198 
199 
200 template < OP_TEMPLATE_DEF_TYPE_SPEC >
201 class ICCTransformProc< OP_TEMPLATE_IMP_TYPE_SPEC(float) >
202 {
203 public:
render(VipsRegion ** ireg,int n,int in_first,VipsRegion * imap,VipsRegion * omap,VipsRegion * oreg,OpParBase * par)204   void render(VipsRegion** ireg, int n, int in_first,
205               VipsRegion* imap, VipsRegion* omap,
206               VipsRegion* oreg, OpParBase* par)
207   {
208     ICCTransformPar* opar = dynamic_cast<ICCTransformPar*>(par);
209     if( !opar ) return;
210     VipsRect *r = &oreg->valid;
211     int line_size_in = ireg[in_first]->valid.width * ireg[in_first]->im->Bands; //layer->in_all[0]->Bands;
212     int line_size_out = r->width * oreg->im->Bands; //layer->in_all[0]->Bands;
213     int line_size_max = (line_size_in > line_size_out) ? line_size_in : line_size_out;
214     int width = r->width;
215     int height = r->height;
216 
217     float* p;
218     //float* pin;
219     float* pout;
220     int x, y;
221 
222     float* line = NULL;
223     if( opar->get_input_cs_type() == cmsSigLabData ||
224         opar->get_input_cs_type() == cmsSigCmykData ||
225         opar->get_gamut_mapping() ) {
226       line = new float[line_size_max];
227     }
228 
229     for( y = 0; y < height; y++ ) {
230       //std::cout<<"icc_transform: ti="<<ti<<" y="<<y<<"  corner="<<r->left<<","<<r->top<<std::endl;
231       p = (float*)VIPS_REGION_ADDR( ireg[in_first], r->left, r->top + y );
232       pout = (float*)VIPS_REGION_ADDR( oreg, r->left, r->top + y );
233 
234       if(opar->get_transform().valid()) {
235         if( opar->get_input_cs_type() == cmsSigLabData ) {
236           for( x = 0; x < line_size_in; x+= 3 ) {
237             line[x] = (cmsFloat32Number) (p[x] * 100.0);
238             line[x+1] = (cmsFloat32Number) (p[x+1]*256.0f - 128.0f);
239             line[x+2] = (cmsFloat32Number) (p[x+2]*256.0f - 128.0f);
240             if( false && r->left==0 && r->top==0 && x==0 && y==0 ) {
241               std::cout<<"ICCTransform::render(): line="<<line[x]<<" "<<line[x+1]<<" "<<line[x+2]<<std::endl;
242             }
243           }
244           //cmsDoTransform( opar->get_transform(), line, pout, width );
245           opar->get_transform().apply(line,pout,width);
246           if( false && r->left==0 && r->top==0 && y==0 ) {
247             std::cout<<"ICCTransform::render(Lab): pout="<<pout[0]<<" "<<pout[1]<<" "<<pout[2]<<std::endl;
248           }
249         } else if( opar->get_input_cs_type() == cmsSigCmykData ) {
250           for( x = 0; x < line_size_in; x+= 4 ) {
251             line[x] = (cmsFloat32Number) (p[x] * 100.0);
252             line[x+1] = (cmsFloat32Number) (p[x+1] * 100.0);
253             line[x+2] = (cmsFloat32Number) (p[x+2] * 100.0);
254             line[x+3] = (cmsFloat32Number) (p[x+3] * 100.0);
255             if( false && r->left==0 && r->top==0 && x==0 && y==0 ) {
256               std::cout<<"ICCTransform::render(CMYK in): line="<<line[x]<<" "<<line[x+1]<<" "<<line[x+2]<<" "<<line[x+3]<<std::endl;
257             }
258           }
259           //cmsDoTransform( opar->get_transform(), line, pout, width );
260           opar->get_transform().apply(line,pout,width);
261           if( false && r->left==0 && r->top==0 && y==0 ) {
262             std::cout<<"ICCTransform::render(CMYK in): pout="<<pout[0]<<" "<<pout[1]<<" "<<pout[2]<<std::endl;
263           }
264         } else {
265           if( opar->get_in_profile() && opar->get_in_profile()->is_rgb() &&
266               opar->get_out_profile() && opar->get_out_profile()->is_rgb() &&
267               opar->get_gamut_mapping() ) {
268             ICCProfile* inprof = opar->get_in_profile();
269             ICCProfile* outprof = opar->get_out_profile();
270             float** gbound = outprof->get_gamut_boundary();
271             float* gLid = outprof->get_gamut_Lid_Cmax();
272             for( x = 0; x < line_size_out; x+= 3 ) {
273               line[x] = p[x]; line[x+1] = p[x+1]; line[x+2] = p[x+2];
274               inprof->gamut_mapping(line[x], line[x+1], line[x+2], gbound, gLid, opar->get_saturation_intent());
275             }
276             opar->get_transform().apply(line,pout,width);
277           } else {
278             //cmsDoTransform( opar->get_transform(), p, pout, width );
279             opar->get_transform().apply(p,pout,width);
280           }
281           if( false && r->left==0 && r->top==0 && y==0 ) {
282             std::cout<<"ICCTransform::render(): pout="<<pout[0]<<" "<<pout[1]<<" "<<pout[2]<<std::endl;
283           }
284         }
285         if( opar->get_output_cs_type() == cmsSigLabData ) {
286           for( x = 0; x < line_size_out; x+= 3 ) {
287 
288             if( opar->get_LCh_format() || opar->get_LSh_format() ) {
289               PF::Lab2LCH( &(pout[x]), &(pout[x]), 1 );
290               if( opar->get_LSh_format() ) {
291                 float den = std::sqrt(pout[x]*pout[x] + pout[x+1]*pout[x+1]);
292                 if( den > 1.0e-10 ) pout[x+1] /= den;
293                 else pout[x+1] = 0;
294               } else {
295                 pout[x+1] /= 256.0f;
296               }
297               pout[x] = (cmsFloat32Number) (pout[x] / 100.0);
298               //std::cout<<"H: "<<pout[x+2]<<std::endl;
299               pout[x+2] /= (M_PI*2);
300             } else {
301               pout[x] = (cmsFloat32Number) (pout[x] / 100.0);
302               pout[x+1] = (cmsFloat32Number) ((pout[x+1] + 128.0f) / 256.0f);
303               pout[x+2] = (cmsFloat32Number) ((pout[x+2] + 128.0f) / 256.0f);
304             }
305 
306             //if( r->left==0 && r->top==0 && x==0 && y==0 ) {
307             //  std::cout<<"Convert2LabProc::render(): pout="<<pout[x]<<" "<<pout[x+1]<<" "<<pout[x+2]<<std::endl;
308             //}
309           }
310         } else if( opar->get_output_cs_type() == cmsSigCmykData ) {
311           for( x = 0; x < line_size_out; x+= 4 ) {
312             if( false && r->left==0 && r->top==0 && x==0 && y==0 ) {
313               std::cout<<"ICCTransform::render(CMYK out): pout="<<pout[x]<<" "<<pout[x+1]<<" "<<pout[x+2]<<" "<<pout[x+3]<<std::endl;
314             }
315             pout[x] = (cmsFloat32Number) (pout[x] / 100.0);
316             pout[x+1] = (cmsFloat32Number) (pout[x+1] / 100.0);
317             pout[x+2] = (cmsFloat32Number) (pout[x+2] / 100.0);
318             pout[x+3] = (cmsFloat32Number) (pout[x+3] / 100.0);
319             if( false && r->left==0 && r->top==0 && x==0 && y==0 ) {
320               std::cout<<"ICCTransform::render(CMYK out): pout="<<pout[x]<<" "<<pout[x+1]<<" "<<pout[x+2]<<" "<<pout[x+3]<<std::endl;
321             }
322           }
323         }
324       } else {
325         memcpy( pout, p, sizeof(float)*line_size_in );
326       }
327       //std::cout<<"opar->get_out_profile(): "<<opar->get_out_profile()
328       //    <<"  opar->get_out_profile()->is_rgb(): "<<opar->get_out_profile()->is_rgb()
329       //    <<"  opar->get_gamut_mapping(): "<<opar->get_gamut_mapping()<<std::endl;
330       if( opar->get_clip_negative() || opar->get_clip_overflow() ) {
331         for( x = 0; x < line_size_out; x+= 1 ) {
332           if( opar->get_clip_negative() ) pout[x] = MAX( pout[x], 0.f );
333           if( opar->get_clip_overflow() ) pout[x] = MIN( pout[x], 1.f );
334         }
335       }
336     }
337 
338     if( line ) {
339       delete( line );
340     }
341   }
342 };
343 
344 
345 
new_icc_transform()346 PF::ProcessorBase* PF::new_icc_transform()
347 {
348   return new PF::Processor<PF::ICCTransformPar,ICCTransformProc>();
349 }
350