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(&parameters_);
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_, &parameters_);
123 #else
124 if (!opj_setup_decoder(dinfo_, &parameters_))
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 }