Mercurial > hg > orthanc-wsi
comparison Framework/Jpeg2000Reader.cpp @ 0:4a7a53257c7d
initial commit
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Sat, 22 Oct 2016 21:48:33 +0200 |
parents | |
children | 7a88c614be04 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4a7a53257c7d |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * | |
6 * This program is free software: you can redistribute it and/or | |
7 * modify it under the terms of the GNU Affero General Public License | |
8 * as published by the Free Software Foundation, either version 3 of | |
9 * the License, or (at your option) any later version. | |
10 * | |
11 * This program is distributed in the hope that it will be useful, but | |
12 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 * Affero General Public License for more details. | |
15 * | |
16 * You should have received a copy of the GNU Affero General Public License | |
17 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 **/ | |
19 | |
20 | |
21 #include "Jpeg2000Reader.h" | |
22 | |
23 #include "Orthanc/Core/OrthancException.h" | |
24 #include "Orthanc/Core/Toolbox.h" | |
25 #include "ImageToolbox.h" | |
26 | |
27 #include <cassert> | |
28 #include <string.h> | |
29 #include <openjpeg.h> | |
30 | |
31 #if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 | |
32 # define OPJ_CLRSPC_GRAY CLRSPC_GRAY | |
33 # define OPJ_CLRSPC_SRGB CLRSPC_SRGB | |
34 # define OPJ_CODEC_J2K CODEC_J2K | |
35 # define OPJ_CODEC_JP2 CODEC_JP2 | |
36 typedef opj_dinfo_t opj_codec_t; | |
37 typedef opj_cio_t opj_stream_t; | |
38 #elif ORTHANC_OPENJPEG_MAJOR_VERSION == 2 | |
39 #else | |
40 #error Unsupported version of OpenJpeg | |
41 #endif | |
42 | |
43 | |
44 namespace OrthancWSI | |
45 { | |
46 namespace | |
47 { | |
48 // Check out opj_dparameters_t::decod_format | |
49 enum InputFormat | |
50 { | |
51 InputFormat_J2K = 0, | |
52 InputFormat_JP2 = 1, | |
53 InputFormat_JPT = 2 | |
54 }; | |
55 | |
56 enum OutputFormat | |
57 { | |
58 #if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 | |
59 OutputFormat_PGX = 1 | |
60 #else | |
61 OutputFormat_PGX = 11 | |
62 #endif | |
63 }; | |
64 | |
65 class OpenJpegDecoder : public boost::noncopyable | |
66 { | |
67 private: | |
68 opj_dparameters_t parameters_; | |
69 opj_codec_t* dinfo_; | |
70 | |
71 void SetupParameters(InputFormat format) | |
72 { | |
73 opj_set_default_decoder_parameters(¶meters_); | |
74 | |
75 parameters_.decod_format = format; | |
76 parameters_.cod_format = OutputFormat_PGX; | |
77 | |
78 #if OPENJPEG_MAJOR_VERSION == 1 | |
79 parameters_.cp_layer = 0; | |
80 parameters_.cp_reduce = 0; | |
81 #endif | |
82 } | |
83 | |
84 void Finalize() | |
85 { | |
86 if (dinfo_ != NULL) | |
87 { | |
88 #if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 | |
89 opj_destroy_decompress(dinfo_); | |
90 #else | |
91 opj_destroy_codec(dinfo_); | |
92 #endif | |
93 dinfo_ = NULL; | |
94 } | |
95 } | |
96 | |
97 public: | |
98 OpenJpegDecoder(Jpeg2000Format format) : dinfo_(NULL) | |
99 { | |
100 switch (format) | |
101 { | |
102 case Jpeg2000Format_J2K: | |
103 SetupParameters(InputFormat_J2K); | |
104 dinfo_ = opj_create_decompress(OPJ_CODEC_J2K); | |
105 break; | |
106 | |
107 case Jpeg2000Format_JP2: | |
108 SetupParameters(InputFormat_JP2); | |
109 dinfo_ = opj_create_decompress(OPJ_CODEC_JP2); | |
110 break; | |
111 | |
112 default: | |
113 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
114 } | |
115 | |
116 if (!dinfo_) | |
117 { | |
118 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
119 } | |
120 | |
121 #if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 | |
122 opj_setup_decoder(dinfo_, ¶meters_); | |
123 #else | |
124 if (!opj_setup_decoder(dinfo_, ¶meters_)) | |
125 { | |
126 Finalize(); | |
127 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
128 } | |
129 #endif | |
130 } | |
131 | |
132 ~OpenJpegDecoder() | |
133 { | |
134 Finalize(); | |
135 } | |
136 | |
137 opj_codec_t* GetObject() | |
138 { | |
139 return dinfo_; | |
140 } | |
141 | |
142 const opj_dparameters_t& GetParameters() const | |
143 { | |
144 return parameters_; | |
145 } | |
146 }; | |
147 | |
148 | |
149 class OpenJpegInput : public boost::noncopyable | |
150 { | |
151 private: | |
152 opj_stream_t* cio_; | |
153 | |
154 const uint8_t* buffer_; | |
155 size_t size_; | |
156 size_t position_; | |
157 | |
158 #if ORTHANC_OPENJPEG_MAJOR_VERSION == 2 | |
159 static void Free(void *userData) | |
160 { | |
161 } | |
162 | |
163 static OPJ_SIZE_T Read(void *target, | |
164 OPJ_SIZE_T size, | |
165 void *userData) | |
166 { | |
167 OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData); | |
168 assert(that.position_ >= 0 && that.position_ <= that.size_); | |
169 assert(size >= 0); | |
170 | |
171 if (that.position_ == that.size_) | |
172 { | |
173 // End of file | |
174 return -1; | |
175 } | |
176 else | |
177 { | |
178 if (that.position_ + size > that.size_) | |
179 { | |
180 size = that.size_ - that.position_; | |
181 } | |
182 | |
183 if (size > 0) | |
184 { | |
185 memcpy(target, that.buffer_ + that.position_, size); | |
186 } | |
187 | |
188 that.position_ += size; | |
189 return size; | |
190 } | |
191 } | |
192 | |
193 static OPJ_OFF_T Skip(OPJ_OFF_T skip, | |
194 void *userData) | |
195 { | |
196 assert(skip >= 0); | |
197 OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData); | |
198 | |
199 if (that.position_ == that.size_) | |
200 { | |
201 // End of file | |
202 return -1; | |
203 } | |
204 else if (that.position_ + skip > that.size_) | |
205 { | |
206 size_t offset = that.size_ - that.position_; | |
207 that.position_ = that.size_; | |
208 return offset; | |
209 } | |
210 else | |
211 { | |
212 that.position_ += skip; | |
213 return skip; | |
214 } | |
215 } | |
216 | |
217 static OPJ_BOOL Seek(OPJ_OFF_T position, | |
218 void *userData) | |
219 { | |
220 assert(position >= 0); | |
221 OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData); | |
222 | |
223 if (static_cast<size_t>(position) > that.size_) | |
224 { | |
225 that.position_ = that.size_; | |
226 return false; | |
227 } | |
228 else | |
229 { | |
230 that.position_ = position; | |
231 return true; | |
232 } | |
233 } | |
234 #endif | |
235 | |
236 public: | |
237 OpenJpegInput(OpenJpegDecoder& decoder, | |
238 const void* buffer, | |
239 size_t size) : | |
240 buffer_(reinterpret_cast<const uint8_t*>(buffer)), | |
241 size_(size), | |
242 position_(0) | |
243 { | |
244 #if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 | |
245 cio_ = opj_cio_open(reinterpret_cast<opj_common_ptr>(decoder.GetObject()), | |
246 reinterpret_cast<unsigned char*>(const_cast<void*>(buffer)), | |
247 size); | |
248 #else | |
249 cio_ = opj_stream_create(size_, 1 /* input stream */); | |
250 if (!cio_) | |
251 { | |
252 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
253 } | |
254 | |
255 // http://openjpeg.narkive.com/zHqG2fMe/opj-stream-set-user-data-length | |
256 // "I'd suggest to precise in the documentation that the skip | |
257 // and read callback functions should return -1 on end of | |
258 // stream, and the seek callback function should return false | |
259 // on end of stream." | |
260 | |
261 opj_stream_set_user_data(cio_, this, Free); | |
262 opj_stream_set_user_data_length(cio_, size); | |
263 opj_stream_set_read_function(cio_, Read); | |
264 opj_stream_set_skip_function(cio_, Skip); | |
265 opj_stream_set_seek_function(cio_, Seek); | |
266 #endif | |
267 } | |
268 | |
269 ~OpenJpegInput() | |
270 { | |
271 if (cio_) | |
272 { | |
273 #if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 | |
274 opj_cio_close(cio_); | |
275 #else | |
276 opj_stream_destroy(cio_); | |
277 #endif | |
278 cio_ = NULL; | |
279 } | |
280 } | |
281 | |
282 opj_stream_t* GetObject() | |
283 { | |
284 return cio_; | |
285 } | |
286 }; | |
287 | |
288 | |
289 class OpenJpegImage | |
290 { | |
291 private: | |
292 opj_image_t* image_; | |
293 | |
294 void CopyChannel(Orthanc::ImageAccessor& target, | |
295 unsigned int channel, | |
296 unsigned int targetIncrement) | |
297 { | |
298 int32_t* q = image_->comps[channel].data; | |
299 assert(q != NULL); | |
300 | |
301 for (unsigned int y = 0; y < target.GetHeight(); y++) | |
302 { | |
303 uint8_t *p = reinterpret_cast<uint8_t*>(target.GetRow(y)) + channel; | |
304 | |
305 for (unsigned int x = 0; x < target.GetWidth(); x++, p += targetIncrement) | |
306 { | |
307 *p = *q; | |
308 q++; | |
309 } | |
310 } | |
311 } | |
312 | |
313 public: | |
314 OpenJpegImage(OpenJpegDecoder& decoder, | |
315 OpenJpegInput& input) : | |
316 image_(NULL) | |
317 { | |
318 #if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 | |
319 image_ = opj_decode(decoder.GetObject(), input.GetObject()); | |
320 if (image_ == NULL) | |
321 { | |
322 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
323 } | |
324 #else | |
325 if (!opj_read_header(input.GetObject(), decoder.GetObject(), &image_) || | |
326 image_ == NULL) | |
327 { | |
328 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
329 } | |
330 | |
331 if (!opj_set_decode_area(decoder.GetObject(), image_, | |
332 static_cast<int32_t>(decoder.GetParameters().DA_x0), | |
333 static_cast<int32_t>(decoder.GetParameters().DA_y0), | |
334 static_cast<int32_t>(decoder.GetParameters().DA_x1), | |
335 static_cast<int32_t>(decoder.GetParameters().DA_y1)) || // Decode the whole image | |
336 !opj_decode(decoder.GetObject(), input.GetObject(), image_) || | |
337 !opj_end_decompress(decoder.GetObject(), input.GetObject())) | |
338 { | |
339 opj_image_destroy(image_); | |
340 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
341 } | |
342 #endif | |
343 } | |
344 | |
345 ~OpenJpegImage() | |
346 { | |
347 if (image_ != NULL) | |
348 { | |
349 opj_image_destroy(image_); | |
350 image_ = NULL; | |
351 } | |
352 } | |
353 | |
354 Orthanc::ImageAccessor* ProvideImage() | |
355 { | |
356 if (image_->x1 < 0 || | |
357 image_->y1 < 0) | |
358 { | |
359 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
360 } | |
361 | |
362 if (image_->x0 != 0 || | |
363 image_->y0 != 0) | |
364 { | |
365 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
366 } | |
367 | |
368 for (unsigned int c = 0; c < static_cast<unsigned int>(image_->numcomps); c++) | |
369 { | |
370 if (image_->comps[c].dx != 1 || | |
371 image_->comps[c].dy != 1 || | |
372 image_->comps[c].x0 != 0 || | |
373 image_->comps[c].y0 != 0 || | |
374 image_->comps[c].w != image_->x1 || | |
375 image_->comps[c].h != image_->y1 || | |
376 image_->comps[c].prec != 8 || | |
377 image_->comps[c].sgnd != 0) | |
378 { | |
379 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
380 } | |
381 } | |
382 | |
383 unsigned int width = static_cast<unsigned int>(image_->x1); | |
384 unsigned int height = static_cast<unsigned int>(image_->y1); | |
385 | |
386 Orthanc::PixelFormat format; | |
387 if (image_->numcomps == 1 && image_->color_space != OPJ_CLRSPC_GRAY) | |
388 { | |
389 format = Orthanc::PixelFormat_Grayscale8; | |
390 } | |
391 else if (image_->numcomps == 3 && image_->color_space != OPJ_CLRSPC_SRGB) | |
392 { | |
393 format = Orthanc::PixelFormat_RGB24; | |
394 } | |
395 else | |
396 { | |
397 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
398 } | |
399 | |
400 std::auto_ptr<Orthanc::ImageAccessor> image(ImageToolbox::Allocate(format, width, height)); | |
401 | |
402 switch (format) | |
403 { | |
404 case Orthanc::PixelFormat_Grayscale8: | |
405 { | |
406 CopyChannel(*image, 0, 1); | |
407 break; | |
408 } | |
409 | |
410 case Orthanc::PixelFormat_RGB24: | |
411 { | |
412 CopyChannel(*image, 0, 3); | |
413 CopyChannel(*image, 1, 3); | |
414 CopyChannel(*image, 2, 3); | |
415 break; | |
416 } | |
417 | |
418 default: | |
419 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
420 } | |
421 | |
422 return image.release(); | |
423 } | |
424 | |
425 }; | |
426 } | |
427 | |
428 | |
429 void Jpeg2000Reader::ReadFromMemory(const void* buffer, | |
430 size_t size) | |
431 { | |
432 OpenJpegDecoder decoder(DetectFormatFromMemory(buffer, size)); | |
433 OpenJpegInput input(decoder, buffer, size); | |
434 OpenJpegImage image(decoder, input); | |
435 | |
436 image_.reset(image.ProvideImage()); | |
437 AssignReadOnly(image_->GetFormat(), | |
438 image_->GetWidth(), | |
439 image_->GetHeight(), | |
440 image_->GetPitch(), | |
441 image_->GetConstBuffer()); | |
442 } | |
443 | |
444 | |
445 void Jpeg2000Reader::ReadFromMemory(const std::string& buffer) | |
446 { | |
447 if (buffer.empty()) | |
448 { | |
449 ReadFromMemory(NULL, 0); | |
450 } | |
451 else | |
452 { | |
453 ReadFromMemory(buffer.c_str(), buffer.size()); | |
454 } | |
455 } | |
456 | |
457 void Jpeg2000Reader::ReadFromFile(const std::string& filename) | |
458 { | |
459 // TODO Use opj_stream_create_file_stream() ? | |
460 | |
461 std::string content; | |
462 Orthanc::Toolbox::ReadFile(content, filename); | |
463 } | |
464 | |
465 | |
466 Jpeg2000Format Jpeg2000Reader::DetectFormatFromMemory(const void* buffer, | |
467 size_t size) | |
468 { | |
469 static const char JP2_RFC3745_HEADER[] = "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a"; | |
470 static const char JP2_HEADER[] = "\x0d\x0a\x87\x0a"; | |
471 static const char J2K_HEADER[] = "\xff\x4f\xff\x51"; | |
472 | |
473 if (size < sizeof(JP2_RFC3745_HEADER) - 1) | |
474 { | |
475 return Jpeg2000Format_Unknown; | |
476 } | |
477 | |
478 if (memcmp(buffer, JP2_RFC3745_HEADER, sizeof(JP2_RFC3745_HEADER) - 1) == 0 || | |
479 memcmp(buffer, JP2_HEADER, sizeof(JP2_HEADER) - 1) == 0) | |
480 { | |
481 return Jpeg2000Format_JP2; | |
482 } | |
483 else if (memcmp(buffer, J2K_HEADER, sizeof(J2K_HEADER) - 1) == 0) | |
484 { | |
485 return Jpeg2000Format_J2K; | |
486 } | |
487 | |
488 return Jpeg2000Format_Unknown; | |
489 } | |
490 } |