diff --git a/data/pref.xml b/data/pref.xml
index 1f108865d..a18597331 100644
--- a/data/pref.xml
+++ b/data/pref.xml
@@ -330,6 +330,7 @@
       <option id="show_alert" type="bool" default="true" />
       <option id="interlaced" type="bool" default="false" />
       <option id="loop" type="bool" default="true" />
+      <option id="preserve_palette_order" type="bool" default="true" />
     </section>
     <section id="jpeg">
       <option id="show_alert" type="bool" default="true" />
diff --git a/data/strings/en.ini b/data/strings/en.ini
index c60179726..85a3030b8 100644
--- a/data/strings/en.ini
+++ b/data/strings/en.ini
@@ -704,6 +704,7 @@ title = GIF Options
 general_options = General Options:
 interlaced = &Interlaced
 animation_loop = Animation &Loop
+preserve_palette_order = &Preserve palette order
 ok = &OK
 cancel = &Cancel
 
diff --git a/data/widgets/gif_options.xml b/data/widgets/gif_options.xml
index f045bb781..321d3d81c 100644
--- a/data/widgets/gif_options.xml
+++ b/data/widgets/gif_options.xml
@@ -1,11 +1,13 @@
 <!-- Aseprite -->
-<!-- Copyright (C) 2014-2018 by David Capello -->
+<!-- Copyright (C) 2020  Igara Studio S.A. -->
+<!-- Copyright (C) 2014-2018  David Capello -->
 <gui>
 <window id="gif_options" text="@.title">
   <vbox>
     <separator text="@.general_options" left="true" horizontal="true" />
     <check text="@.interlaced" id="interlaced" />
     <check text="@.animation_loop" id="loop" />
+    <check text="@.preserve_palette_order" id="preserve_palette_order" />
 
     <separator horizontal="true" />
 
diff --git a/src/app/file/gif_format.cpp b/src/app/file/gif_format.cpp
index 4e2f7a26b..2c9340be1 100644
--- a/src/app/file/gif_format.cpp
+++ b/src/app/file/gif_format.cpp
@@ -356,7 +356,9 @@ private:
     if (!frameBounds.isEmpty())
       frameImage.reset(readFrameIndexedImage(frameBounds));
 
