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

326 lines
7.0 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 "PngReader.h"
#include "../OrthancException.h"
#include "../Toolbox.h"
#if ORTHANC_SANDBOXED == 0
# include "../SystemToolbox.h"
#endif
#include <png.h>
#include <string.h> // For memcpy()
namespace Orthanc
{
#if ORTHANC_SANDBOXED == 0
namespace
{
struct FileRabi
{
FILE* fp_;
explicit FileRabi(const char* filename)
{
fp_ = SystemToolbox::OpenFile(filename, FileMode_ReadBinary);
if (!fp_)
{
throw OrthancException(ErrorCode_InexistentFile);
}
}
~FileRabi()
{
if (fp_)
{
fclose(fp_);
}
}
};
}
#endif
struct PngReader::PngRabi
{
png_structp png_;
png_infop info_;
png_infop endInfo_;
void Destruct()
{
if (png_)
{
png_destroy_read_struct(&png_, &info_, &endInfo_);
png_ = NULL;
info_ = NULL;
endInfo_ = NULL;
}
}
PngRabi() :
png_(NULL),
info_(NULL),
endInfo_(NULL)
{
png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_)
{
throw OrthancException(ErrorCode_NotEnoughMemory);
}
info_ = png_create_info_struct(png_);
if (!info_)
{
png_destroy_read_struct(&png_, NULL, NULL);
throw OrthancException(ErrorCode_NotEnoughMemory);
}
endInfo_ = png_create_info_struct(png_);
if (!info_)
{
png_destroy_read_struct(&png_, &info_, NULL);
throw OrthancException(ErrorCode_NotEnoughMemory);
}
}
~PngRabi()
{
Destruct();
}
static void MemoryCallback(png_structp png_ptr,
png_bytep data,
png_size_t size);
};
void PngReader::CheckHeader(const void* header)
{
int is_png = !png_sig_cmp((png_bytep) header, 0, 8);
if (!is_png)
{
throw OrthancException(ErrorCode_BadFileFormat);
}
}
PngReader::PngReader()
{
}
void PngReader::Read(PngRabi& rabi)
{
png_set_sig_bytes(rabi.png_, 8);
png_read_info(rabi.png_, rabi.info_);
png_uint_32 width, height;
int bit_depth, color_type, interlace_type;
int compression_type, filter_method;
// get size and bit-depth of the PNG-image
png_get_IHDR(rabi.png_, rabi.info_,
&width, &height,
&bit_depth, &color_type, &interlace_type,
&compression_type, &filter_method);
PixelFormat format;
unsigned int pitch;
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 8)
{
format = PixelFormat_Grayscale8;
pitch = width;
}
else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 16)
{
format = PixelFormat_Grayscale16;
pitch = 2 * width;
if (Toolbox::DetectEndianness() == Endianness_Little)
{
png_set_swap(rabi.png_);
}
}
else if (color_type == PNG_COLOR_TYPE_RGB && bit_depth == 8)
{
format = PixelFormat_RGB24;
pitch = 3 * width;
}
else if (color_type == PNG_COLOR_TYPE_RGBA && bit_depth == 8)
{
format = PixelFormat_RGBA32;
pitch = 4 * width;
}
else if (color_type == PNG_COLOR_TYPE_RGBA && bit_depth == 16)
{
format = PixelFormat_RGBA64;
pitch = 8 * width;
if (Toolbox::DetectEndianness() == Endianness_Little)
{
png_set_swap(rabi.png_);
}
}
else
{
throw OrthancException(ErrorCode_NotImplemented);
}
data_.resize(height * pitch);
if (height == 0 || width == 0)
{
// Empty image, we are done
AssignEmpty(format);
return;
}
png_read_update_info(rabi.png_, rabi.info_);
std::vector<png_bytep> rows(height);
for (size_t i = 0; i < height; i++)
{
rows[i] = &data_[0] + i * pitch;
}
png_read_image(rabi.png_, &rows[0]);
AssignWritable(format, width, height, pitch, &data_[0]);
}
#if ORTHANC_SANDBOXED == 0
void PngReader::ReadFromFile(const std::string& filename)
{
FileRabi f(filename.c_str());
char header[8];
if (fread(header, 1, 8, f.fp_) != 8)
{
throw OrthancException(ErrorCode_BadFileFormat);
}
CheckHeader(header);
PngRabi rabi;
if (setjmp(png_jmpbuf(rabi.png_)))
{
rabi.Destruct();
throw OrthancException(ErrorCode_BadFileFormat);
}
png_init_io(rabi.png_, f.fp_);
Read(rabi);
}
#endif
namespace
{
struct MemoryBuffer
{
const uint8_t* buffer_;
size_t size_;
size_t pos_;
bool ok_;
};
}
void PngReader::PngRabi::MemoryCallback(png_structp png_ptr,
png_bytep outBytes,
png_size_t byteCountToRead)
{
MemoryBuffer* from = reinterpret_cast<MemoryBuffer*>(png_get_io_ptr(png_ptr));
if (!from->ok_)
{
return;
}
if (from->pos_ + byteCountToRead > from->size_)
{
from->ok_ = false;
return;
}
memcpy(outBytes, from->buffer_ + from->pos_, byteCountToRead);
from->pos_ += byteCountToRead;
}
void PngReader::ReadFromMemory(const void* buffer,
size_t size)
{
if (size < 8)
{
throw OrthancException(ErrorCode_BadFileFormat);
}
CheckHeader(buffer);
PngRabi rabi;
if (setjmp(png_jmpbuf(rabi.png_)))
{
rabi.Destruct();
throw OrthancException(ErrorCode_BadFileFormat);
}
MemoryBuffer tmp;
tmp.buffer_ = reinterpret_cast<const uint8_t*>(buffer) + 8; // We skip the header
tmp.size_ = size - 8;
tmp.pos_ = 0;
tmp.ok_ = true;
png_set_read_fn(rabi.png_, &tmp, PngRabi::MemoryCallback);
Read(rabi);
if (!tmp.ok_)
{
throw OrthancException(ErrorCode_BadFileFormat);
}
}
void PngReader::ReadFromMemory(const std::string& buffer)
{
if (buffer.size() != 0)
{
ReadFromMemory(&buffer[0], buffer.size());
}
else
{
ReadFromMemory(NULL, 0);
}
}
}