From c15a21e488408e283a2a096e7535416d9492108c Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Tue, 6 Jan 2026 12:36:15 +0300 Subject: [PATCH] Image cropping added --- src/CanvasComponents/ImageCanvasComponent.cpp | 76 +++++++++++++++---- src/CanvasComponents/ImageCanvasComponent.hpp | 9 ++- src/DrawingProgram/Tools/EditTool.cpp | 6 +- src/DrawingProgram/Tools/EditTool.hpp | 1 + .../Tools/EditTools/ImageEditTool.cpp | 16 ++++ .../Tools/EditTools/TextBoxEditTool.cpp | 2 + src/ResourceDisplay/ImageResourceDisplay.cpp | 1 + 7 files changed, 91 insertions(+), 20 deletions(-) diff --git a/src/CanvasComponents/ImageCanvasComponent.cpp b/src/CanvasComponents/ImageCanvasComponent.cpp index e8e4b32..e7820d6 100644 --- a/src/CanvasComponents/ImageCanvasComponent.cpp +++ b/src/CanvasComponents/ImageCanvasComponent.cpp @@ -1,32 +1,51 @@ #include "ImageCanvasComponent.hpp" +#include "Eigen/Core" #include "Helpers/ConvertVec.hpp" #include "Helpers/MathExtras.hpp" #include "Helpers/SCollision.hpp" #include "../SharedTypes.hpp" #include "../World.hpp" #include "../MainProgram.hpp" +#include #include #include "../DrawCollision.hpp" #include "CanvasComponentContainer.hpp" +SkRect get_cropped_rectangle(const Vector2f& p1, const Vector2f& p2, const Vector2f& cropP1, const Vector2f& cropP2) { + float pWidth = p2.x() - p1.x(); + float pHeight = p2.y() - p1.y(); + return SkRect::MakeLTRB(p1.x() + pWidth * cropP1.x(), p1.y() + pHeight * cropP1.y(), p1.x() + pWidth * cropP2.x(), p1.y() + pHeight * cropP2.y()); +} + +SkRect rect_from_points(const Vector2f& p1, const Vector2f& p2) { + return SkRect::MakeLTRB(p1.x(), p1.y(), p2.x(), p2.y()); +} + CanvasComponentType ImageCanvasComponent::get_type() const { return CanvasComponentType::IMAGE; } void ImageCanvasComponent::save(cereal::PortableBinaryOutputArchive& a) const { - a(d.p1, d.p2, d.imageID); + a(d.editing, d.p1, d.p2, d.imageID, d.cropP1, d.cropP2); } void ImageCanvasComponent::load(cereal::PortableBinaryInputArchive& a) { - a(d.p1, d.p2, d.imageID); + a(d.editing, d.p1, d.p2, d.imageID, d.cropP1, d.cropP2); } void ImageCanvasComponent::save_file(cereal::PortableBinaryOutputArchive& a) const { - a(d.p1, d.p2, d.imageID); + a(d.p1, d.p2, d.imageID, d.cropP1, d.cropP2); } void ImageCanvasComponent::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) { a(d.p1, d.p2, d.imageID); + if(version >= VersionNumber(0, 4, 0)) + a(d.cropP1, d.cropP2); + else { + d.cropP1 = Vector2f{0.0f, 0.0f}; + d.cropP2 = Vector2f{1.0f, 1.0f}; + } + d.editing = false; } std::unique_ptr ImageCanvasComponent::get_data_copy() const { @@ -46,7 +65,7 @@ void ImageCanvasComponent::draw_download_progress_bar(SkCanvas* canvas, const Dr p.setStrokeWidth(10.0f); p.setStrokeCap(SkPaint::kRound_Cap); p.setColor4f(drawData.main->toolbar.io->theme->fillColor1); - Vector2f center = convert_vec2(imRect.center()); + Vector2f center = (d.p1 + d.p2) * 0.5f; const float DOWNLOAD_ARC_RADIUS = 50.0f; canvas->drawArc(SkRect::MakeLTRB(center.x() - DOWNLOAD_ARC_RADIUS, center.y() - DOWNLOAD_ARC_RADIUS, center.x() + DOWNLOAD_ARC_RADIUS, center.y() + DOWNLOAD_ARC_RADIUS), 0.0f, progress * 360.0f, false, p); @@ -69,10 +88,37 @@ void ImageCanvasComponent::get_used_resources(std::unordered_setget_display_data(d.imageID); - if(display) - display->draw(canvas, drawData, imRect); - else - canvas->drawRect(imRect, SkPaint({0.5f, 0.5f, 0.5f, 0.5f})); + if(d.editing) { + canvas->saveLayerAlphaf(nullptr, 1.0f); + display->draw(canvas, drawData, rect_from_points(d.p1, d.p2)); + canvas->saveLayerAlphaf(nullptr, 1.0f); + SkPaint rectPaint; + rectPaint.setColor4f(SkColor4f{0.0f, 0.0f, 0.0f, 0.5f}); + canvas->drawRect(rect_from_points(d.p1, d.p2), rectPaint); + SkPaint openingRectPaint; + openingRectPaint.setColor4f(SkColor4f{0.0f, 0.0f, 0.0f, 0.0f}); + openingRectPaint.setBlendMode(SkBlendMode::kSrc); + canvas->drawRect(get_cropped_rectangle(d.p1, d.p2, d.cropP1, d.cropP2), openingRectPaint); + canvas->restore(); + canvas->restore(); + } + else { + bool isCropped = d.cropP1 != Vector2f{0.0f, 0.0f} || d.cropP2 != Vector2f{1.0f, 1.0f}; + if(isCropped) { + canvas->save(); + canvas->clipRect(get_cropped_rectangle(d.p1, d.p2, d.cropP1, d.cropP2)); + } + + if(display) { + display->draw(canvas, drawData, rect_from_points(d.p1, d.p2)); + canvas->restore(); + } + else + canvas->drawRect(rect_from_points(d.p1, d.p2), SkPaint({0.5f, 0.5f, 0.5f, 0.5f})); + + if(isCropped) + canvas->restore(); + } } void ImageCanvasComponent::update(DrawingProgram& drawP) { @@ -84,7 +130,6 @@ void ImageCanvasComponent::update(DrawingProgram& drawP) { } void ImageCanvasComponent::initialize_draw_data(DrawingProgram& drawP) { - create_draw_data(); create_collider(); } @@ -92,14 +137,17 @@ bool ImageCanvasComponent::collides_within_coords(const SCollision::ColliderColl return collisionTree.is_collide(checkAgainst); } -void ImageCanvasComponent::create_draw_data() { - imRect = SkRect::MakeLTRB(d.p1.x(), d.p1.y(), d.p2.x(), d.p2.y()); -} - void ImageCanvasComponent::create_collider() { using namespace SCollision; ColliderCollection strokeObjects; - std::array newT = triangle_from_rect_points(d.p1, d.p2); + + SkRect startingRect; + if(d.editing) + startingRect = rect_from_points(d.p1, d.p2); + else + startingRect = get_cropped_rectangle(d.p1, d.p2, d.cropP1, d.cropP2); + + std::array newT = triangle_from_rect_points(convert_vec2(startingRect.TL()), convert_vec2(startingRect.BR())); strokeObjects.triangle.emplace_back(newT[0], newT[1], newT[2]); strokeObjects.triangle.emplace_back(newT[2], newT[3], newT[0]); collisionTree.clear(); diff --git a/src/CanvasComponents/ImageCanvasComponent.hpp b/src/CanvasComponents/ImageCanvasComponent.hpp index 215633f..a76d129 100644 --- a/src/CanvasComponents/ImageCanvasComponent.hpp +++ b/src/CanvasComponents/ImageCanvasComponent.hpp @@ -22,23 +22,26 @@ class ImageCanvasComponent : public CanvasComponent { struct Data { Vector2f p1; Vector2f p2; + Vector2f cropP1 = {0.0f, 0.0f}; + Vector2f cropP2 = {1.0f, 1.0f}; NetworkingObjects::NetObjID imageID; + + bool editing = false; } d; virtual void update(DrawingProgram& drawP) override; + bool contains_actual_image(); + private: virtual void draw(SkCanvas* canvas, const DrawData& drawData) const override; virtual void initialize_draw_data(DrawingProgram& drawP) override; virtual bool collides_within_coords(const SCollision::ColliderCollection& checkAgainst) const override; - void create_draw_data(); void create_collider(); virtual SCollision::AABB get_obj_coord_bounds() const override; - SkRect imRect; - SCollision::BVHContainer collisionTree; }; diff --git a/src/DrawingProgram/Tools/EditTool.cpp b/src/DrawingProgram/Tools/EditTool.cpp index 1b871a7..cf70afc 100644 --- a/src/DrawingProgram/Tools/EditTool.cpp +++ b/src/DrawingProgram/Tools/EditTool.cpp @@ -187,7 +187,7 @@ void EditTool::tool_update() { bool clickedAway = false; if(!pointDragging && drawP.controls.leftClick) { for(HandleData& h : pointHandles) { - if(SCollision::collide(mouseCircle, SCollision::Circle(drawP.world.drawData.cam.c.to_space(objInfoBeingEdited->obj->coords.from_space(*h.p)), drawP.drag_point_radius()))) { + if(SCollision::collide(mouseCircle, SCollision::Circle(drawP.world.drawData.cam.c.to_space(objInfoBeingEdited->obj->coords.from_space(h.coordMatrix * (*h.p))), drawP.drag_point_radius()))) { pointDragging = &h; isMovingPoint = true; break; @@ -198,7 +198,7 @@ void EditTool::tool_update() { } else if(pointDragging) { if(drawP.controls.leftClickHeld) { - Vector2f newPos = objInfoBeingEdited->obj->coords.get_mouse_pos(drawP.world); + Vector2f newPos = pointDragging->coordMatrix.inverse() * objInfoBeingEdited->obj->coords.get_mouse_pos(drawP.world); if(newPos != *pointDragging->p) { if(pointDragging->min) newPos = cwise_vec_max((*pointDragging->min + Vector2f{pointDragging->minimumDistanceBetweenBoundsAndPoint, pointDragging->minimumDistanceBetweenBoundsAndPoint}).eval(), newPos); @@ -228,7 +228,7 @@ bool EditTool::prevent_undo_or_redo() { void EditTool::draw(SkCanvas* canvas, const DrawData& drawData) { if(objInfoBeingEdited) { for(HandleData& h : pointHandles) - drawP.draw_drag_circle(canvas, drawData.cam.c.to_space((objInfoBeingEdited->obj->coords.from_space(*h.p))), {0.1f, 0.9f, 0.9f, 1.0f}, drawData); + drawP.draw_drag_circle(canvas, drawData.cam.c.to_space((objInfoBeingEdited->obj->coords.from_space(h.coordMatrix * *h.p))), {0.1f, 0.9f, 0.9f, 1.0f}, drawData); } } diff --git a/src/DrawingProgram/Tools/EditTool.hpp b/src/DrawingProgram/Tools/EditTool.hpp index 13d9d28..a964bca 100644 --- a/src/DrawingProgram/Tools/EditTool.hpp +++ b/src/DrawingProgram/Tools/EditTool.hpp @@ -13,6 +13,7 @@ class EditTool : public DrawingProgramToolBase { Vector2f* min; Vector2f* max; float minimumDistanceBetweenBoundsAndPoint = MINIMUM_DISTANCE_BETWEEN_BOUNDS; + Affine2f coordMatrix = Affine2f::Identity(); }; EditTool(DrawingProgram& initDrawP); diff --git a/src/DrawingProgram/Tools/EditTools/ImageEditTool.cpp b/src/DrawingProgram/Tools/EditTools/ImageEditTool.cpp index 0cd7cac..29dc29a 100644 --- a/src/DrawingProgram/Tools/EditTools/ImageEditTool.cpp +++ b/src/DrawingProgram/Tools/EditTools/ImageEditTool.cpp @@ -5,7 +5,9 @@ #include "../../../Toolbar.hpp" #include "../../../MainProgram.hpp" #include "../../../ResourceManager.hpp" +#include "../EditTool.hpp" #include "DrawingProgramEditToolBase.hpp" +#include "Eigen/Geometry" #include #ifdef __EMSCRIPTEN__ @@ -17,9 +19,23 @@ ImageEditTool::ImageEditTool(DrawingProgram& initDrawP): {} void ImageEditTool::edit_start(EditTool& editTool, CanvasComponentContainer::ObjInfo* comp, std::any& prevData) { + auto& a = static_cast(comp->obj->get_comp()); + static Vector2f staticZero = {0.0f, 0.0f}; + static Vector2f staticOne = {1.0f, 1.0f}; + + Affine2f transformMat = Translation2f(a.d.p1) * AlignedScaling2f(a.d.p2 - a.d.p1) * Affine2f::Identity(); + + prevData = a.d; + editTool.add_point_handle({&a.d.cropP1, &staticZero, &a.d.cropP2, 0.0f, transformMat}); + editTool.add_point_handle({&a.d.cropP2, &a.d.cropP1, &staticOne, 0.0f, transformMat}); + a.d.editing = true; + comp->obj->commit_update(drawP); } void ImageEditTool::commit_edit_updates(CanvasComponentContainer::ObjInfo* comp, std::any& prevData) { + auto& a = static_cast(comp->obj->get_comp()); + a.d.editing = false; + comp->obj->commit_update(drawP); } bool ImageEditTool::edit_update(CanvasComponentContainer::ObjInfo* comp) { diff --git a/src/DrawingProgram/Tools/EditTools/TextBoxEditTool.cpp b/src/DrawingProgram/Tools/EditTools/TextBoxEditTool.cpp index 0b8bbc1..90f6ff4 100644 --- a/src/DrawingProgram/Tools/EditTools/TextBoxEditTool.cpp +++ b/src/DrawingProgram/Tools/EditTools/TextBoxEditTool.cpp @@ -278,6 +278,8 @@ void TextBoxEditTool::edit_start(EditTool& editTool, CanvasComponentContainer::O editTool.add_point_handle({&a.d.p1, nullptr, &a.d.p2}); editTool.add_point_handle({&a.d.p2, &a.d.p1, nullptr}); + + comp->obj->commit_update(drawP); } bool TextBoxEditTool::edit_update(CanvasComponentContainer::ObjInfo* comp) { diff --git a/src/ResourceDisplay/ImageResourceDisplay.cpp b/src/ResourceDisplay/ImageResourceDisplay.cpp index 7e74d27..01343ee 100644 --- a/src/ResourceDisplay/ImageResourceDisplay.cpp +++ b/src/ResourceDisplay/ImageResourceDisplay.cpp @@ -7,6 +7,7 @@ //#include #include #include +#include #include "../MainProgram.hpp" #include