1 /*
2     IIP JTL Command Handler Class Member Function
3 
4     Copyright (C) 2006-2019 Ruven Pillay.
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 3 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software Foundation,
18     Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20 
21 #include "Task.h"
22 #include "Transforms.h"
23 
24 #include <cmath>
25 #include <sstream>
26 
27 using namespace std;
28 
29 
send(Session * session,int resolution,int tile)30 void JTL::send( Session* session, int resolution, int tile ){
31 
32   Timer function_timer;
33 
34   if( session->loglevel >= 3 ) (*session->logfile) << "JTL handler reached" << endl;
35 
36 
37   // Make sure we have set our image
38   this->session = session;
39   checkImage();
40 
41 
42   // Time this command
43   if( session->loglevel >= 2 ) command_timer.start();
44 
45 
46   // If we have requested a rotation, remap the tile index to rotated coordinates
47   if( (int)((session->view)->getRotation()) % 360 == 90 ){
48 
49   }
50   else if( (int)((session->view)->getRotation()) % 360 == 270 ){
51 
52   }
53   else if( (int)((session->view)->getRotation()) % 360 == 180 ){
54     int num_res = (*session->image)->getNumResolutions();
55     unsigned int im_width = (*session->image)->image_widths[num_res-resolution-1];
56     unsigned int im_height = (*session->image)->image_heights[num_res-resolution-1];
57     unsigned int tw = (*session->image)->getTileWidth();
58     //    unsigned int th = (*session->image)->getTileHeight();
59     int ntiles = (int) ceil( (double)im_width/tw ) * (int) ceil( (double)im_height/tw );
60     tile = ntiles - tile - 1;
61   }
62 
63 
64   // Sanity check
65   if( (resolution<0) || (tile<0) ){
66     ostringstream error;
67     error << "JTL :: Invalid resolution/tile number: " << resolution << "," << tile;
68     throw error.str();
69   }
70 
71 
72   TileManager tilemanager( session->tileCache, *session->image, session->watermark, session->jpeg, session->logfile, session->loglevel );
73 
74 
75   // First calculate histogram if we have asked for either binarization,
76   //  histogram equalization or contrast stretching
77   if( session->view->requireHistogram() && (*session->image)->histogram.size()==0 ){
78 
79     if( session->loglevel >= 4 ) function_timer.start();
80 
81     // Retrieve an uncompressed version of our smallest tile
82     // which should be sufficient for calculating the histogram
83     RawTile thumbnail = tilemanager.getTile( 0, 0, 0, session->view->yangle, session->view->getLayers(), UNCOMPRESSED );
84 
85     // Calculate histogram
86     (*session->image)->histogram =
87       session->processor->histogram( thumbnail, (*session->image)->max, (*session->image)->min );
88 
89     if( session->loglevel >= 4 ){
90       *(session->logfile) << "JTL :: Calculated histogram in "
91 			  << function_timer.getTime() << " microseconds" << endl;
92     }
93 
94     // Insert the histogram into our image cache
95     const string key = (*session->image)->getImagePath();
96     imageCacheMapType::iterator i = session->imageCache->find(key);
97     if( i != session->imageCache->end() ) (i->second).histogram = (*session->image)->histogram;
98   }
99 
100 
101 
102   CompressionType ct;
103 
104   // Request uncompressed tile if raw pixel data is required for processing
105   if( (*session->image)->getNumBitsPerPixel() > 8 || (*session->image)->getColourSpace() == CIELAB
106       || (*session->image)->getNumChannels() == 2 || (*session->image)->getNumChannels() > 3
107       || ( session->view->colourspace==GREYSCALE && (*session->image)->getNumChannels()==3 &&
108 	   (*session->image)->getNumBitsPerPixel()==8 )
109       || session->view->floatProcessing() || session->view->equalization
110       || session->view->getRotation() != 0.0 || session->view->flip != 0
111       ) ct = UNCOMPRESSED;
112   else ct = JPEG;
113 
114 
115   // Embed ICC profile
116   if( session->view->embedICC() && ((*session->image)->getMetadata("icc").size()>0) ){
117     if( session->loglevel >= 3 ){
118       *(session->logfile) << "JTL :: Embedding ICC profile with size "
119 			  << (*session->image)->getMetadata("icc").size() << " bytes" << endl;
120     }
121     session->jpeg->setICCProfile( (*session->image)->getMetadata("icc") );
122   }
123 
124 
125   RawTile rawtile = tilemanager.getTile( resolution, tile, session->view->xangle,
126 					 session->view->yangle, session->view->getLayers(), ct );
127 
128 
129   int len = rawtile.dataLength;
130 
131   if( session->loglevel >= 2 ){
132     *(session->logfile) << "JTL :: Tile size: " << rawtile.width << " x " << rawtile.height << endl
133 			<< "JTL :: Channels per sample: " << rawtile.channels << endl
134 			<< "JTL :: Bits per channel: " << rawtile.bpc << endl
135 			<< "JTL :: Data size is " << len << " bytes" << endl;
136   }
137 
138 
139   // Convert CIELAB to sRGB
140   if( (*session->image)->getColourSpace() == CIELAB ){
141 
142     if( session->loglevel >= 4 ){
143       *(session->logfile) << "JTL :: Converting from CIELAB->sRGB";
144       function_timer.start();
145     }
146     session->processor->LAB2sRGB( rawtile );
147     if( session->loglevel >= 4 ){
148       *(session->logfile) << " in " << function_timer.getTime() << " microseconds" << endl;
149     }
150   }
151 
152 
153   // Only use our float pipeline if necessary
154   if( rawtile.bpc > 8 || session->view->floatProcessing() ){
155 
156     // Make a copy of our max and min as we may change these
157     vector <float> min = (*session->image)->min;
158     vector <float> max = (*session->image)->max;
159 
160     // Change our image max and min if we have asked for a contrast stretch
161     if( session->view->contrast == -1 ){
162 
163       // Find first non-zero bin in histogram
164       unsigned int n0 = 0;
165       while( (*session->image)->histogram[n0] == 0 ) ++n0;
166 
167       // Find highest bin
168       unsigned int n1 = (*session->image)->histogram.size() - 1;
169       while( (*session->image)->histogram[n1] == 0 ) --n1;
170 
171       // Histogram has been calculated using 8 bits, so scale up to native bit depth
172       if( rawtile.bpc > 8 && rawtile.sampleType == FIXEDPOINT ){
173 	n0 = n0 << (rawtile.bpc-8);
174 	n1 = n1 << (rawtile.bpc-8);
175       }
176 
177       min.assign( rawtile.bpc, (float)n0 );
178       max.assign( rawtile.bpc, (float)n1 );
179 
180       // Reset our contrast
181       session->view->contrast = 1.0;
182 
183       if( session->loglevel >= 5 ){
184 	*(session->logfile) << "JTL :: Applying contrast stretch for image range of "
185 			    << n0 << " - " << n1 << endl;
186       }
187     }
188 
189 
190     // Apply normalization and float conversion
191     if( session->loglevel >= 4 ){
192       *(session->logfile) << "JTL :: Normalizing and converting to float";
193       function_timer.start();
194     }
195     session->processor->normalize( rawtile, max, min );
196     if( session->loglevel >= 4 ){
197       *(session->logfile) << " in " << function_timer.getTime() << " microseconds" << endl;
198     }
199 
200 
201     // Apply hill shading if requested
202     if( session->view->shaded ){
203       if( session->loglevel >= 4 ){
204 	*(session->logfile) << "JTL :: Applying hill-shading";
205 	function_timer.start();
206       }
207       session->processor->shade( rawtile, session->view->shade[0], session->view->shade[1] );
208       if( session->loglevel >= 4 ){
209 	*(session->logfile) << " in " << function_timer.getTime() << " microseconds" << endl;
210       }
211     }
212 
213 
214     // Apply color twist if requested
215     if( session->view->ctw.size() ){
216       if( session->loglevel >= 4 ){
217 	*(session->logfile) << "JTL :: Applying color twist";
218 	function_timer.start();
219       }
220       session->processor->twist( rawtile, session->view->ctw );
221       if( session->loglevel >= 4 ){
222 	*(session->logfile) << " in " << function_timer.getTime() << " microseconds" << endl;
223       }
224     }
225 
226 
227     // Apply any gamma correction
228     if( session->view->gamma != 1.0 ){
229       float gamma = session->view->gamma;
230       if( session->loglevel >= 4 ){
231 	*(session->logfile) << "JTL :: Applying gamma of " << gamma;
232 	function_timer.start();
233       }
234       session->processor->gamma( rawtile, gamma);
235       if( session->loglevel >= 4 ){
236 	*(session->logfile) << " in " << function_timer.getTime() << " microseconds" << endl;
237       }
238     }
239 
240 
241     // Apply inversion if requested
242     if( session->view->inverted ){
243       if( session->loglevel >= 4 ){
244 	*(session->logfile) << "JTL :: Applying inversion";
245 	function_timer.start();
246       }
247       session->processor->inv( rawtile );
248       if( session->loglevel >= 4 ){
249 	*(session->logfile) << " in " << function_timer.getTime() << " microseconds" << endl;
250       }
251     }
252 
253 
254     // Apply color mapping if requested
255     if( session->view->cmapped ){
256       if( session->loglevel >= 4 ){
257 	*(session->logfile) << "JTL :: Applying color map";
258 	function_timer.start();
259       }
260       session->processor->cmap( rawtile, session->view->cmap );
261       if( session->loglevel >= 4 ){
262 	*(session->logfile) << " in " << function_timer.getTime() << " microseconds" << endl;
263       }
264     }
265 
266 
267     // Apply any contrast adjustments and/or clip to 8bit from 16 or 32 bit
268     float contrast = session->view->contrast;
269     if( session->loglevel >= 4 ){
270       *(session->logfile) << "JTL :: Applying contrast of " << contrast << " and converting to 8 bit";
271       function_timer.start();
272     }
273     session->processor->contrast( rawtile, contrast );
274     if( session->loglevel >= 4 ){
275       *(session->logfile) << " in " << function_timer.getTime() << " microseconds" << endl;
276     }
277 
278   }
279 
280 
281   // Reduce to 1 or 3 bands if we have an alpha channel or a multi-band image
282   if( rawtile.channels == 2 || rawtile.channels > 3 ){
283     unsigned int bands = (rawtile.channels==2) ? 1 : 3;
284     if( session->loglevel >= 4 ){
285       *(session->logfile) << "JTL :: Flattening channels to " << bands;
286       function_timer.start();
287     }
288     session->processor->flatten( rawtile, bands );
289     if( session->loglevel >= 4 ){
290       *(session->logfile) << " in " << function_timer.getTime() << " microseconds" << endl;
291     }
292   }
293 
294 
295   // Convert to greyscale if requested
296   if( (*session->image)->getColourSpace() == sRGB && session->view->colourspace == GREYSCALE ){
297     if( session->loglevel >= 4 ){
298       *(session->logfile) << "JTL :: Converting to greyscale";
299       function_timer.start();
300     }
301     session->processor->greyscale( rawtile );
302     if( session->loglevel >= 4 ){
303       *(session->logfile) << " in " << function_timer.getTime() << " microseconds" << endl;
304     }
305   }
306 
307 
308   // Convert to binary (bi-level) if requested
309   if( (*session->image)->getColourSpace() != BINARY && session->view->colourspace == BINARY ){
310     if( session->loglevel >= 4 ){
311       *(session->logfile) << "JTL :: Converting to binary with threshold ";
312       function_timer.start();
313     }
314     unsigned int threshold = session->processor->threshold( (*session->image)->histogram );
315     session->processor->binary( rawtile, threshold );
316     if( session->loglevel >= 4 ){
317       *(session->logfile) << threshold << " in " << function_timer.getTime() << " microseconds" << endl;
318     }
319   }
320 
321 
322   // Apply histogram equalization
323   if( session->view->equalization ){
324     if( session->loglevel >= 4 ) function_timer.start();
325     // Perform histogram equalization
326     session->processor->equalize( rawtile, (*session->image)->histogram );
327     if( session->loglevel >= 4 ){
328       *(session->logfile) << "JTL :: Applying histogram equalization in "
329                           << function_timer.getTime() << " microseconds" << endl;
330     }
331   }
332 
333 
334   // Apply flip
335   if( session->view->flip != 0 ){
336     Timer flip_timer;
337     if( session->loglevel >= 5 ){
338       flip_timer.start();
339     }
340 
341     session->processor->flip( rawtile, session->view->flip  );
342 
343     if( session->loglevel >= 5 ){
344       *(session->logfile) << "JTL :: Flipping image ";
345       if( session->view->flip == 1 ) *(session->logfile) << "horizontally";
346       else *(session->logfile) << "vertically";
347       *(session->logfile) << " in " << flip_timer.getTime() << " microseconds" << endl;
348     }
349   }
350 
351 
352   // Apply rotation - can apply this safely after gamma and contrast adjustment
353   if( session->view->getRotation() != 0.0 ){
354     float rotation = session->view->getRotation();
355     if( session->loglevel >= 4 ){
356       *(session->logfile) << "JTL :: Rotating image by " << rotation << " degrees";
357       function_timer.start();
358     }
359     session->processor->rotate( rawtile, rotation );
360     if( session->loglevel >= 4 ){
361       *(session->logfile) << " in " << function_timer.getTime() << " microseconds" << endl;
362     }
363   }
364 
365 
366   // Compress to JPEG
367   if( rawtile.compressionType == UNCOMPRESSED ){
368     if( session->loglevel >= 4 ){
369       *(session->logfile) << "JTL :: Compressing UNCOMPRESSED to JPEG";
370       function_timer.start();
371     }
372     len = session->jpeg->Compress( rawtile );
373     if( session->loglevel >= 4 ){
374       *(session->logfile) << " in " << function_timer.getTime() << " microseconds to "
375                           << rawtile.dataLength << " bytes" << endl;
376 
377     }
378   }
379 
380 
381 #ifndef DEBUG
382   char str[1024];
383 
384   snprintf( str, 1024,
385 	    "Server: iipsrv/%s\r\n"
386 	    "X-Powered-By: IIPImage\r\n"
387 	    "Content-Type: image/jpeg\r\n"
388             "Content-Length: %d\r\n"
389 	    "Last-Modified: %s\r\n"
390 	    "%s\r\n"
391 	    "\r\n",
392 	    VERSION, len,(*session->image)->getTimestamp().c_str(), session->response->getCacheControl().c_str() );
393 
394   session->out->printf( str );
395 #endif
396 
397 
398   if( session->out->putStr( static_cast<const char*>(rawtile.data), len ) != len ){
399     if( session->loglevel >= 1 ){
400       *(session->logfile) << "JTL :: Error writing jpeg tile" << endl;
401     }
402   }
403 
404 
405   if( session->out->flush() == -1 ) {
406     if( session->loglevel >= 1 ){
407       *(session->logfile) << "JTL :: Error flushing jpeg tile" << endl;
408     }
409   }
410 
411 
412   // Inform our response object that we have sent something to the client
413   session->response->setImageSent();
414 
415   // Total JTL response time
416   if( session->loglevel >= 2 ){
417     *(session->logfile) << "JTL :: Total command time " << command_timer.getTime() << " microseconds" << endl;
418   }
419 
420 }
421