Orthanc/OrthancFramework/Sources/Images/PamReader.cpp
2025-06-23 19:07:37 +05:30

304 lines
8.5 KiB
C++

/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2023 Osimis S.A., Belgium
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/>.
**/
#include "../PrecompiledHeaders.h"
#include "PamReader.h"
#include "../Endianness.h"
#include "../Logging.h"
#include "../OrthancException.h"
#include "../Toolbox.h"
#if ORTHANC_SANDBOXED == 0
# include "../SystemToolbox.h"
#endif
#include <stdlib.h> // For malloc/free
#include <boost/algorithm/string/find.hpp>
#include <boost/lexical_cast.hpp>
namespace Orthanc
{
static void GetPixelFormat(PixelFormat& format,
unsigned int& bytesPerChannel,
const unsigned int& maxValue,
const unsigned int& channelCount,
const std::string& tupleType)
{
if (tupleType == "GRAYSCALE" &&
channelCount == 1)
{
switch (maxValue)
{
case 255:
format = PixelFormat_Grayscale8;
bytesPerChannel = 1;
return;
case 65535:
format = PixelFormat_Grayscale16;
bytesPerChannel = 2;
return;
default:
throw OrthancException(ErrorCode_NotImplemented);
}
}
else if (tupleType == "RGB" &&
channelCount == 3)
{
switch (maxValue)
{
case 255:
format = PixelFormat_RGB24;
bytesPerChannel = 1;
return;
case 65535:
format = PixelFormat_RGB48;
bytesPerChannel = 2;
return;
default:
throw OrthancException(ErrorCode_NotImplemented);
}
}
else
{
throw OrthancException(ErrorCode_NotImplemented);
}
}
typedef std::map<std::string, std::string> Parameters;
static std::string LookupStringParameter(const Parameters& parameters,
const std::string& key)
{
Parameters::const_iterator found = parameters.find(key);
if (found == parameters.end())
{
throw OrthancException(ErrorCode_BadFileFormat);
}
else
{
return found->second;
}
}
static unsigned int LookupIntegerParameter(const Parameters& parameters,
const std::string& key)
{
try
{
int value = boost::lexical_cast<int>(LookupStringParameter(parameters, key));
if (value < 0)
{
throw OrthancException(ErrorCode_BadFileFormat);
}
else
{
return static_cast<unsigned int>(value);
}
}
catch (boost::bad_lexical_cast&)
{
throw OrthancException(ErrorCode_BadFileFormat);
}
}
void PamReader::ParseContent()
{
static const std::string headerDelimiter = "ENDHDR\n";
boost::iterator_range<std::string::const_iterator> headerRange =
boost::algorithm::find_first(content_, headerDelimiter);
if (!headerRange)
{
throw OrthancException(ErrorCode_BadFileFormat);
}
std::string header(static_cast<const std::string&>(content_).begin(), headerRange.begin());
std::vector<std::string> lines;
Toolbox::TokenizeString(lines, header, '\n');
if (lines.size() < 2 ||
lines.front() != "P7" ||
!lines.back().empty())
{
throw OrthancException(ErrorCode_BadFileFormat);
}
Parameters parameters;
for (size_t i = 1; i + 1 < lines.size(); i++)
{
std::vector<std::string> tokens;
Toolbox::TokenizeString(tokens, lines[i], ' ');
if (tokens.size() != 2)
{
throw OrthancException(ErrorCode_BadFileFormat);
}
else
{
parameters[tokens[0]] = tokens[1];
}
}
const unsigned int width = LookupIntegerParameter(parameters, "WIDTH");
const unsigned int height = LookupIntegerParameter(parameters, "HEIGHT");
const unsigned int channelCount = LookupIntegerParameter(parameters, "DEPTH");
const unsigned int maxValue = LookupIntegerParameter(parameters, "MAXVAL");
const std::string tupleType = LookupStringParameter(parameters, "TUPLTYPE");
unsigned int bytesPerChannel;
PixelFormat format;
GetPixelFormat(format, bytesPerChannel, maxValue, channelCount, tupleType);
unsigned int pitch = width * channelCount * bytesPerChannel;
if (content_.size() != header.size() + headerDelimiter.size() + pitch * height)
{
throw OrthancException(ErrorCode_BadFileFormat);
}
size_t offset = content_.size() - pitch * height;
{
intptr_t bufferAddr = reinterpret_cast<intptr_t>(&content_[offset]);
if((bufferAddr % 8) == 0)
LOG(TRACE) << "PamReader::ParseContent() image address = " << bufferAddr;
else
LOG(TRACE) << "PamReader::ParseContent() image address = " << bufferAddr << " (not a multiple of 8!)";
}
// if we want to enforce alignment, we need to use a freshly allocated
// buffer, since we have no alignment guarantees on the original one
if (enforceAligned_)
{
if (alignedImageBuffer_ != NULL)
free(alignedImageBuffer_);
alignedImageBuffer_ = malloc(pitch * height);
memcpy(alignedImageBuffer_, &content_[offset], pitch* height);
content_ = "";
AssignWritable(format, width, height, pitch, alignedImageBuffer_);
}
else
{
AssignWritable(format, width, height, pitch, &content_[offset]);
}
// Byte swapping if needed
if (bytesPerChannel != 1 &&
bytesPerChannel != 2)
{
throw OrthancException(ErrorCode_NotImplemented);
}
if (Toolbox::DetectEndianness() == Endianness_Little &&
bytesPerChannel == 2)
{
for (unsigned int h = 0; h < height; ++h)
{
uint16_t* pixel = reinterpret_cast<uint16_t*>(GetRow(h));
for (unsigned int w = 0; w < width; ++w, ++pixel)
{
/**
* This is Little-Endian computer, and PAM uses
* Big-Endian. Need to do a 16-bit swap. We DON'T use
* "htobe16()", as the latter only works if the "pixel"
* pointer is 16-bit aligned (which is not the case if
* "offset" is an odd number), and the trick that was used
* in Orthanc <= 1.8.0 (i.e. make a "memcpy()" to a local
* uint16_t variable) doesn't seem work for WebAssembly. We
* thus use a plain old C implementation. Check out issue
* #99: https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=99
*
* Here is the crash log on WebAssembly (2019-08-05):
*
* Uncaught abort(alignment fault) at Error
* at jsStackTrace
* at stackTrace
* at abort
* at alignfault
* at SAFE_HEAP_LOAD_i32_2_2 (wasm-function[251132]:39)
* at __ZN7Orthanc9PamReader12ParseContentEv (wasm-function[11457]:8088)
**/
uint8_t* srcdst = reinterpret_cast<uint8_t*>(pixel);
uint8_t tmp = srcdst[0];
srcdst[0] = srcdst[1];
srcdst[1] = tmp;
}
}
}
}
PamReader::PamReader(bool enforceAligned) :
enforceAligned_(enforceAligned),
alignedImageBuffer_(NULL)
{
}
#if ORTHANC_SANDBOXED == 0
void PamReader::ReadFromFile(const std::string& filename)
{
SystemToolbox::ReadFile(content_, filename);
ParseContent();
}
#endif
void PamReader::ReadFromMemory(const std::string& buffer)
{
content_ = buffer;
ParseContent();
}
void PamReader::ReadFromMemory(const void* buffer,
size_t size)
{
content_.assign(reinterpret_cast<const char*>(buffer), size);
ParseContent();
}
PamReader::~PamReader()
{
if (alignedImageBuffer_ != NULL)
{
free(alignedImageBuffer_);
}
}
}