Mercurial > hg > orthanc-wsi
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Jpeg2000Reader.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,490 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "Jpeg2000Reader.h" + +#include "Orthanc/Core/OrthancException.h" +#include "Orthanc/Core/Toolbox.h" +#include "ImageToolbox.h" + +#include <cassert> +#include <string.h> +#include <openjpeg.h> + +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 +# define OPJ_CLRSPC_GRAY CLRSPC_GRAY +# define OPJ_CLRSPC_SRGB CLRSPC_SRGB +# define OPJ_CODEC_J2K CODEC_J2K +# define OPJ_CODEC_JP2 CODEC_JP2 +typedef opj_dinfo_t opj_codec_t; +typedef opj_cio_t opj_stream_t; +#elif ORTHANC_OPENJPEG_MAJOR_VERSION == 2 +#else +#error Unsupported version of OpenJpeg +#endif + + +namespace OrthancWSI +{ + namespace + { + // Check out opj_dparameters_t::decod_format + enum InputFormat + { + InputFormat_J2K = 0, + InputFormat_JP2 = 1, + InputFormat_JPT = 2 + }; + + enum OutputFormat + { +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + OutputFormat_PGX = 1 +#else + OutputFormat_PGX = 11 +#endif + }; + + class OpenJpegDecoder : public boost::noncopyable + { + private: + opj_dparameters_t parameters_; + opj_codec_t* dinfo_; + + void SetupParameters(InputFormat format) + { + opj_set_default_decoder_parameters(¶meters_); + + parameters_.decod_format = format; + parameters_.cod_format = OutputFormat_PGX; + +#if OPENJPEG_MAJOR_VERSION == 1 + parameters_.cp_layer = 0; + parameters_.cp_reduce = 0; +#endif + } + + void Finalize() + { + if (dinfo_ != NULL) + { +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + opj_destroy_decompress(dinfo_); +#else + opj_destroy_codec(dinfo_); +#endif + dinfo_ = NULL; + } + } + + public: + OpenJpegDecoder(Jpeg2000Format format) : dinfo_(NULL) + { + switch (format) + { + case Jpeg2000Format_J2K: + SetupParameters(InputFormat_J2K); + dinfo_ = opj_create_decompress(OPJ_CODEC_J2K); + break; + + case Jpeg2000Format_JP2: + SetupParameters(InputFormat_JP2); + dinfo_ = opj_create_decompress(OPJ_CODEC_JP2); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (!dinfo_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + opj_setup_decoder(dinfo_, ¶meters_); +#else + if (!opj_setup_decoder(dinfo_, ¶meters_)) + { + Finalize(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } +#endif + } + + ~OpenJpegDecoder() + { + Finalize(); + } + + opj_codec_t* GetObject() + { + return dinfo_; + } + + const opj_dparameters_t& GetParameters() const + { + return parameters_; + } + }; + + + class OpenJpegInput : public boost::noncopyable + { + private: + opj_stream_t* cio_; + + const uint8_t* buffer_; + size_t size_; + size_t position_; + +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 2 + static void Free(void *userData) + { + } + + static OPJ_SIZE_T Read(void *target, + OPJ_SIZE_T size, + void *userData) + { + OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData); + assert(that.position_ >= 0 && that.position_ <= that.size_); + assert(size >= 0); + + if (that.position_ == that.size_) + { + // End of file + return -1; + } + else + { + if (that.position_ + size > that.size_) + { + size = that.size_ - that.position_; + } + + if (size > 0) + { + memcpy(target, that.buffer_ + that.position_, size); + } + + that.position_ += size; + return size; + } + } + + static OPJ_OFF_T Skip(OPJ_OFF_T skip, + void *userData) + { + assert(skip >= 0); + OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData); + + if (that.position_ == that.size_) + { + // End of file + return -1; + } + else if (that.position_ + skip > that.size_) + { + size_t offset = that.size_ - that.position_; + that.position_ = that.size_; + return offset; + } + else + { + that.position_ += skip; + return skip; + } + } + + static OPJ_BOOL Seek(OPJ_OFF_T position, + void *userData) + { + assert(position >= 0); + OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData); + + if (static_cast<size_t>(position) > that.size_) + { + that.position_ = that.size_; + return false; + } + else + { + that.position_ = position; + return true; + } + } +#endif + + public: + OpenJpegInput(OpenJpegDecoder& decoder, + const void* buffer, + size_t size) : + buffer_(reinterpret_cast<const uint8_t*>(buffer)), + size_(size), + position_(0) + { +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + cio_ = opj_cio_open(reinterpret_cast<opj_common_ptr>(decoder.GetObject()), + reinterpret_cast<unsigned char*>(const_cast<void*>(buffer)), + size); +#else + cio_ = opj_stream_create(size_, 1 /* input stream */); + if (!cio_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + // http://openjpeg.narkive.com/zHqG2fMe/opj-stream-set-user-data-length + // "I'd suggest to precise in the documentation that the skip + // and read callback functions should return -1 on end of + // stream, and the seek callback function should return false + // on end of stream." + + opj_stream_set_user_data(cio_, this, Free); + opj_stream_set_user_data_length(cio_, size); + opj_stream_set_read_function(cio_, Read); + opj_stream_set_skip_function(cio_, Skip); + opj_stream_set_seek_function(cio_, Seek); +#endif + } + + ~OpenJpegInput() + { + if (cio_) + { + #if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + opj_cio_close(cio_); +#else + opj_stream_destroy(cio_); +#endif + cio_ = NULL; + } + } + + opj_stream_t* GetObject() + { + return cio_; + } + }; + + + class OpenJpegImage + { + private: + opj_image_t* image_; + + void CopyChannel(Orthanc::ImageAccessor& target, + unsigned int channel, + unsigned int targetIncrement) + { + int32_t* q = image_->comps[channel].data; + assert(q != NULL); + + for (unsigned int y = 0; y < target.GetHeight(); y++) + { + uint8_t *p = reinterpret_cast<uint8_t*>(target.GetRow(y)) + channel; + + for (unsigned int x = 0; x < target.GetWidth(); x++, p += targetIncrement) + { + *p = *q; + q++; + } + } + } + + public: + OpenJpegImage(OpenJpegDecoder& decoder, + OpenJpegInput& input) : + image_(NULL) + { +#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1 + image_ = opj_decode(decoder.GetObject(), input.GetObject()); + if (image_ == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } +#else + if (!opj_read_header(input.GetObject(), decoder.GetObject(), &image_) || + image_ == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (!opj_set_decode_area(decoder.GetObject(), image_, + static_cast<int32_t>(decoder.GetParameters().DA_x0), + static_cast<int32_t>(decoder.GetParameters().DA_y0), + static_cast<int32_t>(decoder.GetParameters().DA_x1), + static_cast<int32_t>(decoder.GetParameters().DA_y1)) || // Decode the whole image + !opj_decode(decoder.GetObject(), input.GetObject(), image_) || + !opj_end_decompress(decoder.GetObject(), input.GetObject())) + { + opj_image_destroy(image_); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } +#endif + } + + ~OpenJpegImage() + { + if (image_ != NULL) + { + opj_image_destroy(image_); + image_ = NULL; + } + } + + Orthanc::ImageAccessor* ProvideImage() + { + if (image_->x1 < 0 || + image_->y1 < 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (image_->x0 != 0 || + image_->y0 != 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + for (unsigned int c = 0; c < static_cast<unsigned int>(image_->numcomps); c++) + { + if (image_->comps[c].dx != 1 || + image_->comps[c].dy != 1 || + image_->comps[c].x0 != 0 || + image_->comps[c].y0 != 0 || + image_->comps[c].w != image_->x1 || + image_->comps[c].h != image_->y1 || + image_->comps[c].prec != 8 || + image_->comps[c].sgnd != 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + unsigned int width = static_cast<unsigned int>(image_->x1); + unsigned int height = static_cast<unsigned int>(image_->y1); + + Orthanc::PixelFormat format; + if (image_->numcomps == 1 && image_->color_space != OPJ_CLRSPC_GRAY) + { + format = Orthanc::PixelFormat_Grayscale8; + } + else if (image_->numcomps == 3 && image_->color_space != OPJ_CLRSPC_SRGB) + { + format = Orthanc::PixelFormat_RGB24; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + std::auto_ptr<Orthanc::ImageAccessor> image(ImageToolbox::Allocate(format, width, height)); + + switch (format) + { + case Orthanc::PixelFormat_Grayscale8: + { + CopyChannel(*image, 0, 1); + break; + } + + case Orthanc::PixelFormat_RGB24: + { + CopyChannel(*image, 0, 3); + CopyChannel(*image, 1, 3); + CopyChannel(*image, 2, 3); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + return image.release(); + } + + }; + } + + + void Jpeg2000Reader::ReadFromMemory(const void* buffer, + size_t size) + { + OpenJpegDecoder decoder(DetectFormatFromMemory(buffer, size)); + OpenJpegInput input(decoder, buffer, size); + OpenJpegImage image(decoder, input); + + image_.reset(image.ProvideImage()); + AssignReadOnly(image_->GetFormat(), + image_->GetWidth(), + image_->GetHeight(), + image_->GetPitch(), + image_->GetConstBuffer()); + } + + + void Jpeg2000Reader::ReadFromMemory(const std::string& buffer) + { + if (buffer.empty()) + { + ReadFromMemory(NULL, 0); + } + else + { + ReadFromMemory(buffer.c_str(), buffer.size()); + } + } + + void Jpeg2000Reader::ReadFromFile(const std::string& filename) + { + // TODO Use opj_stream_create_file_stream() ? + + std::string content; + Orthanc::Toolbox::ReadFile(content, filename); + } + + + Jpeg2000Format Jpeg2000Reader::DetectFormatFromMemory(const void* buffer, + size_t size) + { + static const char JP2_RFC3745_HEADER[] = "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a"; + static const char JP2_HEADER[] = "\x0d\x0a\x87\x0a"; + static const char J2K_HEADER[] = "\xff\x4f\xff\x51"; + + if (size < sizeof(JP2_RFC3745_HEADER) - 1) + { + return Jpeg2000Format_Unknown; + } + + if (memcmp(buffer, JP2_RFC3745_HEADER, sizeof(JP2_RFC3745_HEADER) - 1) == 0 || + memcmp(buffer, JP2_HEADER, sizeof(JP2_HEADER) - 1) == 0) + { + return Jpeg2000Format_JP2; + } + else if (memcmp(buffer, J2K_HEADER, sizeof(J2K_HEADER) - 1) == 0) + { + return Jpeg2000Format_J2K; + } + + return Jpeg2000Format_Unknown; + } +}