Esempio n. 1
0
void RemoveFrameCommand::onExecute(Context* context)
{
  ContextWriter writer(context);
  Document* document(writer.document());
  Sprite* sprite(writer.sprite());
  {
    Transaction transaction(writer.context(), "Remove Frame");
    DocumentApi api = document->getApi(transaction);

    // TODO the range of selected frames should be in doc::Site.
    auto range = App::instance()->timeline()->range();
    if (range.enabled()) {
      for (frame_t frame = range.frameEnd(),
             begin = range.frameBegin()-1;
           frame != begin;
           --frame) {
        api.removeFrame(sprite, frame);
      }
    }
    else {
      api.removeFrame(sprite, writer.frame());
    }

    transaction.commit();
  }
  update_screen_for_document(document);
}
Esempio n. 2
0
void CanvasSizeCommand::onExecute(Context* context)
{
  const ContextReader reader(context);
  const Sprite* sprite(reader.sprite());

  if (context->isUiAvailable()) {
    // load the window widget
    base::UniquePtr<CanvasSizeWindow> window(new CanvasSizeWindow(0, 0, 0, 0));

    window->remapWindow();
    window->centerWindow();

    load_window_pos(window, "CanvasSize");
    window->setVisible(true);
    window->openWindowInForeground();
    save_window_pos(window, "CanvasSize");

    if (!window->pressedOk())
      return;

    m_left   = window->getLeft();
    m_right  = window->getRight();
    m_top    = window->getTop();
    m_bottom = window->getBottom();
  }

  // Resize canvas

  int x1 = -m_left;
  int y1 = -m_top;
  int x2 = sprite->width() + m_right;
  int y2 = sprite->height() + m_bottom;

  if (x2 <= x1) x2 = x1+1;
  if (y2 <= y1) y2 = y1+1;

  {
    ContextWriter writer(reader);
    Document* document = writer.document();
    Sprite* sprite = writer.sprite();
    UndoTransaction undoTransaction(writer.context(), "Canvas Size");
    DocumentApi api = document->getApi();
    raster::color_t bgcolor = color_utils::color_for_target(
      context->settings()->getBgColor(),
      ColorTarget(
        ColorTarget::BackgroundLayer,
        sprite->pixelFormat(),
        sprite->transparentColor()));

    api.cropSprite(sprite, gfx::Rect(x1, y1, x2-x1, y2-y1), bgcolor);
    undoTransaction.commit();

    document->generateMaskBoundaries();
    update_screen_for_document(document);
  }
}
void reverse_frames(Document* doc, const DocumentRange& range)
{
  const app::Context* context = static_cast<app::Context*>(doc->context());
  const ContextReader reader(context);
  ContextWriter writer(reader);
  Transaction transaction(writer.context(), "Reverse Frames");
  DocumentApi api = doc->getApi(transaction);
  Sprite* sprite = doc->sprite();
  frame_t frameBegin, frameEnd;
  int layerBegin, layerEnd;
  bool moveFrames = false;

  switch (range.type()) {
    case DocumentRange::kCels:
      frameBegin = range.frameBegin();
      frameEnd = range.frameEnd();
      layerBegin = range.layerBegin();
      layerEnd = range.layerEnd() + 1;
      break;
    case DocumentRange::kFrames:
      frameBegin = range.frameBegin();
      frameEnd = range.frameEnd();
      moveFrames = true;
      break;
    case DocumentRange::kLayers:
      frameBegin = frame_t(0);
      frameEnd = sprite->totalFrames()-1;
      layerBegin = range.layerBegin();
      layerEnd = range.layerEnd() + 1;
      break;
  }

  if (moveFrames) {
    for (frame_t frameRev = frameEnd+1;
         frameRev > frameBegin;
         --frameRev) {
      api.moveFrame(sprite, frameBegin, frameRev);
    }
  }
  else {
    std::vector<Layer*> layers;
    sprite->getLayersList(layers);

    for (int layerIdx = layerBegin; layerIdx != layerEnd; ++layerIdx) {
      for (frame_t frame = frameBegin,
             frameRev = frameEnd;
           frame != (frameBegin+frameEnd)/2+1;
           ++frame, --frameRev) {
        LayerImage* layer = static_cast<LayerImage*>(layers[layerIdx]);
        api.swapCel(layer, frame, frameRev);
      }
    }
  }

  transaction.commit();
}
Esempio n. 4
0
bool MovingCelState::onMouseUp(Editor* editor, MouseMessage* msg)
{
  Document* document = editor->document();

  // Here we put back the cel into its original coordinate (so we can
  // add an undoer before).
  if (m_celOffset != gfx::Point(0, 0)) {
    // Put the cels in the original position.
    for (size_t i=0; i<m_celList.size(); ++i) {
      Cel* cel = m_celList[i];
      const gfx::Point& celStart = m_celStarts[i];

      cel->setPosition(celStart);
    }

    // If the user didn't cancel the operation...
    if (!m_canceled) {
      ContextWriter writer(UIContext::instance(), 500);
      Transaction transaction(writer.context(), "Cel Movement", ModifyDocument);
      DocumentApi api = document->getApi(transaction);

      // And now we move the cel (or all selected range) to the new position.
      for (Cel* cel : m_celList) {
        api.setCelPosition(writer.sprite(), cel,
                           cel->x() + m_celOffset.x,
                           cel->y() + m_celOffset.y);
      }

      // Move selection if it was visible
      if (m_maskVisible)
        api.setMaskPosition(document->mask()->bounds().x + m_celOffset.x,
                            document->mask()->bounds().y + m_celOffset.y);

      transaction.commit();
    }

    // Redraw all editors. We've to notify all views about this
    // general update because MovingCelState::onMouseMove() redraws
    // only the cels in the current editor. And at this point we'd
    // like to update all the editors.
    document->notifyGeneralUpdate();
  }

  // Restore the mask visibility.
  if (m_maskVisible) {
    document->setMaskVisible(m_maskVisible);
    document->generateMaskBoundaries();
  }

  editor->backToPreviousState();
  editor->releaseMouse();
  return true;
}
Esempio n. 5
0
void RemoveLayerCommand::onExecute(Context* context)
{
  std::string layerName;
  ContextWriter writer(context);
  Document* document(writer.document());
  Sprite* sprite(writer.sprite());
  {
    Transaction transaction(writer.context(), "Remove Layer");
    DocumentApi api = document->getApi(transaction);

    const Site* site = writer.site();
    if (site->inTimeline() &&
        !site->selectedLayers().empty()) {
      SelectedLayers selLayers = site->selectedLayers();
      selLayers.removeChildrenIfParentIsSelected();

      layer_t deletedTopLevelLayers = 0;
      for (Layer* layer : selLayers) {
        if (layer->parent() == sprite->root())
          ++deletedTopLevelLayers;
      }

      if (deletedTopLevelLayers == sprite->root()->layersCount()) {
        ui::Alert::show("Error<<You cannot delete all layers.||&OK");
        return;
      }

      for (Layer* layer : selLayers) {
        api.removeLayer(layer);
      }
    }
    else {
      if (sprite->allLayersCount() == 1) {
        ui::Alert::show("Error<<You cannot delete the last layer.||&OK");
        return;
      }

      Layer* layer = writer.layer();
      layerName = layer->name();
      api.removeLayer(layer);
    }

    transaction.commit();
  }
  update_screen_for_document(document);

  StatusBar::instance()->invalidate();
  if (!layerName.empty())
    StatusBar::instance()->showTip(1000, "Layer '%s' removed", layerName.c_str());
  else
    StatusBar::instance()->showTip(1000, "Layers removed");
}
Esempio n. 6
0
void NewLayerCommand::onExecute(Context* context)
{
  ContextWriter writer(context);
  Document* document(writer.document());
  Sprite* sprite(writer.sprite());
  std::string name;

  // Default name (m_name is a name specified in params)
  if (!m_name.empty())
    name = m_name;
  else
    name = get_unique_layer_name(sprite);

  // If params specify to ask the user about the name...
  if (m_ask) {
    // We open the window to ask the name
    base::UniquePtr<Window> window(app::load_widget<Window>("new_layer.xml", "new_layer"));
    Widget* name_widget = app::find_widget<Widget>(window, "name");
    name_widget->setText(name.c_str());
    name_widget->setMinSize(gfx::Size(128, 0));

    window->openWindowInForeground();

    if (window->closer() != window->findChild("ok"))
      return;

    name = window->findChild("name")->text();
  }

  Layer* activeLayer = writer.layer();
  Layer* layer;
  {
    Transaction transaction(writer.context(), "New Layer");
    DocumentApi api = document->getApi(transaction);
    layer = api.newLayer(sprite, name);

    // If "top" parameter is false, create the layer above the active
    // one.
    if (activeLayer && !m_top)
      api.restackLayerAfter(layer, activeLayer);

    transaction.commit();
  }
  update_screen_for_document(document);

  StatusBar::instance()->invalidate();
  StatusBar::instance()->showTip(1000, "Layer `%s' created", name.c_str());

  App::instance()->getMainWindow()->popTimeline();
}
Esempio n. 7
0
  /**
   * [working thread]
   */
  virtual void onJob()
  {
    UndoTransaction undoTransaction(m_writer.context(), "Rotate Canvas");
    DocumentApi api = m_document->getApi();

    // get all sprite cels
    CelList cels;
    m_sprite->getCels(cels);

    // for each cel...
    for (CelIterator it = cels.begin(); it != cels.end(); ++it) {
      Cel* cel = *it;
      Image* image = m_sprite->getStock()->getImage(cel->getImage());

      // change it location
      switch (m_angle) {
        case 180:
          api.setCelPosition(m_sprite, cel,
                             m_sprite->getWidth() - cel->getX() - image->getWidth(),
                             m_sprite->getHeight() - cel->getY() - image->getHeight());
          break;
        case 90:
          api.setCelPosition(m_sprite, cel,
                             m_sprite->getHeight() - cel->getY() - image->getHeight(),
                             cel->getX());
          break;
        case -90:
          api.setCelPosition(m_sprite, cel,
                             cel->getY(),
                             m_sprite->getWidth() - cel->getX() - image->getWidth());
          break;
      }
    }

    // for each stock's image
    for (int i=0; i<m_sprite->getStock()->size(); ++i) {
      Image* image = m_sprite->getStock()->getImage(i);
      if (!image)
        continue;

      // rotate the image
      Image* new_image = Image::create(image->getPixelFormat(),
                                       m_angle == 180 ? image->getWidth(): image->getHeight(),
                                       m_angle == 180 ? image->getHeight(): image->getWidth());
      raster::rotate_image(image, new_image, m_angle);

      api.replaceStockImage(m_sprite, i, new_image);

      jobProgress((float)i / m_sprite->getStock()->size());

      // cancel all the operation?
      if (isCanceled())
        return;        // UndoTransaction destructor will undo all operations
    }

    // rotate mask
    if (m_document->isMaskVisible()) {
      Mask* origMask = m_document->getMask();
      base::UniquePtr<Mask> new_mask(new Mask());
      const gfx::Rect& origBounds = origMask->getBounds();
      int x = 0, y = 0;

      switch (m_angle) {
        case 180:
          x = m_sprite->getWidth() - origBounds.x - origBounds.w;
          y = m_sprite->getHeight() - origBounds.y - origBounds.h;
          break;
        case 90:
          x = m_sprite->getHeight() - origBounds.y - origBounds.h;
          y = origBounds.x;
          break;
        case -90:
          x = origBounds.y;
          y = m_sprite->getWidth() - origBounds.x - origBounds.w;
          break;
      }

      // create the new rotated mask
      new_mask->replace(x, y,
                        m_angle == 180 ? origBounds.w: origBounds.h,
                        m_angle == 180 ? origBounds.h: origBounds.w);
      raster::rotate_image(origMask->getBitmap(), new_mask->getBitmap(), m_angle);

      // Copy new mask
      api.copyToCurrentMask(new_mask);

      // Regenerate mask
      m_document->resetTransformation();
      m_document->generateMaskBoundaries();
    }

    // change the sprite's size
    if (m_angle != 180)
      api.setSpriteSize(m_sprite, m_sprite->getHeight(), m_sprite->getWidth());

    // commit changes
    undoTransaction.commit();
  }