-    GIF_TRACE("GIF: Frame[%d] transparent index = %d\n", (int)m_frameNum, m_localTransparentIndex);
+    GIF_TRACE("GIF: Frame[%d] transparentIndex=%d localMap=%d\n",
+              (int)m_frameNum, m_localTransparentIndex,
+              m_gifFile->Image.ColorMap ? m_gifFile->Image.ColorMap->ColorCount: 0);
 
     if (m_frameNum == 0) {
       if (m_localTransparentIndex >= 0)
@@ -457,8 +459,7 @@ private:
     else if (!m_hasLocalColormaps) {
       if (!global) {
         if (!m_firstLocalColormap)
-          m_firstLocalColormap = GifMakeMapObject(colormap->ColorCount,
-                                                  colormap->Colors);
+          m_firstLocalColormap = GifMakeMapObject(256, colormap->Colors);
         global = m_firstLocalColormap;
       }
 
@@ -526,8 +527,7 @@ private:
     if (m_sprite->pixelFormat() == IMAGE_INDEXED &&
         !m_opaque && m_bgIndex != m_localTransparentIndex) {
       for (const auto& i : LockImageBits<IndexedTraits>(frameImage)) {
-        if (i == m_bgIndex &&
-            i != m_localTransparentIndex) {
+        if (i == m_bgIndex) {
           needsExtraBgColor = true;
           break;
         }
@@ -617,6 +617,18 @@ private:
       int i = m_bgIndex;
       int j = base++;
       palette->setEntry(j, colormap2rgba(colormap, i));
+      // m_firstLocalColorMap, is used only if we have no global color map in the gif source,
+      // and we want to preserve original color indexes, as much we can.
+      // If the palette size is > 256, m_firstLocalColormal is no more useful, because
+      // the sprite pixel format will be converted in RGBA image, and the colors will
+      // be picked from the sprite palette, instead of m_firstLocalColorMap.
+      if (m_firstLocalColormap && m_firstLocalColormap->ColorCount > j) {
+        // We need add this last color to m_firstLocalColormap, because
+        // it was not considered in the function getFrameColormap.
+        m_firstLocalColormap->Colors[j].Red = rgba_getr(palette->getEntry(j));
+        m_firstLocalColormap->Colors[j].Green = rgba_getg(palette->getEntry(j));
+        m_firstLocalColormap->Colors[j].Blue = rgba_getb(palette->getEntry(j));
+      }
       m_remap.map(i, j);
     }
 
@@ -892,6 +904,34 @@ bool GifFormat::onLoad(FileOp* fop)
 
 #ifdef ENABLE_SAVE
 
+// Our stragegy to encode GIF files depends of the sprite color mode:
+//
+// 1) If the sprite is indexed, we have two paths:
+//    * For opaque an opaque sprite we can save it as it is (with the
+//      same indexes/pixels and same color palette). This brings us
+//      the best possible to compress the GIF file (using the best
+//      disposal method to update only the differences between each
+//      frame).
+//    * For transparent sprites we offer to the user the option to
+//      preserve the original palette or not
+//      (m_preservePaletteOrders). If the palette must be preserve,
+//      some level of compression will be sacrificed.
+//
+// 2) For RGB sprites the palette is created on each frame depending
+//    on the updated rectangle between frames, i.e. each to new frame
+//    incorporates a minimal rectangular region with changes from the
+//    previous frame, we can calculate the palette required for this
+//    rectangle and use it as a local colormap for the frame (if each
+//    frame uses previous color in the palette there is no need to
+//    introduce a new palette).
+//
+// Note: In the following algorithm you will find the "pixel clearing"
+// term, this happens when we need to clear an opaque color with the
+// gif transparent bg color. This is the worst possible case, because
+// on transparent gif files, the only way to get the transparent color
+// (bg color) is using the RESTORE_BGCOLOR disposal method (so we lost
+// the chance to use DO_NOT_DISPOSE in these cases).
+//
 class GifEncoder {
 public:
   typedef int gifframe_t;
@@ -902,10 +942,22 @@ public:
     , m_document(fop->document())
     , m_sprite(fop->document()->sprite())
     , m_spriteBounds(m_sprite->bounds())
-    , m_hasBackground(m_sprite->backgroundLayer() ? true: false)
+    , m_hasBackground(m_sprite->isOpaque())
     , m_bitsPerPixel(1)
     , m_globalColormap(nullptr)
-    , m_quantizeColormaps(false) {
+    , m_globalColormapPalette(*m_sprite->palette(0))
+    , m_preservePaletteOrder(false) {
+
+    const auto gifOptions = std::static_pointer_cast<GifOptions>(fop->formatOptions());
+
+    LOG("GIF: Saving with options: interlaced=%d loop=%d\n",
+        gifOptions->interlaced(), gifOptions->loop());
+
+    m_interlaced = gifOptions->interlaced();
+    m_loop = (gifOptions->loop() ? 0: -1);
+    m_lastFrameBounds = m_spriteBounds;
+    m_lastDisposal = DisposalMethod::NONE;
+
     if (m_sprite->pixelFormat() == IMAGE_INDEXED) {
       for (Palette* palette : m_sprite->getPalettes()) {
         int bpp = GifBitSizeLimited(palette->size());
@@ -920,46 +972,107 @@ public:
         m_sprite->getPalettes().size() == 1) {
       // If some layer has opacity < 255 or a different blend mode, we
       // need to create color palettes.
+      bool quantizeColormaps = false;
       for (const Layer* layer : m_sprite->allVisibleLayers()) {
         if (layer->isVisible() && layer->isImage()) {
           const LayerImage* imageLayer = static_cast<const LayerImage*>(layer);
           if (imageLayer->opacity() < 255 ||
               imageLayer->blendMode() != BlendMode::NORMAL) {
-            m_quantizeColormaps = true;
+            quantizeColormaps = true;
             break;
           }
         }
       }
 
-      if (!m_quantizeColormaps) {
-        m_globalColormap = createColorMap(m_sprite->palette(0));
+      if (!quantizeColormaps) {
+        m_globalColormap = createColorMap(&m_globalColormapPalette);
         m_bgIndex = m_sprite->transparentColor();
+        // For indexed and opaque sprite, we can preserve the exact
+        // palette order without lossing compression rate.
+        if (m_hasBackground)
+          m_preservePaletteOrder = true;
+        // Only for transparent indexed images the user can choose to
+        // preserve or not the palette order.
+        else
+          m_preservePaletteOrder = gifOptions->preservePaletteOrder();
       }
       else
         m_bgIndex = 0;
     }
     else {
       m_bgIndex = 0;
-      m_quantizeColormaps = true;
     }
 
+    // This is the transparent index to use as "local transparent"
+    // index for each gif frame. In case that we use a global colormap
+    // (and we don't need to preserve the original palette), we can
+    // try to find a place for a global transparent index.
     m_transparentIndex = (m_hasBackground ? -1: m_bgIndex);
+    if (m_globalColormap) {
+      // The variable m_globalColormap is != nullptr only on indexed images
+      ASSERT(m_sprite->pixelFormat() == IMAGE_INDEXED);
 
-    if (m_hasBackground)
-      m_clearColor = m_sprite->palette(0)->getEntry(m_bgIndex);
-    else
-      m_clearColor = rgba(0, 0, 0, 0);
+      const Palette* pal = m_sprite->palette(0);
+      bool maskColorFounded = false;
+      for (int i=0; i<pal->size(); i++) {
+        if (doc::rgba_geta(pal->getEntry(i)) == 0) {
+          maskColorFounded = true;
+          m_transparentIndex = i;
+          break;
+        }
+      }
 
-    const auto gifOptions = std::static_pointer_cast<GifOptions>(fop->formatOptions());
+#if 0
+      // If the palette contains room for one extra color for the
+      // mask, we can use that index.
+      if (!maskColorFounded && pal->size() < 256) {
+        maskColorFounded = true;
 
-    LOG("GIF: Saving with options: interlaced=%d loop=%d\n",
-        gifOptions->interlaced(), gifOptions->loop());
+        Palette newPalette(*pal);
+        newPalette.addEntry(0);
+        ASSERT(newPalette.size() <= 256);
 
-    m_interlaced = gifOptions->interlaced();
-    m_loop = (gifOptions->loop() ? 0: -1);
+        m_transparentIndex = newPalette.size() - 1;
+        m_globalColormapPalette = newPalette;
+        m_globalColormap = createColorMap(&m_globalColormapPalette);
+      }
+      else
+#endif
+      if (// If all colors are opaque/used in the sprite
+        !maskColorFounded &&
+        // We aren't obligated to preserve the original palette
+        !m_preservePaletteOrder &&
+        // And the sprite is transparent
+        !m_hasBackground) {
+        // We create a new palette with 255 colors + one extra entry
+        // for the transparent color
+        Palette newPalette(0, 255);
+        render::create_palette_from_sprite(
+          m_sprite,
+          0,
+          totalFrames()-1,
+          false,
+          &newPalette,
+          nullptr,
+          m_fop->newBlend(),
+          RgbMapAlgorithm::DEFAULT, // TODO configurable?
+          false); // Do not add the transparent color yet
 
+        // We will use the last palette entry (e.g. index=255) as the
+        // transparent index
+        newPalette.addEntry(0);
+        ASSERT(newPalette.size() <= 256);
+
+        m_transparentIndex = newPalette.size() - 1;
+        m_globalColormapPalette = newPalette;
+        m_globalColormap = createColorMap(&m_globalColormapPalette);
+      }
+    }
+
+    // Create the 3 temporary images (previous/current/next) to
+    // compare pixels between them.
     for (int i=0; i<3; ++i)
-      m_images[i].reset(Image::create(IMAGE_RGB,
+      m_images[i].reset(Image::create((m_preservePaletteOrder)? IMAGE_INDEXED : IMAGE_RGB,
                                       m_spriteBounds.w,
                                       m_spriteBounds.h));
   }
@@ -1005,25 +1118,22 @@ public:
       if (gifFrame+1 < nframes)
         renderFrame(*frame_it, m_nextImage);
 
-      gfx::Rect frameBounds;
-      DisposalMethod disposal;
-      calculateBestDisposalMethod(gifFrame, frameBounds, disposal);
+      gfx::Rect frameBounds = m_spriteBounds;
+      DisposalMethod disposal = DisposalMethod::DO_NOT_DISPOSE;
 
-      // TODO We could join both frames in a longer one (with more duration)
-      if (frameBounds.isEmpty())
-        frameBounds = gfx::Rect(0, 0, 1, 1);
+      // Creation of the deltaImage (difference image result respect
+      // to current VS previous frame image).  At the same time we
+      // must scan the next image, to check if some pixel turns to
+      // transparent (0), if the case, we need to force disposal
+      // method of the current image to RESTORE_BG.  Further, at the
+      // same time, we must check if we can go without color zero (0).
+
+      calculateDeltaImageFrameBoundsDisposal(gifFrame, frameBounds, disposal);
 
       writeImage(gifFrame, frame, frameBounds, disposal,
                  // Only the last frame in the animation needs the fix
                  (fix_last_frame_duration && gifFrame == nframes-1));
 
-      // Dispose/clear frame content
-      process_disposal_method(m_previousImage,
-                              m_currentImage,
-                              disposal,
-                              frameBounds,
-                              m_clearColor);
-
       m_fop->setProgress(double(gifFrame+1) / double(nframes));
     }
     return true;
@@ -1031,6 +1141,126 @@ public:
 
 private:
 
+  void calculateDeltaImageFrameBoundsDisposal(gifframe_t gifFrame,
+                                              gfx::Rect& frameBounds,
+                                              DisposalMethod& disposal) {
+    if (gifFrame == 0) {
+      m_deltaImage.reset(Image::createCopy(m_currentImage));
+      frameBounds = m_spriteBounds;
+
+      // The first frame (frame 0) is good to force to disposal = DO_NOT_DISPOSE,
+      // but when the next frame (frame 1) has a "pixel clearing",
+      // we must change disposal to RESTORE_BGCOLOR.
+
+      // "Pixel clearing" detection:
+      if (!m_hasBackground && !m_preservePaletteOrder) {
+        const LockImageBits<RgbTraits> bits2(m_currentImage);
+        const LockImageBits<RgbTraits> bits3(m_nextImage);
+        typename LockImageBits<RgbTraits>::const_iterator it2, it3, end2, end3;
+        for (it2 = bits2.begin(), end2 = bits2.end(),
+             it3 = bits3.begin(), end3 = bits3.end();
+              it2 != end2 && it3 != end3; ++it2, ++it3) {
+          if (*it2 != 0 && *it3 == 0) {
+            disposal = DisposalMethod::RESTORE_BGCOLOR;
+            break;
+          }
+        }
+      }
+      else if (m_preservePaletteOrder)
+        disposal = DisposalMethod::RESTORE_BGCOLOR;
+    }
+    else {
+      int x1 = 0;
+      int y1 = 0;
+      int x2 = 0;
+      int y2 = 0;
+
+      if (!m_preservePaletteOrder) {
+        // When m_lastDisposal was RESTORE_BGBOLOR it implies
+        // we will have to cover with colors the entire previous frameBounds plus
+        // the current frameBounds due to color changes, so we must start with
+        // a frameBounds equal to the previous frame iteration (saved in m_lastFrameBounds).
+        // Then we must cover all the resultant frameBounds with full color
+        // in m_currentImage, the output image will be saved in deltaImage.
+        if (m_lastDisposal == DisposalMethod::RESTORE_BGCOLOR) {
+          x1 = m_lastFrameBounds.x;
+          y1 = m_lastFrameBounds.y;
+          x2 = m_lastFrameBounds.x + m_lastFrameBounds.w - 1;
+          y2 = m_lastFrameBounds.y + m_lastFrameBounds.h - 1;
+        }
+        else {
+          x1 = m_spriteBounds.w - 1;
+          y1 = m_spriteBounds.h - 1;
+        }
+
+        int i = 0;
+        int x, y;
+        const LockImageBits<RgbTraits> bits1(m_previousImage);
+        const LockImageBits<RgbTraits> bits2(m_currentImage);
+        const LockImageBits<RgbTraits> bits3(m_nextImage);
+        m_deltaImage.reset(Image::create(PixelFormat::IMAGE_RGB, m_spriteBounds.w, m_spriteBounds.h));
+        clear_image(m_deltaImage.get(), 0);
+        LockImageBits<RgbTraits> deltaBits(m_deltaImage.get());
+        typename LockImageBits<RgbTraits>::iterator deltaIt;
+        typename LockImageBits<RgbTraits>::const_iterator it1, it2, it3, end1, end2, end3, deltaEnd;
+
+        bool previousImageMatchsCurrent = true;
+        for (it1 = bits1.begin(), end1 = bits1.end(),
+             it2 = bits2.begin(), end2 = bits2.end(),
+             it3 = bits3.begin(), end2 = bits3.end(),
+             deltaIt = deltaBits.begin();
+             it1 != end1 && it2 != end2; ++it1, ++it2, ++it3, ++deltaIt, ++i) {
+          x = i % m_spriteBounds.w;
+          y = i / m_spriteBounds.w;
+          // While we are checking color differences,
+          // we enlarge the frameBounds where the color differences take place
+          if (*it1 != *it2 || *it3 == 0) {
+            previousImageMatchsCurrent = false;
+            *deltaIt = *it2;
+            if (x < x1) x1 = x;
+            if (x > x2) x2 = x;
+            if (y < y1) y1 = y;
+            if (y > y2) y2 = y;
+          }
+
+          // We need to change disposal mode DO_NOT_DISPOSE to RESTORE_BGCOLOR only
+          // if we found a "pixel clearing" in the next Image. RESTORE_BGCOLOR is
+          // our way to clear pixels.
+          if (*it2 != 0 && *it3 == 0) {
+            disposal = DisposalMethod::RESTORE_BGCOLOR;
+          }
+        }
+        if (previousImageMatchsCurrent)
+          frameBounds = gfx::Rect(m_lastFrameBounds);
+        else
+          frameBounds = gfx::Rect(x1, y1, x2-x1+1, y2-y1+1);
+      }
+      else
+        disposal = DisposalMethod::RESTORE_BGCOLOR;
+
+      // We need to conditionate the deltaImage to the next step: 'writeImage()'
+      // To do it, we need to crop deltaImage in frameBounds.
+      // If disposal method changed to RESTORE_BGCOLOR deltaImage we need to reproduce ALL the colors of m_currentImage
+      // contained in frameBounds (so, we will overwrite delta image with a cropped current image).
+      // In the other hand, if disposal is still DO_NOT_DISPOSAL, delta image will be a cropped image
+      // from itself in frameBounds.
+      if (disposal == DisposalMethod::RESTORE_BGCOLOR || m_lastDisposal == DisposalMethod::RESTORE_BGCOLOR) {
+        m_deltaImage.reset(crop_image(m_currentImage, frameBounds, 0));
+      }
+      else {
+        m_deltaImage.reset(crop_image(m_deltaImage.get(), frameBounds, 0));
+        disposal = DisposalMethod::DO_NOT_DISPOSE;
+      }
+      m_lastFrameBounds = frameBounds;
+    }
+
+    // TODO We could join both frames in a longer one (with more duration)
+    if (frameBounds.isEmpty())
+      frameBounds = gfx::Rect(0, 0, 1, 1);
+
+    m_lastDisposal = disposal;
+  }
+
   doc::frame_t totalFrames() const {
     return m_fop->roi().frames();
   }
@@ -1123,99 +1353,38 @@ private:
     return frameBounds;
   }
 
-  void calculateBestDisposalMethod(gifframe_t gifFrame, gfx::Rect& frameBounds,
-                                   DisposalMethod& disposal) {
-    if (m_hasBackground) {
-      disposal = DisposalMethod::DO_NOT_DISPOSE;
-    }
-    else {
-      disposal = DisposalMethod::RESTORE_BGCOLOR;
-    }
-
-    if (gifFrame == 0) {
-      frameBounds = m_spriteBounds;
-    }
-    else {
-      gfx::Rect prev, next;
-
-      if (gifFrame-1 >= 0)
-        prev = calculateFrameBounds(m_currentImage, m_previousImage);
-
-      if (!m_hasBackground &&
-          gifFrame+1 < totalFrames())
-        next = calculateFrameBounds(m_currentImage, m_nextImage);
-
-      frameBounds = prev.createUnion(next);
-
-      // Special case were it's better to restore the previous frame
-      // when we dispose the current one than clearing with the bg
-      // color.
-      if (m_hasBackground && !prev.isEmpty()) {
-        gfx::Rect prevNext = calculateFrameBounds(m_previousImage, m_nextImage);
-        if (!prevNext.isEmpty() &&
-            frameBounds.contains(prevNext) &&
-            prevNext.w*prevNext.h < frameBounds.w*frameBounds.h) {
-          disposal = DisposalMethod::RESTORE_PREVIOUS;
-        }
-      }
-
-      GIF_TRACE("GIF: frameBounds=%d %d %d %d  prev=%d %d %d %d  next=%d %d %d %d\n",
-                frameBounds.x, frameBounds.y, frameBounds.w, frameBounds.h,
-                prev.x, prev.y, prev.w, prev.h,
-                next.x, next.y, next.w, next.h);
-    }
-  }
 
   void writeImage(const gifframe_t gifFrame,
                   const frame_t frame,
                   const gfx::Rect& frameBounds,
                   const DisposalMethod disposal,
                   const bool fixDuration) {
-    std::unique_ptr<Palette> framePaletteRef;
-    std::unique_ptr<RgbMapRGB5A3> rgbmapRef;
-    Palette* framePalette = m_sprite->palette(frame);
-    RgbMap* rgbmap = m_sprite->rgbMap(frame);
+    Palette framePalette;
+    if (m_globalColormap)
+      framePalette = m_globalColormapPalette;
+    else
+      framePalette = calculatePalette(frameBounds, disposal);
 
-    // Create optimized palette for RGB/Grayscale images
-    if (m_quantizeColormaps) {
-      framePaletteRef.reset(createOptimizedPalette(frameBounds));
-      framePalette = framePaletteRef.get();
-
-      rgbmapRef.reset(new RgbMapRGB5A3);
-      rgbmapRef->regenerateMap(framePalette, m_transparentIndex);
-      rgbmap = rgbmapRef.get();
-    }
-
-    // We will store the frameBounds pixels in frameImage, with the
-    // indexes that must be stored in the GIF file for this specific
-    // frame.
-    if (!m_frameImageBuf)
-      m_frameImageBuf.reset(new ImageBuffer);
+    RgbMapRGB5A3 rgbmap;        // TODO RgbMapRGB5A3 configurable?
+    rgbmap.regenerateMap(&framePalette, m_transparentIndex);
 
     ImageRef frameImage(Image::create(IMAGE_INDEXED,
                                       frameBounds.w,
                                       frameBounds.h,
                                       m_frameImageBuf));
 
-    // Convert the frameBounds area of m_currentImage (RGB) to frameImage (Indexed)
-    // bool needsTransparent = false;
-    PalettePicks usedColors(framePalette->size());
+    // Every frame might use a small portion of the global palette,
+    // to optimize the gif file size, we will analize which colors
+    // will be used in each processed frame.
+    PalettePicks usedColors(framePalette.size());
 
-    // If the sprite needs a transparent color we mark it as used so
-    // the palette includes a spot for it. It doesn't matter if the
-    // image doesn't use the transparent index, if the sprite isn't
-    // opaque we need the transparent index anyway.
-    if (m_transparentIndex >= 0) {
-      int i = m_transparentIndex;
-      if (i >= usedColors.size())
-        usedColors.resize(i+1);
-      usedColors[i] = true;
-    }
+    int localTransparent = m_transparentIndex;
+    ColorMapObject* colormap = m_globalColormap;
+    Remap remap(256);
 
-    {
-      const LockImageBits<RgbTraits> srcBits(m_currentImage, frameBounds);
-      LockImageBits<IndexedTraits> dstBits(
-        frameImage.get(), gfx::Rect(0, 0, frameBounds.w, frameBounds.h));
+    if (!m_preservePaletteOrder) {
+      const LockImageBits<RgbTraits> srcBits(m_deltaImage.get());
+      LockImageBits<IndexedTraits> dstBits(frameImage.get());
 
       auto srcIt = srcBits.begin();
       auto dstIt = dstBits.begin();
@@ -1229,19 +1398,16 @@ private:
           int i;
 
           if (rgba_geta(color) >= 128) {
-            color |= rgba_a_mask; // Set alpha=255
-
-            i = framePalette->findExactMatch(
+            i = framePalette.findExactMatch(
               rgba_getr(color),
               rgba_getg(color),
               rgba_getb(color),
               255,
               m_transparentIndex);
             if (i < 0)
-              i = rgbmap->mapColor(color);
+              i = rgbmap.mapColor(color | rgba_a_mask); // alpha=255
           }
           else {
-            ASSERT(m_transparentIndex >= 0);
             if (m_transparentIndex >= 0)
               i = m_transparentIndex;
             else
@@ -1260,34 +1426,36 @@ private:
           *dstIt = i;
         }
       }
-    }
 
-    int usedNColors = usedColors.picks();
+      int usedNColors = usedColors.picks();
 
-    Remap remap(256);
-    for (int i=0; i<remap.size(); ++i)
-      remap.map(i, i);
+      for (int i=0; i<remap.size(); ++i)
+        remap.map(i, i);
 
-    int localTransparent = m_transparentIndex;
-    ColorMapObject* colormap = m_globalColormap;
-    if (!colormap) {
-      Palette reducedPalette(0, usedNColors);
+      if (!colormap) {
+        Palette reducedPalette(0, usedNColors);
 
-      for (int i=0, j=0; i<framePalette->size(); ++i) {
-        if (usedColors[i]) {
-          reducedPalette.setEntry(j, framePalette->getEntry(i));
-          remap.map(i, j);
-          ++j;
+        for (int i=0, j=0; i<framePalette.size(); ++i) {
+          if (usedColors[i]) {
+            reducedPalette.setEntry(j, framePalette.getEntry(i));
+            remap.map(i, j);
+            ++j;
+          }
         }
+
+        colormap = createColorMap(&reducedPalette);
+        if (localTransparent >= 0)
+          localTransparent = remap[localTransparent];
       }
 
-      colormap = createColorMap(&reducedPalette);
-      if (localTransparent >= 0)
-        localTransparent = remap[localTransparent];
+      if (localTransparent >= 0 && m_transparentIndex != localTransparent)
+        remap.map(m_transparentIndex, localTransparent);
+    }
+    else {
+      frameImage.reset(Image::createCopy(m_deltaImage.get()));
+      for (int i=0; i<colormap->ColorCount; ++i)
+        remap.map(i, i);
     }
-
-    if (localTransparent >= 0 && m_transparentIndex != localTransparent)
-      remap.map(m_transparentIndex, localTransparent);
 
     // Write extension record.
     writeExtension(gifFrame, frame, localTransparent,
@@ -1337,20 +1505,173 @@ private:
       GifFreeMapObject(colormap);
   }
 
-  Palette* createOptimizedPalette(const gfx::Rect& frameBounds) {
+  Palette calculatePalette(const gfx::Rect& frameBounds,
+                           const DisposalMethod disposal) {
+    // First, we must check the palette color count in m_deltaImage (our best shot
+    // to find the smaller palette color count)
+    Palette pal(createOptimizedPalette(m_deltaImage.get(), m_deltaImage->bounds(), 256));
+    if (pal.size() == 256) {
+      // Here the palette has 256 colors, there is no place to include
+      // the 0 color (createOptimizedPalette() doesn't create an entry
+      // for it).
+      //
+      // We have two paths:
+      // 1- Giving a try to palette generation on m_currentImage in frameBouns limits.
+      // 2- If the previous step is not possible (color count > 256), we will to start
+      //    to approximate colors from m_deltaImage with some criterion. Final target:
+      //    to approximate the palette to 255 colors + clear color (0)).
+
+      // 1- Giving a try to palette generation on m_currentImage in frameBouns limits.
+      // if disposal == RESTORE_BGCOLOR m_deltaImage already is a cropped copy of m_currentImage.
+      Palette auxPalette;
+      if (disposal == DisposalMethod::DO_NOT_DISPOSE)
+        auxPalette = createOptimizedPalette(m_currentImage, frameBounds, 257);
+      else
+        auxPalette = pal;
+
+      if (auxPalette.size() <= 256) {
+        // We are fine with color count in m_currentImage contained in
+        // frameBounds (we got 256 or less colors):
+        m_transparentIndex = -1;
+        pal = auxPalette;
+        if (disposal == DisposalMethod::DO_NOT_DISPOSE) {
+          ASSERT(frameBounds.w >= 1);
+          m_deltaImage.reset(crop_image(m_currentImage, frameBounds, 0));
+        }
+      }
+      else {
+        // 2- If the previous step fails, we will to start to approximate colors from m_deltaImage
+        //    with some criterion:
+
+        // Final target: to approximate the palette to 255 colors + clear color (0)).
+        // CRITERION:
+        // Find a palette of 220 or less colors (in high precision) into the square border
+        // contained in m_deltaImage, then into the center square quantize the remaining colors
+        // to complete a palette of 255 colors, finally add the transparent color (0).
+        //
+        //  m_currentImage__      __ m_deltaImage (same rectangle size as `frameBounds` variable)
+        //                 |    |
+        //   --------------*----|-----------
+        //  |                   |           |
+        //  |     --------------*-          |
+        //  |    |                |         |
+        //  |    |    ________    |         |
+        //  |    |   |        | *--------------- square border (we will collect
+        //  |    |   |        |   |         |    high precision colors from this area, less than 220)
+        //  |    |   |        |   |         |
+        //  |    |   |    *--------------------- center rectangle (we will to quantize
+        //  |    |   |        |   |         |    colors contained in this area)
+        //  |    |   |________|   |         |
+        //  |    |                |         |
+        //  |    |________________|         |
+        //  |                               |
+        //  |_______________________________|
+        //
+
+        const gfx::Size deltaSize = m_deltaImage->size();
+        int thicknessTop = deltaSize.h / 4;
+        int thicknessLeft = deltaSize.w / 4;
+        int repeatCounter = 0;
+        while (repeatCounter < 10 && thicknessTop > 0 && thicknessLeft > 0) {
+
+          //      ----------------
+          //     |________________|
+          //     |   |        |   |
+          //     |   |        |   |
+          //     |   |________|   |
+          //     |________________|
+          render::PaletteOptimizer optimizer;
+          gfx::Rect auxRect(0, 0, deltaSize.w, thicknessTop);
+          optimizer.feedWithImage(m_deltaImage.get(), auxRect, false);
+
+          //      ----------------
+          //     |    ________    |
+          //     |   |        |   |
+          //     |   |        |   |
+          //     |___|________|___|
+          //     |________________|
+          auxRect = gfx::Rect(0, deltaSize.h - thicknessTop - 1, deltaSize.w, thicknessTop);
+          optimizer.feedWithImage(m_deltaImage.get(), auxRect, false);
+
+          //      ----------------
+          //     |____________    |
+          //     |   |        |   |
+          //     |   |        |   |
+          //     |___|________|   |
+          //     |________________|
+          auxRect = gfx::Rect(0, thicknessTop, thicknessLeft, deltaSize.h - 2 * thicknessTop);
+          optimizer.feedWithImage(m_deltaImage.get(), auxRect, false);
+
+          //      ----------------
+          //     |   _____________|
+          //     |   |        |   |
+          //     |   |        |   |
+          //     |   |________|___|
+          //     |________________|
+          auxRect = gfx::Rect(deltaSize.w - thicknessLeft - 1, thicknessTop, thicknessLeft, deltaSize.h - 2 * thicknessTop);
+          optimizer.feedWithImage(m_deltaImage.get(), auxRect, false);
+
+          int maxBorderColorCount = 220;
+          if (optimizer.isHighPrecision() && (optimizer.highPrecisionSize() < maxBorderColorCount)) {
+            pal.resize(optimizer.highPrecisionSize());
+            optimizer.calculate(&pal, -1);
+            break;
+          }
+          else if (thicknessTop <= 1 || thicknessLeft <= 1) {
+            pal.resize(0);
+            thicknessTop = 0;
+            thicknessLeft = 0;
+            break;
+          }
+          else {
+            thicknessTop -= thicknessTop / 2;
+            thicknessLeft -= thicknessLeft / 2;
+          }
+
+          repeatCounter++;
+        }
+        // Quantize the colors contained into center rectangle and add these in `pal`:
+        if (pal.size() < 255) {
+          gfx::Rect centerRect(thicknessLeft,
+                               thicknessTop,
+                               deltaSize.w - 2 * thicknessLeft,
+                               deltaSize.h - 2 * thicknessTop);
+          Palette centerPalette(0, 255 - pal.size());
+          centerPalette = createOptimizedPalette(m_deltaImage.get(),
+                                                 centerRect, 255 - pal.size());
+          for (int i=0; i < centerPalette.size(); i++)
+            pal.addEntry(centerPalette.getEntry(i));
+        }
+        // Finally add transparent color:
+        ASSERT(pal.size() <= 255);
+        pal.addEntry(0);
+        m_transparentIndex = pal.size() - 1;
+      }
+    }
+    // We are fine, we got 255 or less, there is room for the transparent color
+    else if (pal.size() <= 255) {
+      pal.addEntry(0);
+      m_transparentIndex = pal.size() - 1;
+    }
+    return pal;
+  }
+
+  static Palette createOptimizedPalette(const Image* image,
+                                        const gfx::Rect& bounds,
+                                        const int ncolors) {
     render::PaletteOptimizer optimizer;
 
-    // Feed the palette optimizer with pixels inside frameBounds
-    for (const auto& color : LockImageBits<RgbTraits>(m_currentImage, frameBounds)) {
-      if (rgba_geta(color) >= 128)
+    // Feed the palette optimizer with pixels inside the given bounds
+    for (const auto& color : LockImageBits<RgbTraits>(image, bounds)) {
+      if (rgba_geta(color) >= 128) // Note: the mask color won't be part of the final palette
         optimizer.feedWithRgbaColor(
           rgba(rgba_getr(color),
                rgba_getg(color),
                rgba_getb(color), 255));
     }
 
-    Palette* palette = new Palette(0, 256);
-    optimizer.calculate(palette, m_transparentIndex);
+    Palette palette(0, ncolors);
+    optimizer.calculate(&palette, -1);
     return palette;
   }
 
@@ -1359,7 +1680,10 @@ private:
     render.setNewBlend(m_fop->newBlend());
 
     render.setBgType(render::BgType::NONE);
-    clear_image(dst, m_clearColor);
+    if (m_preservePaletteOrder)
+      clear_image(dst, m_bgIndex);
+    else
+      clear_image(dst, 0);
     render.renderSprite(dst, m_sprite, frame);
   }
 
@@ -1397,18 +1721,23 @@ private:
   gfx::Rect m_spriteBounds;
   bool m_hasBackground;
   int m_bgIndex;
-  color_t m_clearColor;
   int m_transparentIndex;
   int m_bitsPerPixel;
+  // Global palette to use on all frames, or nullptr in case that we
+  // have to quantize the palette on each frame.
   ColorMapObject* m_globalColormap;
-  bool m_quantizeColormaps;
+  Palette m_globalColormapPalette;
   bool m_interlaced;
   int m_loop;
+  bool m_preservePaletteOrder;
+  gfx::Rect m_lastFrameBounds;
+  DisposalMethod m_lastDisposal;
   ImageBufferPtr m_frameImageBuf;
   ImageRef m_images[3];
   Image* m_previousImage;
   Image* m_currentImage;
   Image* m_nextImage;
+  std::unique_ptr<Image> m_deltaImage;
 };
 
 bool GifFormat::onSave(FileOp* fop)
@@ -1447,21 +1776,37 @@ FormatOptionsPtr GifFormat::onAskUserForFormatOptions(FileOp* fop)
         opts->setInterlaced(pref.gif.interlaced());
       if (pref.isSet(pref.gif.loop))
         opts->setLoop(pref.gif.loop());
+      if (pref.isSet(pref.gif.preservePaletteOrder))
+        opts->setPreservePaletteOrder(pref.gif.preservePaletteOrder());
 
       if (pref.gif.showAlert()) {
         app::gen::GifOptions win;
         win.interlaced()->setSelected(opts->interlaced());
         win.loop()->setSelected(opts->loop());
+        win.preservePaletteOrder()->setSelected(opts->preservePaletteOrder());
+
+        if (fop->document()->sprite()->pixelFormat() == PixelFormat::IMAGE_INDEXED &&
+            !fop->document()->sprite()->isOpaque())
+          win.preservePaletteOrder()->setEnabled(true);
+        else {
+          win.preservePaletteOrder()->setEnabled(false);
+          if (fop->document()->sprite()->pixelFormat() == PixelFormat::IMAGE_INDEXED && fop->document()->sprite()->isOpaque())
+            win.preservePaletteOrder()->setSelected(true);
+          else
+            win.preservePaletteOrder()->setSelected(false);
+        }
 
         win.openWindowInForeground();
 
         if (win.closer() == win.ok()) {
           pref.gif.interlaced(win.interlaced()->isSelected());
           pref.gif.loop(win.loop()->isSelected());
+          pref.gif.preservePaletteOrder(win.preservePaletteOrder()->isSelected());
           pref.gif.showAlert(!win.dontShow()->isSelected());
 
           opts->setInterlaced(pref.gif.interlaced());
           opts->setLoop(pref.gif.loop());
+          opts->setPreservePaletteOrder(pref.gif.preservePaletteOrder());
         }
         else {
           opts.reset();
diff --git a/src/app/file/gif_options.h b/src/app/file/gif_options.h
index d5b468003..3dfb393d6 100644
--- a/src/app/file/gif_options.h
+++ b/src/app/file/gif_options.h
@@ -1,4 +1,5 @@
 // Aseprite
+// Copyright (C)      2020  Igara Studio S.A.
 // Copyright (C) 2001-2017  David Capello
 //
 // This program is distributed under the terms of
@@ -18,20 +19,25 @@ namespace app {
   public:
     GifOptions(
       bool interlaced = false,
-      bool loop = true)
+      bool loop = true,
+      bool preservePaletteOrder = true)
       : m_interlaced(interlaced)
-      , m_loop(loop) {
+      , m_loop(loop)
+      , m_preservePaletteOrder(preservePaletteOrder) {
     }
 
     bool interlaced() const { return m_interlaced; }
     bool loop() const { return m_loop; }
+    bool preservePaletteOrder() const { return m_preservePaletteOrder; }
 
     void setInterlaced(bool interlaced) { m_interlaced = interlaced; }
     void setLoop(bool loop) { m_loop = loop; }
+    void setPreservePaletteOrder(bool preservePaletteOrder) {m_preservePaletteOrder = preservePaletteOrder; }
 
   private:
     bool m_interlaced;
     bool m_loop;
+    bool m_preservePaletteOrder;
   };
 
 } // namespace app
diff --git a/src/doc/palette.cpp b/src/doc/palette.cpp
index 9894928d9..b8bc9ec3f 100644
--- a/src/doc/palette.cpp
+++ b/src/doc/palette.cpp
@@ -22,6 +22,11 @@ namespace doc {
 
 using namespace gfx;
 
+Palette::Palette()
+  : Palette(0, 256)
+{
+}
+
 Palette::Palette(frame_t frame, int ncolors)
   : Object(ObjectType::Palette)
 {
@@ -58,6 +63,18 @@ Palette::~Palette()
 {
 }
 
+Palette& Palette::operator=(const Palette& that)
+{
+  m_frame = that.m_frame;
+  m_colors = that.m_colors;
+  m_names = that.m_names;
+  m_filename = that.m_filename;
+  m_comment = that.m_comment;
+
+  ++m_modifications;
+  return *this;
+}
+
 Palette* Palette::createGrayscale()
 {
   Palette* graypal = new Palette(frame_t(0), 256);
diff --git a/src/doc/palette.h b/src/doc/palette.h
index 2b28d9eb7..04f713775 100644
--- a/src/doc/palette.h
+++ b/src/doc/palette.h
@@ -1,6 +1,6 @@
 // Aseprite Document Library
-// Copyright (c)      2020 Igara Studio S.A.
-// Copyright (c) 2001-2018 David Capello
+// Copyright (C) 2020  Igara Studio S.A.
+// Copyright (C) 2001-2018  David Capello
 //
 // This file is released under the terms of the MIT license.
 // Read LICENSE.txt for more information.
@@ -23,11 +23,14 @@ namespace doc {
 
   class Palette : public Object {
   public:
+    Palette();
     Palette(frame_t frame, int ncolors);
     Palette(const Palette& palette);
     Palette(const Palette& palette, const Remap& remap);
     ~Palette();
 
+    Palette& operator=(const Palette& that);
+
     static Palette* createGrayscale();
 
     int size() const { return (int)m_colors.size(); }
diff --git a/src/render/color_histogram.h b/src/render/color_histogram.h
index 77d326bc1..e591b80ff 100644
--- a/src/render/color_histogram.h
+++ b/src/render/color_histogram.h
@@ -1,4 +1,5 @@
 // Aseprite Render Library
+// Copyright (c)      2020 Igara Studio S.A.
 // Copyright (c) 2001-2015 David Capello
 //
 // This file is released under the terms of the MIT license.
@@ -102,6 +103,9 @@ namespace render {
       }
     }
 
+    bool isHighPrecision() { return m_useHighPrecision; }
+    int highPrecisionSize() { return m_highPrecision.size(); }
+
   private:
     // Converts input color in a index for the histogram. It reduces
     // each 8-bit component to the resolution given in the template
diff --git a/src/render/quantization.cpp b/src/render/quantization.cpp
index 4129e87d9..a30663d29 100644
--- a/src/render/quantization.cpp
+++ b/src/render/quantization.cpp
@@ -43,7 +43,8 @@ Palette* create_palette_from_sprite(
   Palette* palette,
   TaskDelegate* delegate,
   const bool newBlend,
-  const RgbMapAlgorithm mappingAlgorithm)
+  const RgbMapAlgorithm mappingAlgorithm,
+  const bool calculateWithTransparent)
 {
   PaletteOptimizer optimizer;
   OctreeMap octreemap;
@@ -88,10 +89,13 @@ Palette* create_palette_from_sprite(
       optimizer.calculate(
         palette,
         // Transparent color is needed if we have transparent layers
-        (sprite->backgroundLayer() &&
-         sprite->allLayersCount() == 1 ? -1: sprite->transparentColor()));
+        ((sprite->backgroundLayer() &&
+          sprite->allLayersCount() == 1) ||
+         !calculateWithTransparent)? -1: sprite->transparentColor());
       break;
     case RgbMapAlgorithm::OCTREE:
+      // TODO check calculateWithTransparent flag
+
       if (!octreemap.makePalette(palette, palette->size())) {
         // We can use an 8-bit deep octree map, instead of 7-bit of the
         // first attempt.
@@ -379,7 +383,15 @@ Image* convert_pixel_format(
 // Creation of optimized palette for RGB images
 // by David Capello
 
-void PaletteOptimizer::feedWithImage(Image* image, bool withAlpha)
+void PaletteOptimizer::feedWithImage(const Image* image,
+                                     const bool withAlpha)
+{
+  feedWithImage(image, image->bounds(), withAlpha);
+}
+
+void PaletteOptimizer::feedWithImage(const Image* image,
+                                     const gfx::Rect& bounds,
+                                     const bool withAlpha)
 {
   uint32_t color;
 
@@ -391,8 +403,8 @@ void PaletteOptimizer::feedWithImage(Image* image, bool withAlpha)
 
     case IMAGE_RGB:
       {
-        const LockImageBits<RgbTraits> bits(image);
-        LockImageBits<RgbTraits>::const_iterator it = bits.begin(), end = bits.end();
+        const LockImageBits<RgbTraits> bits(image, bounds);
+        auto it = bits.begin(), end = bits.end();
 
         for (; it != end; ++it) {
           color = *it;
@@ -408,8 +420,8 @@ void PaletteOptimizer::feedWithImage(Image* image, bool withAlpha)
 
     case IMAGE_GRAYSCALE:
       {
-        const LockImageBits<RgbTraits> bits(image);
-        LockImageBits<RgbTraits>::const_iterator it = bits.begin(), end = bits.end();
+        const LockImageBits<GrayscaleTraits> bits(image, bounds);
+        auto it = bits.begin(), end = bits.end();
 
         for (; it != end; ++it) {
           color = *it;
diff --git a/src/render/quantization.h b/src/render/quantization.h
index 1ab8627f1..97e7e12fd 100644
--- a/src/render/quantization.h
+++ b/src/render/quantization.h
@@ -29,9 +29,15 @@ namespace render {
 
   class PaletteOptimizer {
   public:
-    void feedWithImage(doc::Image* image, bool withAlpha);
+    void feedWithImage(const doc::Image* image,
+                       const bool withAlpha);
+    void feedWithImage(const doc::Image* image,
+                       const gfx::Rect& bounds,
+                       const bool withAlpha);
     void feedWithRgbaColor(doc::color_t color);
     void calculate(doc::Palette* palette, int maskIndex);
+    bool isHighPrecision() { return m_histogram.isHighPrecision(); }
+    int highPrecisionSize() { return m_histogram.highPrecisionSize(); }
 
   private:
     render::ColorHistogram<5, 6, 5, 5> m_histogram;
@@ -47,7 +53,8 @@ namespace render {
     doc::Palette* newPalette, // Can be NULL to create a new palette
     TaskDelegate* delegate,
     const bool newBlend,
-    const RgbMapAlgorithm mappingAlgorithm);
+    const RgbMapAlgorithm mappingAlgorithm,
+    const bool calculateWithTransparent = true);
 
   // Changes the image pixel format. The dithering method is used only
   // when you want to convert from RGB to Indexed.