changeset 2705:5c18a22cb981

fix portability of PAM reader/writer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 06 Jul 2018 16:33:03 +0200
parents b71c59312bae
children 0511feaf0ec2
files Core/Images/PamReader.cpp Core/Images/PamReader.h Core/Images/PamWriter.cpp Core/Images/PamWriter.h
diffstat 4 files changed, 207 insertions(+), 195 deletions(-) [+]
line wrap: on
line diff
--- a/Core/Images/PamReader.cpp	Fri Jul 06 15:18:53 2018 +0200
+++ b/Core/Images/PamReader.cpp	Fri Jul 06 16:33:03 2018 +0200
@@ -38,149 +38,204 @@
 #include "../OrthancException.h"
 #include "../Toolbox.h"
 
-#include <istream>
-#include <sstream>
-#include <fstream>
-
 #if ORTHANC_SANDBOXED == 0
 #  include "../SystemToolbox.h"
 #endif
 
-#include <string.h>
+#include <boost/algorithm/string/find.hpp>
+#include <boost/lexical_cast.hpp>
+
 
 namespace Orthanc
 {
-  namespace
+  static void GetPixelFormat(PixelFormat& format,
+                             unsigned int& bytesPerChannel,
+                             const unsigned int& maxValue,
+                             const unsigned int& channelCount,
+                             const std::string& tupleType)
   {
-    void GetPixelFormat(PixelFormat& format, unsigned int& bytesPerChannel, const unsigned int& maxValue, const unsigned int& channelCount, const char* tupleType)
+    if (tupleType == "GRAYSCALE" &&
+        channelCount == 1)
     {
-      if (strcmp(tupleType, "GRAYSCALE") == 0 && channelCount == 1)
+      switch (maxValue)
       {
-        if (maxValue == 255)
-        {
+        case 255:
           format = PixelFormat_Grayscale8;
           bytesPerChannel = 1;
           return;
-        }
-        else if (maxValue == 65535)
-        {
+
+        case 65535:
           format = PixelFormat_Grayscale16;
           bytesPerChannel = 2;
           return;
-        }
+
+        default:
+          throw OrthancException(ErrorCode_NotImplemented);
       }
-      else if (strcmp(tupleType, "RGB") == 0 && channelCount == 3)
+    }
+    else if (tupleType == "RGB" &&
+             channelCount == 3)
+    {
+      switch (maxValue)
       {
-        if (maxValue == 255)
-        {
+        case 255:
           format = PixelFormat_RGB24;
           bytesPerChannel = 1;
           return;
-        }
-        else if (maxValue == 65535)
-        {
+
+        case 65535:
           format = PixelFormat_RGB48;
           bytesPerChannel = 2;
           return;
-        }
+
+        default:
+          throw OrthancException(ErrorCode_NotImplemented);
       }
+    }
+    else
+    {
       throw OrthancException(ErrorCode_NotImplemented);
     }
+  }
 
-    void ReadDelimiter(std::istream& input, const char* expectedDelimiter)
+  
+  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())
     {
-      std::string delimiter;
-      input >> delimiter;
-      if (delimiter != expectedDelimiter)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+    else
+    {
+      return found->second;
     }
+  }
+  
 
-    unsigned int ReadKeyValueUint(std::istream& input, const char* expectedKey)
+  static unsigned int LookupIntegerParameter(const Parameters& parameters,
+                                             const std::string& key)
+  {
+    try
     {
-      std::string key;
-      unsigned int value;
-      input >> key >> value;
-      if (key != expectedKey)
+      int value = boost::lexical_cast<int>(LookupStringParameter(parameters, key));
+
+      if (value < 0)
       {
         throw OrthancException(ErrorCode_BadFileFormat);
       }
-      return value;
+      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 ReadKeyValueString(std::istream& input, const char* expectedKey)
+    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())
     {
-      std::string key;
-      std::string value;
-      input >> key >> value;
-      if (key != expectedKey)
+      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);
       }
-      return value;
+      else
+      {
+        parameters[tokens[0]] = tokens[1];
+      }
     }
-  }
 
