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