Esempio n. 8
0
void SpritePropertiesCommand::onExecute(Context* context)
{
  std::string imgtype_text;
  char buf[256];
  ColorButton* color_button = NULL;

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

  // Get sprite properties and fill frame fields
  {
    const ContextReader reader(context);
    const Document* document(reader.document());
    const Sprite* sprite(reader.sprite());

    // Update widgets values
    switch (sprite->pixelFormat()) {
      case IMAGE_RGB:
        imgtype_text = "RGB";
        break;
      case IMAGE_GRAYSCALE:
        imgtype_text = "Grayscale";
        break;
      case IMAGE_INDEXED:
        std::sprintf(buf, "Indexed (%d colors)", sprite->palette(0)->size());
        imgtype_text = buf;
        break;
      default:
        ASSERT(false);
        imgtype_text = "Unknown";
        break;
    }

    // Filename
    window.name()->setText(document->filename());

    // Color mode
    window.type()->setText(imgtype_text.c_str());

    // Sprite size (width and height)
    window.size()->setTextf(
      "%dx%d (%s)",
      sprite->width(),
      sprite->height(),
      base::get_pretty_memory_size(sprite->getMemSize()).c_str());

    // How many frames
    window.frames()->setTextf("%d", (int)sprite->totalFrames());

    if (sprite->pixelFormat() == IMAGE_INDEXED) {
      color_button = new ColorButton(app::Color::fromIndex(sprite->transparentColor()),
                                     IMAGE_INDEXED);

      window.transparentColorPlaceholder()->addChild(color_button);
    }
    else {
      window.transparentColorPlaceholder()->addChild(new Label("(only for indexed images)"));
    }
  }

  window.remapWindow();
  window.centerWindow();

  load_window_pos(&window, "SpriteProperties");
  window.setVisible(true);
  window.openWindowInForeground();

  if (window.closer() == window.ok()) {
    if (color_button) {
      ContextWriter writer(context);
      Sprite* sprite(writer.sprite());

      // If the transparent color index has changed, we update the
      // property in the sprite.
      int index = color_button->getColor().getIndex();
      if (color_t(index) != sprite->transparentColor()) {
        Transaction transaction(writer.context(), "Set Transparent Color");
        DocumentApi api = writer.document()->getApi(transaction);
        api.setSpriteTransparentColor(sprite, index);
        transaction.commit();

        update_screen_for_document(writer.document());
      }
    }
  }

  save_window_pos(&window, "SpriteProperties");
}
static DocumentRange drop_range_op(
  Document* doc, Op op, const DocumentRange& from,
  DocumentRangePlace place, const DocumentRange& to)
{
  if (place != kDocumentRangeBefore &&
      place != kDocumentRangeAfter) {
    ASSERT(false);
    throw std::invalid_argument("Invalid 'place' argument");
  }

  Sprite* sprite = doc->sprite();

  // Check noop/trivial/do nothing cases, i.e., move a range to the same place.
  // Also check invalid cases, like moving a Background layer.
  switch (from.type()) {
    case DocumentRange::kCels:
      if (from == to)
        return from;
      break;
    case DocumentRange::kFrames:
      if (op == Move) {
        if ((to.frameBegin() >= from.frameBegin() && to.frameEnd() <= from.frameEnd()) ||
            (place == kDocumentRangeBefore && to.frameBegin() == from.frameEnd()+1) ||
            (place == kDocumentRangeAfter && to.frameEnd() == from.frameBegin()-1))
          return from;
      }
      break;
    case DocumentRange::kLayers:
      if (op == Move) {
        if ((to.layerBegin() >= from.layerBegin() && to.layerEnd() <= from.layerEnd()) ||
            (place == kDocumentRangeBefore && to.layerBegin() == from.layerEnd()+1) ||
            (place == kDocumentRangeAfter && to.layerEnd() == from.layerBegin()-1))
          return from;

        // We cannot move the background
        for (LayerIndex i = from.layerBegin(); i <= from.layerEnd(); ++i)
          if (sprite->indexToLayer(i)->isBackground())
            throw std::runtime_error("The background layer cannot be moved");

        // Before background
        if (place == kDocumentRangeBefore) {
          Layer* background = sprite->indexToLayer(to.layerBegin());
          if (background && background->isBackground())
            throw std::runtime_error("You cannot move something below the background layer");
        }
      }
      break;
  }

  const char* undoLabel = NULL;
  switch (op) {
    case Move: undoLabel = "Move Range"; break;
    case Copy: undoLabel = "Copy Range"; break;
    default:
      ASSERT(false);
      throw std::invalid_argument("Invalid 'op' argument");
  }
  DocumentRange resultRange;

  {
    const app::Context* context = static_cast<app::Context*>(doc->context());
    const ContextReader reader(context);
    ContextWriter writer(reader);
    Transaction transaction(writer.context(), undoLabel, ModifyDocument);
    DocumentApi api = doc->getApi(transaction);

    // TODO Try to add the range with just one call to DocumentApi
    // methods, to avoid generating a lot of SetCelFrame undoers (see
    // DocumentApi::setCelFramePosition).

    switch (from.type()) {

      case DocumentRange::kCels:
        {
          std::vector<Layer*> layers;
          sprite->getLayersList(layers);

          int srcLayerBegin, srcLayerStep, srcLayerEnd;
          int dstLayerBegin, dstLayerStep;
          frame_t srcFrameBegin, srcFrameStep, srcFrameEnd;
          frame_t dstFrameBegin, dstFrameStep;

          if (to.layerBegin() <= from.layerBegin()) {
            srcLayerBegin = from.layerBegin();
            srcLayerStep = 1;
            srcLayerEnd = from.layerEnd()+1;
            dstLayerBegin = to.layerBegin();
            dstLayerStep = 1;
          }
          else {
            srcLayerBegin = from.layerEnd();
            srcLayerStep = -1;
            srcLayerEnd = from.layerBegin()-1;
            dstLayerBegin = to.layerEnd();
            dstLayerStep = -1;
          }

          if (to.frameBegin() <= from.frameBegin()) {
            srcFrameBegin = from.frameBegin();
            srcFrameStep = frame_t(1);
            srcFrameEnd = from.frameEnd()+1;
            dstFrameBegin = to.frameBegin();
            dstFrameStep = frame_t(1);
          }
          else {
            srcFrameBegin = from.frameEnd();
            srcFrameStep = frame_t(-1);
            srcFrameEnd = from.frameBegin()-1;
            dstFrameBegin = to.frameEnd();
            dstFrameStep = frame_t(-1);
          }

          for (int srcLayerIdx = srcLayerBegin,
                 dstLayerIdx = dstLayerBegin; srcLayerIdx != srcLayerEnd; ) {
            for (frame_t srcFrame = srcFrameBegin,
                   dstFrame = dstFrameBegin; srcFrame != srcFrameEnd; ) {
              LayerImage* srcLayer = static_cast<LayerImage*>(layers[srcLayerIdx]);
              LayerImage* dstLayer = static_cast<LayerImage*>(layers[dstLayerIdx]);

              switch (op) {
                case Move: api.moveCel(srcLayer, srcFrame, dstLayer, dstFrame); break;
                case Copy: api.copyCel(srcLayer, srcFrame, dstLayer, dstFrame); break;
              }

              srcFrame += srcFrameStep;
              dstFrame += dstFrameStep;
            }
            srcLayerIdx += srcLayerStep;
            dstLayerIdx += dstLayerStep;
          }

          resultRange = to;
        }
        break;

      case DocumentRange::kFrames:
        {
          frame_t srcFrameBegin = 0, srcFrameStep, srcFrameEnd = 0;
          frame_t dstFrameBegin = 0, dstFrameStep;

          switch (op) {

            case Move:
              if (place == kDocumentRangeBefore) {
                if (to.frameBegin() <= from.frameBegin()) {
                  srcFrameBegin = from.frameBegin();
                  srcFrameStep = frame_t(1);
                  srcFrameEnd = from.frameEnd()+1;
                  dstFrameBegin = to.frameBegin();
                  dstFrameStep = frame_t(1);
                }
                else {
                  srcFrameBegin = from.frameEnd();
                  srcFrameStep = frame_t(-1);
                  srcFrameEnd = from.frameBegin()-1;
                  dstFrameBegin = to.frameBegin();
                  dstFrameStep = frame_t(-1);
                }
              }
              else if (place == kDocumentRangeAfter) {
                if (to.frameEnd() <= from.frameBegin()) {
                  srcFrameBegin = from.frameBegin();
                  srcFrameStep = frame_t(1);
                  srcFrameEnd = from.frameEnd()+1;
                  dstFrameBegin = to.frameEnd()+1;
                  dstFrameStep = frame_t(1);
                }
                else {
                  srcFrameBegin = from.frameEnd();
                  srcFrameStep = frame_t(-1);
                  srcFrameEnd = from.frameBegin()-1;
                  dstFrameBegin = to.frameEnd()+1;
                  dstFrameStep = frame_t(-1);
                }
              }
              break;

            case Copy:
              if (place == kDocumentRangeBefore) {
                if (to.frameBegin() <= from.frameBegin()) {
                  srcFrameBegin = from.frameBegin();
                  srcFrameStep = frame_t(2);
                  srcFrameEnd = from.frameBegin() + 2*from.frames();
                  dstFrameBegin = to.frameBegin();
                  dstFrameStep = frame_t(1);
                }
                else {
                  srcFrameBegin = from.frameEnd();
                  srcFrameStep = frame_t(-1);
                  srcFrameEnd = from.frameBegin()-1;
                  dstFrameBegin = to.frameBegin();
                  dstFrameStep = frame_t(0);
                }
              }
              else if (place == kDocumentRangeAfter) {
                if (to.frameEnd() <= from.frameBegin()) {
                  srcFrameBegin = from.frameBegin();
                  srcFrameStep = frame_t(2);
                  srcFrameEnd = from.frameBegin() + 2*from.frames();
                  dstFrameBegin = to.frameEnd()+1;
                  dstFrameStep = frame_t(1);
                }
                else {
                  srcFrameBegin = from.frameEnd();
                  srcFrameStep = frame_t(-1);
                  srcFrameEnd = from.frameBegin()-1;
                  dstFrameBegin = to.frameEnd()+1;
                  dstFrameStep = frame_t(0);
                }
              }
              break;
          }

          for (frame_t srcFrame = srcFrameBegin,
                 dstFrame = dstFrameBegin; srcFrame != srcFrameEnd; ) {
            switch (op) {
              case Move: api.moveFrame(sprite, srcFrame, dstFrame); break;
              case Copy: api.copyFrame(sprite, srcFrame, dstFrame); break;
            }
            srcFrame += srcFrameStep;
            dstFrame += dstFrameStep;
          }

          if (place == kDocumentRangeBefore) {
            resultRange.startRange(LayerIndex::NoLayer, frame_t(to.frameBegin()), from.type());
            resultRange.endRange(LayerIndex::NoLayer, frame_t(to.frameBegin()+from.frames()-1));
          }
          else if (place == kDocumentRangeAfter) {
            resultRange.startRange(LayerIndex::NoLayer, frame_t(to.frameEnd()+1), from.type());
            resultRange.endRange(LayerIndex::NoLayer, frame_t(to.frameEnd()+1+from.frames()-1));
          }

          if (op == Move && from.frameBegin() < to.frameBegin())
            resultRange.displace(0, -from.frames());
        }
        break;

      case DocumentRange::kLayers:
        {
          std::vector<Layer*> layers;
          sprite->getLayersList(layers);

          if (layers.empty())
            break;

          switch (op) {

            case Move:
              if (place == kDocumentRangeBefore) {
                for (LayerIndex i = from.layerBegin(); i <= from.layerEnd(); ++i) {
                  api.restackLayerBefore(
                    layers[i],
                    layers[to.layerBegin()]);
                }
              }
              else if (place == kDocumentRangeAfter) {
                for (LayerIndex i = from.layerEnd(); i >= from.layerBegin(); --i) {
                  api.restackLayerAfter(
                    layers[i],
                    layers[to.layerEnd()]);
                }
              }
              break;

            case Copy:
              if (place == kDocumentRangeBefore) {
                for (LayerIndex i = from.layerBegin(); i <= from.layerEnd(); ++i) {
                  api.duplicateLayerBefore(
                    layers[i],
                    layers[to.layerBegin()]);
                }
              }
              else if (place == kDocumentRangeAfter) {
                for (LayerIndex i = from.layerEnd(); i >= from.layerBegin(); --i) {
                  api.duplicateLayerAfter(
                    layers[i],
                    layers[to.layerEnd()]);
                }
              }
              break;
          }

          if (place == kDocumentRangeBefore) {
            resultRange.startRange(LayerIndex(to.layerBegin()), frame_t(-1), from.type());
            resultRange.endRange(LayerIndex(to.layerBegin()+from.layers()-1), frame_t(-1));
          }
          else if (place == kDocumentRangeAfter) {
            resultRange.startRange(LayerIndex(to.layerEnd()+1), frame_t(-1), from.type());
            resultRange.endRange(LayerIndex(to.layerEnd()+1+from.layers()-1), frame_t(-1));
          }

          if (op == Move && from.layerBegin() < to.layerBegin())
            resultRange.displace(-from.layers(), 0);
        }
        break;
    }

    transaction.commit();
  }

  return resultRange;
}
Esempio n. 10
0
  // [working thread]
  void onJob() override {
    DocumentApi api = writer().document()->getApi(transaction());

    int cels_count = 0;
    for (Cel* cel : sprite()->uniqueCels()) { // TODO add size() member function to CelsRange
      (void)cel;
      ++cels_count;
    }

    // For each cel...
    int progress = 0;
    for (Cel* cel : sprite()->uniqueCels()) {
      // Get cel's image
      Image* image = cel->image();
      if (image && !cel->link()) {
        // Resize the cel bounds only if it's from a reference layer
        if (cel->layer()->isReference()) {
          gfx::RectF newBounds = scale_rect<double>(cel->boundsF());
          transaction().execute(new cmd::SetCelBoundsF(cel, newBounds));
        }
        else {
          // Change its location
          api.setCelPosition(sprite(), cel, scale_x(cel->x()), scale_y(cel->y()));

          // Resize the image
          int w = scale_x(image->width());
          int h = scale_y(image->height());
          ImageRef new_image(Image::create(image->pixelFormat(), MAX(1, w), MAX(1, h)));
          new_image->setMaskColor(image->maskColor());

          doc::algorithm::fixup_image_transparent_colors(image);
          doc::algorithm::resize_image(
            image, new_image.get(),
            m_resize_method,
            sprite()->palette(cel->frame()),
            sprite()->rgbMap(cel->frame()),
            (cel->layer()->isBackground() ? -1: sprite()->transparentColor()));

          api.replaceImage(sprite(), cel->imageRef(), new_image);
        }
      }

      jobProgress((float)progress / cels_count);
      ++progress;

      // Cancel all the operation?
      if (isCanceled())
        return;        // Transaction destructor will undo all operations
    }

    // Resize mask
    if (document()->isMaskVisible()) {
      ImageRef old_bitmap
        (crop_image(document()->mask()->bitmap(), -1, -1,
                    document()->mask()->bitmap()->width()+2,
                    document()->mask()->bitmap()->height()+2, 0));

      int w = scale_x(old_bitmap->width());
      int h = scale_y(old_bitmap->height());
      base::UniquePtr<Mask> new_mask(new Mask);
      new_mask->replace(
        gfx::Rect(
          scale_x(document()->mask()->bounds().x-1),
          scale_y(document()->mask()->bounds().y-1), MAX(1, w), MAX(1, h)));
      algorithm::resize_image(
        old_bitmap.get(), new_mask->bitmap(),
        m_resize_method,
        sprite()->palette(0), // Ignored
        sprite()->rgbMap(0),  // Ignored
        -1);                  // Ignored

      // Reshrink
      new_mask->intersect(new_mask->bounds());

      // Copy new mask
      api.copyToCurrentMask(new_mask);

      // Regenerate mask
      document()->resetTransformation();
      document()->generateMaskBoundaries();
    }

    // Resize slices
    for (auto& slice : sprite()->slices()) {
      for (auto& k : *slice) {
        const SliceKey& key = *k.value();
        if (key.isEmpty())
          continue;

        SliceKey newKey = key;
        newKey.setBounds(scale_rect(newKey.bounds()));

        if (newKey.hasCenter())
          newKey.setCenter(scale_rect(newKey.center()));

        if (newKey.hasPivot())
          newKey.setPivot(gfx::Point(scale_x(newKey.pivot().x),
                                     scale_y(newKey.pivot().y)));

        transaction().execute(
          new cmd::SetSliceKey(slice, k.frame(), newKey));
      }
    }

    // Resize Sprite
    api.setSpriteSize(sprite(), m_new_width, m_new_height);
  }