-  void PamReader::ReadFromStream(std::istream& input)
-  {
-    ReadDelimiter(input, "P7");
-    unsigned int width = ReadKeyValueUint(input, "WIDTH");
-    unsigned int height = ReadKeyValueUint(input, "HEIGHT");
-    unsigned int channelCount = ReadKeyValueUint(input, "DEPTH");
-    unsigned int maxValue = ReadKeyValueUint(input, "MAXVAL");
-    std::string tupleType = ReadKeyValueString(input, "TUPLTYPE");
-    ReadDelimiter(input, "ENDHDR");
-    // skip last EOL
-    char tmp[16];
-    input.getline(tmp, 16);
+    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.c_str());
 
-    // read the pixels data
-    unsigned int sizeInBytes = width * height * channelCount * bytesPerChannel;
-    data_.reserve(sizeInBytes);
-    input.read(data_.data(), sizeInBytes);
+    unsigned int pitch = width * channelCount * bytesPerChannel;
+
+    if (content_.size() != header.size() + headerDelimiter.size() + pitch * height)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
 
-    AssignWritable(format, width, height, width * channelCount * bytesPerChannel, data_.data());
+    size_t offset = content_.size() - pitch * height;
+    AssignWritable(format, width, height, pitch, &content_[offset]);
 
-    // swap bytes
+    // Byte swapping if needed
+    if (bytesPerChannel != 1 &&
+        bytesPerChannel != 2)
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+    
     if (Toolbox::DetectEndianness() == Endianness_Little && bytesPerChannel == 2)
     {
-      uint16_t* pixel = NULL;
       for (unsigned int h = 0; h < height; ++h)
       {
-        pixel = reinterpret_cast<uint16_t*> (data_.data() + h * width * channelCount * bytesPerChannel);
-        for (unsigned int w = 0; w < (width * channelCount); ++w, ++pixel)
+        uint16_t* pixel = reinterpret_cast<uint16_t*>(GetRow(h));
+        
+        for (unsigned int w = 0; w < GetWidth(); ++w, ++pixel)
         {
           *pixel = htobe16(*pixel);
         }
       }
     }
-
   }
 
+  
 #if ORTHANC_SANDBOXED == 0
   void PamReader::ReadFromFile(const std::string& filename)
   {
-    std::ifstream inputStream(filename, std::ofstream::binary);
-    ReadFromStream(inputStream);
+    SystemToolbox::ReadFile(content_, filename);
+    ParseContent();
   }
 #endif
-
-  void PamReader::ReadFromMemory(const void* buffer,
-                                 size_t size)
-  {
-    std::istringstream inputStream(std::string(reinterpret_cast<const char*>(buffer), size));
-    ReadFromStream(inputStream);
-  }
+  
 
   void PamReader::ReadFromMemory(const std::string& buffer)
   {
-    std::istringstream inputStream(buffer);
-    ReadFromStream(inputStream);
+    content_ = buffer;
+    ParseContent();
   }
-
 }
--- a/Core/Images/PamReader.h	Fri Jul 06 15:18:53 2018 +0200
+++ b/Core/Images/PamReader.h	Fri Jul 06 16:33:03 2018 +0200
@@ -35,18 +35,12 @@
 
 #include "ImageAccessor.h"
 
-#include "../Enumerations.h"
-
-#include <vector>
-#include <stdint.h>
-#include <boost/shared_ptr.hpp>
-#include <boost/noncopyable.hpp>
-#include <istream>
-
 #if !defined(ORTHANC_SANDBOXED)
 #  error The macro ORTHANC_SANDBOXED must be defined
 #endif
 
+#include <boost/noncopyable.hpp>
+
 namespace Orthanc
 {
   class PamReader :
@@ -54,22 +48,15 @@
       public boost::noncopyable
   {
   private:
-    std::vector<char> data_;
+    void ParseContent();
+    
+    std::string content_;
 
   public:
-    PamReader() {}
-    virtual ~PamReader() {}
-
 #if ORTHANC_SANDBOXED == 0
     void ReadFromFile(const std::string& filename);
 #endif
 
-    void ReadFromMemory(const void* buffer,
-                        size_t size);
-
     void ReadFromMemory(const std::string& buffer);
-
-  protected:
-    void ReadFromStream(std::istream& input);
   };
 }
