0
|
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 }
|