Esempio n. 11
0
void ImportSpriteSheetCommand::onExecute(Context* context)
{
  ImportSpriteSheetWindow window(context);

retry:;
  window.openWindowInForeground();
  if (!window.ok())
    return;

  Document* document = window.document();
  DocumentPreferences* docPref = window.docPref();
  gfx::Rect frameBounds = window.frameBounds();

  // The user don't select a sheet yet.
  if (!document) {
    Alert::show("Import Sprite Sheet<<Select a sprite first.||&Close");
    goto retry;
  }

  // The list of frames imported from the sheet
  std::vector<ImageRef> animation;

  try {
    Sprite* sprite = document->sprite();
    frame_t currentFrame = context->activeSite().frame();
    render::Render render;

    // As first step, we cut each tile and add them into "animation" list.
    for (int y=frameBounds.y; y<sprite->height(); y += frameBounds.h) {
      for (int x=frameBounds.x; x<sprite->width(); x += frameBounds.w) {
        ImageRef resultImage(
          Image::create(sprite->pixelFormat(), frameBounds.w, frameBounds.h));

        // Render the portion of sheet.
        render.renderSprite(resultImage.get(), sprite, currentFrame,
          gfx::Clip(0, 0, x, y, frameBounds.w, frameBounds.h));

        animation.push_back(resultImage);
      }
    }

    if (animation.size() == 0) {
      Alert::show("Import Sprite Sheet"
        "<<The specified rectangle does not create any tile."
        "<<Select a rectangle inside the sprite region."
        "||&OK");
      return;
    }

    // The following steps modify the sprite, so we wrap all
    // operations in a undo-transaction.
    ContextWriter writer(context);
    Transaction transaction(writer.context(), "Import Sprite Sheet", ModifyDocument);
    DocumentApi api = document->getApi(transaction);

    // Add the layer in the sprite.
    LayerImage* resultLayer = api.newLayer(sprite);

    // Add all frames+cels to the new layer
    for (size_t i=0; i<animation.size(); ++i) {
      // Create the cel.
      base::UniquePtr<Cel> resultCel(new Cel(frame_t(i), animation[i]));

      // Add the cel in the layer.
      api.addCel(resultLayer, resultCel);
      resultCel.release();
    }

    // Copy the list of layers (because we will modify it in the iteration).
    LayerList layers = sprite->folder()->getLayersList();

    // Remove all other layers
    for (LayerIterator it=layers.begin(), end=layers.end(); it!=end; ++it) {
      if (*it != resultLayer)
        api.removeLayer(*it);
    }

    // Change the number of frames
    api.setTotalFrames(sprite, frame_t(animation.size()));

    // Set the size of the sprite to the tile size.
    api.setSpriteSize(sprite, frameBounds.w, frameBounds.h);

    transaction.commit();

    ASSERT(docPref);
    if (docPref)
      docPref->importSpriteSheet.bounds(frameBounds);
  }
  catch (...) {
    throw;
  }

  update_screen_for_document(document);
}
  /**
   * [working thread]
   */
  virtual void onJob()
  {
    UndoTransaction undoTransaction(m_writer.context(), "Sprite Size");
    DocumentApi api = m_writer.document()->getApi();

    // Get all sprite cels
    CelList cels;
    m_sprite->getCels(cels);

    // For each cel...
    int progress = 0;
    for (CelIterator it = cels.begin(); it != cels.end(); ++it, ++progress) {
      Cel* cel = *it;

      // Change its location
      api.setCelPosition(m_sprite, cel, scale_x(cel->x()), scale_y(cel->y()));

      // Get cel's image
      Image* image = cel->image();
      if (!image)
        continue;

      // Resize the image
      int w = scale_x(image->width());
      int h = scale_y(image->height());
      Image* new_image = Image::create(image->pixelFormat(), MAX(1, w), MAX(1, h));

      doc::algorithm::fixup_image_transparent_colors(image);
      doc::algorithm::resize_image(image, new_image,
                                      m_resize_method,
                                      m_sprite->getPalette(cel->frame()),
                                      m_sprite->getRgbMap(cel->frame()));

      api.replaceStockImage(m_sprite, cel->imageIndex(), new_image);

      jobProgress((float)progress / cels.size());

      // cancel all the operation?
      if (isCanceled())
        return;        // UndoTransaction destructor will undo all operations
    }

    // Resize mask
    if (m_document->isMaskVisible()) {
      base::UniquePtr<Image> old_bitmap
        (crop_image(m_document->mask()->bitmap(), -1, -1,
                    m_document->mask()->bitmap()->width()+2,
                    m_document->mask()->bitmap()->height()+2, 0));

      int w = scale_x(old_bitmap->width());
      int h = scale_y(old_bitmap->height());
      base::UniquePtr<Mask> new_mask(new Mask);
      new_mask->replace(scale_x(m_document->mask()->bounds().x-1),
                        scale_y(m_document->mask()->bounds().y-1), MAX(1, w), MAX(1, h));
      algorithm::resize_image(old_bitmap, new_mask->bitmap(),
                              m_resize_method,
                              m_sprite->getPalette(FrameNumber(0)), // Ignored
                              m_sprite->getRgbMap(FrameNumber(0))); // Ignored

      // Reshrink
      new_mask->intersect(new_mask->bounds());

      // Copy new mask
      api.copyToCurrentMask(new_mask);

      // Regenerate mask
      m_document->resetTransformation();
      m_document->generateMaskBoundaries();
    }

    // resize sprite
    api.setSpriteSize(m_sprite, m_new_width, m_new_height);

    // commit changes
    undoTransaction.commit();
  }
