void ContextFlags::update(Context* context) { Site site = context->activeSite(); Document* document = static_cast<Document*>(site.document()); m_flags = 0; if (document) { m_flags |= HasActiveDocument; if (document->lock(Document::ReadLock, 0)) { m_flags |= ActiveDocumentIsReadable; if (document->isMaskVisible()) m_flags |= HasVisibleMask; Sprite* sprite = site.sprite(); if (sprite) { m_flags |= HasActiveSprite; if (sprite->backgroundLayer()) m_flags |= HasBackgroundLayer; Layer* layer = site.layer(); if (layer) { m_flags |= HasActiveLayer; if (layer->isBackground()) m_flags |= ActiveLayerIsBackground; if (layer->isVisible()) m_flags |= ActiveLayerIsVisible; if (layer->isEditable()) m_flags |= ActiveLayerIsEditable; if (layer->isImage()) { m_flags |= ActiveLayerIsImage; Cel* cel = layer->cel(site.frame()); if (cel) { m_flags |= HasActiveCel; if (cel->image()) m_flags |= HasActiveImage; } } } } if (document->lockToWrite(0)) m_flags |= ActiveDocumentIsWritable; document->unlock(); } } }
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 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); } }
void FramePropertiesCommand::onExecute(Context* context) { const ContextReader reader(context); const Sprite* sprite = reader.sprite(); auto& docPref = Preferences::instance().document(context->activeDocument()); int base = docPref.timeline.firstFrame(); app::gen::FrameProperties window; SelectedFrames selFrames; switch (m_target) { case ALL_FRAMES: selFrames.insert(0, sprite->lastFrame()); break; case CURRENT_RANGE: { Site site = context->activeSite(); if (site.inTimeline()) { selFrames = site.selectedFrames(); } else { selFrames.insert(site.frame()); } break; } case SPECIFIC_FRAME: selFrames.insert(m_frame-base); break; } ASSERT(!selFrames.empty()); if (selFrames.empty()) return; if (selFrames.size() == 1) window.frame()->setTextf("%d", selFrames.firstFrame()+base); else if (selFrames.ranges() == 1) { window.frame()->setTextf("[%d...%d]", selFrames.firstFrame()+base, selFrames.lastFrame()+base); } else window.frame()->setTextf("Multiple Frames"); window.frlen()->setTextf( "%d", sprite->frameDuration(selFrames.firstFrame())); window.openWindowInForeground(); if (window.closer() == window.ok()) { int newMsecs = window.frlen()->textInt(); ContextWriter writer(reader); Transaction transaction(writer.context(), "Frame Duration"); DocApi api = writer.document()->getApi(transaction); for (frame_t frame : selFrames) api.setFrameDuration(writer.sprite(), frame, newMsecs); transaction.commit(); } }
SpritePosition CmdTransaction::calcSpritePosition() const { Site site = context()->activeSite(); return SpritePosition(site.layer(), site.frame()); }
ExpandCelCanvas::ExpandCelCanvas(Site site, TiledMode tiledMode, Transaction& transaction, Flags flags) : m_document(static_cast<app::Document*>(site.document())) , m_sprite(site.sprite()) , m_layer(site.layer()) , m_cel(NULL) , m_celImage(NULL) , m_celCreated(false) , m_flags(flags) , m_srcImage(NULL) , m_dstImage(NULL) , m_closed(false) , m_committed(false) , m_transaction(transaction) { ASSERT(!singleton); singleton = this; create_buffers(); if (m_layer->isImage()) { m_cel = m_layer->cel(site.frame()); if (m_cel) m_celImage = m_cel->imageRef(); } // Create a new cel if (m_cel == NULL) { m_celCreated = true; m_cel = new Cel(site.frame(), ImageRef(NULL)); } m_origCelPos = m_cel->position(); // Region to draw gfx::Rect celBounds( m_cel->x(), m_cel->y(), m_celImage ? m_celImage->width(): m_sprite->width(), m_celImage ? m_celImage->height(): m_sprite->height()); gfx::Rect spriteBounds(0, 0, m_sprite->width(), m_sprite->height()); if (tiledMode == TiledMode::NONE) { // Non-tiled m_bounds = celBounds.createUnion(spriteBounds); } else { // Tiled m_bounds = spriteBounds; } // We have to adjust the cel position to match the m_dstImage // position (the new m_dstImage will be used in RenderEngine to // draw this cel). m_cel->setPosition(m_bounds.x, m_bounds.y); if (m_celCreated) { getDestCanvas(); m_cel->data()->setImage(m_dstImage); static_cast<LayerImage*>(m_layer)->addCel(m_cel); } }
// Draws the brush cursor in the specified absolute mouse position // given in 'pos' param. Warning: You should clean the cursor before // to use this routine with other editor. void BrushPreview::show(const gfx::Point& screenPos) { if (m_onScreen) hide(); app::Document* document = m_editor->document(); Sprite* sprite = m_editor->sprite(); Layer* layer = m_editor->layer(); ASSERT(sprite); // Get drawable region m_editor->getDrawableRegion(m_clippingRegion, ui::Widget::kCutTopWindows); // Remove the invalidated region in the editor. m_clippingRegion.createSubtraction(m_clippingRegion, m_editor->getUpdateRegion()); // Get cursor color const auto& pref = Preferences::instance(); app::Color app_cursor_color = pref.editor.cursorColor(); gfx::Color ui_cursor_color = color_utils::color_for_ui(app_cursor_color); m_blackAndWhiteNegative = (app_cursor_color.getType() == app::Color::MaskType); // Cursor in the screen (view) m_screenPosition = screenPos; // Get cursor position in the editor gfx::Point spritePos = m_editor->screenToEditor(screenPos); // Get the current tool tools::Ink* ink = m_editor->getCurrentEditorInk(); bool isFloodfill = m_editor->getCurrentEditorTool()->getPointShape(0)->isFloodFill(); // Setup the cursor type depending on several factors (current tool, // foreground color, layer transparency, brush size, etc.). Brush* brush = getCurrentBrush(); color_t brush_color = getBrushColor(sprite, layer); color_t mask_index = sprite->transparentColor(); if (ink->isSelection() || ink->isSlice()) { m_type = SELECTION_CROSS; } else if ( (brush->type() == kImageBrushType || brush->size() > 1.0 / m_editor->zoom().scale()) && (// Use cursor bounds for inks that are effects (eraser, blur, etc.) (ink->isEffect()) || // or when the brush color is transparent and we are not in the background layer (!ink->isShading() && (layer && !layer->isBackground()) && ((sprite->pixelFormat() == IMAGE_INDEXED && brush_color == mask_index) || (sprite->pixelFormat() == IMAGE_RGB && rgba_geta(brush_color) == 0) || (sprite->pixelFormat() == IMAGE_GRAYSCALE && graya_geta(brush_color) == 0))))) { m_type = BRUSH_BOUNDARIES; } else { m_type = CROSS; } bool usePreview = false; auto brushPreview = pref.editor.brushPreview(); if (!m_editor->docPref().show.brushPreview()) brushPreview = app::gen::BrushPreview::NONE; switch (brushPreview) { case app::gen::BrushPreview::NONE: m_type = CROSS; break; case app::gen::BrushPreview::EDGES: m_type = BRUSH_BOUNDARIES; break; case app::gen::BrushPreview::FULL: usePreview = m_editor->getState()->requireBrushPreview(); break; } // For cursor type 'bounds' we have to generate cursor boundaries if (m_type & BRUSH_BOUNDARIES) generateBoundaries(); // Draw pixel/brush preview if ((m_type & CROSS) && usePreview) { gfx::Rect origBrushBounds = (isFloodfill ? gfx::Rect(0, 0, 1, 1): brush->bounds()); gfx::Rect brushBounds = origBrushBounds; brushBounds.offset(spritePos); // Create the extra cel to show the brush preview Site site = m_editor->getSite(); Cel* cel = site.cel(); int t, opacity = 255; if (cel) opacity = MUL_UN8(opacity, cel->opacity(), t); if (layer) opacity = MUL_UN8(opacity, static_cast<LayerImage*>(layer)->opacity(), t); if (!m_extraCel) m_extraCel.reset(new ExtraCel); m_extraCel->create(document->sprite(), brushBounds, site.frame(), opacity); m_extraCel->setType(render::ExtraType::NONE); m_extraCel->setBlendMode( (layer ? static_cast<LayerImage*>(layer)->blendMode(): BlendMode::NORMAL)); document->setExtraCel(m_extraCel); Image* extraImage = m_extraCel->image(); extraImage->setMaskColor(mask_index); clear_image(extraImage, (extraImage->pixelFormat() == IMAGE_INDEXED ? mask_index: 0)); if (layer) { render::Render().renderLayer( extraImage, layer, site.frame(), gfx::Clip(0, 0, brushBounds), BlendMode::SRC); // This extra cel is a patch for the current layer/frame m_extraCel->setType(render::ExtraType::PATCH); } { base::UniquePtr<tools::ToolLoop> loop( create_tool_loop_preview( m_editor, extraImage, brushBounds.origin())); if (loop) { loop->getInk()->prepareInk(loop); loop->getIntertwine()->prepareIntertwine(); loop->getController()->prepareController(ui::kKeyNoneModifier); loop->getPointShape()->preparePointShape(loop); loop->getPointShape()->transformPoint( loop, -origBrushBounds.x, -origBrushBounds.y); } } document->notifySpritePixelsModified( sprite, gfx::Region(m_lastBounds = brushBounds), m_lastFrame = site.frame()); m_withRealPreview = true; } // Save area and draw the cursor { ui::ScreenGraphics g; ui::SetClip clip(&g, gfx::Rect(0, 0, g.width(), g.height())); forEachBrushPixel(&g, m_screenPosition, spritePos, ui_cursor_color, &BrushPreview::savePixelDelegate); forEachBrushPixel(&g, m_screenPosition, spritePos, ui_cursor_color, &BrushPreview::drawPixelDelegate); } // Cursor in the editor (model) m_onScreen = true; m_editorPosition = spritePos; // Save the clipping-region to know where to clean the pixels m_oldClippingRegion = m_clippingRegion; }
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); } }
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); }