Mercurial > hg > orthanc-wsi
diff Framework/ImageToolbox.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/ImageToolbox.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,367 @@ +/** + * 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 "ImageToolbox.h" + +#include "Jpeg2000Reader.h" +#include "Jpeg2000Writer.h" + +#include "Orthanc/Core/OrthancException.h" +#include "Orthanc/Core/Images/ImageProcessing.h" +#include "Orthanc/Core/Images/PngReader.h" +#include "Orthanc/Core/Images/PngWriter.h" +#include "Orthanc/Core/Images/JpegReader.h" +#include "Orthanc/Core/Images/JpegWriter.h" +#include "Orthanc/Core/Logging.h" + +#include <string.h> +#include <memory> + + +namespace OrthancWSI +{ + namespace ImageToolbox + { + Orthanc::ImageAccessor* Allocate(Orthanc::PixelFormat format, + unsigned int width, + unsigned int height) + { + return new Orthanc::Image(format, width, height, false); + } + + + void Embed(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source, + unsigned int x, + unsigned int y) + { + if (target.GetFormat() != source.GetFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (x >= target.GetWidth() || + y >= target.GetHeight()) + { + return; + } + + unsigned int h = std::min(source.GetHeight(), target.GetHeight() - y); + unsigned int w = std::min(source.GetWidth(), target.GetWidth() - x); + + Orthanc::ImageAccessor targetRegion = target.GetRegion(x, y, w, h); + Orthanc::ImageAccessor sourceRegion = source.GetRegion(0, 0, w, h); + Orthanc::ImageProcessing::Copy(targetRegion, sourceRegion); + } + + + + void Set(Orthanc::ImageAccessor& image, + uint8_t r, + uint8_t g, + uint8_t b) + { + if (image.GetWidth() == 0 || + image.GetHeight() == 0) + { + return; + } + + uint8_t grayscale = (2126 * static_cast<uint16_t>(r) + + 7152 * static_cast<uint16_t>(g) + + 0722 * static_cast<uint16_t>(b)) / 10000; + + switch (image.GetFormat()) + { + case Orthanc::PixelFormat_Grayscale8: + { + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + memset(image.GetRow(y), grayscale, image.GetWidth()); + } + + break; + } + + case Orthanc::PixelFormat_RGB24: + { + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + uint8_t* p = reinterpret_cast<uint8_t*>(image.GetRow(y)); + for (unsigned int x = 0; x < image.GetWidth(); x++, p += 3) + { + p[0] = r; + p[1] = g; + p[2] = b; + } + } + + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + Orthanc::ImageAccessor* DecodeTile(const std::string& source, + ImageCompression compression) + { + switch (compression) + { + case ImageCompression_Png: + { + std::auto_ptr<Orthanc::PngReader> reader(new Orthanc::PngReader); + reader->ReadFromMemory(source); + return reader.release(); + } + + case ImageCompression_Jpeg: + { + std::auto_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader); + reader->ReadFromMemory(source); + return reader.release(); + } + + case ImageCompression_Jpeg2000: + { + std::auto_ptr<Jpeg2000Reader> reader(new Jpeg2000Reader); + reader->ReadFromMemory(source); + return reader.release(); + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + void EncodeTile(std::string& target, + const Orthanc::ImageAccessor& source, + ImageCompression compression, + uint8_t quality) + { + if (compression == ImageCompression_None) + { + unsigned int pitch = GetBytesPerPixel(source.GetFormat()) * source.GetWidth(); + target.resize(pitch * source.GetHeight()); + + for (unsigned int i = 0; i < source.GetHeight(); i++) + { + memcpy(&target[i * pitch], source.GetConstRow(i), pitch); + } + } + else + { + std::auto_ptr<Orthanc::IImageWriter> writer; + + switch (compression) + { + case ImageCompression_Png: + writer.reset(new Orthanc::PngWriter); + break; + + case ImageCompression_Jpeg: + writer.reset(new Orthanc::JpegWriter); + dynamic_cast<Orthanc::JpegWriter&>(*writer).SetQuality(quality); + break; + + case ImageCompression_Jpeg2000: + writer.reset(new Jpeg2000Writer); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + writer->WriteToMemory(target, source); + } + } + + + void ChangeTileCompression(std::string& target, + const std::string& source, + ImageCompression sourceCompression, + ImageCompression targetCompression, + uint8_t quality) + { + if (sourceCompression == targetCompression) + { + target = source; + } + else + { + std::auto_ptr<Orthanc::ImageAccessor> decoded(DecodeTile(source, sourceCompression)); + EncodeTile(target, *decoded, targetCompression, quality); + } + } + + + static uint8_t GetPixelValue(const Orthanc::ImageAccessor& source, + unsigned int x, + unsigned int y, + unsigned int channel, + int offsetX, + int offsetY) + { + assert(channel < source.GetBytesPerPixel()); + assert(source.GetFormat() == Orthanc::PixelFormat_Grayscale8 || + source.GetFormat() == Orthanc::PixelFormat_RGB24 || + source.GetFormat() == Orthanc::PixelFormat_RGBA32); // 16bpp is unsupported + + if (static_cast<int>(x) + offsetX < 0) + { + x = 0; + } + else + { + x += offsetX; + if (x >= source.GetWidth()) + { + x = source.GetWidth() - 1; + } + } + + if (static_cast<int>(y) + offsetY < 0) + { + y = 0; + } + else + { + y += offsetY; + if (y >= source.GetHeight()) + { + y = source.GetHeight() - 1; + } + } + + return *(reinterpret_cast<const uint8_t*>(source.GetConstBuffer()) + + y * source.GetPitch() + x * source.GetBytesPerPixel() + channel); + } + + + static uint8_t SmoothPixelValue(const Orthanc::ImageAccessor& source, + unsigned int x, + unsigned int y, + unsigned int channel) + { + static const uint32_t kernel[5] = { 1, 4, 6, 4, 1 }; + static const uint32_t normalization = 2 * (1 + 4 + 6 + 4 + 1); + + uint32_t accumulator = 0; + + // Horizontal smoothing + for (int offset = -2; offset <= 2; offset++) + { + accumulator += kernel[offset + 2] * GetPixelValue(source, x, y, channel, offset, 0); + } + + // Vertical smoothing + for (int offset = -2; offset <= 2; offset++) + { + accumulator += kernel[offset + 2] * GetPixelValue(source, x, y, channel, 0, offset); + } + + return static_cast<uint8_t>(accumulator / normalization); + } + + + Orthanc::ImageAccessor* Halve(const Orthanc::ImageAccessor& source, + bool smooth) + { + if (source.GetWidth() % 2 == 1 || + source.GetHeight() % 2 == 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + if (source.GetFormat() != Orthanc::PixelFormat_Grayscale8 && + source.GetFormat() != Orthanc::PixelFormat_RGB24 && + source.GetFormat() != Orthanc::PixelFormat_RGBA32) // 16bpp is not supported (*) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + unsigned int channelsCount = source.GetBytesPerPixel(); // OK tx (*) + + std::auto_ptr<Orthanc::ImageAccessor> result(Allocate(source.GetFormat(), + source.GetWidth() / 2, + source.GetHeight() / 2)); + + for (unsigned int y = 0; y < source.GetHeight() / 2; y++) + { + uint8_t* q = reinterpret_cast<uint8_t*>(result->GetRow(y)); + + for (unsigned int x = 0; x < source.GetWidth() / 2; x++, q += 3) + { + for (unsigned int c = 0; c < channelsCount; c++) + { + if (smooth) + { + q[c] = SmoothPixelValue(source, 2 * x, 2 * y, c); + } + else + { + q[c] = GetPixelValue(source, 2 * x, 2 * y, c, 0, 0); + } + } + } + } + + return result.release(); + } + + + Orthanc::ImageAccessor* Clone(const Orthanc::ImageAccessor& accessor) + { + std::auto_ptr<Orthanc::ImageAccessor> result(Allocate(accessor.GetFormat(), + accessor.GetWidth(), + accessor.GetHeight())); + Embed(*result, accessor, 0, 0); + + return result.release(); + } + + + Orthanc::ImageAccessor* Render(ITiledPyramid& pyramid, + unsigned int level) + { + std::auto_ptr<Orthanc::ImageAccessor> result(Allocate(pyramid.GetPixelFormat(), + pyramid.GetLevelWidth(level), + pyramid.GetLevelHeight(level))); + + LOG(INFO) << "Rendering a tiled image of size " << result->GetWidth() << "x" << result->GetHeight(); + + for (unsigned int y = 0; y < result->GetHeight(); y += pyramid.GetTileHeight()) + { + for (unsigned int x = 0; x < result->GetWidth(); x += pyramid.GetTileWidth()) + { + std::auto_ptr<Orthanc::ImageAccessor> tile(pyramid.DecodeTile(level, + x / pyramid.GetTileWidth(), + y / pyramid.GetTileHeight())); + Embed(*result, *tile, x, y); + } + } + + return result.release(); + } + } +}