Esempio n. 13
0
  // [working thread]
  virtual void onJob()
  {
    Transaction transaction(m_writer.context(), "Rotate Canvas");
    DocumentApi api = m_document->getApi(transaction);

    // 1) Rotate cel positions
    for (Cel* cel : m_cels) {
      Image* image = cel->image();
      if (!image)
        continue;

      switch (m_angle) {
        case 180:
          api.setCelPosition(m_sprite, cel,
            m_sprite->width() - cel->x() - image->width(),
            m_sprite->height() - cel->y() - image->height());
          break;
        case 90:
          api.setCelPosition(m_sprite, cel,
            m_sprite->height() - cel->y() - image->height(),
            cel->x());
          break;
        case -90:
          api.setCelPosition(m_sprite, cel,
            cel->y(),
            m_sprite->width() - cel->x() - image->width());
          break;
      }
    }

    // 2) Rotate images
    int i = 0;
    for (Cel* cel : m_cels) {
      Image* image = cel->image();
      if (image) {
        ImageRef new_image(Image::create(image->pixelFormat(),
            m_angle == 180 ? image->width(): image->height(),
            m_angle == 180 ? image->height(): image->width()));
        doc::rotate_image(image, new_image.get(), m_angle);

        api.replaceImage(m_sprite, cel->imageRef(), new_image);
      }

      jobProgress((float)i / m_cels.size());
      ++i;

      // cancel all the operation?
      if (isCanceled())
        return;        // Transaction destructor will undo all operations
    }

    // rotate mask
    if (m_document->isMaskVisible()) {
      Mask* origMask = m_document->mask();
      base::UniquePtr<Mask> new_mask(new Mask());
      const gfx::Rect& origBounds = origMask->bounds();
      int x = 0, y = 0;

      switch (m_angle) {
        case 180:
          x = m_sprite->width() - origBounds.x - origBounds.w;
          y = m_sprite->height() - origBounds.y - origBounds.h;
          break;
        case 90:
          x = m_sprite->height() - origBounds.y - origBounds.h;
          y = origBounds.x;
          break;
        case -90:
          x = origBounds.y;
          y = m_sprite->width() - origBounds.x - origBounds.w;
          break;
      }

      // create the new rotated mask
      new_mask->replace(
        gfx::Rect(x, y,
          m_angle == 180 ? origBounds.w: origBounds.h,
          m_angle == 180 ? origBounds.h: origBounds.w));
      doc::rotate_image(origMask->bitmap(), new_mask->bitmap(), m_angle);

      // Copy new mask
      api.copyToCurrentMask(new_mask);

      // Regenerate mask
      m_document->resetTransformation();
      m_document->generateMaskBoundaries();
    }

    // change the sprite's size
    if (m_rotateSprite && m_angle != 180)
      api.setSpriteSize(m_sprite, m_sprite->height(), m_sprite->width());

    // commit changes
    transaction.commit();
  }
  void onImport()
  {
    // The user don't select a sheet yet.
    if (!m_document) {
      Alert::show("Import Sprite Sheet<<Select a sprite first.||&Close");
      return;
    }

    // The list of frames imported from the sheet
    std::vector<Image*> animation;

    try {
      Sprite* sprite = m_document->getSprite();
      FrameNumber currentFrame = m_context->getActiveLocation().frame();

      // As first step, we cut each tile and add them into "animation" list.
      for (int y=m_rect.y; y<sprite->getHeight(); y += m_rect.h) {
        for (int x=m_rect.x; x<sprite->getWidth(); x += m_rect.w) {
          base::UniquePtr<Image> resultImage(Image::create(sprite->getPixelFormat(), m_rect.w, m_rect.h));

          // Clear the image with mask color.
          image_clear(resultImage, 0);

          // Render the portion of sheet.
          sprite->render(resultImage, -x, -y, currentFrame);
          animation.push_back(resultImage);
          resultImage.release();
        }
      }

      if (animation.size() == 0) {
        Alert::show("Import Sprite Sheet"
                    "<<The specified rectangle does not create any tile."
                    "<<Select a rectangle inside the sprite region."
                    "||&OK");
        return;
      }

      // The following steps modify the sprite, so we wrap all
      // operations in a undo-transaction.
      ContextWriter writer(m_context);
      UndoTransaction undoTransaction(writer.context(), "Import Sprite Sheet", undo::ModifyDocument);
      DocumentApi api = m_document->getApi();

      // Add the layer in the sprite.
      LayerImage* resultLayer = api.newLayer(sprite);

      // Add all frames+cels to the new layer
      for (size_t i=0; i<animation.size(); ++i) {
        int indexInStock;

        // Add the image into the sprite's stock
        indexInStock = api.addImageInStock(sprite, animation[i]);
        animation[i] = NULL;

        // Create the cel.
        base::UniquePtr<Cel> resultCel(new Cel(FrameNumber(i), indexInStock));

        // Add the cel in the layer.
        api.addCel(resultLayer, resultCel);
        resultCel.release();
      }

      // Copy the list of layers (because we will modify it in the iteration).
      LayerList layers = sprite->getFolder()->getLayersList();

      // Remove all other layers
      for (LayerIterator it=layers.begin(), end=layers.end(); it!=end; ++it) {
        if (*it != resultLayer)
          api.removeLayer(*it);
      }

      // Change the number of frames
      api.setTotalFrames(sprite, FrameNumber(animation.size()));

      // Set the size of the sprite to the tile size.
      api.setSpriteSize(sprite, m_rect.w, m_rect.h);

      undoTransaction.commit();
    }
    catch (...) {
      for (size_t i=0; i<animation.size(); ++i)
        delete animation[i];
      throw;
    }

    update_screen_for_document(m_document);
    closeWindow(NULL);
  }