--- a/Core/Images/PamWriter.cpp	Fri Jul 06 15:18:53 2018 +0200
+++ b/Core/Images/PamWriter.cpp	Fri Jul 06 16:33:03 2018 +0200
@@ -34,136 +34,120 @@
 #include "../PrecompiledHeaders.h"
 #include "PamWriter.h"
 
-#include "../ChunkedBuffer.h"
 #include "../Endianness.h"
 #include "../OrthancException.h"
 #include "../Toolbox.h"
 
-#include <vector>
-#include <stdint.h>
-#include <iostream>
-#include <sstream>
-#include <fstream>
-
-#if ORTHANC_SANDBOXED == 0
-#  include "../SystemToolbox.h"
-#endif
+#include <boost/lexical_cast.hpp>
 
 
 namespace Orthanc
 {
-  namespace
+  static void GetPixelFormatInfo(const PixelFormat& format,
+                                 unsigned int& maxValue,
+                                 unsigned int& channelCount,
+                                 unsigned int& bytesPerChannel,
+                                 std::string& tupleType)
   {
-    void GetPixelFormatInfo(const PixelFormat& format, unsigned int& maxValue, unsigned int& channelCount, unsigned int& bytesPerChannel, const char*& tupleType) {
-      maxValue = 255;
-      channelCount = 1;
-      bytesPerChannel = 1;
-      tupleType = NULL;
-
-      switch (format) {
+    switch (format)
+    {
       case PixelFormat_Grayscale8:
         maxValue = 255;
         channelCount = 1;
         bytesPerChannel = 1;
         tupleType = "GRAYSCALE";
         break;
+          
       case PixelFormat_Grayscale16:
         maxValue = 65535;
         channelCount = 1;
         bytesPerChannel = 2;
         tupleType = "GRAYSCALE";
         break;
+
       case PixelFormat_RGB24:
         maxValue = 255;
         channelCount = 3;
         bytesPerChannel = 1;
         tupleType = "RGB";
         break;
+
       case PixelFormat_RGB48:
         maxValue = 255;
         channelCount = 3;
         bytesPerChannel = 2;
         tupleType = "RGB";
         break;
+
       default:
         throw OrthancException(ErrorCode_NotImplemented);
-      }
-
     }
-
-    void WriteToStream(std::ostream& output,
-                       unsigned int width,
-                       unsigned int height,
-                       unsigned int pitch,
-                       PixelFormat format,
-                       const void* buffer)
-    {
-      unsigned int maxValue = 255;
-      unsigned int channelCount = 1;
-      unsigned int bytesPerChannel = 1;
-      const char* tupleType = "GRAYSCALE";
-      GetPixelFormatInfo(format, maxValue, channelCount, bytesPerChannel, tupleType);
-
-      output << "P7" << "\n";
-      output << "WIDTH " << width << "\n";
-      output << "HEIGHT " << height << "\n";
-      output << "DEPTH " << channelCount << "\n";
-      output << "MAXVAL " << maxValue << "\n";
-      output << "TUPLTYPE " << tupleType << "\n";
-      output << "ENDHDR" << "\n";
-
-      if (Toolbox::DetectEndianness() == Endianness_Little && bytesPerChannel == 2)
-      {
-        uint16_t tmp;
-        const uint16_t* pixel = NULL;
-        for (unsigned int h = 0; h < height; ++h)
-        {
-          pixel = reinterpret_cast<const uint16_t*> (reinterpret_cast<const uint8_t*>(buffer) + h * pitch);
-          for (unsigned int w = 0; w < (width * channelCount); ++w, ++pixel)
-          {
-            tmp = htobe16(*pixel);
-            output.write(reinterpret_cast<const char*>(&tmp), 2);
-          }
-        }
-      }
-      else
-      {
-        for (unsigned int h = 0; h < height; ++h)
-        {
-          output.write(reinterpret_cast<const char*>(reinterpret_cast<const uint8_t*>(buffer) + h * pitch), channelCount * bytesPerChannel * width);
-        }
-      }
-
-    }
-
   }
 
-#if ORTHANC_SANDBOXED == 0
-  void PamWriter::WriteToFileInternal(const std::string& filename,
-                                      unsigned int width,
-                                      unsigned int height,
-                                      unsigned int pitch,
-                                      PixelFormat format,
-                                      const void* buffer)
-  {
-    std::ofstream outfile (filename, std::ofstream::binary);
-
-    WriteToStream(outfile, width, height, pitch, format, buffer);
-    outfile.close();
-  }
-#endif
-
-
-  void PamWriter::WriteToMemoryInternal(std::string& output,
+      
+  void PamWriter::WriteToMemoryInternal(std::string& target,
                                         unsigned int width,
                                         unsigned int height,
-                                        unsigned int pitch,
+                                        unsigned int sourcePitch,
                                         PixelFormat format,
                                         const void* buffer)
   {
-    std::ostringstream outStream;  // todo: try to write directly in output and avoid copy
+    unsigned int maxValue, channelCount, bytesPerChannel;
+    std::string tupleType;
+    GetPixelFormatInfo(format, maxValue, channelCount, bytesPerChannel, tupleType);
+
+    target = (std::string("P7") +
+              std::string("\nWIDTH ")  + boost::lexical_cast<std::string>(width) + 
+              std::string("\nHEIGHT ") + boost::lexical_cast<std::string>(height) + 
+              std::string("\nDEPTH ")  + boost::lexical_cast<std::string>(channelCount) + 
+              std::string("\nMAXVAL ") + boost::lexical_cast<std::string>(maxValue) + 
+              std::string("\nTUPLTYPE ") + tupleType + 
+              std::string("\nENDHDR\n"));
+
+    if (bytesPerChannel != 1 &&
+        bytesPerChannel != 2)
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    size_t targetPitch = channelCount * bytesPerChannel * width;
+    size_t offset = target.size();
+
+    target.resize(offset + targetPitch * height);
+
+    assert(target.size() != 0);
 
-    WriteToStream(outStream, width, height, pitch, format, buffer);
-    output = outStream.str();
+    if (Toolbox::DetectEndianness() == Endianness_Little &&
+        bytesPerChannel == 2)
+    {
+      // Byte swapping
+      for (unsigned int h = 0; h < height; ++h)
+      {
+        const uint16_t* p = reinterpret_cast<const uint16_t*>
+          (reinterpret_cast<const uint8_t*>(buffer) + h * sourcePitch);
+        uint16_t* q = reinterpret_cast<uint16_t*>
+          (reinterpret_cast<uint8_t*>(&target[offset]) + h * targetPitch);
+        
+        for (unsigned int w = 0; w < width * channelCount; ++w)
+        {
+          *q = htobe16(*p);
+          p++;
+          q++;
+        }
+      }
+    }
+    else
+    {
+      // Either "bytesPerChannel == 1" (and endianness is not
+      // relevant), or we run on a big endian architecture (and no
+      // byte swapping is necessary, as PAM uses big endian)
+      
+      for (unsigned int h = 0; h < height; ++h)
+      {
+        const void* p = reinterpret_cast<const uint8_t*>(buffer) + h * sourcePitch;
+        void* q = reinterpret_cast<uint8_t*>(&target[offset]) + h * targetPitch;
+        memcpy(q, p, targetPitch);
+      }
+    }
   }
 }
--- a/Core/Images/PamWriter.h	Fri Jul 06 15:18:53 2018 +0200
+++ b/Core/Images/PamWriter.h	Fri Jul 06 16:33:03 2018 +0200
@@ -41,25 +41,11 @@
   class PamWriter : public IImageWriter
   {
   protected:
-#if ORTHANC_SANDBOXED == 0
-    virtual void WriteToFileInternal(const std::string& filename,
-                                     unsigned int width,
-                                     unsigned int height,
-                                     unsigned int pitch,
-                                     PixelFormat format,
-                                     const void* buffer);
-#endif
-
-    virtual void WriteToMemoryInternal(std::string& png,
+    virtual void WriteToMemoryInternal(std::string& target,
                                        unsigned int width,
                                        unsigned int height,
                                        unsigned int pitch,
                                        PixelFormat format,
                                        const void* buffer);
-
-  public:
-    PamWriter() {}
-
-    virtual ~PamWriter() {}
   };
 }