diff --git a/toonz/sources/image/CMakeLists.txt b/toonz/sources/image/CMakeLists.txt
index b610a1d..514ba0c 100644
--- a/toonz/sources/image/CMakeLists.txt
+++ b/toonz/sources/image/CMakeLists.txt
@@ -24,6 +24,7 @@ set(HEADERS
     ffmpeg/tiio_webm.h
     ffmpeg/tiio_mp4.h
     ffmpeg/tiio_ffmpeg.h
+    sprite/tiio_sprite.h
     mesh/tiio_mesh.h
 )
 
@@ -55,6 +56,7 @@ set(SOURCES
     ffmpeg/tiio_webm.cpp
     ffmpeg/tiio_mp4.cpp
     ffmpeg/tiio_ffmpeg.cpp
+    sprite/tiio_sprite.cpp
     mesh/tiio_mesh.cpp
 )
 
diff --git a/toonz/sources/image/sprite/tiio_sprite.cpp b/toonz/sources/image/sprite/tiio_sprite.cpp
new file mode 100644
index 0000000..6f839de
--- /dev/null
+++ b/toonz/sources/image/sprite/tiio_sprite.cpp
@@ -0,0 +1,281 @@
+
+#include "tiio_sprite.h"
+#include "../toonz/tapp.h"
+#include "tsystem.h"
+#include "tsound.h"
+
+#include <QProcess>
+#include <QDir>
+#include <QtGui/QImage>
+#include <QStringList>
+#include <QPainter>
+#include <QTextStream>
+#include "toonz/preferences.h"
+#include "toonz/toonzfolders.h"
+
+#include "trasterimage.h"
+#include "timageinfo.h"
+
+//===========================================================
+//
+//  TImageWriterSprite
+//
+//===========================================================
+
+class TImageWriterSprite : public TImageWriter {
+public:
+  int m_frameIndex;
+
+  TImageWriterSprite(const TFilePath &path, int frameIndex,
+                     TLevelWriterSprite *lwg)
+      : TImageWriter(path), m_frameIndex(frameIndex), m_lwg(lwg) {
+    m_lwg->addRef();
+  }
+  ~TImageWriterSprite() { m_lwg->release(); }
+
+  bool is64bitOutputSupported() override { return false; }
+  void save(const TImageP &img) override { m_lwg->save(img, m_frameIndex); }
+
+private:
+  TLevelWriterSprite *m_lwg;
+};
+
+//===========================================================
+//
+//  TLevelWriterSprite;
+//
+//===========================================================
+
+TLevelWriterSprite::TLevelWriterSprite(const TFilePath &path,
+                                       TPropertyGroup *winfo)
+    : TLevelWriter(path, winfo) {
+  if (!m_properties) m_properties = new Tiio::SpriteWriterProperties();
+  std::string scale = m_properties->getProperty("Scale")->getValueAsString();
+  m_scale           = QString::fromStdString(scale).toInt();
+  std::string topPadding =
+      m_properties->getProperty("Top Padding")->getValueAsString();
+  m_topPadding = QString::fromStdString(topPadding).toInt();
+  std::string bottomPadding =
+      m_properties->getProperty("Bottom Padding")->getValueAsString();
+  m_bottomPadding = QString::fromStdString(bottomPadding).toInt();
+  std::string leftPadding =
+      m_properties->getProperty("Left Padding")->getValueAsString();
+  m_leftPadding = QString::fromStdString(leftPadding).toInt();
+  std::string rightPadding =
+      m_properties->getProperty("Right Padding")->getValueAsString();
+  m_rightPadding = QString::fromStdString(rightPadding).toInt();
+  m_format       = QString::fromStdWString(
+      ((TEnumProperty *)(m_properties->getProperty("Format")))->getValue());
+
+  if (TSystem::doesExistFileOrLevel(m_path)) TSystem::deleteFile(m_path);
+}
+
+//-----------------------------------------------------------
+
+TLevelWriterSprite::~TLevelWriterSprite() {
+  int finalWidth    = m_right - m_left + 1;
+  int finalHeight   = m_bottom - m_top + 1;
+  int resizedWidth  = finalWidth * m_scale / 100;
+  int resizedHeight = finalHeight * m_scale / 100;
+  for (QImage *image : m_images) {
+    QImage copy = image->copy(m_left, m_top, finalWidth, finalHeight);
+    if (m_scale != 100) {
+      int width  = (copy.width() * m_scale) / 100;
+      int height = (copy.height() * m_scale) / 100;
+      copy       = copy.scaled(width, height);
+    }
+    m_imagesResized.push_back(copy);
+  }
+  for (QImage *image : m_images) {
+    delete image;
+  }
+  m_images.clear();
+  int horizDim          = 1;
+  int vertDim           = 1;
+  int vertPadding       = m_topPadding + m_bottomPadding;
+  int horizPadding      = m_leftPadding + m_rightPadding;
+  int totalVertPadding  = 0;
+  int totalHorizPadding = 0;
+  int spriteSheetWidth;
+  int spriteSheetHeight;
+  if (m_format == "Grid") {
+    while (horizDim * horizDim < m_imagesResized.size()) horizDim++;
+    totalHorizPadding = horizDim * horizPadding;
+    spriteSheetWidth  = horizDim * resizedWidth + totalHorizPadding;
+    vertDim           = horizDim;
+    if (vertDim * vertDim - vertDim >= m_imagesResized.size()) {
+      vertDim          = vertDim - 1;
+      totalVertPadding = vertDim * vertPadding;
+    }
+    spriteSheetHeight = vertDim * resizedHeight + totalVertPadding;
+  } else if (m_format == "Vertical") {
+    spriteSheetWidth  = resizedWidth + horizPadding;
+    spriteSheetHeight = m_imagesResized.size() * (resizedHeight + vertPadding);
+    vertDim           = m_imagesResized.size();
+  } else if (m_format == "Horizontal") {
+    spriteSheetWidth  = m_imagesResized.size() * (resizedWidth + horizPadding);
+    spriteSheetHeight = resizedHeight + vertPadding;
+    ;
+    horizDim = m_imagesResized.size();
+  }
+
+  QImage spriteSheet =
+      QImage(spriteSheetWidth, spriteSheetHeight, QImage::Format_ARGB32);
+  spriteSheet.fill(qRgba(0, 0, 0, 0));
+  QPainter painter;
+  painter.begin(&spriteSheet);
+  int row    = 0;
+  int column = 0;
+  int rowPadding;
+  int columnPadding;
+  int currentImage = 0;
+  while (row < vertDim) {
+    while (column < horizDim) {
+      rowPadding    = m_topPadding;
+      columnPadding = m_leftPadding;
+      rowPadding += row * vertPadding;
+      columnPadding += column * horizPadding;
+      painter.drawImage(column * resizedWidth + columnPadding,
+                        row * resizedHeight + rowPadding,
+                        m_imagesResized[currentImage]);
+      currentImage++;
+      column++;
+      if (currentImage >= m_imagesResized.size()) break;
+    }
+    column = 0;
+    row++;
+    if (currentImage >= m_imagesResized.size()) break;
+  }
+  painter.end();
+  QString path = m_path.getQString();
+  path         = path.replace(".spritesheet", ".png");
+  spriteSheet.save(path, "PNG", -1);
+
+  path = path.replace(".png", ".txt");
+  QFile file(path);
+  file.open(QIODevice::WriteOnly | QIODevice::Text);
+  QTextStream out(&file);
+  out << "Total Images: " << m_imagesResized.size() << "\n";
+  out << "Individual Image Width: " << resizedWidth << "\n";
+  out << "Individual Image Height: " << resizedHeight << "\n";
+  out << "Individual Image Width with Padding: " << resizedWidth + horizPadding
+      << "\n";
+  out << "Individual Image Height with Padding: " << resizedHeight + vertPadding
+      << "\n";
+  out << "Images Across: " << horizDim << "\n";
+  out << "Images Down : " << vertDim << "\n";
+  out << "Top Padding: " << m_topPadding << "\n";
+  out << "Bottom Padding: " << m_bottomPadding << "\n";
+  out << "Left Padding: " << m_leftPadding << "\n";
+  out << "Right Padding: " << m_rightPadding << "\n";
+  out << "Horizontal Space Between Images: " << horizPadding << "\n";
+  out << "Vertical Space Between Images: " << vertPadding << "\n";
+
+  file.close();
+  m_imagesResized.clear();
+}
+
+//-----------------------------------------------------------
+
+TImageWriterP TLevelWriterSprite::getFrameWriter(TFrameId fid) {
+  if (fid.getLetter() != 0) return TImageWriterP(0);
+  int index               = fid.getNumber();
+  TImageWriterSprite *iwg = new TImageWriterSprite(m_path, index, this);
+  return TImageWriterP(iwg);
+}
+
+//-----------------------------------------------------------
+void TLevelWriterSprite::setFrameRate(double fps) {}
+
+void TLevelWriterSprite::saveSoundTrack(TSoundTrack *st) {}
+
+//-----------------------------------------------------------
+
+void TLevelWriterSprite::save(const TImageP &img, int frameIndex) {
+  TRasterImageP tempImage(img);
+  TRasterImage *image = (TRasterImage *)tempImage->cloneImage();
+
+  m_lx           = image->getRaster()->getLx();
+  m_ly           = image->getRaster()->getLy();
+  int m_bpp      = image->getRaster()->getPixelSize();
+  int totalBytes = m_lx * m_ly * m_bpp;
+  image->getRaster()->yMirror();
+
+  // lock raster to get data
+  image->getRaster()->lock();
+  void *buffin = image->getRaster()->getRawData();
+  assert(buffin);
+  void *buffer = malloc(totalBytes);
+  memcpy(buffer, buffin, totalBytes);
+
+  image->getRaster()->unlock();
+
+  // create QImage save format
+  QString m_intermediateFormat = "png";
+  QByteArray ba                = m_intermediateFormat.toUpper().toLatin1();
+  const char *format           = ba.data();
+
+  QImage *qi = new QImage((uint8_t *)buffer, m_lx, m_ly, QImage::Format_ARGB32);
+
+  int l = qi->width(), r = 0, t = qi->height(), b = 0;
+  for (int y = 0; y < qi->height(); ++y) {
+    QRgb *row      = (QRgb *)qi->scanLine(y);
+    bool rowFilled = false;
+    for (int x = 0; x < qi->width(); ++x) {
+      if (qAlpha(row[x])) {
+        rowFilled = true;
+        r         = std::max(r, x);
+        if (l > x) {
+          l = x;
+          x = r;
+        }
+      }
+    }
+    if (rowFilled) {
+      t = std::min(t, y);
+      b = y;
+    }
+  }
+  if (m_firstPass) {
+    m_firstPass = false;
+    m_left      = l;
+    m_right     = r;
+    m_top       = t;
+    m_bottom    = b;
+  } else {
+    if (l < m_left) m_left     = l;
+    if (r > m_right) m_right   = r;
+    if (t < m_top) m_top       = t;
+    if (b > m_bottom) m_bottom = b;
+  }
+  QImage *newQi = new QImage(m_lx, m_ly, QImage::Format_ARGB32);
+  newQi->fill(qRgba(0, 0, 0, 0));
+  QPainter painter(newQi);
+  painter.drawImage(QPoint(0, 0), *qi);
+  m_images.push_back(newQi);
+  delete image;
+  delete qi;
+  free(buffer);
+}
+
+Tiio::SpriteWriterProperties::SpriteWriterProperties()
+    : m_topPadding("Top Padding", 0, 100, 0)
+    , m_bottomPadding("Bottom Padding", 0, 100, 0)
+    , m_leftPadding("Left Padding", 0, 100, 0)
+    , m_rightPadding("Right Padding", 0, 100, 0)
+    , m_scale("Scale", 1, 100, 100)
+    , m_format("Format") {
+  m_format.addValue(L"Grid");
+  m_format.addValue(L"Vertical");
+  m_format.addValue(L"Horizontal");
+  m_format.setValue(L"Grid");
+  bind(m_format);
+  bind(m_topPadding);
+  bind(m_bottomPadding);
+  bind(m_leftPadding);
+  bind(m_rightPadding);
+  bind(m_scale);
+}
+
+// Tiio::Reader* Tiio::makeSpriteReader(){ return nullptr; }
+// Tiio::Writer* Tiio::makeSpriteWriter(){ return nullptr; }
\ No newline at end of file
diff --git a/toonz/sources/image/sprite/tiio_sprite.h b/toonz/sources/image/sprite/tiio_sprite.h
new file mode 100644
index 0000000..623363e
--- /dev/null
+++ b/toonz/sources/image/sprite/tiio_sprite.h
@@ -0,0 +1,63 @@
+#pragma once
+
+#ifndef TTIO_SPRITE_INCLUDED
+#define TTIO_SPRITE_INCLUDED
+
+#include "tproperty.h"
+#include "tlevel_io.h"
+#include "trasterimage.h"
+#include <QVector>
+#include <QStringList>
+#include <QtGui/QImage>
+//===========================================================
+//
+//  TLevelWriterSprite
+//
+//===========================================================
+
+class TLevelWriterSprite : public TLevelWriter {
+public:
+  TLevelWriterSprite(const TFilePath &path, TPropertyGroup *winfo);
+  ~TLevelWriterSprite();
+  void setFrameRate(double fps);
+
+  TImageWriterP getFrameWriter(TFrameId fid) override;
+  void save(const TImageP &image, int frameIndex);
+
+  void saveSoundTrack(TSoundTrack *st);
+
+  static TLevelWriter *create(const TFilePath &path, TPropertyGroup *winfo) {
+    return new TLevelWriterSprite(path, winfo);
+  }
+
+private:
+  int m_lx, m_ly;
+  int m_scale;
+  int m_topPadding, m_bottomPadding, m_leftPadding, m_rightPadding;
+  int m_left = 0, m_right = 0, m_top = 0, m_bottom = 0;
+  std::vector<QImage *> m_images;
+  std::vector<QImage> m_imagesResized;
+  bool m_firstPass = true;
+  QString m_format;
+  // void *m_buffer;
+};
+
+//===========================================================================
+
+namespace Tiio {
+
+//===========================================================================
+
+class SpriteWriterProperties : public TPropertyGroup {
+public:
+  TEnumProperty m_format;
+  TIntProperty m_topPadding, m_bottomPadding, m_leftPadding, m_rightPadding;
+  TIntProperty m_scale;
+  SpriteWriterProperties();
+};
+
+//===========================================================================
+
+}  // namespace
+
+#endif
diff --git a/toonz/sources/image/tiio.cpp b/toonz/sources/image/tiio.cpp
index 9f3ae19..dfbd74d 100644
--- a/toonz/sources/image/tiio.cpp
+++ b/toonz/sources/image/tiio.cpp
@@ -76,6 +76,7 @@
 #include "./ffmpeg/tiio_webm.h"
 #include "./ffmpeg/tiio_mp4.h"
 #include "./mesh/tiio_mesh.h"