Esempio n. 15
0
void FlipCommand::onExecute(Context* context)
{
  ContextWriter writer(context);
  Document* document = writer.document();
  Sprite* sprite = writer.sprite();
  DocumentApi api = document->getApi();

  {
    UndoTransaction undoTransaction(writer.context(),
                                    m_flipMask ?
                                    (m_flipType == raster::algorithm::FlipHorizontal ?
                                     "Flip Horizontal":
                                     "Flip Vertical"):
                                    (m_flipType == raster::algorithm::FlipHorizontal ?
                                     "Flip Canvas Horizontal":
                                     "Flip Canvas Vertical"));

    if (m_flipMask) {
      int x, y;
      Image* image = writer.image(&x, &y);
      if (!image)
        return;

      Mask* mask = NULL;
      bool alreadyFlipped = false;

      // This variable will be the area to be flipped inside the image.
      gfx::Rect bounds(image->getBounds());

      // If there is some portion of sprite selected, we flip the
      // selected region only. If the mask isn't visible, we flip the
      // whole image.
      if (document->isMaskVisible()) {
        mask = document->getMask();

        // Intersect the full area of the image with the mask's
        // bounds, so we don't request to flip an area outside the
        // image's bounds.
        bounds = bounds.createIntersect(gfx::Rect(mask->getBounds()).offset(-x, -y));

        // If the mask isn't a rectangular area, we've to flip the mask too.
        if (mask->getBitmap() != NULL && !mask->isRectangular()) {
          int bgcolor = app_get_color_to_clear_layer(writer.layer());

          // Flip the portion of image specified by the mask.
          mask->offsetOrigin(-x, -y);
          api.flipImageWithMask(image, mask, m_flipType, bgcolor);
          mask->offsetOrigin(x, y);
          alreadyFlipped = true;

          // Flip the mask.
          Image* maskBitmap = mask->getBitmap();
          if (maskBitmap != NULL) {
            // Create a flipped copy of the current mask.
            base::UniquePtr<Mask> newMask(new Mask(*mask));
            newMask->freeze();
            raster::algorithm::flip_image(newMask->getBitmap(),
              maskBitmap->getBounds(), m_flipType);
            newMask->unfreeze();

            // Change the current mask and generate the new boundaries.
            api.copyToCurrentMask(newMask);

            document->generateMaskBoundaries();
          }
        }
      }

      // Flip the portion of image specified by "bounds" variable.
      if (!alreadyFlipped) {
        api.flipImage(image, bounds, m_flipType);
      }
    }
    else {
      // get all sprite cels
      CelList cels;
      sprite->getCels(cels);

      // for each cel...
      for (CelIterator it = cels.begin(); it != cels.end(); ++it) {
        Cel* cel = *it;
        Image* image = sprite->getStock()->getImage(cel->getImage());

        api.setCelPosition
          (sprite, cel,
           (m_flipType == raster::algorithm::FlipHorizontal ?
            sprite->getWidth() - image->getWidth() - cel->getX():
            cel->getX()),
           (m_flipType == raster::algorithm::FlipVertical ?
            sprite->getHeight() - image->getHeight() - cel->getY():
            cel->getY()));

        api.flipImage(image, image->getBounds(), m_flipType);
      }
    }

    undoTransaction.commit();
  }

  update_screen_for_document(document);
}
Esempio n. 16
0
void FlipCommand::onExecute(Context* context)
{
  ContextWriter writer(context);
  Document* document = writer.document();
  Sprite* sprite = writer.sprite();

  {
    Transaction transaction(writer.context(),
      m_flipMask ?
      (m_flipType == doc::algorithm::FlipHorizontal ?
        "Flip Horizontal":
        "Flip Vertical"):
      (m_flipType == doc::algorithm::FlipHorizontal ?
        "Flip Canvas Horizontal":
        "Flip Canvas Vertical"));
    DocumentApi api = document->getApi(transaction);

    CelList cels;
    if (m_flipMask) {
      auto range = App::instance()->timeline()->range();
      if (range.enabled())
        cels = get_unique_cels(sprite, range);
      else if (writer.cel())
        cels.push_back(writer.cel());
    }
    else {
      for (Cel* cel : sprite->uniqueCels())
        cels.push_back(cel);
    }

    Mask* mask = document->mask();
    if (m_flipMask && document->isMaskVisible()) {
      Site site = *writer.site();

      for (Cel* cel : cels) {
        site.frame(cel->frame());
        site.layer(cel->layer());

        int x, y;
        Image* image = site.image(&x, &y);
        if (!image)
          continue;

        // When the mask is inside the cel, we can try to flip the
        // pixels inside the image.
        if (cel->bounds().contains(mask->bounds())) {
          gfx::Rect flipBounds = mask->bounds();
          flipBounds.offset(-x, -y);
          flipBounds &= image->bounds();
          if (flipBounds.isEmpty())
            continue;

          if (mask->bitmap() && !mask->isRectangular())
            transaction.execute(new cmd::FlipMaskedCel(cel, m_flipType));
          else
            api.flipImage(image, flipBounds, m_flipType);

          if (cel->layer()->isTransparent())
            transaction.execute(new cmd::TrimCel(cel));
        }
        // When the mask is bigger than the cel bounds, we have to
        // expand the cel, make the flip, and shrink it again.
        else {
          gfx::Rect flipBounds = (sprite->bounds() & mask->bounds());
          if (flipBounds.isEmpty())
            continue;

          ExpandCelCanvas expand(
            site, cel->layer(),
            TiledMode::NONE, transaction,
            ExpandCelCanvas::None);

          expand.validateDestCanvas(gfx::Region(flipBounds));

          if (mask->bitmap() && !mask->isRectangular())
            doc::algorithm::flip_image_with_mask(
              expand.getDestCanvas(), mask, m_flipType,
              document->bgColor(cel->layer()));
          else
            doc::algorithm::flip_image(
              expand.getDestCanvas(),
              flipBounds, m_flipType);

          expand.commit();
        }
      }
    }
    else {
      for (Cel* cel : cels) {
        Image* image = cel->image();

        api.setCelPosition
          (sprite, cel,
            (m_flipType == doc::algorithm::FlipHorizontal ?
              sprite->width() - image->width() - cel->x():
              cel->x()),
            (m_flipType == doc::algorithm::FlipVertical ?
              sprite->height() - image->height() - cel->y():
              cel->y()));

        api.flipImage(image, image->bounds(), m_flipType);
      }
    }

    // Flip the mask.
    Image* maskBitmap = mask->bitmap();
    if (maskBitmap) {
      transaction.execute(new cmd::FlipMask(document, m_flipType));

      // Flip the mask position because the
      if (!m_flipMask)
        transaction.execute(
          new cmd::SetMaskPosition(
            document,
            gfx::Point(
              (m_flipType == doc::algorithm::FlipHorizontal ?
               sprite->width() - mask->bounds().x2():
               mask->bounds().x),
              (m_flipType == doc::algorithm::FlipVertical ?
               sprite->height() - mask->bounds().y2():
               mask->bounds().y))));

      document->generateMaskBoundaries();
    }

    transaction.commit();
  }

  update_screen_for_document(document);
}
void ImportSpriteSheetCommand::onExecute(Context* context)
{
  ImportSpriteSheetWindow window(context);

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

  Document* document = window.document();
  DocumentPreferences* docPref = window.docPref();
  gfx::Rect frameBounds = window.frameBounds();
  bool partialTiles = window.partialTilesValue();
  auto sheetType = window.sheetTypeValue();

  ASSERT(document);
  if (!document)
    return;

  // The list of frames imported from the sheet
  std::vector<ImageRef> animation;

  try {
    Sprite* sprite = document->sprite();
    frame_t currentFrame = context->activeSite().frame();
    render::Render render;

    // Each sprite in the sheet
    std::vector<gfx::Rect> tileRects;
    int widthStop = sprite->width();
    int heightStop = sprite->height();
    if (partialTiles) {
      widthStop += frameBounds.w-1;
      heightStop += frameBounds.h-1;
    }

    switch (sheetType) {
      case app::SpriteSheetType::Horizontal:
        for (int x=frameBounds.x; x+frameBounds.w<=widthStop; x += frameBounds.w) {
          tileRects.push_back(gfx::Rect(x, frameBounds.y, frameBounds.w, frameBounds.h));
        }
        break;
      case app::SpriteSheetType::Vertical:
        for (int y=frameBounds.y; y+frameBounds.h<=heightStop; y += frameBounds.h) {
          tileRects.push_back(gfx::Rect(frameBounds.x, y, frameBounds.w, frameBounds.h));
        }
        break;
      case app::SpriteSheetType::Rows:
        for (int y=frameBounds.y; y+frameBounds.h<=heightStop; y += frameBounds.h) {
          for (int x=frameBounds.x; x+frameBounds.w<=widthStop; x += frameBounds.w) {
            tileRects.push_back(gfx::Rect(x, y, frameBounds.w, frameBounds.h));
          }
        }
        break;
      case app::SpriteSheetType::Columns:
        for (int x=frameBounds.x; x+frameBounds.w<=sprite->width(); x += frameBounds.w) {
          for (int y=frameBounds.y; y+frameBounds.h<=sprite->height(); y += frameBounds.h) {
            tileRects.push_back(gfx::Rect(x, y, frameBounds.w, frameBounds.h));
          }
        }
        break;
    }

    // As first step, we cut each tile and add them into "animation" list.
    for (const auto& tileRect : tileRects) {
      ImageRef resultImage(
        Image::create(
          sprite->pixelFormat(), tileRect.w, tileRect.h));

      // Render the portion of sheet.
      render.renderSprite(
        resultImage.get(), sprite, currentFrame,
        gfx::Clip(0, 0, tileRect));

      animation.push_back(resultImage);
    }

    if (animation.size() == 0) {
      Alert::show("Import Sprite Sheet"
        "<<The specified rectangle does not create any tile."
        "<<Select a rectangle inside the sprite region."
        "||&OK");
      return;
    }

    // The following steps modify the sprite, so we wrap all
    // operations in a undo-transaction.
    ContextWriter writer(context);
    Transaction transaction(writer.context(), "Import Sprite Sheet", ModifyDocument);
    DocumentApi api = document->getApi(transaction);

    // Add the layer in the sprite.
    LayerImage* resultLayer = api.newLayer(sprite, "Sprite Sheet");

    // Add all frames+cels to the new layer
    for (size_t i=0; i<animation.size(); ++i) {
      // Create the cel.
      base::UniquePtr<Cel> resultCel(new Cel(frame_t(i), animation[i]));

      // Add the cel in the layer.
      api.addCel(resultLayer, resultCel);
      resultCel.release();
    }

    // Copy the list of layers (because we will modify it in the iteration).
    LayerList layers = sprite->folder()->getLayersList();

    // Remove all other layers
    for (LayerIterator it=layers.begin(), end=layers.end(); it!=end; ++it) {
      if (*it != resultLayer)
        api.removeLayer(*it);
    }

    // Change the number of frames
    api.setTotalFrames(sprite, frame_t(animation.size()));

    // Set the size of the sprite to the tile size.
    api.setSpriteSize(sprite, frameBounds.w, frameBounds.h);

    transaction.commit();

    ASSERT(docPref);
    if (docPref) {
      docPref->importSpriteSheet.type(sheetType);
      docPref->importSpriteSheet.bounds(frameBounds);
      docPref->importSpriteSheet.partialTiles(partialTiles);
    }
  }
  catch (...) {
    throw;
  }

  update_screen_for_document(document);
}