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