Esempio n. 1
0
/**
 * Updates palette and redraw the screen.
 */
void app_refresh_screen(const Document* document)
{
  ASSERT(screen != NULL);

  if (document && document->getSprite())
    set_current_palette(document->getSprite()->getCurrentPalette(), false);
  else
    set_current_palette(NULL, false);

  // Invalidate the whole screen.
  gui::Manager::getDefault()->invalidate();
}
Esempio n. 2
0
void PaletteSizeCommand::onExecute(Context* context)
{
  ContextReader reader(context);
  frame_t frame = reader.frame();
  Palette palette(*reader.sprite()->palette(frame));

  app::gen::PaletteSize window;
  window.colors()->setTextf("%d", palette.size());
  window.openWindowInForeground();
  if (window.closer() == window.ok()) {
    int ncolors = window.colors()->textInt();
    if (ncolors == palette.size())
      return;

    palette.resize(MID(1, ncolors, std::numeric_limits<int>::max()));

    ContextWriter writer(reader);
    Transaction transaction(context, "Palette Size", ModifyDocument);
    transaction.execute(new cmd::SetPalette(writer.sprite(), frame, &palette));
    transaction.commit();

    set_current_palette(&palette, false);
    ui::Manager::getDefault()->invalidate();
  }
}
Esempio n. 3
0
void PaletteSizeCommand::onExecute(Context* context)
{
  ContextWriter writer(context);
  Sprite* sprite = writer.sprite();
  frame_t frame = writer.frame();
  Palette palette(*sprite->palette(frame));

  app::gen::PaletteSize window;
  window.colors()->setTextf("%d", palette.size());
  window.openWindowInForeground();
  if (window.getKiller() == window.ok()) {
    int ncolors = window.colors()->getTextInt();
    if (ncolors == palette.size())
      return;

    palette.resize(MID(1, ncolors, INT_MAX));

    Transaction transaction(context, "Palette Size", ModifyDocument);
    transaction.execute(new cmd::SetPalette(sprite, frame, &palette));
    transaction.commit();

    set_current_palette(&palette, false);
    ui::Manager::getDefault()->invalidate();
  }
}
Esempio n. 4
0
void UndoCommand::onExecute(Context* context)
{
    ContextWriter writer(context);
    Document* document(writer.document());
    DocumentUndo* undo = document->getUndo();
    Sprite* sprite = document->sprite();

    if (context->settings()->undoGotoModified()) {
        SpritePosition spritePosition;
        SpritePosition currentPosition(writer.location()->layerIndex(),
                                       writer.location()->frame());

        if (m_type == Undo)
            spritePosition = undo->getNextUndoSpritePosition();
        else
            spritePosition = undo->getNextRedoSpritePosition();

        if (spritePosition != currentPosition) {
            current_editor->setLayer(sprite->indexToLayer(spritePosition.layerIndex()));
            current_editor->setFrame(spritePosition.frameNumber());

            // Draw the current layer/frame (which is not undone yet) so the
            // user can see the doUndo/doRedo effect.
            current_editor->drawSpriteClipped(
                gfx::Region(gfx::Rect(0, 0, sprite->width(), sprite->height())));

            ui::dirty_display_flag = true;
            gui_feedback();

            base::this_thread::sleep_for(0.01);
        }
    }

    StatusBar::instance()
    ->showTip(1000, "%s %s",
              (m_type == Undo ? "Undid": "Redid"),
              (m_type == Undo ? undo->getNextUndoLabel():
               undo->getNextRedoLabel()));

    // Effectively undo/redo.
    if (m_type == Undo)
        undo->doUndo();
    else
        undo->doRedo();

    document->generateMaskBoundaries();
    document->destroyExtraCel(); // Regenerate extras

    update_screen_for_document(document);
    set_current_palette(writer.palette(), false);
}
Esempio n. 5
0
// Initializes the application loading the modules, setting the
// graphics mode, loading the configuration and resources, etc.
App::App(int argc, char* argv[])
  : m_configModule(NULL)
  , m_checkArgs(NULL)
  , m_loggerModule(NULL)
  , m_modules(NULL)
  , m_legacy(NULL)
  , m_isGui(false)
{
  ASSERT(m_instance == NULL);
  m_instance = this;

  for (int i = 0; i < argc; ++i)
    m_args.push_back(argv[i]);

  m_configModule = new ConfigModule();
  m_checkArgs = new CheckArgs(m_args);
  m_loggerModule = new LoggerModule(m_checkArgs->isVerbose());
  m_modules = new Modules();
  m_isGui = !(m_checkArgs->isConsoleOnly());
  m_legacy = new LegacyModules(isGui() ? REQUIRE_INTERFACE: 0);

  // Initialize editors.
  init_module_editors();

  // Register well-known image file types.
  FileFormatsManager::instance().registerAllFormats();

  // init editor cursor
  Editor::editor_cursor_init();

  // Load RenderEngine configuration
  RenderEngine::loadConfig();

  /* custom default palette? */
  if (palette_filename) {
    PRINTF("Loading custom palette file: %s\n", palette_filename);

    UniquePtr<Palette> pal(Palette::load(palette_filename));
    if (pal.get() == NULL)
      throw base::Exception("Error loading default palette from: %s",
                            static_cast<const char*>(palette_filename));

    set_default_palette(pal.get());
  }

  /* set system palette to the default one */
  set_current_palette(NULL, true);
}
Esempio n. 6
0
void update_screen_for_document(Document* document)
{
    // Without document.
    if (!document) {
        // Well, change to the default palette.
        if (set_current_palette(NULL, false)) {
            // If the palette changes, refresh the whole screen.
            Manager::getDefault()->invalidate();
        }
    }
    // With a document.
    else {
        document->notifyGeneralUpdate();

        // Update the tabs (maybe the modified status has been changed).
        app_rebuild_documents_tabs();
    }
}
Esempio n. 7
0
  void onChangeAction() {
    Item* item = static_cast<Item*>(
      actions()->getSelectedChild());

    if (m_document &&
        m_document->undoHistory()->currentState() != item->state()) {
      try {
        DocumentWriter writer(m_document, 100);
        m_document->undoHistory()->moveToState(item->state());
        m_document->generateMaskBoundaries();

        // TODO this should be an observer of the current document palette
        set_current_palette(m_document->sprite()->palette(m_frame),
                            false);

        m_document->notifyGeneralUpdate();
      }
      catch (const std::exception& ex) {
        selectState(m_document->undoHistory()->currentState());
        Console::showException(ex);
      }
    }
  }
