Mercurial > hg > orthanc-wsi
view Framework/Jpeg2000Reader.cpp @ 100:16b8e1cc9bbf
Fix issue #30: Bad colorspace if using OpenSlide
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 20 Jan 2017 13:15:05 +0100 |
parents | ff0ef01c332c |
children | a51dee6a1515 |
line wrap: on
line source
/** * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017 Osimis, 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 "PrecompiledHeadersWSI.h" #include "Jpeg2000Reader.h" #include "../Resources/Orthanc/Core/OrthancException.h" #include "../Resources/Orthanc/Core/SystemToolbox.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::SystemToolbox::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; } }