#include "tstroke.h"
#include "tools/toolutils.h"
#include "tools/tool.h"
#include "tmathutil.h"
#include "tools/cursors.h"
#include "drawutil.h"
#include "tcolorstyles.h"
#include "tundo.h"
#include "tvectorimage.h"
#include "ttoonzimage.h"
#include "tproperty.h"
#include "toonz/strokegenerator.h"
#include "toonz/ttilesaver.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/observer.h"
#include "toonz/toonzimageutils.h"
#include "toonz/levelproperties.h"
#include "toonz/stage2.h"
#include "toonz/ttileset.h"
#include "toonz/rasterstrokegenerator.h"
#include "toonz/preferences.h"
#include "tgl.h"
#include "tenv.h"
#include "trop.h"
#include "tinbetween.h"
#include "ttile.h"
#include "toonz/tpalettehandle.h"
#include "toonz/txsheethandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/tframehandle.h"
#include "tools/toolhandle.h"
#include "tools/toolutils.h"
// For Qt translation support
#include <QCoreApplication>
using namespace ToolUtils;
#define LINES L"Lines"
#define AREAS L"Areas"
#define ALL L"Lines & Areas"
TEnv::StringVar PaintBrushColorType("InknpaintPaintBrushColorType", "Areas");
TEnv::IntVar PaintBrushSelective("InknpaintPaintBrushSelective", 0);
TEnv::DoubleVar PaintBrushSize("InknpaintPaintBrushSize", 10);
//-----------------------------------------------------------------------------
const int BackgroundStyle = 0;
//-----------------------------------------------------------------------------
namespace {
class BrushUndo final : public TRasterUndo {
std::vector<TThickPoint> m_points;
int m_styleId;
bool m_selective;
ColorType m_colorType;
public:
BrushUndo(TTileSetCM32 *tileSet, const std::vector<TThickPoint> &points,
ColorType colorType, int styleId, bool selective,
TXshSimpleLevel *level, const TFrameId &frameId)
: TRasterUndo(tileSet, level, frameId, false, false, 0)
, m_points(points)
, m_styleId(styleId)
, m_selective(selective)
, m_colorType(colorType) {}
void redo() const override {
TToonzImageP image = m_level->getFrame(m_frameId, true);
TRasterCM32P ras = image->getRaster();
RasterStrokeGenerator m_rasterTrack(ras, PAINTBRUSH, m_colorType, m_styleId,
m_points[0], m_selective, 0, false);
m_rasterTrack.setPointsSequence(m_points);
m_rasterTrack.generateStroke(true);
image->setSavebox(image->getSavebox() +
m_rasterTrack.getBBox(m_rasterTrack.getPointsSequence()));
ToolUtils::updateSaveBox();
TTool::getApplication()->getCurrentXsheet()->notifyXsheetChanged();
notifyImageChanged();
}
int getSize() const override {
return sizeof(*this) + TRasterUndo::getSize();
}
QString getToolName() override { return QString("Paint Brush Tool"); }
int getHistoryType() override { return HistoryType::PaintBrushTool; }
};
//-------------------------------------------------------------------------------------------
void drawLine(const TPointD &point, const TPointD ¢re, bool horizontal,
bool isDecimal) {
if (!isDecimal) {
if (horizontal) {
tglDrawSegment(TPointD(point.x - 1.5, point.y + 0.5) + centre,
TPointD(point.x - 0.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(point.y - 0.5, -point.x + 1.5) + centre,
TPointD(point.y - 0.5, -point.x + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, -point.y + 0.5) + centre,
TPointD(-point.x - 0.5, -point.y + 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, point.x - 0.5) + centre,
TPointD(-point.y - 0.5, point.x + 0.5) + centre);
tglDrawSegment(TPointD(point.y - 0.5, point.x + 0.5) + centre,
TPointD(point.y - 0.5, point.x - 0.5) + centre);
tglDrawSegment(TPointD(point.x - 0.5, -point.y + 0.5) + centre,
TPointD(point.x - 1.5, -point.y + 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, -point.x + 0.5) + centre,
TPointD(-point.y - 0.5, -point.x + 1.5) + centre);
tglDrawSegment(TPointD(-point.x - 0.5, point.y + 0.5) + centre,
TPointD(-point.x + 0.5, point.y + 0.5) + centre);
} else {
tglDrawSegment(TPointD(point.x - 1.5, point.y + 1.5) + centre,
TPointD(point.x - 1.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(point.x - 1.5, point.y + 0.5) + centre,
TPointD(point.x - 0.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(point.y + 0.5, -point.x + 1.5) + centre,
TPointD(point.y - 0.5, -point.x + 1.5) + centre);
tglDrawSegment(TPointD(point.y - 0.5, -point.x + 1.5) + centre,
TPointD(point.y - 0.5, -point.x + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, -point.y - 0.5) + centre,
TPointD(-point.x + 0.5, -point.y + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, -point.y + 0.5) + centre,
TPointD(-point.x - 0.5, -point.y + 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 1.5, point.x - 0.5) + centre,
TPointD(-point.y - 0.5, point.x - 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, point.x - 0.5) + centre,
TPointD(-point.y - 0.5, point.x + 0.5) + centre);
tglDrawSegment(TPointD(point.y + 0.5, point.x - 0.5) + centre,
TPointD(point.y - 0.5, point.x - 0.5) + centre);
tglDrawSegment(TPointD(point.y - 0.5, point.x - 0.5) + centre,
TPointD(point.y - 0.5, point.x + 0.5) + centre);
tglDrawSegment(TPointD(point.x - 1.5, -point.y - 0.5) + centre,
TPointD(point.x - 1.5, -point.y + 0.5) + centre);
tglDrawSegment(TPointD(point.x - 1.5, -point.y + 0.5) + centre,
TPointD(point.x - 0.5, -point.y + 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 1.5, -point.x + 1.5) + centre,
TPointD(-point.y - 0.5, -point.x + 1.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, -point.x + 1.5) + centre,
TPointD(-point.y - 0.5, -point.x + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, point.y + 1.5) + centre,
TPointD(-point.x + 0.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, point.y + 0.5) + centre,
TPointD(-point.x - 0.5, point.y + 0.5) + centre);
}
} else {
if (horizontal) {
tglDrawSegment(TPointD(point.x - 0.5, point.y + 0.5) + centre,
TPointD(point.x + 0.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(point.y + 0.5, point.x - 0.5) + centre,
TPointD(point.y + 0.5, point.x + 0.5) + centre);
tglDrawSegment(TPointD(point.y + 0.5, -point.x + 0.5) + centre,
TPointD(point.y + 0.5, -point.x - 0.5) + centre);
tglDrawSegment(TPointD(point.x + 0.5, -point.y - 0.5) + centre,
TPointD(point.x - 0.5, -point.y - 0.5) + centre);
tglDrawSegment(TPointD(-point.x - 0.5, -point.y - 0.5) + centre,
TPointD(-point.x + 0.5, -point.y - 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, -point.x + 0.5) + centre,
TPointD(-point.y - 0.5, -point.x - 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, point.x - 0.5) + centre,
TPointD(-point.y - 0.5, point.x + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, point.y + 0.5) + centre,
TPointD(-point.x - 0.5, point.y + 0.5) + centre);
} else {
tglDrawSegment(TPointD(point.x - 0.5, point.y + 1.5) + centre,
TPointD(point.x - 0.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(point.x - 0.5, point.y + 0.5) + centre,
TPointD(point.x + 0.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(point.y + 1.5, point.x - 0.5) + centre,
TPointD(point.y + 0.5, point.x - 0.5) + centre);
tglDrawSegment(TPointD(point.y + 0.5, point.x - 0.5) + centre,
TPointD(point.y + 0.5, point.x + 0.5) + centre);
tglDrawSegment(TPointD(point.y + 1.5, -point.x + 0.5) + centre,
TPointD(point.y + 0.5, -point.x + 0.5) + centre);
tglDrawSegment(TPointD(point.y + 0.5, -point.x + 0.5) + centre,
TPointD(point.y + 0.5, -point.x - 0.5) + centre);
tglDrawSegment(TPointD(point.x - 0.5, -point.y - 1.5) + centre,
TPointD(point.x - 0.5, -point.y - 0.5) + centre);
tglDrawSegment(TPointD(point.x - 0.5, -point.y - 0.5) + centre,
TPointD(point.x + 0.5, -point.y - 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, -point.y - 1.5) + centre,
TPointD(-point.x + 0.5, -point.y - 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, -point.y - 0.5) + centre,
TPointD(-point.x - 0.5, -point.y - 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 1.5, -point.x + 0.5) + centre,
TPointD(-point.y - 0.5, -point.x + 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, -point.x + 0.5) + centre,
TPointD(-point.y - 0.5, -point.x - 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 1.5, point.x - 0.5) + centre,
TPointD(-point.y - 0.5, point.x - 0.5) + centre);
tglDrawSegment(TPointD(-point.y - 0.5, point.x - 0.5) + centre,
TPointD(-point.y - 0.5, point.x + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, point.y + 1.5) + centre,
TPointD(-point.x + 0.5, point.y + 0.5) + centre);
tglDrawSegment(TPointD(-point.x + 0.5, point.y + 0.5) + centre,
TPointD(-point.x - 0.5, point.y + 0.5) + centre);
}
}
}
//-------------------------------------------------------------------------------------------------------
void drawEmptyCircle(int thick, const TPointD &mousePos, bool isPencil,
bool isLxEven, bool isLyEven) {
TPointD pos = mousePos;
if (isLxEven) pos.x += 0.5;
if (isLyEven) pos.y += 0.5;
if (!isPencil)
tglDrawCircle(pos, (thick + 1) * 0.5);
else {
int x = 0, y = tround((thick * 0.5) - 0.5);
int d = 3 - 2 * (int)(thick * 0.5);
bool horizontal = true, isDecimal = thick % 2 != 0;
drawLine(TPointD(x, y), pos, horizontal, isDecimal);
while (y > x) {
if (d < 0) {
d = d + 4 * x + 6;
horizontal = true;
} else {
d = d + 4 * (x - y) + 10;
horizontal = false;
y--;
}
x++;
drawLine(TPointD(x, y), pos, horizontal, isDecimal);
}
}
}
} // namespace
//-----------------------------------------------------------------------------
class PaintBrushTool final : public TTool {
Q_DECLARE_TR_FUNCTIONS(PaintBrushTool)
RasterStrokeGenerator *m_rasterTrack;
bool m_firstTime;
double m_pointSize, m_distance2;
bool m_selecting;
TTileSaverCM32 *m_tileSaver;
TPointD m_mousePos;
TIntProperty m_toolSize;
TBoolProperty m_onlyEmptyAreas;
TEnumProperty m_colorType;
TPropertyGroup m_prop;
int m_cursor;
ColorType m_colorTypeBrush;
/*--
描画開始時のFrameIdを保存し、マウスリリース時(Undoの登録時)に別のフレームに
移動している場合に備える --*/
TFrameId m_workingFrameId;
public:
PaintBrushTool();
ToolType getToolType() const override { return TTool::LevelWriteTool; }
void draw() override;
void update(TToonzImageP ti, TRectD area);
void updateTranslation() override;
void leftButtonDown(const TPointD &pos, const TMouseEvent &e) override;
void leftButtonDrag(const TPointD &pos, const TMouseEvent &e) override;
void leftButtonUp(const TPointD &pos, const TMouseEvent &) override;
void mouseMove(const TPointD &pos, const TMouseEvent &e) override;
void onEnter() override;
void onLeave() override;
void onActivate() override;
void onDeactivate() override;
bool onPropertyChanged(std::string propertyName) override;
TPropertyGroup *getProperties(int targetType) override { return &m_prop; }
int getCursorId() const override { return m_cursor; }
int getColorClass() const { return 2; }
/*---
* 描画中にツールが切り替わった場合に備え、onDeactivateにもMouseReleaseと同じ終了処理を行う
* ---*/
void finishBrush();
/*--- Brush、PaintBrush、EraserToolがPencilModeのときにTrueを返す。
PaintBrushはピクセルのStyleIndexを入れ替えるツールのため、
アンチエイリアスは存在しない、いわば常にPencilMode ---*/
bool isPencilModeActive() override { return true; }
};
PaintBrushTool paintBrushTool;
//=============================================================================
//
// InkPaintTool implemention
//
//-----------------------------------------------------------------------------
PaintBrushTool::PaintBrushTool()
: TTool("T_PaintBrush")
, m_rasterTrack(0)
, m_pointSize(-1)
, m_selecting(false)
, m_tileSaver(0)
, m_cursor(ToolCursor::EraserCursor)
// sostituire i nomi con quelli del current, tipo W_ToolOptions...
, m_toolSize("Size:", 1, 100, 10, false) // W_ToolOptions_BrushToolSize
, m_colorType("Mode:") // W_ToolOptions_InkOrPaint
, m_onlyEmptyAreas("Selective", false) // W_ToolOptions_Selective
, m_firstTime(true)
, m_workingFrameId(TFrameId()) {
m_colorType.addValue(LINES);
m_colorType.addValue(AREAS);
m_colorType.addValue(ALL);
bind(TTool::ToonzImage);
m_prop.bind(m_toolSize);
m_prop.bind(m_colorType);
m_prop.bind(m_onlyEmptyAreas);
m_onlyEmptyAreas.setId("Selective");
m_colorType.setId("Mode");
}
//-----------------------------------------------------------------------------
void PaintBrushTool::updateTranslation() {
m_toolSize.setQStringName(tr("Size:"));
m_colorType.setQStringName(tr("Mode:"));
m_colorType.setItemUIName(LINES, tr("Lines"));
m_colorType.setItemUIName(AREAS, tr("Areas"));
m_colorType.setItemUIName(ALL, tr("Lines & Areas"));
m_onlyEmptyAreas.setQStringName(tr("Selective", NULL));
}
//-----------------------------------------------------------------------------
void PaintBrushTool::draw() {
/*-- MouseLeave時にBrushTipが描かれるのを防止する --*/
if (m_pointSize == -1) return;
// If toggled off, don't draw brush outline
if (!Preferences::instance()->isCursorOutlineEnabled()) return;
TToonzImageP ti = (TToonzImageP)getImage(false);
if (!ti) return;
TRasterP ras = ti->getRaster();
int lx = ras->getLx();
int ly = ras->getLy();
if ((ToonzCheck::instance()->getChecks() & ToonzCheck::eInk) ||
(ToonzCheck::instance()->getChecks() & ToonzCheck::ePaint) ||
(ToonzCheck::instance()->getChecks() & ToonzCheck::eInk1))
glColor3d(0.5, 0.8, 0.8);
else
glColor3d(1.0, 0.0, 0.0);
drawEmptyCircle(m_toolSize.getValue(), m_mousePos, true, lx % 2 == 0,
ly % 2 == 0);
}
//-----------------------------------------------------------------------------
const UINT pointCount = 20;
//-----------------------------------------------------------------------------
bool PaintBrushTool::onPropertyChanged(std::string propertyName) {
/*-- Size ---*/
if (propertyName == m_toolSize.getName()) {
PaintBrushSize = m_toolSize.getValue();
double x = m_toolSize.getValue();
double minRange = 1;
double maxRange = 100;
double minSize = 0.01;
double maxSize = 100;
m_pointSize =
(x - minRange) / (maxRange - minRange) * (maxSize - minSize) + minSize;
invalidate();
}
// Selective
else if (propertyName == m_onlyEmptyAreas.getName()) {
if (m_onlyEmptyAreas.getValue() && m_colorType.getValue() == LINES) {
m_colorType.setValue(AREAS);
PaintBrushColorType = ::to_string(m_colorType.getValue());
}
PaintBrushSelective = (int)(m_onlyEmptyAreas.getValue());
}
// Areas, Lines etc.
else if (propertyName == m_colorType.getName()) {
if (m_colorType.getValue() == LINES) {
PaintBrushSelective = (int)(m_onlyEmptyAreas.getValue());
}
PaintBrushColorType = ::to_string(m_colorType.getValue());
/*--- ColorModelのCursor更新のためにSIGNALを出す ---*/
TTool::getApplication()->getCurrentTool()->notifyToolChanged();
}
return true;
}
//-----------------------------------------------------------------------------
void PaintBrushTool::leftButtonDown(const TPointD &pos, const TMouseEvent &e) {
m_selecting = true;
TImageP image(getImage(true));
if (m_colorType.getValue() == LINES) m_colorTypeBrush = INK;
if (m_colorType.getValue() == AREAS) m_colorTypeBrush = PAINT;
if (m_colorType.getValue() == ALL) m_colorTypeBrush = INKNPAINT;
if (TToonzImageP ti = image) {
TRasterCM32P ras = ti->getRaster();
if (ras) {
int thickness = m_toolSize.getValue();
int styleId = TTool::getApplication()->getCurrentLevelStyleIndex();
TTileSetCM32 *tileSet = new TTileSetCM32(ras->getSize());
m_tileSaver = new TTileSaverCM32(ras, tileSet);
m_rasterTrack = new RasterStrokeGenerator(
ras, PAINTBRUSH, m_colorTypeBrush, styleId,
TThickPoint(pos + convert(ras->getCenter()), thickness),
m_onlyEmptyAreas.getValue(), 0, false);
/*-- 現在のFidを記憶 --*/
m_workingFrameId = getFrameId();
m_tileSaver->save(m_rasterTrack->getLastRect());
m_rasterTrack->generateLastPieceOfStroke(true);
invalidate();
}
}
}
//-----------------------------------------------------------------------------
void PaintBrushTool::leftButtonDrag(const TPointD &pos, const TMouseEvent &e) {
if (!m_selecting) return;
m_mousePos = pos;
if (TToonzImageP ri = TImageP(getImage(true))) {
/*--- マウスを動かしながらショートカットでこのツールに切り替わった場合、
いきなりleftButtonDragから呼ばれることがあり、m_rasterTrackが無い可能性がある
---*/
if (m_rasterTrack) {
int thickness = m_toolSize.getValue();
bool isAdded = m_rasterTrack->add(
TThickPoint(pos + convert(ri->getRaster()->getCenter()), thickness));
if (isAdded) {
m_tileSaver->save(m_rasterTrack->getLastRect());
TRect modifiedBbox = m_rasterTrack->generateLastPieceOfStroke(true);
invalidate();
}
}
}
}
//-----------------------------------------------------------------------------
void PaintBrushTool::leftButtonUp(const TPointD &pos, const TMouseEvent &) {
if (!m_selecting) return;
m_mousePos = pos;
finishBrush();
}
//-----------------------------------------------------------------------------
void PaintBrushTool::mouseMove(const TPointD &pos, const TMouseEvent &e) {
m_mousePos = pos;
TPointD pp(tround(pos.x), tround(pos.y));
m_mousePos = pp;
invalidate();
}
//-----------------------------------------------------------------------------
void PaintBrushTool::onEnter() {
if (m_firstTime) {
m_onlyEmptyAreas.setValue(PaintBrushSelective ? 1 : 0);
m_colorType.setValue(::to_wstring(PaintBrushColorType.getValue()));
m_toolSize.setValue(PaintBrushSize);
m_firstTime = false;
}
double x = m_toolSize.getValue();
double minRange = 1;
double maxRange = 100;
double minSize = 0.01;
double maxSize = 100;
m_pointSize =
(x - minRange) / (maxRange - minRange) * (maxSize - minSize) + minSize;
if ((TToonzImageP)getImage(false))
m_cursor = ToolCursor::PenCursor;
else
m_cursor = ToolCursor::CURSOR_NO;
}
//-----------------------------------------------------------------------------
void PaintBrushTool::onLeave() { m_pointSize = -1; }
//-----------------------------------------------------------------------------
void PaintBrushTool::onActivate() { onEnter(); }
//-----------------------------------------------------------------------------
void PaintBrushTool::onDeactivate() {
/*--マウスドラッグ中(m_selecting=true)にツールが切り替わったときに描画の終了処理を行う---*/
if (m_selecting) finishBrush();
}
//-----------------------------------------------------------------------------
/*!
* 描画中にツールが切り替わった場合に備え、onDeactivateでもMouseReleaseと同じ終了処理を行う
*/
void PaintBrushTool::finishBrush() {
if (TToonzImageP ti = (TToonzImageP)getImage(true)) {
if (m_rasterTrack) {
int thickness = m_toolSize.getValue();
bool isAdded = m_rasterTrack->add(TThickPoint(
m_mousePos + convert(ti->getRaster()->getCenter()), thickness));
if (isAdded) {
m_tileSaver->save(m_rasterTrack->getLastRect());
m_rasterTrack->generateLastPieceOfStroke(true, true);
}
TTool::Application *app = TTool::getApplication();
TXshLevel *level = app->getCurrentLevel()->getLevel();
TXshSimpleLevelP simLevel = level->getSimpleLevel();
/*-- 描画中にフレームが動いても、描画開始時のFidに対してUndoを記録する
* --*/
TFrameId frameId =
m_workingFrameId.isEmptyFrame() ? getCurrentFid() : m_workingFrameId;
TUndoManager::manager()->add(new BrushUndo(
m_tileSaver->getTileSet(), m_rasterTrack->getPointsSequence(),
m_colorTypeBrush, m_rasterTrack->getStyleId(),
m_rasterTrack->isSelective(), simLevel.getPointer(), frameId));
ToolUtils::updateSaveBox();
/*--- FIdを指定して、描画中にフレームが変わっても、
クリック時のFidのサムネイルが更新されるようにする。---*/
notifyImageChanged(frameId);
invalidate();
delete m_rasterTrack;
m_rasterTrack = 0;
delete m_tileSaver;
/*--- 作業中のフレームをリセット ---*/
m_workingFrameId = TFrameId();
}
ToolUtils::updateSaveBox();
}
m_selecting = false;
}