# HG changeset patch # User Sebastien Jodogne # Date 1566807831 -7200 # Node ID c160eafc42a904741f98ffe7442ab7ca0e001b9a # Parent 27b53c61aa9921a582641a774c4bfe1901ff3497 new functions in ImageProcessing toolbox: FlipX/Y(), Resize(), Halve() diff -r 27b53c61aa99 -r c160eafc42a9 Core/Images/ImageProcessing.cpp --- a/Core/Images/ImageProcessing.cpp Sat Aug 24 15:40:36 2019 +0200 +++ b/Core/Images/ImageProcessing.cpp Mon Aug 26 10:23:51 2019 +0200 @@ -34,6 +34,8 @@ #include "../PrecompiledHeaders.h" #include "ImageProcessing.h" +#include "Image.h" +#include "ImageTraits.h" #include "PixelTraits.h" #include "../OrthancException.h" @@ -1552,4 +1554,215 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } } + + + template + static void ResizeInternal(ImageAccessor& target, + const ImageAccessor& source) + { + assert(target.GetFormat() == source.GetFormat() && + target.GetFormat() == Format); + + const unsigned int sourceWidth = source.GetWidth(); + const unsigned int sourceHeight = source.GetHeight(); + const unsigned int targetWidth = target.GetWidth(); + const unsigned int targetHeight = target.GetHeight(); + + if (targetWidth == 0 || targetHeight == 0) + { + return; + } + + if (sourceWidth == 0 || sourceHeight == 0) + { + // Avoids division by zero below + ImageProcessing::Set(target, 0); + return; + } + + const float scaleX = static_cast(sourceWidth) / static_cast(targetWidth); + const float scaleY = static_cast(sourceHeight) / static_cast(targetHeight); + + + /** + * Create two lookup tables to quickly know the (x,y) position + * in the source image, given the (x,y) position in the target + * image. + **/ + + std::vector lookupX(targetWidth); + + for (unsigned int x = 0; x < targetWidth; x++) + { + int sourceX = std::floor((static_cast(x) + 0.5f) * scaleX); + if (sourceX < 0) + { + sourceX = 0; // Should never happen + } + else if (sourceX >= static_cast(sourceWidth)) + { + sourceX = sourceWidth - 1; + } + + lookupX[x] = static_cast(sourceX); + } + + std::vector lookupY(targetHeight); + + for (unsigned int y = 0; y < targetHeight; y++) + { + int sourceY = std::floor((static_cast(y) + 0.5f) * scaleY); + if (sourceY < 0) + { + sourceY = 0; // Should never happen + } + else if (sourceY >= static_cast(sourceHeight)) + { + sourceY = sourceHeight - 1; + } + + lookupY[y] = static_cast(sourceY); + } + + + /** + * Actual resizing + **/ + + for (unsigned int targetY = 0; targetY < targetHeight; targetY++) + { + unsigned int sourceY = lookupY[targetY]; + + for (unsigned int targetX = 0; targetX < targetWidth; targetX++) + { + unsigned int sourceX = lookupX[targetX]; + + typename ImageTraits::PixelType pixel; + ImageTraits::GetPixel(pixel, source, sourceX, sourceY); + ImageTraits::SetPixel(target, pixel, targetX, targetY); + } + } + } + + + + void ImageProcessing::Resize(ImageAccessor& target, + const ImageAccessor& source) + { + if (source.GetFormat() != source.GetFormat()) + { + throw OrthancException(ErrorCode_IncompatibleImageFormat); + } + + if (source.GetWidth() == target.GetWidth() && + source.GetHeight() == target.GetHeight()) + { + Copy(target, source); + return; + } + + switch (source.GetFormat()) + { + case PixelFormat_Grayscale8: + ResizeInternal(target, source); + break; + + case PixelFormat_RGB24: + ResizeInternal(target, source); + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + ImageAccessor* ImageProcessing::Halve(const ImageAccessor& source, + bool forceMinimalPitch) + { + std::auto_ptr target(new Image(source.GetFormat(), source.GetWidth() / 2, + source.GetHeight() / 2, forceMinimalPitch)); + Resize(*target, source); + return target.release(); + } + + + template + static void FlipXInternal(ImageAccessor& image) + { + const unsigned int height = image.GetHeight(); + const unsigned int width = image.GetWidth(); + + for (unsigned int y = 0; y < height; y++) + { + for (unsigned int x1 = 0; x1 < width / 2; x1++) + { + unsigned int x2 = width - 1 - x1; + + typename ImageTraits::PixelType a, b; + ImageTraits::GetPixel(a, image, x1, y); + ImageTraits::GetPixel(b, image, x2, y); + ImageTraits::SetPixel(image, a, x2, y); + ImageTraits::SetPixel(image, b, x1, y); + } + } + } + + + void ImageProcessing::FlipX(ImageAccessor& image) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + FlipXInternal(image); + break; + + case PixelFormat_RGB24: + FlipXInternal(image); + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + template + static void FlipYInternal(ImageAccessor& image) + { + const unsigned int height = image.GetHeight(); + const unsigned int width = image.GetWidth(); + + for (unsigned int y1 = 0; y1 < height / 2; y1++) + { + unsigned int y2 = height - 1 - y1; + + for (unsigned int x = 0; x < width; x++) + { + typename ImageTraits::PixelType a, b; + ImageTraits::GetPixel(a, image, x, y1); + ImageTraits::GetPixel(b, image, x, y2); + ImageTraits::SetPixel(image, a, x, y2); + ImageTraits::SetPixel(image, b, x, y1); + } + } + } + + + void ImageProcessing::FlipY(ImageAccessor& image) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + FlipYInternal(image); + break; + + case PixelFormat_RGB24: + FlipYInternal(image); + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } } diff -r 27b53c61aa99 -r c160eafc42a9 Core/Images/ImageProcessing.h --- a/Core/Images/ImageProcessing.h Sat Aug 24 15:40:36 2019 +0200 +++ b/Core/Images/ImageProcessing.h Mon Aug 26 10:23:51 2019 +0200 @@ -138,5 +138,15 @@ void FillPolygon(ImageAccessor& image, const std::vector& points, int64_t value); + + void Resize(ImageAccessor& target, + const ImageAccessor& source); + + ImageAccessor* Halve(const ImageAccessor& source, + bool forceMinimalPitch); + + void FlipX(ImageAccessor& image); + + void FlipY(ImageAccessor& image); } }