bool expect_cel(int expected_layer, int expected_frame, int layer, frame_t frame) { color_t expected_color = white; Cel* cel = sprite->indexToLayer(LayerIndex(layer))->cel(frame); if (!cel) return false; color_t color = get_pixel( cel->image(), expected_layer, expected_frame); EXPECT_EQ(expected_color, color); return (expected_color == color); }
void UnlinkCelCommand::onExecute(Context* context) { ContextWriter writer(context); Document* document(writer.document()); bool nonEditableLayers = false; { Transaction transaction(writer.context(), "Unlink Cel"); const Site* site = writer.site(); if (site->inTimeline() && !site->selectedLayers().empty()) { for (Layer* layer : site->selectedLayers()) { if (!layer->isImage()) continue; if (!layer->isEditableHierarchy()) { nonEditableLayers = true; continue; } LayerImage* layerImage = static_cast<LayerImage*>(layer); for (frame_t frame : site->selectedFrames().reversed()) { Cel* cel = layerImage->cel(frame); if (cel && cel->links()) transaction.execute(new cmd::UnlinkCel(cel)); } } } else { Cel* cel = writer.cel(); if (cel && cel->links()) { if (cel->layer()->isEditableHierarchy()) transaction.execute(new cmd::UnlinkCel(writer.cel())); else nonEditableLayers = true; } } transaction.commit(); } if (nonEditableLayers) StatusBar::instance()->showTip(1000, "There are locked layers"); update_screen_for_document(document); }
Cel* Cel::link() const { ASSERT(m_data); if (m_data.get() == NULL) return NULL; if (!m_data.unique()) { for (frame_t fr=0; fr<m_frame; ++fr) { Cel* possible = m_layer->cel(fr); if (possible && possible->dataRef().get() == m_data.get()) return possible; } } return NULL; }
void LayerImage::destroyAllCels() { CelIterator it = getCelBegin(); CelIterator end = getCelEnd(); for (; it != end; ++it) { Cel* cel = *it; Image* image = cel->image(); ASSERT(image != NULL); sprite()->stock()->removeImage(image); delete image; delete cel; } m_cels.clear(); }
void write_layer(std::ostream& os, LayerSubObjectsSerializer* subObjects, Layer* layer) { std::string name = layer->getName(); write16(os, name.size()); // Name length if (!name.empty()) os.write(name.c_str(), name.size()); // Name write32(os, layer->getFlags()); // Flags write16(os, layer->getType()); // Type switch (layer->getType()) { case GFXOBJ_LAYER_IMAGE: { // Number of cels write16(os, static_cast<LayerImage*>(layer)->getCelsCount()); CelIterator it = static_cast<LayerImage*>(layer)->getCelBegin(); CelIterator end = static_cast<LayerImage*>(layer)->getCelEnd(); for (; it != end; ++it) { Cel* cel = *it; subObjects->write_cel(os, cel); Image* image = layer->getSprite()->getStock()->getImage(cel->getImage()); ASSERT(image != NULL); subObjects->write_image(os, image); } break; } case GFXOBJ_LAYER_FOLDER: { LayerIterator it = static_cast<LayerFolder*>(layer)->get_layer_begin(); LayerIterator end = static_cast<LayerFolder*>(layer)->get_layer_end(); // Number of sub-layers write16(os, static_cast<LayerFolder*>(layer)->get_layers_count()); for (; it != end; ++it) subObjects->write_layer(os, *it); break; } } }
static void slider_change_hook(Slider* slider) { try { ContextWriter writer(UIContext::instance()); Cel* cel = writer.cel(); if (cel) { // Update the opacity cel->setOpacity(slider->getValue()); // Update the editors update_screen_for_document(writer.document()); } } catch (LockedDocumentException&) { // do nothing } }
void Sprite::pickCels(int x, int y, frame_t frame, int opacityThreshold, CelList& cels) const { std::vector<Layer*> layers; getLayersList(layers); for (int i=(int)layers.size()-1; i>=0; --i) { Layer* layer = layers[i]; if (!layer->isImage() || !layer->isVisible()) continue; Cel* cel = layer->cel(frame); if (!cel) continue; Image* image = cel->image(); if (!image) continue; if (!cel->bounds().contains(gfx::Point(x, y))) continue; color_t color = get_pixel(image, x - cel->x(), y - cel->y()); bool isOpaque = true; switch (image->pixelFormat()) { case IMAGE_RGB: isOpaque = (rgba_geta(color) >= opacityThreshold); break; case IMAGE_INDEXED: isOpaque = (color != image->maskColor()); break; case IMAGE_GRAYSCALE: isOpaque = (graya_geta(color) >= opacityThreshold); break; } if (!isOpaque) continue; cels.push_back(cel); } fflush(stdout); }
DocRangeOps() { black = rgba(0, 0, 0, 0); white = rgba(255, 255, 255, 255); doc.reset(static_cast<app::Document*>(ctx.documents().add(4, 4))); sprite = doc->sprite(); layer1 = dynamic_cast<LayerImage*>(sprite->folder()->getFirstLayer()); layer2 = new LayerImage(sprite); layer3 = new LayerImage(sprite); layer4 = new LayerImage(sprite); sprite->folder()->addLayer(layer2); sprite->folder()->addLayer(layer3); sprite->folder()->addLayer(layer4); EXPECT_LAYER_ORDER(layer1, layer2, layer3, layer4); layer1->setName("layer1"); layer2->setName("layer2"); layer3->setName("layer3"); layer4->setName("layer4"); sprite->setTotalFrames(frame_t(4)); sprite->setFrameDuration(frame_t(0), 1); // These durations can be used to identify sprite->setFrameDuration(frame_t(1), 2); // frames after a move operation sprite->setFrameDuration(frame_t(2), 3); sprite->setFrameDuration(frame_t(3), 4); for (int i=0; i<4; i++) { LayerImage* layer = static_cast<LayerImage*>(sprite->indexToLayer(LayerIndex(i))); for (int j=0; j<4; j++) { Cel* cel = layer->cel(frame_t(j)); ImageRef image; if (cel) image = cel->imageRef(); else { image.reset(Image::create(IMAGE_RGB, 4, 4)); cel = new Cel(frame_t(j), image); layer->addCel(cel); } clear_image(image.get(), black); put_pixel(image.get(), i, j, white); } } }
void ShiftMaskedCel::shift(int dx, int dy) { Cel* cel = this->cel(); Image* image = cel->image(); Mask* mask = static_cast<app::Document*>(cel->document())->mask(); ASSERT(mask->bitmap()); if (!mask->bitmap()) return; int x = cel->x(); int y = cel->y(); mask->offsetOrigin(-x, -y); doc::algorithm::shift_image_with_mask(image, mask, dx, dy); mask->offsetOrigin(x, y); image->incrementVersion(); }
void PixelsMovement::redrawExtraImage() { int t, opacity = static_cast<LayerImage*>(m_layer)->opacity(); Cel* cel = m_site.cel(); if (cel) opacity = MUL_UN8(opacity, cel->opacity(), t); gfx::Rect bounds = m_currentData.transformedBounds(); m_extraCel.reset(new ExtraCel); m_extraCel->create(m_document->sprite(), bounds, m_site.frame(), opacity); m_extraCel->setType(render::ExtraType::PATCH); m_extraCel->setBlendMode(static_cast<LayerImage*>(m_layer)->blendMode()); m_document->setExtraCel(m_extraCel); // Draw the transformed pixels in the extra-cel which is the chunk // of pixels that the user is moving. drawImage(m_extraCel->image(), bounds.origin(), true); }
void UndoTransaction::pasteImage(const Image* src_image, int x, int y, int opacity) { const Layer* layer = m_sprite->getCurrentLayer(); ASSERT(layer); ASSERT(layer->is_image()); ASSERT(layer->is_readable()); ASSERT(layer->is_writable()); Cel* cel = ((LayerImage*)layer)->getCel(m_sprite->getCurrentFrame()); ASSERT(cel); Image* cel_image = m_sprite->getStock()->getImage(cel->getImage()); Image* cel_image2 = Image::createCopy(cel_image); image_merge(cel_image2, src_image, x-cel->getX(), y-cel->getY(), opacity, BLEND_MODE_NORMAL); replaceStockImage(cel->getImage(), cel_image2); // TODO fix this, improve, avoid replacing the whole image }
void PatchCel::onExecute() { Cel* cel = this->cel(); const gfx::Rect newBounds = cel->bounds() | gfx::Rect(m_region.bounds()).offset(m_pos); if (cel->bounds() != newBounds) { executeAndAdd(new CropCel(cel, newBounds)); } executeAndAdd( new CopyRegion(cel->image(), m_patch, m_region, m_pos - cel->position())); executeAndAdd(new TrimCel(cel)); m_patch = nullptr; }
void DocumentApi::removeCel(LayerImage* layer, Cel* cel) { ASSERT(layer); ASSERT(cel); Sprite* sprite = layer->getSprite(); DocumentEvent ev(m_document); ev.sprite(sprite); ev.layer(layer); ev.cel(cel); m_document->notifyObservers<DocumentEvent&>(&DocumentObserver::onRemoveCel, ev); // find if the image that use the cel to remove, is used by // another cels bool used = false; for (FrameNumber frame(0); frame<sprite->getTotalFrames(); ++frame) { Cel* it = layer->getCel(frame); if (it && it != cel && it->getImage() == cel->getImage()) { used = true; break; } } // if the image is only used by this cel, // we can remove the image from the stock if (!used) removeImageFromStock(sprite, cel->getImage()); DocumentUndo* undo = m_document->getUndo(); if (undo->isEnabled()) m_undoers->pushUndoer(new undoers::RemoveCel(getObjects(), layer, cel)); // remove the cel from the layer layer->removeCel(cel); // and here we destroy the cel delete cel; }
static void remove_cel(Sprite* sprite, UndoTransaction& undo, LayerImage *layer, Cel *cel) { Image *image; Cel *it; bool used; if (sprite != NULL && layer->isImage() && cel != NULL) { /* find if the image that use the cel to remove, is used by another cels */ used = false; for (FrameNumber frame(0); frame<sprite->getTotalFrames(); ++frame) { it = layer->getCel(frame); if (it != NULL && it != cel && it->getImage() == cel->getImage()) { used = true; break; } } if (!used) { // If the image is only used by this cel, we can remove the // image from the stock. image = sprite->getStock()->getImage(cel->getImage()); if (undo.isEnabled()) undo.pushUndoer(new undoers::RemoveImage(undo.getObjects(), sprite->getStock(), cel->getImage())); sprite->getStock()->removeImage(image); image_free(image); } if (undo.isEnabled()) { undo.pushUndoer(new undoers::RemoveCel(undo.getObjects(), layer, cel)); } // Remove the cel layer->removeCel(cel); delete cel; } }
void UndoTransaction::moveFrameBeforeLayer(Layer* layer, int frame, int before_frame) { ASSERT(layer); switch (layer->getType()) { case GFXOBJ_LAYER_IMAGE: { CelIterator it = ((LayerImage*)layer)->getCelBegin(); CelIterator end = ((LayerImage*)layer)->getCelEnd(); for (; it != end; ++it) { Cel* cel = *it; int new_frame = cel->getFrame(); // moving the frame to the future if (frame < before_frame) { if (cel->getFrame() == frame) { new_frame = before_frame-1; } else if (cel->getFrame() > frame && cel->getFrame() < before_frame) { new_frame--; } } // moving the frame to the past else if (before_frame < frame) { if (cel->getFrame() == frame) { new_frame = before_frame; } else if (cel->getFrame() >= before_frame && cel->getFrame() < frame) { new_frame++; } } if (cel->getFrame() != new_frame) setCelFramePosition(cel, new_frame); } break; } case GFXOBJ_LAYER_FOLDER: { LayerIterator it = static_cast<LayerFolder*>(layer)->get_layer_begin(); LayerIterator end = static_cast<LayerFolder*>(layer)->get_layer_end(); for (; it != end; ++it) moveFrameBeforeLayer(*it, frame, before_frame); break; } } }
void DocumentApi::moveFrameBeforeLayer(Layer* layer, FrameNumber frame, FrameNumber beforeFrame) { ASSERT(layer); switch (layer->getType()) { case GFXOBJ_LAYER_IMAGE: { CelIterator it = ((LayerImage*)layer)->getCelBegin(); CelIterator end = ((LayerImage*)layer)->getCelEnd(); for (; it != end; ++it) { Cel* cel = *it; FrameNumber newFrame = cel->getFrame(); // moving the frame to the future if (frame < beforeFrame) { if (cel->getFrame() == frame) { newFrame = beforeFrame.previous(); } else if (cel->getFrame() > frame && cel->getFrame() < beforeFrame) { --newFrame; } } // moving the frame to the past else if (beforeFrame < frame) { if (cel->getFrame() == frame) { newFrame = beforeFrame; } else if (cel->getFrame() >= beforeFrame && cel->getFrame() < frame) { ++newFrame; } } if (cel->getFrame() != newFrame) setCelFramePosition(layer->getSprite(), cel, newFrame); } break; } case GFXOBJ_LAYER_FOLDER: { LayerIterator it = static_cast<LayerFolder*>(layer)->getLayerBegin(); LayerIterator end = static_cast<LayerFolder*>(layer)->getLayerEnd(); for (; it != end; ++it) moveFrameBeforeLayer(*it, frame, beforeFrame); break; } } }
/** * Moves every frame in @a layer with the offset (@a dx, @a dy). */ void UndoTransaction::displaceLayers(Layer* layer, int dx, int dy) { switch (layer->getType()) { case GFXOBJ_LAYER_IMAGE: { CelIterator it = ((LayerImage*)layer)->getCelBegin(); CelIterator end = ((LayerImage*)layer)->getCelEnd(); for (; it != end; ++it) { Cel* cel = *it; setCelPosition(cel, cel->getX()+dx, cel->getY()+dy); } break; } case GFXOBJ_LAYER_FOLDER: { LayerIterator it = ((LayerFolder*)layer)->get_layer_begin(); LayerIterator end = ((LayerFolder*)layer)->get_layer_end(); for (; it != end; ++it) displaceLayers(*it, dx, dy); break; } } }
void Sprite::remapImages(FrameNumber frameFrom, FrameNumber frameTo, const std::vector<uint8_t>& mapping) { ASSERT(m_format == IMAGE_INDEXED); ASSERT(mapping.size() == 256); CelList cels; getCels(cels); for (CelIterator it = cels.begin(); it != cels.end(); ++it) { Cel* cel = *it; // Remap this Cel because is inside the specified range if (cel->getFrame() >= frameFrom && cel->getFrame() <= frameTo) { Image* image = getStock()->getImage(cel->getImage()); for (int y=0; y<image->h; ++y) { IndexedTraits::address_t ptr = image_address_fast<IndexedTraits>(image, 0, y); for (int x=0; x<image->w; ++x, ++ptr) *ptr = mapping[*ptr]; } } } }
void MaskContentCommand::onExecute(Context* context) { Document* document; { ContextWriter writer(context); document = writer.document(); Cel* cel = writer.cel(); // Get current cel (can be NULL) if (!cel) return; gfx::Color color; if (writer.layer()->isBackground()) { ColorPicker picker; picker.pickColor(*writer.site(), gfx::PointF(0.0, 0.0), current_editor->projection(), ColorPicker::FromComposition); color = color_utils::color_for_layer(picker.color(), writer.layer()); } else color = cel->image()->maskColor(); Mask newMask; gfx::Rect imgBounds = cel->image()->bounds(); if (algorithm::shrink_bounds(cel->image(), imgBounds, color)) { newMask.replace(imgBounds.offset(cel->bounds().origin())); } else { newMask.replace(cel->bounds()); } Transaction transaction(writer.context(), "Select Content", DoesntModifyDocument); transaction.execute(new cmd::SetMask(document, &newMask)); transaction.commit(); document->resetTransformation(); document->generateMaskBoundaries(); } // Select marquee tool if (tools::Tool* tool = App::instance()->toolBox() ->getToolById(tools::WellKnownTools::RectangularMarquee)) { ToolBar::instance()->selectTool(tool); } update_screen_for_document(document); }
// Gives to the user the possibility to move the sprite's layer in the // current editor, returns true if the position was changed. int interactive_move_layer(int mode, bool use_undo, int (*callback)()) { Editor* editor = current_editor; Document* document = editor->getDocument(); undo::UndoHistory* undo = document->getUndoHistory(); Sprite* sprite = document->getSprite(); ASSERT(sprite->getCurrentLayer()->is_image()); LayerImage* layer = static_cast<LayerImage*>(sprite->getCurrentLayer()); Cel *cel = layer->getCel(sprite->getCurrentFrame()); int start_x, new_x; int start_y, new_y; int start_b; int ret; int update = false; int quiet_clock = -1; int first_time = true; int begin_x; int begin_y; if (!cel) return false; begin_x = cel->getX(); begin_y = cel->getY(); editor->hideDrawingCursor(); jmouse_set_cursor(JI_CURSOR_MOVE); editor->editor_click_start(mode, &start_x, &start_y, &start_b); do { if (update) { cel->setPosition(begin_x - start_x + new_x, begin_y - start_y + new_y); // Update layer-bounds. editor->invalidate(); // Update status bar. app_get_statusbar()->setStatusText (0, "Pos %3d %3d Offset %3d %3d", (int)cel->getX(), (int)cel->getY(), (int)(cel->getX() - begin_x), (int)(cel->getY() - begin_y)); /* update clock */ quiet_clock = ji_clock; first_time = false; } /* call the user's routine */ if (callback) (*callback)(); /* redraw dirty widgets */ jwidget_flush_redraw(ji_get_default_manager()); jmanager_dispatch_messages(ji_get_default_manager()); gui_feedback(); } while (editor->editor_click(&new_x, &new_y, &update, NULL)); new_x = cel->getX(); new_y = cel->getY(); cel->setPosition(begin_x, begin_y); /* the position was changed */ if (!editor->editor_click_cancel()) { if (use_undo && undo->isEnabled()) { undo->setLabel("Cel Movement"); undo->setModification(undo::ModifyDocument); undo->pushUndoer(new undoers::SetCelPosition(undo->getObjects(), cel)); } cel->setPosition(new_x, new_y); ret = true; } /* the position wasn't changed */ else { ret = false; } /* redraw the sprite in all editors */ update_screen_for_document(document); /* restore the cursor */ editor->showDrawingCursor(); editor->editor_click_done(); return ret; }
// 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); // Get cursor color app::Color app_cursor_color = Preferences::instance().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 debrushding of several factors (current tool, // foreground color, and layer transparency). color_t brush_color = getBrushColor(sprite, layer); color_t mask_color = sprite->transparentColor(); if (ink->isSelection() || ink->isSlice()) { m_type = SELECTION_CROSS; } else if ( // 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 (layer && !layer->isBackground() && brush_color == mask_color)) { m_type = BRUSH_BOUNDARIES; } else { m_type = CROSS; } // For cursor type 'bounds' we have to generate cursor boundaries if (m_type & BRUSH_BOUNDARIES) generateBoundaries(); // Draw pixel/brush preview if ((m_type & CROSS) && m_editor->getState()->requireBrushPreview()) { Brush* brush = getCurrentBrush(); 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); document->prepareExtraCel(brushBounds, opacity); document->setExtraCelType(render::ExtraType::NONE); document->setExtraCelBlendMode( (layer ? static_cast<LayerImage*>(layer)->blendMode(): BlendMode::NORMAL)); Image* extraImage = document->getExtraCelImage(); extraImage->setMaskColor(mask_color); clear_image(extraImage, mask_color); if (layer) { render::Render().renderLayer( extraImage, layer, m_editor->frame(), gfx::Clip(0, 0, brushBounds), BlendMode::SRC); // This extra cel is a patch for the current layer/frame document->setExtraCelType(render::ExtraType::PATCH); } tools::ToolLoop* loop = create_tool_loop_preview( m_editor, UIContext::instance(), extraImage, -gfx::Point(brushBounds.x, brushBounds.y)); if (loop) { loop->getInk()->prepareInk(loop); loop->getIntertwine()->prepareIntertwine(); loop->getController()->prepareController(); loop->getPointShape()->preparePointShape(loop); loop->getPointShape()->transformPoint( loop, -origBrushBounds.x, -origBrushBounds.y); delete loop; } document->notifySpritePixelsModified( sprite, gfx::Region(m_lastBounds = brushBounds)); } // 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; }
/** * [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(); }
/** * [working thread] */ virtual void onJob() { UndoTransaction undoTransaction(m_document, "Rotate Canvas"); // 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: undoTransaction.setCelPosition(cel, m_sprite->getWidth() - cel->getX() - image->w, m_sprite->getHeight() - cel->getY() - image->h); break; case 90: undoTransaction.setCelPosition(cel, m_sprite->getHeight() - cel->getY() - image->h, cel->getX()); break; case -90: undoTransaction.setCelPosition(cel, cel->getY(), m_sprite->getWidth() - cel->getX() - image->w); 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_new(image->imgtype, m_angle == 180 ? image->w: image->h, m_angle == 180 ? image->h: image->w); image_rotate(image, new_image, m_angle); undoTransaction.replaceStockImage(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(); Mask* new_mask = mask_new(); int x = 0, y = 0; switch (m_angle) { case 180: x = m_sprite->getWidth() - origMask->x - origMask->w; y = m_sprite->getHeight() - origMask->y - origMask->h; break; case 90: x = m_sprite->getHeight() - origMask->y - origMask->h; y = origMask->x; break; case -90: x = origMask->y; y = m_sprite->getWidth() - origMask->x - origMask->w; break; } // create the new rotated mask mask_replace(new_mask, x, y, m_angle == 180 ? origMask->w: origMask->h, m_angle == 180 ? origMask->h: origMask->w); image_rotate(origMask->bitmap, new_mask->bitmap, m_angle); // copy new mask undoTransaction.copyToCurrentMask(new_mask); mask_free(new_mask); // regenerate mask m_document->generateMaskBoundaries(); } // change the sprite's size if (m_angle != 180) undoTransaction.setSpriteSize(m_sprite->getHeight(), m_sprite->getWidth()); // commit changes undoTransaction.commit(); }
void RenderEngine::renderLayer( const Layer* layer, Image *image, int source_x, int source_y, FrameNumber frame, Zoom zoom, void (*zoomed_func)(Image*, const Image*, const Palette*, int, int, int, int, Zoom), bool render_background, bool render_transparent, int blend_mode) { // we can't read from this layer if (!layer->isVisible()) return; switch (layer->type()) { case ObjectType::LayerImage: { if ((!render_background && layer->isBackground()) || (!render_transparent && !layer->isBackground())) break; const Cel* cel = static_cast<const LayerImage*>(layer)->getCel(frame); if (cel != NULL) { Image* src_image; // Is the 'preview_image' set to be used with this layer? if ((selected_layer == layer) && (selected_frame == frame) && (preview_image != NULL)) { src_image = preview_image; } // If not, we use the original cel-image from the images' stock else { src_image = cel->image(); } if (src_image) { int t, output_opacity; output_opacity = MID(0, cel->opacity(), 255); output_opacity = INT_MULT(output_opacity, global_opacity, t); ASSERT(src_image->maskColor() == m_sprite->transparentColor()); (*zoomed_func)(image, src_image, m_sprite->getPalette(frame), zoom.apply(cel->x()) - source_x, zoom.apply(cel->y()) - source_y, output_opacity, (blend_mode < 0 ? static_cast<const LayerImage*>(layer)->getBlendMode(): blend_mode), zoom); } } break; } case ObjectType::LayerFolder: { LayerConstIterator it = static_cast<const LayerFolder*>(layer)->getLayerBegin(); LayerConstIterator end = static_cast<const LayerFolder*>(layer)->getLayerEnd(); for (; it != end; ++it) { renderLayer(*it, image, source_x, source_y, frame, zoom, zoomed_func, render_background, render_transparent, blend_mode); } break; } } // Draw extras if (m_document->getExtraCel() && layer == m_currentLayer && frame == m_currentFrame) { Cel* extraCel = m_document->getExtraCel(); if (extraCel->opacity() > 0) { Image* extraImage = m_document->getExtraCelImage(); (*zoomed_func)(image, extraImage, m_sprite->getPalette(frame), zoom.apply(extraCel->x()) - source_x, zoom.apply(extraCel->y()) - source_y, extraCel->opacity(), m_document->getExtraCelBlendMode(), zoom); } } }
void CopyCel::onExecute() { LayerImage* srcLayer = static_cast<LayerImage*>(m_srcLayer.layer()); LayerImage* dstLayer = static_cast<LayerImage*>(m_dstLayer.layer()); ASSERT(srcLayer); ASSERT(dstLayer); Sprite* srcSprite = srcLayer->sprite(); Sprite* dstSprite = dstLayer->sprite(); ASSERT(srcSprite); ASSERT(dstSprite); ASSERT(m_srcFrame >= 0 && m_srcFrame < srcSprite->totalFrames()); ASSERT(m_dstFrame >= 0); (void)srcSprite; // To avoid unused variable warning on Release mode Cel* srcCel = srcLayer->cel(m_srcFrame); Cel* dstCel = dstLayer->cel(m_dstFrame); // Clear destination cel if it does exist. It'll be overriden by the // copy of srcCel. if (dstCel) { if (dstCel->links()) executeAndAdd(new cmd::UnlinkCel(dstCel)); executeAndAdd(new cmd::ClearCel(dstCel)); } // Add empty frames until newFrame while (dstSprite->totalFrames() <= m_dstFrame) executeAndAdd(new cmd::AddFrame(dstSprite, dstSprite->totalFrames())); Image* srcImage = (srcCel ? srcCel->image(): NULL); ImageRef dstImage; dstCel = dstLayer->cel(m_dstFrame); if (dstCel) dstImage = dstCel->imageRef(); bool createLink = (srcLayer == dstLayer && dstLayer->isContinuous()); // For background layer if (dstLayer->isBackground()) { ASSERT(dstCel); ASSERT(dstImage); if (!dstCel || !dstImage || !srcCel || !srcImage) return; if (createLink) { executeAndAdd(new cmd::SetCelData(dstCel, srcCel->dataRef())); } else { BlendMode blend = (srcLayer->isBackground() ? BlendMode::SRC: BlendMode::NORMAL); ImageRef tmp(Image::createCopy(dstImage.get())); render::composite_image(tmp.get(), srcImage, srcCel->x(), srcCel->y(), 255, blend); executeAndAdd(new cmd::CopyRect(dstImage.get(), tmp.get(), gfx::Clip(tmp->bounds()))); } } // For transparent layers else { if (dstCel) executeAndAdd(new cmd::RemoveCel(dstCel)); if (srcCel) { if (createLink) dstCel = Cel::createLink(srcCel); else dstCel = Cel::createCopy(srcCel); dstCel->setFrame(m_dstFrame); executeAndAdd(new cmd::AddCel(dstLayer, dstCel)); } } }
Layer* read_layer(std::istream& is, LayerSubObjectsSerializer* subObjects, Sprite* sprite) { uint16_t name_length = read16(is); // Name length std::vector<char> name(name_length+1); if (name_length > 0) { is.read(&name[0], name_length); // Name name[name_length] = 0; } else name[0] = 0; uint32_t flags = read32(is); // Flags uint16_t layer_type = read16(is); // Type UniquePtr<Layer> layer; switch (layer_type) { case GFXOBJ_LAYER_IMAGE: { // Create layer layer.reset(new LayerImage(sprite)); // Read cels int cels = read16(is); // Number of cels for (int c=0; c<cels; ++c) { // Read the cel Cel* cel = subObjects->read_cel(is); // Add the cel in the layer static_cast<LayerImage*>(layer.get())->addCel(cel); // Read the cel's image Image* image = subObjects->read_image(is); sprite->getStock()->replaceImage(cel->getImage(), image); } break; } case GFXOBJ_LAYER_FOLDER: { // Create the layer set layer.reset(new LayerFolder(sprite)); // Number of sub-layers int layers = read16(is); for (int c=0; c<layers; c++) { Layer* child = subObjects->read_layer(is); if (child) static_cast<LayerFolder*>(layer.get())->addLayer(child); else break; } break; } default: throw InvalidLayerType("Invalid layer type found in stream"); } if (layer != NULL) { layer->setName(&name[0]); layer->setFlags(flags); } return layer.release(); }
/** * [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(); }
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); }
// 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 FlattenLayers::onExecute() { Sprite* sprite = this->sprite(); auto doc = static_cast<Doc*>(sprite->document()); // Create a temporary image. ImageRef image(Image::create(sprite->pixelFormat(), sprite->width(), sprite->height())); LayerImage* flatLayer; // The layer onto which everything will be flattened. color_t bgcolor; // The background color to use for flatLayer. flatLayer = sprite->backgroundLayer(); if (flatLayer && flatLayer->isVisible()) { // There exists a visible background layer, so we will flatten onto that. bgcolor = doc->bgColor(flatLayer); } else { // Create a new transparent layer to flatten everything onto. flatLayer = new LayerImage(sprite); ASSERT(flatLayer->isVisible()); executeAndAdd(new cmd::AddLayer(sprite->root(), flatLayer, nullptr)); executeAndAdd(new cmd::SetLayerName(flatLayer, "Flattened")); bgcolor = sprite->transparentColor(); } render::Render render; render.setBgType(render::BgType::NONE); // Copy all frames to the background. for (frame_t frame(0); frame<sprite->totalFrames(); ++frame) { // Clear the image and render this frame. clear_image(image.get(), bgcolor); render.renderSprite(image.get(), sprite, frame); // TODO Keep cel links when possible ImageRef cel_image; Cel* cel = flatLayer->cel(frame); if (cel) { if (cel->links()) executeAndAdd(new cmd::UnlinkCel(cel)); cel_image = cel->imageRef(); ASSERT(cel_image); executeAndAdd(new cmd::CopyRect(cel_image.get(), image.get(), gfx::Clip(0, 0, image->bounds()))); } else { cel_image.reset(Image::createCopy(image.get())); cel = new Cel(frame, cel_image); flatLayer->addCel(cel); } } // Delete old layers. LayerList layers = sprite->root()->layers(); for (Layer* layer : layers) if (layer != flatLayer) executeAndAdd(new cmd::RemoveLayer(layer)); }