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