+#include "./sprite/tiio_sprite.h"
 
 //-------------------------------------------------------------------
 
@@ -162,6 +163,11 @@ void initImageIo(bool lightVersion) {
   TFileType::declare("rgb", TFileType::RASTER_IMAGE);
   Tiio::defineWriterProperties("rgb", new Tiio::SgiWriterProperties());
 
+  TLevelWriter::define("spritesheet", TLevelWriterSprite::create, true);
+  TFileType::declare("spritesheet", TFileType::RASTER_LEVEL);
+  Tiio::defineWriterProperties("spritesheet",
+                               new Tiio::SpriteWriterProperties());
+
 // ffmpeg
 #if !defined(_WIN32) || defined(x64)
   if (Ffmpeg::checkFfmpeg()) {
diff --git a/toonz/sources/toonz/outputsettingspopup.cpp b/toonz/sources/toonz/outputsettingspopup.cpp
index e8a57b9..705cc87 100644
--- a/toonz/sources/toonz/outputsettingspopup.cpp
+++ b/toonz/sources/toonz/outputsettingspopup.cpp
@@ -943,7 +943,7 @@ void OutputSettingsPopup::onFormatChanged(const QString &str) {
   TApp::instance()->getCurrentScene()->setDirtyFlag(true);
 
   if (m_presetCombo) m_presetCombo->setCurrentIndex(0);
-  if (str == "mp4" || str == "gif" || str == "webm") {
+  if (str == "mp4" || str == "gif" || str == "webm" || str == "spritesheet") {
     m_threadsComboOm->setDisabled(true);
     m_threadsComboOm->setCurrentIndex(0);
   } else {
diff --git a/toonz/sources/toonz/rendercommand.cpp b/toonz/sources/toonz/rendercommand.cpp
index 5af6af1..3f25c3c 100644
--- a/toonz/sources/toonz/rendercommand.cpp
+++ b/toonz/sources/toonz/rendercommand.cpp
@@ -172,7 +172,7 @@ public:
             app->getCurrentXsheet()->getXsheet()->makeSound(prop);
         // keeps ffmpeg files from being previewed until import is fixed
         if (m_fp.getType() != "mp4" && m_fp.getType() != "webm" &&
-            m_fp.getType() != "gif") {
+            m_fp.getType() != "gif" && m_fp.getType() != "spritesheet") {
           if (outputSettings.getRenderSettings().m_stereoscopic) {
             assert(!isPreview);
             ::viewFile(m_fp.withName(m_fp.getName() + "_l"), r0 + 1, r1 + 1,