void ColorQuantizationCommand::onExecute(Context* context)
{
  try {
    ContextWriter writer(UIContext::instance(), 500);
    Sprite* sprite = writer.sprite();
    frame_t frame = writer.frame();
    if (sprite) {
      PalettePicks entries;
      ColorBar::instance()->getPaletteView()->getSelectedEntries(entries);

      entries.pickAllIfNeeded();
      int n = entries.picks();

      Palette palette(frame, n);
      render::create_palette_from_rgb(sprite, frame, &palette);

      Palette newPalette(*get_current_palette());

      int i = 0, j = 0;
      for (bool state : entries) {
        if (state)
          newPalette.setEntry(i, palette.getEntry(j++));
        ++i;
      }

      Transaction transaction(writer.context(), "Color Quantization", ModifyDocument);
      transaction.execute(new cmd::SetPalette(sprite, frame, &newPalette));
      transaction.commit();

      set_current_palette(&newPalette, false);
      ui::Manager::getDefault()->invalidate();
    }
  }
  catch (base::Exception& e) {
    Console::showException(e);
  }
}
void ColorQuantizationCommand::onExecute(Context* context)
{
  try {
    app::gen::PaletteFromSprite window;
    PalettePicks entries;

    Sprite* sprite;
    frame_t frame;
    Palette* curPalette;
    {
      ContextReader reader(context);
      Site site = context->activeSite();
      sprite = site.sprite();
      frame = site.frame();
      curPalette = sprite->palette(frame);

      window.newPalette()->setSelected(true);
      window.alphaChannel()->setSelected(
        App::instance()->preferences().quantization.withAlpha());
      window.ncolors()->setText("256");

      ColorBar::instance()->getPaletteView()->getSelectedEntries(entries);
      if (entries.picks() > 1) {
        window.currentRange()->setTextf(
          "%s, %d color(s)",
          window.currentRange()->text().c_str(),
          entries.picks());
      }
      else
        window.currentRange()->setEnabled(false);

      window.currentPalette()->setTextf(
        "%s, %d color(s)",
        window.currentPalette()->text().c_str(),
        curPalette->size());
    }

    window.openWindowInForeground();
    if (window.closer() != window.ok())
      return;

    bool withAlpha = window.alphaChannel()->isSelected();
    App::instance()->preferences().quantization.withAlpha(withAlpha);

    bool createPal = false;
    if (window.newPalette()->isSelected()) {
      int n = window.ncolors()->textInt();
      n = MAX(1, n);
      entries = PalettePicks(n);
      entries.all();
      createPal = true;
    }
    else if (window.currentPalette()->isSelected()) {
      entries.all();
    }
    if (entries.picks() == 0)
      return;

    Palette tmpPalette(frame, entries.picks());
    ColorQuantizationJob job(sprite, withAlpha, &tmpPalette);
    job.startJob();
    job.waitJob();
    if (job.isCanceled())
      return;

    base::UniquePtr<Palette> newPalette(
      new Palette(createPal ? tmpPalette:
                              *get_current_palette()));

    if (createPal) {
      entries = PalettePicks(newPalette->size());
      entries.all();
    }

    int i = 0, j = 0;
    for (bool state : entries) {
      if (state)
        newPalette->setEntry(i, tmpPalette.getEntry(j++));
      ++i;
    }

    if (*curPalette != *newPalette) {
      ContextWriter writer(UIContext::instance(), 500);
      Transaction transaction(writer.context(), "Color Quantization", ModifyDocument);
      transaction.execute(new cmd::SetPalette(sprite, frame, newPalette.get()));
      transaction.commit();

      set_current_palette(newPalette.get(), false);
      ui::Manager::getDefault()->invalidate();
    }
  }
  catch (base::Exception& e) {
    Console::showException(e);
  }
}
Esempio n. 10
0
void set_black_palette()
{
  Palette* p = new Palette(FrameNumber(0), 256);
  set_current_palette(p, true);
  delete p;
}
Esempio n. 11
0
void AddColorCommand::onExecute(Context* ctx)
{
  app::Color appColor;

  switch (m_source) {
    case Source::Fg:
      appColor = ColorBar::instance()->getFgColor();
      break;
    case Source::Bg:
      appColor = ColorBar::instance()->getBgColor();
      break;
    case Source::Color:
      appColor = m_color;
      break;
  }

  try {
    Palette* newPalette = get_current_palette(); // System current pal
    color_t color = doc::rgba(
      appColor.getRed(),
      appColor.getGreen(),
      appColor.getBlue(),
      appColor.getAlpha());
    int index = newPalette->findExactMatch(
      appColor.getRed(),
      appColor.getGreen(),
      appColor.getBlue(),
      appColor.getAlpha(), -1);

    // It should be -1, because the user has pressed the warning
    // button that is available only when the color isn't in the
    // palette.
    ASSERT(index < 0);
    if (index >= 0)
      return;

    ContextWriter writer(ctx, 500);
    Doc* document(writer.document());
    Sprite* sprite = writer.sprite();
    if (!document || !sprite) {
      ASSERT(false);
      return;
    }

    newPalette->addEntry(color);
    index = newPalette->size()-1;

    if (document) {
      frame_t frame = writer.frame();

      Tx tx(writer.context(), friendlyName(), ModifyDocument);
      tx(new cmd::SetPalette(sprite, frame, newPalette));
      tx.commit();
    }

    set_current_palette(newPalette, true);
    ui::Manager::getDefault()->invalidate();
  }
  catch (base::Exception& e) {
    Console::showException(e);
  }
}
Esempio n. 12
0
void load_default_palette(const std::string& userDefined)
{
    base::UniquePtr<Palette> pal;

    // Load specific palette file defined by the user in the command line.
    std::string palFile = userDefined;
    if (!palFile.empty())
        pal.reset(load_palette(palFile.c_str()));
    // Load default palette file
    else {
        std::string defaultPalName = get_preset_palette_filename(
                                         get_default_palette_preset_name(), ".ase");

        // If there is no palette in command line, we use the default one.
        palFile = defaultPalName;
        if (base::is_file(palFile)) {
            pal.reset(load_palette(palFile.c_str()));
        }
        else {
            // Migrate old default.gpl to default.ase format
            palFile = get_preset_palette_filename(
                          get_default_palette_preset_name(), ".gpl");

            if (base::is_file(palFile)) {
                pal.reset(load_palette(palFile.c_str()));

                // Remove duplicate black entries at the end (as old palettes
                // contains 256 colors)
                if (pal && pal->size() == 256) {
                    doc::color_t black = rgba(0, 0, 0, 255);

                    // Get the last non-black entry
                    int i = 0;
                    for (i=pal->size()-1; i>0; --i) {
                        if (pal->getEntry(i) != black)
                            break;
                    }

                    if (i < pal->size()-1) {
                        // Check if there is a black entry in the first entries.
                        bool hasBlack = false;
                        for (int j=0; j<i; ++j) {
                            if (pal->getEntry(j) == black) {
                                hasBlack = true;
                                break;
                            }
                        }
                        if (!hasBlack)
                            ++i;                // Leave one black entry

                        // Resize the palette
                        if (i < pal->size()-1)
                            pal->resize(i+1);
                    }
                }

                // We could remove the old .gpl file (palFile), but as the
                // user could be using multiple versions of Aseprite, it's a
                // good idea to keep both formats (.gpl for Aseprite <=
                // v1.1-beta5, and .ase for future versions).
            }
            // If the default palette file doesn't exist, we copy db32.gpl
            // as the default one (default.ase).
            else {
                ResourceFinder rf;
                rf.includeDataDir("palettes/db32.gpl");
                if (rf.findFirst()) {
                    pal.reset(load_palette(rf.filename().c_str()));
                }
            }

            // Save default.ase file
            if (pal) {
                palFile = defaultPalName;
                save_palette(palFile.c_str(), pal.get(), 0);
            }
        }
    }

    if (pal)
        set_default_palette(pal.get());

    set_current_palette(nullptr, true);
}
Esempio n. 13
0
void UndoCommand::onExecute(Context* context)
{
  ContextWriter writer(context);
  Document* document(writer.document());
  DocumentUndo* undo = document->undoHistory();
  Sprite* sprite = document->sprite();
  SpritePosition spritePosition;
  const bool gotoModified =
    Preferences::instance().undo.gotoModified();

  if (gotoModified) {
    SpritePosition currentPosition(writer.site()->layerIndex(),
                                   writer.site()->frame());

    if (m_type == Undo)
      spritePosition = undo->nextUndoSpritePosition();
    else
      spritePosition = undo->nextRedoSpritePosition();

    if (spritePosition != currentPosition) {
      current_editor->setLayer(sprite->indexToLayer(spritePosition.layerIndex()));
      current_editor->setFrame(spritePosition.frame());

      // Draw the current layer/frame (which is not undone yet) so the
      // user can see the doUndo/doRedo effect.
      current_editor->drawSpriteClipped(
        gfx::Region(gfx::Rect(0, 0, sprite->width(), sprite->height())));

      current_editor->manager()->flipDisplay();
      base::this_thread::sleep_for(0.01);
    }
  }

  StatusBar* statusbar = StatusBar::instance();
  if (statusbar)
    statusbar->showTip(1000, "%s %s",
      (m_type == Undo ? "Undid": "Redid"),
      (m_type == Undo ?
        undo->nextUndoLabel().c_str():
        undo->nextRedoLabel().c_str()));

  // Effectively undo/redo.
  if (m_type == Undo)
    undo->undo();
  else
    undo->redo();

  // After redo/undo, we retry to change the current SpritePosition
  // (because new frames/layers could be added, positions that we
  // weren't able to reach before the undo).
  if (gotoModified) {
    SpritePosition currentPosition(
      writer.site()->layerIndex(),
      writer.site()->frame());

    if (spritePosition != currentPosition) {
      current_editor->setLayer(sprite->indexToLayer(spritePosition.layerIndex()));
      current_editor->setFrame(spritePosition.frame());
    }
  }

  document->generateMaskBoundaries();
  document->setExtraCel(ExtraCelRef(nullptr));

  update_screen_for_document(document);
  set_current_palette(writer.palette(), false);
}
Esempio n. 14
0
void ColorQuantizationCommand::onExecute(Context* context)
{
  try {
    app::gen::PaletteFromSprite window;
    PalettePicks entries;

    Sprite* sprite;
    frame_t frame;
    Palette* curPalette;
    {
      ContextReader reader(context);
      Site site = context->activeSite();
      sprite = site.sprite();
      frame = site.frame();
      curPalette = sprite->palette(frame);

      window.newPalette()->setSelected(true);
      window.alphaChannel()->setSelected(
        App::instance()->preferences().quantization.withAlpha());
      window.ncolors()->setText("256");

      ColorBar::instance()->getPaletteView()->getSelectedEntries(entries);
      if (entries.picks() > 1) {
        window.currentRange()->setTextf(
          "%s, %d color(s)",
          window.currentRange()->text().c_str(),
          entries.picks());
      }
      else
        window.currentRange()->setEnabled(false);

      window.currentPalette()->setTextf(
        "%s, %d color(s)",
        window.currentPalette()->text().c_str(),
        curPalette->size());
    }

    window.openWindowInForeground();
    if (window.closer() != window.ok())
      return;

    bool withAlpha = window.alphaChannel()->isSelected();
    App::instance()->preferences().quantization.withAlpha(withAlpha);

    bool createPal = false;
    if (window.newPalette()->isSelected()) {
      int n = window.ncolors()->textInt();
      n = MAX(1, n);
      entries = PalettePicks(n);
      entries.all();
      createPal = true;
    }
    else if (window.currentPalette()->isSelected()) {
      entries.all();
    }
    if (entries.picks() == 0)
      return;

    Palette tmpPalette(frame, entries.picks());

    ContextReader reader(context);
    SpriteJob job(reader, "Color Quantization");
    const bool newBlend = Preferences::instance().experimental.newBlend();
    job.startJobWithCallback(
      [sprite, withAlpha, &tmpPalette, &job, newBlend]{
        render::create_palette_from_sprite(
          sprite, 0, sprite->lastFrame(),
          withAlpha, &tmpPalette,
          &job,
          newBlend);     // SpriteJob is a render::TaskDelegate
      });
    job.waitJob();
    if (job.isCanceled())
      return;

    std::unique_ptr<Palette> newPalette(
      new Palette(createPal ? tmpPalette:
                              *get_current_palette()));

    if (createPal) {
      entries = PalettePicks(newPalette->size());
      entries.all();
    }

    int i = 0, j = 0;
    for (bool state : entries) {
      if (state)
        newPalette->setEntry(i, tmpPalette.getEntry(j++));
      ++i;
    }

    if (*curPalette != *newPalette)
      job.tx()(new cmd::SetPalette(sprite, frame, newPalette.get()));

    set_current_palette(newPalette.get(), false);
    ui::Manager::getDefault()->invalidate();
  }
  catch (const base::Exception& e) {
    Console::showException(e);
  }
}
Esempio n. 15
0
/**
 * Shows the "New Sprite" dialog.
 */
