From 693a68844c4763abc9a07d8d545792ee8dfbd271 Mon Sep 17 00:00:00 2001
From: David Capello <davidcapello@gmail.com>
Date: Wed, 26 Oct 2016 19:37:42 -0300
Subject: [PATCH] New docio-lib to detect file format by content (fix #776)

---
 src/CMakeLists.txt                    |   1 +
 src/README.md                         |   4 +-
 src/app/CMakeLists.txt                |   1 +
 src/app/file/ase_format.cpp           |   1 +
 src/app/file/bmp_format.cpp           |   3 +-
 src/app/file/file.cpp                 |  26 ++---
 src/app/file/file_format.cpp          |   7 +-
 src/app/file/file_format.h            |   6 +-
 src/app/file/file_formats_manager.cpp |  22 ++--
 src/app/file/file_formats_manager.h   |   6 +-
 src/app/file/fli_format.cpp           |   3 +-
 src/app/file/gif_format.cpp           |   3 +-
 src/app/file/ico_format.cpp           |   3 +-
 src/app/file/jpeg_format.cpp          |   3 +-
 src/app/file/palette_file.cpp         |  89 +++++++++-------
 src/app/file/pcx_format.cpp           |   3 +-
 src/app/file/pixly_format.cpp         |   1 +
 src/app/file/png_format.cpp           |   1 +
 src/app/file/tga_format.cpp           |   3 +-
 src/app/file/webp_format.cpp          |   3 +-
 src/doc/LICENSE.txt                   |   2 +-
 src/doc/README.md                     |   2 +-
 src/docio/CMakeLists.txt              |   9 ++
 src/{iff => docio}/LICENSE.txt        |   2 +-
 src/docio/README.md                   |   4 +
 src/docio/detect_format.cpp           | 140 ++++++++++++++++++++++++++
 src/docio/detect_format.h             |  23 +++++
 src/docio/file_format.h               |  37 +++++++
 src/iff/README.md                     |   4 -
 29 files changed, 326 insertions(+), 86 deletions(-)
 create mode 100644 src/docio/CMakeLists.txt
 rename src/{iff => docio}/LICENSE.txt (96%)
 create mode 100644 src/docio/README.md
 create mode 100644 src/docio/detect_format.cpp
 create mode 100644 src/docio/detect_format.h
 create mode 100644 src/docio/file_format.h
 delete mode 100644 src/iff/README.md

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 65ee95ee1..e59d915c3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -109,6 +109,7 @@ add_subdirectory(gen)
 add_subdirectory(gfx)
 add_subdirectory(net)
 add_subdirectory(render)
+add_subdirectory(docio)
 add_subdirectory(script)
 add_subdirectory(she)
 add_subdirectory(ui)
diff --git a/src/README.md b/src/README.md
index c67c5b937..99986a5e5 100644
--- a/src/README.md
+++ b/src/README.md
@@ -47,11 +47,11 @@ because they don't depend on any other component.
 
 ## Level 4
 
-  * [iff](iff/) (base, doc, render): Image File Formats library (load/save documents).
+  * [docio](docio/) (base, flic): Load/save documents.
 
 ## Level 5
 
-  * [app](app/) (allegro, base, doc, filters, fixmath, gfx, iff, pen, render, scripting, she, ui, undo, updater, webserver)
+  * [app](app/) (allegro, base, doc, docio, filters, fixmath, flic, gfx, pen, render, scripting, she, ui, undo, updater, webserver)
 
 ## Level 6
 
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index 8ddfa44cd..9bcf8a0fc 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -451,6 +451,7 @@ target_link_libraries(app-lib
   clip
   css-lib
   doc-lib
+  docio-lib
   filters-lib
   fixmath-lib
   flic-lib
diff --git a/src/app/file/ase_format.cpp b/src/app/file/ase_format.cpp
index 1f1f8aa42..1db1f49a3 100644
--- a/src/app/file/ase_format.cpp
+++ b/src/app/file/ase_format.cpp
@@ -145,6 +145,7 @@ private:
 class AseFormat : public FileFormat {
   const char* onGetName() const override { return "ase"; }
   const char* onGetExtensions() const override { return "ase,aseprite"; }
+  docio::FileFormat onGetDocioFormat() const override { return docio::FileFormat::ASE_ANIMATION; }
   int onGetFlags() const override {
     return
       FILE_SUPPORT_LOAD |
diff --git a/src/app/file/bmp_format.cpp b/src/app/file/bmp_format.cpp
index 64cc23542..be82a6869 100644
--- a/src/app/file/bmp_format.cpp
+++ b/src/app/file/bmp_format.cpp
@@ -1,5 +1,5 @@
 // Aseprite
-// Copyright (C) 2001-2015  David Capello
+// Copyright (C) 2001-2016  David Capello
 //
 // This program is distributed under the terms of
 // the End-User License Agreement for Aseprite.
@@ -45,6 +45,7 @@ class BmpFormat : public FileFormat {
 
   const char* onGetName() const override { return "bmp"; }
   const char* onGetExtensions() const override { return "bmp"; }
+  docio::FileFormat onGetDocioFormat() const override { return docio::FileFormat::BMP_IMAGE; }
   int onGetFlags() const override {
     return
       FILE_SUPPORT_LOAD |
diff --git a/src/app/file/file.cpp b/src/app/file/file.cpp
index 308a73785..7b19f78f6 100644
--- a/src/app/file/file.cpp
+++ b/src/app/file/file.cpp
@@ -28,6 +28,7 @@
 #include "base/shared_ptr.h"
 #include "base/string.h"
 #include "doc/doc.h"
+#include "docio/detect_format.h"
 #include "render/quantization.h"
 #include "render/render.h"
 #include "ui/alert.h"
@@ -127,10 +128,7 @@ FileOp* FileOp::createLoadDocumentOperation(Context* context, const char* filena
   if (!fop)
     return nullptr;
 
-  // Get the extension of the filename (in lower case)
-  std::string extension = base::string_to_lower(base::get_file_extension(filename));
-
-  LOG("Loading file \"%s\" (%s)\n", filename, extension.c_str());
+  LOG("Loading file \"%s\"\n", filename);
 
   // Does file exist?
   if (!base::is_file(filename)) {
@@ -139,12 +137,12 @@ FileOp* FileOp::createLoadDocumentOperation(Context* context, const char* filena
   }
 
   // Get the format through the extension of the filename
-  fop->m_format = FileFormatsManager::instance()
-    ->getFileFormatByExtension(extension.c_str());
-
+  fop->m_format = FileFormatsManager::instance()->getFileFormat(
+    docio::detect_format(filename));
   if (!fop->m_format ||
       !fop->m_format->support(FILE_SUPPORT_LOAD)) {
-    fop->setError("%s can't load \"%s\" files\n", PACKAGE, extension.c_str());
+    fop->setError("%s can't load \"%s\" file (\"%s\")\n", PACKAGE,
+                  filename, base::get_file_extension(filename).c_str());
     goto done;
   }
 
@@ -228,17 +226,15 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
   fop->m_document = const_cast<Document*>(document);
 
   // Get the extension of the filename (in lower case)
-  std::string extension = base::string_to_lower(base::get_file_extension(filename));
-
-  LOG("Saving document \"%s\" (%s)\n", filename, extension.c_str());
+  LOG("Saving document \"%s\"\n", filename);
 
   // Get the format through the extension of the filename
-  fop->m_format = FileFormatsManager::instance()
-    ->getFileFormatByExtension(extension.c_str());
-
+  fop->m_format = FileFormatsManager::instance()->getFileFormat(
+    docio::detect_format_by_file_extension(filename));
   if (!fop->m_format ||
       !fop->m_format->support(FILE_SUPPORT_SAVE)) {
-    fop->setError("%s can't save \"%s\" files\n", PACKAGE, extension.c_str());
+    fop->setError("%s can't save \"%s\" file (\"%s\")\n", PACKAGE,
+                  filename, base::get_file_extension(filename).c_str());
     return fop.release();
   }
 
diff --git a/src/app/file/file_format.cpp b/src/app/file/file_format.cpp
index 39f81735c..4c3a86632 100644
--- a/src/app/file/file_format.cpp
+++ b/src/app/file/file_format.cpp
@@ -1,5 +1,5 @@
 // Aseprite
-// Copyright (C) 2001-2015  David Capello
+// Copyright (C) 2001-2016  David Capello
 //
 // This program is distributed under the terms of
 // the End-User License Agreement for Aseprite.
@@ -33,6 +33,11 @@ const char* FileFormat::extensions() const
   return onGetExtensions();
 }
 
+docio::FileFormat FileFormat::docioFormat() const
+{
+  return onGetDocioFormat();
+}
+
 bool FileFormat::load(FileOp* fop)
 {
   ASSERT(support(FILE_SUPPORT_LOAD));
diff --git a/src/app/file/file_format.h b/src/app/file/file_format.h
index ff74eb899..f9cd4cd56 100644
--- a/src/app/file/file_format.h
+++ b/src/app/file/file_format.h
@@ -1,5 +1,5 @@
 // Aseprite
-// Copyright (C) 2001-2015  David Capello
+// Copyright (C) 2001-2016  David Capello
 //
 // This program is distributed under the terms of
 // the End-User License Agreement for Aseprite.
@@ -9,6 +9,7 @@
 #pragma once
 
 #include "base/shared_ptr.h"
+#include "docio/file_format.h"
 
 #include <vector>
 
@@ -44,6 +45,8 @@ namespace app {
 
     const char* name() const;       // File format name
     const char* extensions() const; // Extensions (e.g. "jpeg,jpg")
+    docio::FileFormat docioFormat() const;
+
     bool load(FileOp* fop);
 #ifdef ENABLE_SAVE
     bool save(FileOp* fop);
@@ -70,6 +73,7 @@ namespace app {
   protected:
     virtual const char* onGetName() const = 0;
     virtual const char* onGetExtensions() const = 0;
+    virtual docio::FileFormat onGetDocioFormat() const = 0;
     virtual int onGetFlags() const = 0;
 
     virtual bool onLoad(FileOp* fop) = 0;
diff --git a/src/app/file/file_formats_manager.cpp b/src/app/file/file_formats_manager.cpp
index b77a6ba6c..134b23771 100644
--- a/src/app/file/file_formats_manager.cpp
+++ b/src/app/file/file_formats_manager.cpp
@@ -1,5 +1,5 @@
 // Aseprite
-// Copyright (C) 2001-2015  David Capello
+// Copyright (C) 2001-2016  David Capello
 //
 // This program is distributed under the terms of
 // the End-User License Agreement for Aseprite.
@@ -13,6 +13,7 @@
 #include "app/file/file_format.h"
 #include "app/file/format_options.h"
 #include "base/string.h"
+#include "docio/detect_format.h"
 
 #include <algorithm>
 #include <cstring>
@@ -93,21 +94,12 @@ FileFormatsList::iterator FileFormatsManager::end()
   return m_formats.end();
 }
 
-FileFormat* FileFormatsManager::getFileFormatByExtension(const char* extension) const
+FileFormat* FileFormatsManager::getFileFormat(const docio::FileFormat docioFormat) const
 {
-  char buf[512], *tok;
-
-  for (FileFormat* ff : m_formats) {
-    std::strcpy(buf, ff->extensions());
-
-    for (tok=std::strtok(buf, ","); tok;
-         tok=std::strtok(NULL, ",")) {
-      if (base::utf8_icmp(extension, tok) == 0)
-        return ff;
-    }
-  }
-
-  return NULL;
+  for (FileFormat* ff : m_formats)
+    if (ff->docioFormat() == docioFormat)
+      return ff;
+  return nullptr;
 }
 
 } // namespace app
diff --git a/src/app/file/file_formats_manager.h b/src/app/file/file_formats_manager.h
index 3eb04e626..e337c5341 100644
--- a/src/app/file/file_formats_manager.h
+++ b/src/app/file/file_formats_manager.h
@@ -1,5 +1,5 @@
 // Aseprite
-// Copyright (C) 2001-2015  David Capello
+// Copyright (C) 2001-2016  David Capello
 //
 // This program is distributed under the terms of
 // the End-User License Agreement for Aseprite.
@@ -8,6 +8,8 @@
 #define APP_FILE_FILE_FORMATS_MANAGER_H_INCLUDED
 #pragma once
 
+#include "docio/file_format.h"
+
 #include <vector>
 
 namespace app {
@@ -34,7 +36,7 @@ namespace app {
     FileFormatsList::iterator begin();
     FileFormatsList::iterator end();
 
-    FileFormat* getFileFormatByExtension(const char* extension) const;
+    FileFormat* getFileFormat(const docio::FileFormat docioFormat) const;
 
   private:
     // Register one format.
diff --git a/src/app/file/fli_format.cpp b/src/app/file/fli_format.cpp
index e0a16fd56..9831f80d1 100644
--- a/src/app/file/fli_format.cpp
+++ b/src/app/file/fli_format.cpp
@@ -1,5 +1,5 @@
 // Aseprite
-// Copyright (C) 2001-2015  David Capello
+// Copyright (C) 2001-2016  David Capello
 //
 // This program is distributed under the terms of
 // the End-User License Agreement for Aseprite.
@@ -27,6 +27,7 @@ using namespace base;
 class FliFormat : public FileFormat {
   const char* onGetName() const override { return "flc"; }
   const char* onGetExtensions() const  override{ return "flc,fli"; }
+  docio::FileFormat onGetDocioFormat() const override { return docio::FileFormat::FLIC_ANIMATION; }
   int onGetFlags() const override {
     return
       FILE_SUPPORT_LOAD |
diff --git a/src/app/file/gif_format.cpp b/src/app/file/gif_format.cpp
index 20ded906c..e451c38fc 100644
--- a/src/app/file/gif_format.cpp
+++ b/src/app/file/gif_format.cpp
@@ -1,5 +1,5 @@
 // Aseprite
-// Copyright (C) 2001-2015  David Capello
+// Copyright (C) 2001-2016  David Capello
 //
 // This program is distributed under the terms of
 // the End-User License Agreement for Aseprite.
@@ -63,6 +63,7 @@ class GifFormat : public FileFormat {
 
   const char* onGetName() const override { return "gif"; }
   const char* onGetExtensions() const override { return "gif"; }
+  docio::FileFormat onGetDocioFormat() const override { return docio::FileFormat::GIF_ANIMATION; }
   int onGetFlags() const override {
     return
       FILE_SUPPORT_LOAD |
diff --git a/src/app/file/ico_format.cpp b/src/app/file/ico_format.cpp
index 8fbcb6438..1dbce9adc 100644
--- a/src/app/file/ico_format.cpp
+++ b/src/app/file/ico_format.cpp
@@ -1,5 +1,5 @@
 // Aseprite
-// Copyright (C) 2001-2015  David Capello
+// Copyright (C) 2001-2016  David Capello
 //
 // This program is distributed under the terms of
 // the End-User License Agreement for Aseprite.
@@ -26,6 +26,7 @@ using namespace base;
 class IcoFormat : public FileFormat {
   const char* onGetName() const override { return "ico"; }
   const char* onGetExtensions() const override { return "ico"; }
+  docio::FileFormat onGetDocioFormat() const override { return docio::FileFormat::ICO_IMAGES; }
   int onGetFlags() const override {
     return
       FILE_SUPPORT_LOAD |
diff --git a/src/app/file/jpeg_format.cpp b/src/app/file/jpeg_format.cpp
index 930111f8d..71796c64c 100644
--- a/src/app/file/jpeg_format.cpp
+++ b/src/app/file/jpeg_format.cpp
@@ -1,5 +1,5 @@
 // Aseprite
-// Copyright (C) 2001-2015  David Capello
+// Copyright (C) 2001-2016  David Capello
 //
 // This program is distributed under the terms of
 // the End-User License Agreement for Aseprite.
@@ -42,6 +42,7 @@ class JpegFormat : public FileFormat {
 
   const char* onGetName() const override { return "jpeg"; }
   const char* onGetExtensions() const override { return "jpeg,jpg"; }
+  docio::FileFormat onGetDocioFormat() const override { return docio::FileFormat::JPEG_IMAGE; }
   int onGetFlags() const override {
     return
       FILE_SUPPORT_LOAD |
diff --git a/src/app/file/palette_file.cpp b/src/app/file/palette_file.cpp
index ab6d164f0..5c11c749b 100644
--- a/src/app/file/palette_file.cpp
+++ b/src/app/file/palette_file.cpp
@@ -24,6 +24,7 @@
 #include "doc/layer.h"
 #include "doc/palette.h"
 #include "doc/sprite.h"
+#include "docio/detect_format.h"
 
 #include <cstring>
 
@@ -45,26 +46,34 @@ std::string get_writable_palette_extensions()
   return buf;
 }
 
-Palette* load_palette(const char *filename)
+Palette* load_palette(const char* filename)
 {
-  std::string ext = base::string_to_lower(base::get_file_extension(filename));
-  Palette* pal = NULL;
+  docio::FileFormat docioFormat = docio::detect_format(filename);
+  Palette* pal = nullptr;
+
+  switch (docioFormat) {
+
+    case docio::FileFormat::COL_PALETTE:
+      pal = doc::file::load_col_file(filename);
+      break;
+
+    case docio::FileFormat::GPL_PALETTE:
+      pal = doc::file::load_gpl_file(filename);
+      break;
+
+    case docio::FileFormat::HEX_PALETTE:
+      pal = doc::file::load_hex_file(filename);
+      break;
+
+    case docio::FileFormat::PAL_PALETTE:
+      pal = doc::file::load_pal_file(filename);
+      break;
+
+    default: {
+      FileFormat* ff = FileFormatsManager::instance()->getFileFormat(docioFormat);
+      if (!ff || !ff->support(FILE_SUPPORT_LOAD))
+        break;
 
-  if (ext == "col") {
-    pal = doc::file::load_col_file(filename);
-  }
-  else if (ext == "gpl") {
-    pal = doc::file::load_gpl_file(filename);
-  }
-  else if (ext == "hex") {
-    pal = doc::file::load_hex_file(filename);
-  }
-  else if (ext == "pal") {
-    pal = doc::file::load_pal_file(filename);
-  }
-  else {
-    FileFormat* ff = FileFormatsManager::instance()->getFileFormatByExtension(ext.c_str());
-    if (ff && ff->support(FILE_SUPPORT_LOAD)) {
       base::UniquePtr<FileOp> fop(
         FileOp::createLoadDocumentOperation(
           nullptr, filename,
@@ -85,6 +94,7 @@ Palette* load_palette(const char *filename)
         delete fop->releaseDocument();
         fop->done();
       }
+      break;
     }
   }
 
@@ -94,26 +104,34 @@ Palette* load_palette(const char *filename)
   return pal;
 }
 
-bool save_palette(const char *filename, const Palette* pal, int columns)
+bool save_palette(const char* filename, const Palette* pal, int columns)
 {
-  std::string ext = base::string_to_lower(base::get_file_extension(filename));
+  docio::FileFormat docioFormat = docio::detect_format_by_file_extension(filename);
   bool success = false;
 
-  if (ext == "col") {
-    success = doc::file::save_col_file(pal, filename);
-  }
-  else if (ext == "gpl") {
-    success = doc::file::save_gpl_file(pal, filename);
-  }
-  else if (ext == "hex") {
-    success = doc::file::save_hex_file(pal, filename);
-  }
-  else if (ext == "pal") {
-    success = doc::file::save_pal_file(pal, filename);
-  }
-  else {
-    FileFormat* ff = FileFormatsManager::instance()->getFileFormatByExtension(ext.c_str());
-    if (ff && ff->support(FILE_SUPPORT_SAVE)) {
+  switch (docioFormat) {
+
+    case docio::FileFormat::COL_PALETTE:
+      success = doc::file::save_col_file(pal, filename);
+      break;
+
+    case docio::FileFormat::GPL_PALETTE:
+      success = doc::file::save_gpl_file(pal, filename);
+      break;
+
+    case docio::FileFormat::HEX_PALETTE:
+      success = doc::file::save_hex_file(pal, filename);
+      break;
+
+    case docio::FileFormat::PAL_PALETTE:
+      success = doc::file::save_pal_file(pal, filename);
+      break;
+
+    default: {
+      FileFormat* ff = FileFormatsManager::instance()->getFileFormat(docioFormat);
+      if (!ff || !ff->support(FILE_SUPPORT_SAVE))
+        break;
+
       int w = (columns > 0 ? MID(0, columns, pal->size()): pal->size());
       int h = (pal->size() / w) + (pal->size() % w > 0 ? 1: 0);
 
@@ -150,6 +168,7 @@ bool save_palette(const char *filename, const Palette* pal, int columns)
 
       doc->close();
       delete doc;
+      break;
     }
   }
 
diff --git a/src/app/file/pcx_format.cpp b/src/app/file/pcx_format.cpp
index 35a484d97..881c30764 100644
--- a/src/app/file/pcx_format.cpp
+++ b/src/app/file/pcx_format.cpp
@@ -1,5 +1,5 @@
 // Aseprite
-// Copyright (C) 2001-2015  David Capello
+// Copyright (C) 2001-2016  David Capello
 //
 // This program is distributed under the terms of
 // the End-User License Agreement for Aseprite.
@@ -24,6 +24,7 @@ using namespace base;
 class PcxFormat : public FileFormat {
   const char* onGetName() const override { return "pcx"; }
   const char* onGetExtensions() const override { return "pcx"; }
+  docio::FileFormat onGetDocioFormat() const override { return docio::FileFormat::PCX_IMAGE; }
   int onGetFlags() const override {
     return
       FILE_SUPPORT_LOAD |
diff --git a/src/app/file/pixly_format.cpp b/src/app/file/pixly_format.cpp
index 38e8720b0..c5bc5e581 100644
--- a/src/app/file/pixly_format.cpp
+++ b/src/app/file/pixly_format.cpp
@@ -31,6 +31,7 @@ using namespace base;
 class PixlyFormat : public FileFormat {
   const char* onGetName() const override { return "anim"; }
   const char* onGetExtensions() const override { return "anim"; }
+  docio::FileFormat onGetDocioFormat() const override { return docio::FileFormat::PIXLY_ANIMATION; }
   int onGetFlags() const override {
     return
       FILE_SUPPORT_LOAD |
diff --git a/src/app/file/png_format.cpp b/src/app/file/png_format.cpp
index 576e5ac5d..dd4ed596e 100644
--- a/src/app/file/png_format.cpp
+++ b/src/app/file/png_format.cpp
@@ -29,6 +29,7 @@ using namespace base;
 class PngFormat : public FileFormat {
   const char* onGetName() const override { return "png"; }
   const char* onGetExtensions() const override { return "png"; }
+  docio::FileFormat onGetDocioFormat() const override { return docio::FileFormat::PNG_IMAGE; }
   int onGetFlags() const override {
     return
       FILE_SUPPORT_LOAD |
diff --git a/src/app/file/tga_format.cpp b/src/app/file/tga_format.cpp
index 211b59311..ee09ab4c2 100644
--- a/src/app/file/tga_format.cpp
+++ b/src/app/file/tga_format.cpp
@@ -1,5 +1,5 @@
 // Aseprite
-// Copyright (C) 2001-2015  David Capello
+// Copyright (C) 2001-2016  David Capello
 //
 // This program is distributed under the terms of
 // the End-User License Agreement for Aseprite.
@@ -25,6 +25,7 @@ using namespace base;
 class TgaFormat : public FileFormat {
   const char* onGetName() const override { return "tga"; }
   const char* onGetExtensions() const override { return "tga"; }
+  docio::FileFormat onGetDocioFormat() const override { return docio::FileFormat::TARGA_IMAGE; }
   int onGetFlags() const override {
     return
       FILE_SUPPORT_LOAD |
diff --git a/src/app/file/webp_format.cpp b/src/app/file/webp_format.cpp
index be6892d7a..842f7fa78 100644
--- a/src/app/file/webp_format.cpp
+++ b/src/app/file/webp_format.cpp
@@ -1,6 +1,6 @@
 // Aseprite
 // Copyright (C) 2015 Gabriel Rauter
-// Copyright (C) 2015 David Capello
+// Copyright (C) 2015-2016 David Capello
 //
 // This program is distributed under the terms of
 // the End-User License Agreement for Aseprite.
@@ -41,6 +41,7 @@ class WebPFormat : public FileFormat {
 
   const char* onGetName() const override { return "webp"; }
   const char* onGetExtensions() const override { return "webp"; }
+  docio::FileFormat onGetDocioFormat() const override { return docio::WEBP_ANIMATION; }
   int onGetFlags() const override {
     return
       FILE_SUPPORT_LOAD |
diff --git a/src/doc/LICENSE.txt b/src/doc/LICENSE.txt
index 7ed8f201a..0d2d81fd4 100644
--- a/src/doc/LICENSE.txt
+++ b/src/doc/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2001-2015 David Capello
+Copyright (c) 2001-2016 David Capello
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
diff --git a/src/doc/README.md b/src/doc/README.md
index e32066553..6c5409397 100644
--- a/src/doc/README.md
+++ b/src/doc/README.md
@@ -1,4 +1,4 @@
 # Aseprite Document Library
-*Copyright (C) 2001-2015 David Capello*
+*Copyright (C) 2001-2016 David Capello*
 
 > Distributed under [MIT license](LICENSE.txt)
diff --git a/src/docio/CMakeLists.txt b/src/docio/CMakeLists.txt
new file mode 100644
index 000000000..cf8a2cf6b
--- /dev/null
+++ b/src/docio/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Aseprite Document IO Library
+# Copyright (c) 2016 David Capello
+
+add_library(docio-lib
+  detect_format.cpp)
+
+target_link_libraries(docio-lib
+  flic-lib
+  base-lib)
diff --git a/src/iff/LICENSE.txt b/src/docio/LICENSE.txt
similarity index 96%
rename from src/iff/LICENSE.txt
rename to src/docio/LICENSE.txt
index aea0f6e8f..9a182c0f3 100644
--- a/src/iff/LICENSE.txt
+++ b/src/docio/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2014 David Capello
+Copyright (c) 2016 David Capello
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
diff --git a/src/docio/README.md b/src/docio/README.md
new file mode 100644
index 000000000..34e04c3c6
--- /dev/null
+++ b/src/docio/README.md
@@ -0,0 +1,4 @@
+# Aseprite Document IO Library
+*Copyright (C) 2016 David Capello*
+
+> Distributed under [MIT license](LICENSE.txt)
diff --git a/src/docio/detect_format.cpp b/src/docio/detect_format.cpp
new file mode 100644
index 000000000..686dd881c
--- /dev/null
+++ b/src/docio/detect_format.cpp
@@ -0,0 +1,140 @@
+// Aseprite Document IO Library
+// Copyright (c) 2016 David Capello
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#include "docio/detect_format.h"
+
+#include "base/file_handle.h"
+#include "base/path.h"
+#include "base/string.h"
+#include "flic/flic_details.h"
+
+#include <cstring>
+
+#define ASE_MAGIC_NUMBER 0xA5E0
+#define BMP_MAGIC_NUMBER 0x4D42 // "BM"
+#define JPG_MAGIC_NUMBER 0xD8FF
+#define GIF_87_STAMP     "GIF87a"
+#define GIF_89_STAMP     "GIF89a"
+#define PNG_MAGIC_DWORD1 0x474E5089
+#define PNG_MAGIC_DWORD2 0x0A1A0A0D
+
+namespace docio {
+
+FileFormat detect_format(const std::string& filename)
+{
+  FileFormat ff = detect_format_by_file_content(filename);
+  if (ff == FileFormat::UNKNOWN)
+    ff = detect_format_by_file_extension(filename);
+  return ff;
+}
+
+FileFormat detect_format_by_file_content(const std::string& filename)
+{
+#define IS_MAGIC_WORD(offset, word)             \
+  ((buf[offset+0] == (word & 0xff)) &&          \
+   (buf[offset+1] == ((word & 0xff00) >> 8)))
+
+#define IS_MAGIC_DWORD(offset, dword)                     \
+  ((buf[offset+0] == (dword & 0xff)) &&                   \
+   (buf[offset+1] == ((dword & 0xff00) >> 8)) &&          \
+   (buf[offset+2] == ((dword & 0xff0000) >> 16)) &&       \
+   (buf[offset+3] == ((dword & 0xff000000) >> 24)))
+
+  base::FileHandle handle(base::open_file(filename.c_str(), "rb"));
+  if (!handle)
+    return FileFormat::ERROR;
+
+  FILE* f = handle.get();
+  unsigned char buf[8];
+  int count = fread(buf, 1, 8, f);
+
+  if (count >= 2) {
+    if (IS_MAGIC_WORD(0, BMP_MAGIC_NUMBER))
+      return FileFormat::BMP_IMAGE;
+
+    if (IS_MAGIC_WORD(0, JPG_MAGIC_NUMBER))
+      return FileFormat::JPEG_IMAGE;
+
+    if (count >= 6) {
+      if (std::strncmp((const char*)buf, GIF_87_STAMP, 6) == 0 ||
+          std::strncmp((const char*)buf, GIF_89_STAMP, 6) == 0)
+        return FileFormat::GIF_ANIMATION;
+
+      if (IS_MAGIC_WORD(4, ASE_MAGIC_NUMBER))
+        return FileFormat::ASE_ANIMATION;
+
+      if (IS_MAGIC_WORD(4, FLI_MAGIC_NUMBER) ||
+          IS_MAGIC_WORD(4, FLC_MAGIC_NUMBER))
+        return FileFormat::FLIC_ANIMATION;
+
+      if (count >= 8) {
+        if (IS_MAGIC_DWORD(0, PNG_MAGIC_DWORD1) &&
+            IS_MAGIC_DWORD(4, PNG_MAGIC_DWORD2))
+          return FileFormat::PNG_IMAGE;
+      }
+    }
+  }
+
+  return FileFormat::UNKNOWN;
+}
+
+FileFormat detect_format_by_file_extension(const std::string& filename)
+{
+  // By extension
+  const std::string ext = base::string_to_lower(base::get_file_extension(filename));
+
+  if (ext == "ase" ||
+      ext == "aseprite")
+    return FileFormat::ASE_ANIMATION;
+
+  if (ext == "bmp")
+    return FileFormat::BMP_IMAGE;
+
+  if (ext == "col")
+    return FileFormat::COL_PALETTE;
+
+  if (ext == "flc" ||
+      ext == "fli")
+    return FileFormat::FLIC_ANIMATION;
+
+  if (ext == "gif")
+    return FileFormat::GIF_ANIMATION;
+
+  if (ext == "gpl")
+    return FileFormat::GPL_PALETTE;
+
+  if (ext == "hex")
+    return FileFormat::HEX_PALETTE;
+
+  if (ext == "ico")
+    return FileFormat::ICO_IMAGES;
+
+  if (ext == "jpg" ||
+      ext == "jpeg")
+    return FileFormat::JPEG_IMAGE;
+
+  if (ext == "pal")
+    return FileFormat::PAL_PALETTE;
+
+  if (ext == "pcx")
+    return FileFormat::PCX_IMAGE;
+
+  if (ext == "anim")
+    return FileFormat::PIXLY_ANIMATION;
+
+  if (ext == "png")
+    return FileFormat::PNG_IMAGE;
+
+  if (ext == "tga")
+    return FileFormat::TARGA_IMAGE;
+
+  if (ext == "webp")
+    return FileFormat::WEBP_ANIMATION;
+
+  return FileFormat::UNKNOWN;
+}
+
+} // namespace docio
diff --git a/src/docio/detect_format.h b/src/docio/detect_format.h
new file mode 100644
index 000000000..d5a869164
--- /dev/null
+++ b/src/docio/detect_format.h
@@ -0,0 +1,23 @@
+// Aseprite Document IO Library
+// Copyright (c) 2016 David Capello
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifndef DOCIO_DETECT_FORMAT_H_INCLUDED
+#define DOCIO_DETECT_FORMAT_H_INCLUDED
+#pragma once
+
+#include "docio/file_format.h"
+
+#include <string>
+
+namespace docio {
+
+FileFormat detect_format(const std::string& filename);
+FileFormat detect_format_by_file_content(const std::string& filename);
+FileFormat detect_format_by_file_extension(const std::string& filename);
+
+} // namespace docio
+
+#endif
diff --git a/src/docio/file_format.h b/src/docio/file_format.h
new file mode 100644
index 000000000..bbf33b54b
--- /dev/null
+++ b/src/docio/file_format.h
@@ -0,0 +1,37 @@
+// Aseprite Document IO Library
+// Copyright (c) 2016 David Capello
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifndef DOCIO_FILE_FORMAT_H_INCLUDED
+#define DOCIO_FILE_FORMAT_H_INCLUDED
+#pragma once
+
+namespace docio {
+
+enum class FileFormat {
+  ERROR = -1,
+  UNKNOWN = 0,
+
+  ASE_ANIMATION,                // Aseprite File Format
+  ASE_PALETTE,                  // Adobe Swatch Exchange
+  BMP_IMAGE,
+  COL_PALETTE,
+  FLIC_ANIMATION,
+  GIF_ANIMATION,
+  GPL_PALETTE,
+  HEX_PALETTE,
+  ICO_IMAGES,
+  JPEG_IMAGE,
+  PAL_PALETTE,
+  PCX_IMAGE,
+  PIXLY_ANIMATION,
+  PNG_IMAGE,
+  TARGA_IMAGE,
+  WEBP_ANIMATION,
+};
+
+} // namespace docio
+
+#endif
diff --git a/src/iff/README.md b/src/iff/README.md
deleted file mode 100644
index efe59b660..000000000
--- a/src/iff/README.md
+++ /dev/null
@@ -1,4 +0,0 @@
-# Aseprite Image File Formats Library
-*Copyright (C) 2014 David Capello*
-
-> Distributed under [MIT license](LICENSE.txt)