2026-05-11 23:06:39 +03:00
|
|
|
/*
|
|
|
|
|
* InfiniPaint
|
|
|
|
|
* Copyright (C) 2025-2026 Yousef Khadadeh
|
|
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-12-13 13:03:27 +03:00
|
|
|
#include "ImageCanvasComponent.hpp"
|
2026-01-06 12:36:15 +03:00
|
|
|
#include "Eigen/Core"
|
2025-12-13 13:03:27 +03:00
|
|
|
#include "Helpers/ConvertVec.hpp"
|
|
|
|
|
#include "Helpers/MathExtras.hpp"
|
|
|
|
|
#include "Helpers/SCollision.hpp"
|
|
|
|
|
#include "../SharedTypes.hpp"
|
|
|
|
|
#include "../World.hpp"
|
|
|
|
|
#include "../MainProgram.hpp"
|
2026-01-06 12:36:15 +03:00
|
|
|
#include <include/core/SkBlendMode.h>
|
2025-12-13 13:03:27 +03:00
|
|
|
#include <include/core/SkSamplingOptions.h>
|
|
|
|
|
#include "../DrawCollision.hpp"
|
|
|
|
|
#include "CanvasComponentContainer.hpp"
|
2026-05-23 18:27:45 +03:00
|
|
|
#include <include/pathops/SkPathOps.h>
|
2025-12-13 13:03:27 +03:00
|
|
|
|
2026-01-06 12:36:15 +03:00
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-15 15:36:42 +03:00
|
|
|
CanvasComponentType ImageCanvasComponent::get_type() const {
|
|
|
|
|
return CanvasComponentType::IMAGE;
|
2025-12-13 13:03:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImageCanvasComponent::save(cereal::PortableBinaryOutputArchive& a) const {
|
2026-01-06 12:36:15 +03:00
|
|
|
a(d.editing, d.p1, d.p2, d.imageID, d.cropP1, d.cropP2);
|
2025-12-13 13:03:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImageCanvasComponent::load(cereal::PortableBinaryInputArchive& a) {
|
2026-01-06 12:36:15 +03:00
|
|
|
a(d.editing, d.p1, d.p2, d.imageID, d.cropP1, d.cropP2);
|
2025-12-13 13:03:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImageCanvasComponent::save_file(cereal::PortableBinaryOutputArchive& a) const {
|
2026-01-06 12:36:15 +03:00
|
|
|
a(d.p1, d.p2, d.imageID, d.cropP1, d.cropP2);
|
2025-12-13 13:03:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImageCanvasComponent::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
|
|
|
|
|
a(d.p1, d.p2, d.imageID);
|
2026-01-06 12:36:15 +03:00
|
|
|
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;
|
2025-12-13 13:03:27 +03:00
|
|
|
}
|
|
|
|
|
|
2025-12-29 19:57:36 +03:00
|
|
|
std::unique_ptr<CanvasComponent> ImageCanvasComponent::get_data_copy() const {
|
|
|
|
|
auto toRet = std::make_unique<ImageCanvasComponent>();
|
|
|
|
|
toRet->d = d;
|
|
|
|
|
return toRet;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-13 13:03:27 +03:00
|
|
|
void ImageCanvasComponent::draw_download_progress_bar(SkCanvas* canvas, const DrawData& drawData, float progress) const {
|
|
|
|
|
if(progress == 0.0f)
|
|
|
|
|
return;
|
|
|
|
|
if(compContainer->should_draw(drawData)) {
|
|
|
|
|
canvas->save();
|
2026-05-26 11:59:55 +03:00
|
|
|
compContainer->canvas_do_transform(canvas, compContainer->calculate_draw_transform(drawData.cam.c, compContainer->coords));
|
2025-12-13 13:03:27 +03:00
|
|
|
SkPaint p;
|
|
|
|
|
p.setStroke(true);
|
|
|
|
|
p.setStrokeWidth(10.0f);
|
|
|
|
|
p.setStrokeCap(SkPaint::kRound_Cap);
|
2026-03-31 13:17:58 +03:00
|
|
|
p.setColor4f(drawData.main->g.gui.io.theme->fillColor1);
|
2026-03-20 20:53:59 +03:00
|
|
|
p.setAntiAlias(drawData.skiaAA);
|
2026-01-06 12:36:15 +03:00
|
|
|
Vector2f center = (d.p1 + d.p2) * 0.5f;
|
2025-12-13 13:03:27 +03:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
canvas->restore();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-15 15:36:42 +03:00
|
|
|
void ImageCanvasComponent::set_data_from(const CanvasComponent& other) {
|
|
|
|
|
auto& otherImage = static_cast<const ImageCanvasComponent&>(other);
|
|
|
|
|
d = otherImage.d;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-29 19:57:36 +03:00
|
|
|
void ImageCanvasComponent::remap_resource_ids(const std::unordered_map<NetworkingObjects::NetObjID, NetworkingObjects::NetObjID>& resourceOldToNewMap) {
|
|
|
|
|
d.imageID = resourceOldToNewMap.at(d.imageID);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImageCanvasComponent::get_used_resources(std::unordered_set<NetworkingObjects::NetObjID>& resourceSet) const {
|
|
|
|
|
resourceSet.emplace(d.imageID);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 18:49:56 +03:00
|
|
|
void ImageCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const {
|
2025-12-13 13:03:27 +03:00
|
|
|
ResourceDisplay* display = drawData.rMan->get_display_data(d.imageID);
|
2026-01-06 12:36:15 +03:00
|
|
|
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});
|
2026-03-20 20:53:59 +03:00
|
|
|
rectPaint.setAntiAlias(drawData.skiaAA);
|
2026-01-06 12:36:15 +03:00
|
|
|
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);
|
2026-03-20 20:53:59 +03:00
|
|
|
openingRectPaint.setAntiAlias(drawData.skiaAA);
|
2026-01-06 12:36:15 +03:00
|
|
|
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();
|
2026-03-20 20:53:59 +03:00
|
|
|
canvas->clipRect(get_cropped_rectangle(d.p1, d.p2, d.cropP1, d.cropP2), drawData.skiaAA);
|
2026-01-06 12:36:15 +03:00
|
|
|
}
|
|
|
|
|
|
2026-01-06 13:07:03 +03:00
|
|
|
if(display)
|
2026-01-06 12:36:15 +03:00
|
|
|
display->draw(canvas, drawData, rect_from_points(d.p1, d.p2));
|
2026-03-20 20:53:59 +03:00
|
|
|
else {
|
|
|
|
|
SkPaint p(SkColor4f{0.5f, 0.5f, 0.5f, 0.5f});
|
|
|
|
|
p.setAntiAlias(drawData.skiaAA);
|
|
|
|
|
canvas->drawRect(rect_from_points(d.p1, d.p2), p);
|
|
|
|
|
}
|
2026-01-06 12:36:15 +03:00
|
|
|
|
|
|
|
|
if(isCropped)
|
|
|
|
|
canvas->restore();
|
|
|
|
|
}
|
2025-12-13 13:03:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImageCanvasComponent::update(DrawingProgram& drawP) {
|
|
|
|
|
ResourceDisplay* display = drawP.world.drawData.rMan->get_display_data(d.imageID);
|
|
|
|
|
if(display) {
|
2025-12-16 12:54:12 +03:00
|
|
|
if(display->update_draw())
|
2025-12-30 19:46:08 +03:00
|
|
|
drawP.invalidate_cache_at_component(&(*compContainer->objInfo));
|
2026-04-03 14:18:04 +03:00
|
|
|
if(compContainer->get_world_bounds().has_value())
|
|
|
|
|
display->camera_view_update(compContainer->coords, compContainer->get_world_bounds().value(), drawP.world.drawData, rect_from_points(d.p1, d.p2));
|
2025-12-13 13:03:27 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ImageCanvasComponent::initialize_draw_data(DrawingProgram& drawP) {
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 11:58:52 +03:00
|
|
|
bool ImageCanvasComponent::collides_within_coords_point(const Vector2f& checkAgainst) const {
|
|
|
|
|
SkPath p = SkPath::Rect(SCollision::AABB<float>(d.p1, d.p2).get_sk_rect());
|
|
|
|
|
bool intersectsAABB = p.getBounds().contains(checkAgainst.x(), checkAgainst.y());
|
|
|
|
|
if(!intersectsAABB)
|
|
|
|
|
return false;
|
|
|
|
|
return p.contains(checkAgainst.x(), checkAgainst.y());
|
2025-12-13 13:03:27 +03:00
|
|
|
}
|
|
|
|
|
|
2026-05-23 18:27:45 +03:00
|
|
|
bool ImageCanvasComponent::collides_within_coords_skpath(const SkPath& checkAgainst) const {
|
2026-01-06 12:36:15 +03:00
|
|
|
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);
|
2026-05-23 18:27:45 +03:00
|
|
|
SkPath p = SkPath::Rect(startingRect);
|
2026-05-24 11:58:52 +03:00
|
|
|
bool intersectsAABB = p.getBounds().intersects(checkAgainst.getBounds());
|
|
|
|
|
if(!intersectsAABB)
|
|
|
|
|
return false;
|
|
|
|
|
std::optional<SkPath> pathIntersectCheck = Op(checkAgainst, p, SkPathOp::kIntersect_SkPathOp);
|
|
|
|
|
return pathIntersectCheck.has_value() && !pathIntersectCheck.value().isEmpty();
|
2025-12-13 13:03:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCollision::AABB<float> ImageCanvasComponent::get_obj_coord_bounds() const {
|
2026-05-23 18:27:45 +03:00
|
|
|
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);
|
|
|
|
|
return startingRect;
|
2025-12-13 13:03:27 +03:00
|
|
|
}
|