[img-editor] Added ability to save tool size and color separately.

This commit is contained in:
23rd
2026-03-11 08:53:54 +03:00
committed by John Preston
parent d0d1d91ef3
commit 02eac1ee14
4 changed files with 221 additions and 56 deletions
+62 -30
View File
@@ -36,6 +36,26 @@ inline float64 InterpolationRatio(int from, int to, int result) {
return (result - from) / float64(to - from);
};
[[nodiscard]] int ToolIndex(Brush::Tool tool) {
switch (tool) {
case Brush::Tool::Pen: return 0;
case Brush::Tool::Arrow: return 1;
case Brush::Tool::Marker: return 2;
case Brush::Tool::Eraser: return 3;
}
return 0;
}
[[nodiscard]] Brush::Tool ToolFromIndex(int index) {
switch (index) {
case 0: return Brush::Tool::Pen;
case 1: return Brush::Tool::Arrow;
case 2: return Brush::Tool::Marker;
case 3: return Brush::Tool::Eraser;
}
return Brush::Tool::Pen;
}
class PlusCircle final : public Ui::AbstractButton {
public:
using Ui::AbstractButton::AbstractButton;
@@ -165,7 +185,8 @@ std::vector<QColor> PaletteColors() {
ColorPicker::ColorPicker(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<Ui::Show> show,
const Brush &savedBrush)
const std::array<Brush, 4> &savedBrushes,
Brush::Tool savedTool)
: _parent(parent)
, _show(std::move(show))
, _colorButton(
@@ -182,21 +203,20 @@ ColorPicker::ColorPicker(
, _sizeControlHoverArea(std::in_place, parent)
, _sizeControl(std::in_place, parent)
, _toolSelection(std::in_place, parent)
, _brush(Brush{
.sizeRatio = (savedBrush.sizeRatio
? savedBrush.sizeRatio
: kMinBrushSize),
.color = (savedBrush.color.isValid()
? savedBrush.color
: QColor(234, 39, 57)),
.tool = savedBrush.tool,
}) {
, _brush(savedBrushes[ToolIndex(savedTool)])
, _toolBrushes(savedBrushes) {
_colorButton->resize(
st::photoEditorColorButtonSize,
st::photoEditorColorButtonSize);
_colorButton->setSelectionCutout(true);
_colorButton->setForceCircle(true);
for (auto i = 0; i != int(_toolBrushes.size()); ++i) {
_toolBrushes[i].tool = ToolFromIndex(i);
}
_brush = _toolBrushes[ToolIndex(savedTool)];
_brush.tool = savedTool;
_toolSelection->setAttribute(Qt::WA_TransparentForMouseEvents);
_toolSelection->setAttribute(Qt::WA_TranslucentBackground, true);
_toolSelection->setVisible(false);
@@ -237,26 +257,21 @@ ColorPicker::ColorPicker(
st::photoEditorToolButtonSize);
button->show();
}
const auto setTool = [=](Brush::Tool tool) {
if (_brush.tool == tool) {
return;
}
_brush.tool = tool;
updateToolSelection(true);
_saveBrushRequests.fire_copy(_brush);
const auto setToolRequest = [=](Brush::Tool tool) {
setTool(tool);
};
if (_toolButtons.size() >= 4) {
_toolButtons[0]->setClickedCallback([=] {
setTool(Brush::Tool::Pen);
setToolRequest(Brush::Tool::Pen);
});
_toolButtons[1]->setClickedCallback([=] {
setTool(Brush::Tool::Arrow);
setToolRequest(Brush::Tool::Arrow);
});
_toolButtons[2]->setClickedCallback([=] {
setTool(Brush::Tool::Marker);
setToolRequest(Brush::Tool::Marker);
});
_toolButtons[3]->setClickedCallback([=] {
setTool(Brush::Tool::Eraser);
setToolRequest(Brush::Tool::Eraser);
});
}
updateToolSelection(false);
@@ -397,15 +412,7 @@ void ColorPicker::updateToolSelection(bool animated) {
if (_toolButtons.size() < 4) {
return;
}
const auto index = [&] {
switch (_brush.tool) {
case Brush::Tool::Pen: return 0;
case Brush::Tool::Arrow: return 1;
case Brush::Tool::Marker: return 2;
case Brush::Tool::Eraser: return 3;
}
return 0;
}();
const auto index = ToolIndex(_brush.tool);
if (index < 0 || index >= int(_toolButtons.size())) {
return;
}
@@ -438,6 +445,28 @@ void ColorPicker::updateToolSelection(bool animated) {
anim::easeOutCirc);
}
void ColorPicker::setTool(Brush::Tool tool) {
if (_brush.tool == tool) {
return;
}
storeCurrentBrush();
_brush = _toolBrushes[ToolIndex(tool)];
_brush.tool = tool;
updateSizeControlPositionFromRatio();
if (_paletteVisible) {
rebuildPalette();
} else {
_colorButton->update();
}
updateToolSelection(true);
_sizeControl->update();
_saveBrushRequests.fire_copy(_brush);
}
void ColorPicker::storeCurrentBrush() {
_toolBrushes[ToolIndex(_brush.tool)] = _brush;
}
void ColorPicker::paintSizeControl(QPainter &p) {
auto hq = PainterHighQualityEnabler(p);
@@ -517,6 +546,7 @@ void ColorPicker::rebuildPalette() {
button->setSelectionCutout(true);
button->setClickedCallback([=] {
_brush.color = c;
storeCurrentBrush();
rebuildPalette();
_colorButton->update();
_saveBrushRequests.fire_copy(_brush);
@@ -550,6 +580,7 @@ void ColorPicker::rebuildPalette() {
}, editor->lifetime());
box->addButton(tr::lng_box_done(), [=] {
_brush.color = state->color;
storeCurrentBrush();
rebuildPalette();
_colorButton->update();
_saveBrushRequests.fire_copy(_brush);
@@ -667,6 +698,7 @@ void ColorPicker::updateSizeControlExpanded() {
void ColorPicker::updateSizeControlMousePosition(int y) {
_sizeDown.y = std::clamp(y, sizeControlTop(), sizeControlBottom());
_brush.sizeRatio = sizeControlRatioFromY(_sizeDown.y);
storeCurrentBrush();
}
void ColorPicker::updateSizeControlPositionFromRatio() {
+5 -1
View File
@@ -24,7 +24,8 @@ public:
ColorPicker(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<Ui::Show> show,
const Brush &savedBrush);
const std::array<Brush, 4> &savedBrushes,
Brush::Tool savedTool);
void moveLine(const QPoint &position);
void setCanvasRect(const QRect &rect);
@@ -38,6 +39,8 @@ private:
void rebuildPalette();
void updateToolButtonsGeometry();
void updateToolSelection(bool animated);
void setTool(Brush::Tool tool);
void storeCurrentBrush();
void updatePaletteGeometry();
void setPaletteVisible(bool visible);
void moveSizeControl(const QSize &size);
@@ -77,6 +80,7 @@ private:
QPoint _colorButtonCenter;
bool _paletteVisible = false;
Brush _brush;
std::array<Brush, 4> _toolBrushes;
Ui::Animations::Simple _sizeControlAnimation;
Ui::Animations::Simple _toolSelectionAnimation;
+150 -25
View File
@@ -22,42 +22,162 @@ namespace Editor {
namespace {
constexpr auto kPrecision = 100000;
constexpr auto kBrushesVersion = -1;
constexpr auto kDefaultBrushSizeRatio = 0.9;
[[nodiscard]] QByteArray Serialize(const Brush &brush) {
[[nodiscard]] int ToolIndex(Brush::Tool tool) {
switch (tool) {
case Brush::Tool::Pen: return 0;
case Brush::Tool::Arrow: return 1;
case Brush::Tool::Marker: return 2;
case Brush::Tool::Eraser: return 3;
}
return 0;
}
[[nodiscard]] Brush::Tool ToolFromIndex(int index) {
switch (index) {
case 0: return Brush::Tool::Pen;
case 1: return Brush::Tool::Arrow;
case 2: return Brush::Tool::Marker;
case 3: return Brush::Tool::Eraser;
}
return Brush::Tool::Pen;
}
[[nodiscard]] Brush::Tool ToolFromSerialized(qint32 value) {
switch (value) {
case int(Brush::Tool::Pen): return Brush::Tool::Pen;
case int(Brush::Tool::Arrow): return Brush::Tool::Arrow;
case int(Brush::Tool::Marker): return Brush::Tool::Marker;
case int(Brush::Tool::Eraser): return Brush::Tool::Eraser;
}
return Brush::Tool::Pen;
}
[[nodiscard]] QColor DefaultBrushColor(Brush::Tool tool) {
switch (tool) {
case Brush::Tool::Pen: return QColor(234, 39, 57);
case Brush::Tool::Arrow: return QColor(252, 150, 77);
case Brush::Tool::Marker: return QColor(252, 222, 101);
case Brush::Tool::Eraser: return QColor(234, 39, 57);
}
return QColor(234, 39, 57);
}
[[nodiscard]] Brush DefaultBrush(Brush::Tool tool) {
auto result = Brush();
result.sizeRatio = kDefaultBrushSizeRatio;
result.color = DefaultBrushColor(tool);
result.tool = tool;
return result;
}
[[nodiscard]] std::array<Brush, 4> DefaultBrushes() {
auto result = std::array<Brush, 4>();
for (auto i = 0; i != int(result.size()); ++i) {
const auto tool = ToolFromIndex(i);
result[i] = DefaultBrush(tool);
}
return result;
}
struct BrushState {
std::array<Brush, 4> brushes = DefaultBrushes();
Brush::Tool tool = Brush::Tool::Pen;
};
[[nodiscard]] QByteArray Serialize(
const std::array<Brush, 4> &brushes,
Brush::Tool tool) {
auto result = QByteArray();
auto stream = QDataStream(&result, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_3);
stream << qint32(brush.sizeRatio * kPrecision)
<< brush.color
<< qint32(int(brush.tool));
stream
<< qint32(kBrushesVersion)
<< qint32(int(tool))
<< qint32(brushes.size());
for (auto i = 0; i != int(brushes.size()); ++i) {
const auto tool = ToolFromIndex(i);
const auto &brush = brushes[i];
stream
<< qint32(int(tool))
<< qint32(brush.sizeRatio * kPrecision)
<< brush.color;
}
stream.device()->close();
return result;
}
[[nodiscard]] Brush Deserialize(const QByteArray &data) {
auto stream = QDataStream(data);
auto result = Brush();
auto size = qint32(0);
auto tool = qint32(int(Brush::Tool::Pen));
stream >> size >> result.color;
if (stream.status() != QDataStream::Ok) {
return Brush();
[[nodiscard]] BrushState Deserialize(const QByteArray &data) {
auto result = BrushState();
if (data.isEmpty()) {
return result;
}
if (!stream.atEnd()) {
stream >> tool;
if (stream.status() == QDataStream::Ok) {
switch (tool) {
case int(Brush::Tool::Pen):
case int(Brush::Tool::Arrow):
case int(Brush::Tool::Marker):
case int(Brush::Tool::Eraser):
result.tool = Brush::Tool(tool);
break;
auto stream = QDataStream(data);
auto head = qint32(0);
stream >> head;
if (stream.status() != QDataStream::Ok) {
return result;
}
if (head < 0) {
auto toolValue = qint32(int(Brush::Tool::Pen));
auto count = qint32(0);
stream >> toolValue >> count;
if (stream.status() != QDataStream::Ok) {
return result;
}
result.tool = ToolFromSerialized(toolValue);
auto limit = int(count);
if (limit < 0) {
limit = 0;
} else if (limit > int(result.brushes.size())) {
limit = int(result.brushes.size());
}
for (auto i = 0; i != limit; ++i) {
auto entryTool = qint32(int(Brush::Tool::Pen));
auto size = qint32(0);
auto color = QColor();
stream >> entryTool >> size >> color;
if (stream.status() != QDataStream::Ok) {
return result;
}
const auto tool = ToolFromSerialized(entryTool);
const auto index = ToolIndex(tool);
if (size > 0) {
result.brushes[index].sizeRatio = size / float(kPrecision);
}
if (color.isValid()) {
result.brushes[index].color = color;
}
result.brushes[index].tool = tool;
}
return result;
}
auto size = head;
auto color = QColor();
stream >> color;
if (stream.status() != QDataStream::Ok) {
return result;
}
auto toolValue = qint32(int(Brush::Tool::Pen));
if (!stream.atEnd()) {
stream >> toolValue;
if (stream.status() != QDataStream::Ok) {
toolValue = qint32(int(Brush::Tool::Pen));
}
}
result.sizeRatio = size / float(kPrecision);
const auto tool = ToolFromSerialized(toolValue);
const auto index = ToolIndex(tool);
if (size > 0) {
result.brushes[index].sizeRatio = size / float(kPrecision);
}
if (color.isValid()) {
result.brushes[index].color = color;
}
result.brushes[index].tool = tool;
result.tool = tool;
return result;
}
@@ -108,10 +228,13 @@ PhotoEditor::PhotoEditor(
_controllers,
_modifications,
data))
, _brushes(Deserialize(Core::App().settings().photoEditorBrush()).brushes)
, _brushTool(Deserialize(Core::App().settings().photoEditorBrush()).tool)
, _colorPicker(std::make_unique<ColorPicker>(
this,
std::move(show),
Deserialize(Core::App().settings().photoEditorBrush()))) {
_brushes,
_brushTool)) {
sizeValue(
) | rpl::on_next([=](const QSize &size) {
@@ -211,7 +334,9 @@ PhotoEditor::PhotoEditor(
) | rpl::on_next([=](const Brush &brush) {
_content->applyBrush(brush);
const auto serialized = Serialize(brush);
_brushTool = brush.tool;
_brushes[ToolIndex(brush.tool)] = brush;
const auto serialized = Serialize(_brushes, _brushTool);
if (Core::App().settings().photoEditorBrush() != serialized) {
Core::App().settings().setPhotoEditorBrush(serialized);
Core::App().saveSettingsDelayed();
@@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "editor/photo_editor_common.h"
#include "editor/photo_editor_inner_common.h"
#include <array>
namespace Ui {
class LayerWidget;
class Show;
@@ -62,6 +64,8 @@ private:
base::unique_qptr<PhotoEditorContent> _content;
base::unique_qptr<PhotoEditorControls> _controls;
std::array<Brush, 4> _brushes;
Brush::Tool _brushTool = Brush::Tool::Pen;
const std::unique_ptr<ColorPicker> _colorPicker;
rpl::variable<PhotoEditorMode> _mode = PhotoEditorMode{