void NewFileCommand::onExecute(Context* context)
{
  Preferences& pref = Preferences::instance();
  int ncolors = get_default_palette()->size();
  char buf[1024];
  app::Color bg_table[] = {
    app::Color::fromMask(),
    app::Color::fromRgb(255, 255, 255),
    app::Color::fromRgb(0, 0, 0),
  };

  // Load the window widget
  app::gen::NewSprite window;

  // Default values: Indexed, 320x240, Background color
  PixelFormat format = pref.newFile.colorMode();
  // Invalid format in config file.
  if (format != IMAGE_RGB &&
      format != IMAGE_INDEXED &&
      format != IMAGE_GRAYSCALE) {
    format = IMAGE_INDEXED;
  }
  int w = pref.newFile.width();
  int h = pref.newFile.height();
  int bg = pref.newFile.backgroundColor();
  bg = MID(0, bg, 2);

  // If the clipboard contains an image, we can show the size of the
  // clipboard as default image size.
  gfx::Size clipboardSize;
  if (clipboard::get_image_size(clipboardSize)) {
    w = clipboardSize.w;
    h = clipboardSize.h;
  }

  window.width()->setTextf("%d", MAX(1, w));
  window.height()->setTextf("%d", MAX(1, h));

  // Select image-type
  window.colorMode()->setSelectedItem(format);

  // Select background color
  window.bgColor()->setSelectedItem(bg);

  // Advance options
  bool advanced = pref.newFile.advanced();
  window.advancedCheck()->setSelected(advanced);
  window.advancedCheck()->Click.connect(
    base::Bind<void>(
      [&]{
        gfx::Rect bounds = window.bounds();
        window.advanced()->setVisible(window.advancedCheck()->isSelected());
        window.setBounds(gfx::Rect(window.bounds().origin(),
                                   window.sizeHint()));
        window.layout();

        window.manager()->invalidateRect(bounds);
      }));
  window.advanced()->setVisible(advanced);
  if (advanced)
    window.pixelRatio()->setValue(pref.newFile.pixelRatio());
  else
    window.pixelRatio()->setValue("1:1");

  // Open the window
  window.openWindowInForeground();

  if (window.closer() == window.okButton()) {
    bool ok = false;

    // Get the options
    format = (doc::PixelFormat)window.colorMode()->selectedItem();
    w = window.width()->textInt();
    h = window.height()->textInt();
    bg = window.bgColor()->selectedItem();

    static_assert(IMAGE_RGB == 0, "RGB pixel format should be 0");
    static_assert(IMAGE_INDEXED == 2, "Indexed pixel format should be 2");

    format = MID(IMAGE_RGB, format, IMAGE_INDEXED);
    w = MID(1, w, DOC_SPRITE_MAX_WIDTH);
    h = MID(1, h, DOC_SPRITE_MAX_HEIGHT);
    bg = MID(0, bg, 2);

    // Select the color
    app::Color color = app::Color::fromMask();

    if (bg >= 0 && bg <= 3) {
      color = bg_table[bg];
      ok = true;
    }

    if (ok) {
      // Save the configuration
      pref.newFile.width(w);
      pref.newFile.height(h);
      pref.newFile.colorMode(format);
      pref.newFile.backgroundColor(bg);
      pref.newFile.advanced(window.advancedCheck()->isSelected());
      pref.newFile.pixelRatio(window.pixelRatio()->getValue());

      // Create the new sprite
      ASSERT(format == IMAGE_RGB || format == IMAGE_GRAYSCALE || format == IMAGE_INDEXED);
      ASSERT(w > 0 && h > 0);

      std::unique_ptr<Sprite> sprite(Sprite::createBasicSprite(format, w, h, ncolors));

      if (window.advancedCheck()->isSelected()) {
        sprite->setPixelRatio(
          base::convert_to<PixelRatio>(window.pixelRatio()->getValue()));
      }

      if (sprite->pixelFormat() != IMAGE_GRAYSCALE)
        get_default_palette()->copyColorsTo(sprite->palette(frame_t(0)));

      // If the background color isn't transparent, we have to
      // convert the `Layer 1' in a `Background'
      if (color.getType() != app::Color::MaskType) {
        Layer* layer = sprite->root()->firstLayer();

        if (layer && layer->isImage()) {
          LayerImage* layerImage = static_cast<LayerImage*>(layer);
          layerImage->configureAsBackground();

          Image* image = layerImage->cel(frame_t(0))->image();

          // TODO Replace this adding a new parameter to color utils
          Palette oldPal = *get_current_palette();
          set_current_palette(get_default_palette(), false);

          doc::clear_image(image,
            color_utils::color_for_target(color,
              ColorTarget(
                ColorTarget::BackgroundLayer,
                sprite->pixelFormat(),
                sprite->transparentColor())));

          set_current_palette(&oldPal, false);
        }
      }

      // Show the sprite to the user
      std::unique_ptr<Doc> doc(new Doc(sprite.get()));
      sprite.release();
      sprintf(buf, "Sprite-%04d", ++_sprite_counter);
      doc->setFilename(buf);
      doc->setContext(context);
      doc.release();
    }
  }
}
Esempio n. 16
0
void UndoCommand::onExecute(Context* context)
{
  ContextWriter writer(context);
  Doc* document(writer.document());
  DocUndo* undo = document->undoHistory();

#ifdef ENABLE_UI
  Sprite* sprite = document->sprite();
  SpritePosition spritePosition;
  const bool gotoModified =
    (Preferences::instance().undo.gotoModified() &&
     context->isUIAvailable() && current_editor);
  if (gotoModified) {
    SpritePosition currentPosition(writer.site()->layer(),
                                   writer.site()->frame());

    if (m_type == Undo)
      spritePosition = undo->nextUndoSpritePosition();
    else
      spritePosition = undo->nextRedoSpritePosition();

    if (spritePosition != currentPosition) {
      Layer* selectLayer = spritePosition.layer();
      if (selectLayer)
        current_editor->setLayer(selectLayer);
      current_editor->setFrame(spritePosition.frame());

      // Draw the current layer/frame (which is not undone yet) so the
      // user can see the doUndo/doRedo effect.
      current_editor->drawSpriteClipped(
        gfx::Region(gfx::Rect(0, 0, sprite->width(), sprite->height())));

      current_editor->manager()->flipDisplay();
      base::this_thread::sleep_for(0.01);
    }
  }

  // Get the stream to deserialize the document range after executing
  // the undo/redo action. We cannot yet deserialize the document
  // range because there could be inexistent layers.
  std::istream* docRangeStream;
  if (m_type == Undo)
    docRangeStream = undo->nextUndoDocRange();
  else
    docRangeStream = undo->nextRedoDocRange();

  StatusBar* statusbar = StatusBar::instance();
  if (statusbar) {
    std::string msg;
    if (m_type == Undo)
      msg = "Undid " + undo->nextUndoLabel();
    else
      msg = "Redid " + undo->nextRedoLabel();
    if (Preferences::instance().undo.showTooltip())
      statusbar->showTip(1000, msg.c_str());
    else
      statusbar->setStatusText(0, msg.c_str());
  }
#endif // ENABLE_UI

  // Effectively undo/redo.
  if (m_type == Undo)
    undo->undo();
  else
    undo->redo();

#ifdef ENABLE_UI
  // After redo/undo, we retry to change the current SpritePosition
  // (because new frames/layers could be added, positions that we
  // weren't able to reach before the undo).
  if (gotoModified) {
    Site newSite = context->activeSite();
    SpritePosition currentPosition(
      newSite.layer(),
      newSite.frame());

    if (spritePosition != currentPosition) {
      Layer* selectLayer = spritePosition.layer();
      if (selectLayer)
        current_editor->setLayer(selectLayer);
      current_editor->setFrame(spritePosition.frame());
    }
  }

  // Update timeline range. We've to deserialize the DocRange at
  // this point when objects (possible layers) are re-created after
  // the undo and we can deserialize them.
  if (docRangeStream) {
    Timeline* timeline = App::instance()->timeline();
    if (timeline) {
      DocRange docRange;
      if (docRange.read(*docRangeStream))
        timeline->setRange(docRange);
    }
  }
#endif  // ENABLE_UI

  document->generateMaskBoundaries();
  document->setExtraCel(ExtraCelRef(nullptr));

#ifdef ENABLE_UI
  update_screen_for_document(document);
#endif
  set_current_palette(writer.palette(), false);
}