mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-14 13:21:34 +00:00
Merge 9cc740bc20f08feedc006b598e5a4c94418fcd7e into c949f4a5a6ca6adb411674583a57551ff99b8e88
This commit is contained in:
commit
edf5e8f7d6
@ -526,6 +526,7 @@
|
||||
<section id="grid" canforce="true">
|
||||
<option id="snap" type="bool" default="false" />
|
||||
<option id="bounds" type="gfx::Rect" default="doc::Sprite::DefaultGridBounds()" />
|
||||
<option id="type" type="doc::Grid::Type" default="doc::Sprite::DefaultGridType()" />
|
||||
<option id="color" type="app::Color" default="app::Color::fromRgb(0, 0, 255)" />
|
||||
<option id="opacity" type="int" default="160" />
|
||||
<option id="auto_opacity" type="bool" default="true" />
|
||||
|
@ -837,6 +837,8 @@ x = X:
|
||||
y = Y:
|
||||
width = Width:
|
||||
height = Height:
|
||||
type_orthogonal = Orthogonal
|
||||
type_isometric = Isometric
|
||||
|
||||
[home_view]
|
||||
title = Home
|
||||
@ -1462,6 +1464,8 @@ grid_x = X:
|
||||
grid_y = Y:
|
||||
grid_width = Width:
|
||||
grid_height = Height:
|
||||
grid_type_orthogonal = Orthogonal
|
||||
grid_type_isometric = Isometric
|
||||
grid_color = Color:
|
||||
grid_opacity = Opacity:
|
||||
grid_auto = Auto
|
||||
|
@ -15,6 +15,10 @@
|
||||
|
||||
<label text="@.height" />
|
||||
<expr id="grid_h" text="" />
|
||||
<combobox id="grid_type" editable="true" expansive="true">
|
||||
<listitem text="@.type_orthogonal" />
|
||||
<listitem text="@.type_isometric" />
|
||||
</combobox>
|
||||
|
||||
<separator horizontal="true" cell_hspan="4" />
|
||||
|
||||
|
@ -423,6 +423,10 @@
|
||||
<expr id="grid_w" text="" />
|
||||
<label text="@.grid_height" />
|
||||
<expr id="grid_h" text="" />
|
||||
<combobox id="grid_type" editable="true" expansive="true">
|
||||
<listitem text="@.grid_type_orthogonal" />
|
||||
<listitem text="@.grid_type_isometric" />
|
||||
</combobox>
|
||||
<hbox />
|
||||
|
||||
<label text="@.grid_color" />
|
||||
|
@ -321,6 +321,7 @@ target_sources(app-lib PRIVATE
|
||||
cmd/set_cel_zindex.cpp
|
||||
cmd/set_frame_duration.cpp
|
||||
cmd/set_grid_bounds.cpp
|
||||
cmd/set_grid_type.cpp
|
||||
cmd/set_last_point.cpp
|
||||
cmd/set_layer_blend_mode.cpp
|
||||
cmd/set_layer_flags.cpp
|
||||
|
49
src/app/cmd/set_grid_type.cpp
Normal file
49
src/app/cmd/set_grid_type.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/cmd/set_grid_type.h"
|
||||
|
||||
#include "app/doc.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "doc/sprite.h"
|
||||
|
||||
namespace app { namespace cmd {
|
||||
|
||||
using namespace doc;
|
||||
|
||||
SetGridType::SetGridType(Sprite* sprite, const doc::Grid::Type type)
|
||||
: WithSprite(sprite)
|
||||
, m_oldType(sprite->gridType())
|
||||
, m_newType(type)
|
||||
{
|
||||
}
|
||||
|
||||
void SetGridType::onExecute()
|
||||
{
|
||||
setGrid(m_newType);
|
||||
}
|
||||
|
||||
void SetGridType::onUndo()
|
||||
{
|
||||
setGrid(m_oldType);
|
||||
}
|
||||
|
||||
void SetGridType::setGrid(const doc::Grid::Type type)
|
||||
{
|
||||
Sprite* spr = sprite();
|
||||
spr->setGridType(type);
|
||||
|
||||
Doc* doc = static_cast<Doc*>(spr->document());
|
||||
auto& docPref = Preferences::instance().document(doc);
|
||||
docPref.grid.type(type);
|
||||
spr->incrementVersion();
|
||||
}
|
||||
|
||||
}} // namespace app::cmd
|
40
src/app/cmd/set_grid_type.h
Normal file
40
src/app/cmd/set_grid_type.h
Normal file
@ -0,0 +1,40 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_CMD_SET_GRID_TYPE_H_INCLUDED
|
||||
#define APP_CMD_SET_GRID_TYPE_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/cmd.h"
|
||||
#include "app/cmd/with_sprite.h"
|
||||
#include "doc/grid.h"
|
||||
|
||||
namespace doc {
|
||||
class Sprite;
|
||||
}
|
||||
|
||||
namespace app { namespace cmd {
|
||||
|
||||
class SetGridType : public Cmd,
|
||||
public WithSprite {
|
||||
public:
|
||||
SetGridType(doc::Sprite* sprite, doc::Grid::Type type);
|
||||
|
||||
protected:
|
||||
void onExecute() override;
|
||||
void onUndo() override;
|
||||
size_t onMemSize() const override { return sizeof(*this); }
|
||||
|
||||
private:
|
||||
void setGrid(doc::Grid::Type type);
|
||||
|
||||
doc::Grid::Type m_oldType;
|
||||
doc::Grid::Type m_newType;
|
||||
};
|
||||
|
||||
}} // namespace app::cmd
|
||||
|
||||
#endif
|
@ -11,11 +11,13 @@
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/cmd/set_grid_bounds.h"
|
||||
#include "app/cmd/set_grid_type.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/context.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/doc.h"
|
||||
#include "app/find_widget.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/load_widget.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/tx.h"
|
||||
@ -107,6 +109,10 @@ void GridSettingsCommand::onExecute(Context* context)
|
||||
|
||||
Site site = context->activeSite();
|
||||
Rect bounds = site.gridBounds();
|
||||
doc::Grid::Type type = site.gridType();
|
||||
std::string typestr = (type == doc::Grid::Type::Isometric ?
|
||||
app::Strings::grid_settings_type_isometric() :
|
||||
app::Strings::grid_settings_type_orthogonal());
|
||||
|
||||
window.gridX()->setTextf("%d", bounds.x);
|
||||
window.gridY()->setTextf("%d", bounds.y);
|
||||
@ -122,6 +128,7 @@ void GridSettingsCommand::onExecute(Context* context)
|
||||
if (window.gridH()->textInt() <= 0)
|
||||
window.gridH()->setText("1");
|
||||
});
|
||||
window.gridType()->getEntryWidget()->setText(typestr);
|
||||
window.openWindowInForeground();
|
||||
|
||||
if (window.closer() == window.ok()) {
|
||||
@ -132,9 +139,15 @@ void GridSettingsCommand::onExecute(Context* context)
|
||||
bounds.w = std::max(bounds.w, 1);
|
||||
bounds.h = std::max(bounds.h, 1);
|
||||
|
||||
typestr = window.gridType()->getEntryWidget()->text();
|
||||
type = (typestr == app::Strings::grid_settings_type_isometric() ?
|
||||
doc::Grid::Type::Isometric :
|
||||
doc::Grid::Type::Orthogonal);
|
||||
|
||||
ContextWriter writer(context);
|
||||
Tx tx(writer, friendlyName(), ModifyDocument);
|
||||
tx(new cmd::SetGridBounds(site.sprite(), bounds));
|
||||
tx(new cmd::SetGridType(site.sprite(), type));
|
||||
tx.commit();
|
||||
|
||||
auto& docPref = Preferences::instance().document(site.document());
|
||||
|
@ -194,10 +194,12 @@ void OpenFileCommand::onExecute(Context* context)
|
||||
// interact with old versions of Aseprite saving the grid
|
||||
// bounds in the aseprite.ini file)
|
||||
docPref.grid.bounds(doc->sprite()->gridBounds());
|
||||
docPref.grid.type(doc->sprite()->gridType());
|
||||
}
|
||||
else {
|
||||
// Get grid bounds from preferences
|
||||
doc->sprite()->setGridBounds(docPref.grid.bounds());
|
||||
doc->sprite()->setGridType(docPref.grid.type());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/cmd/set_grid_bounds.h"
|
||||
#include "app/cmd/set_grid_type.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/console.h"
|
||||
#include "app/context.h"
|
||||
@ -837,6 +838,20 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Change sprite grid type
|
||||
if (m_context && m_context->activeDocument() && m_context->activeDocument()->sprite() &&
|
||||
m_context->activeDocument()->sprite()->gridType() != gridTypeInt()) {
|
||||
try {
|
||||
ContextWriter writer(m_context, 1000);
|
||||
Tx tx(writer, Strings::commands_GridSettings(), ModifyDocument);
|
||||
tx(new cmd::SetGridType(writer.sprite(), gridTypeInt()));
|
||||
tx.commit();
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
Console::showException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
m_curPref->show.grid(gridVisible()->isSelected());
|
||||
m_curPref->grid.bounds(gridBounds());
|
||||
m_curPref->grid.color(gridColor()->getColor());
|
||||
@ -1516,6 +1531,12 @@ private:
|
||||
gridW()->setTextf("%d", m_curPref->grid.bounds().w);
|
||||
gridH()->setTextf("%d", m_curPref->grid.bounds().h);
|
||||
|
||||
doc::Grid::Type type = m_curPref->grid.type();
|
||||
std::string typestr = (type == doc::Grid::Type::Isometric ?
|
||||
app::Strings::grid_settings_type_isometric() :
|
||||
app::Strings::grid_settings_type_orthogonal());
|
||||
gridType()->getEntryWidget()->setText(typestr);
|
||||
|
||||
gridColor()->setColor(m_curPref->grid.color());
|
||||
gridOpacity()->setValue(m_curPref->grid.opacity());
|
||||
gridAutoOpacity()->setSelected(m_curPref->grid.autoOpacity());
|
||||
@ -1560,6 +1581,12 @@ private:
|
||||
gridW()->setTextf("%d", pref.grid.bounds.defaultValue().w);
|
||||
gridH()->setTextf("%d", pref.grid.bounds.defaultValue().h);
|
||||
|
||||
doc::Grid::Type type = pref.grid.type.defaultValue();
|
||||
std::string typestr = (type == doc::Grid::Type::Isometric ?
|
||||
app::Strings::grid_settings_type_isometric() :
|
||||
app::Strings::grid_settings_type_orthogonal());
|
||||
gridType()->getEntryWidget()->setText(typestr);
|
||||
|
||||
gridColor()->setColor(pref.grid.color.defaultValue());
|
||||
gridOpacity()->setValue(pref.grid.opacity.defaultValue());
|
||||
gridAutoOpacity()->setSelected(pref.grid.autoOpacity.defaultValue());
|
||||
@ -1577,6 +1604,12 @@ private:
|
||||
gridW()->setTextf("%d", pref.grid.bounds().w);
|
||||
gridH()->setTextf("%d", pref.grid.bounds().h);
|
||||
|
||||
doc::Grid::Type type = pref.grid.type();
|
||||
std::string typestr = (type == doc::Grid::Type::Isometric ?
|
||||
app::Strings::grid_settings_type_isometric() :
|
||||
app::Strings::grid_settings_type_orthogonal());
|
||||
gridType()->getEntryWidget()->setText(typestr);
|
||||
|
||||
gridColor()->setColor(pref.grid.color());
|
||||
gridOpacity()->setValue(pref.grid.opacity());
|
||||
gridAutoOpacity()->setSelected(pref.grid.autoOpacity());
|
||||
@ -2045,6 +2078,15 @@ private:
|
||||
return gfx::Rect(gridX()->textInt(), gridY()->textInt(), gridW()->textInt(), gridH()->textInt());
|
||||
}
|
||||
|
||||
doc::Grid::Type gridTypeInt() const
|
||||
{
|
||||
std::string typestr = gridType()->getEntryWidget()->text();
|
||||
if (typestr == app::Strings::grid_settings_type_isometric())
|
||||
return doc::Grid::Type::Isometric;
|
||||
|
||||
return doc::Grid::Type::Orthogonal;
|
||||
}
|
||||
|
||||
static std::string userThemeFolder()
|
||||
{
|
||||
ResourceFinder rf;
|
||||
|
@ -559,6 +559,7 @@ static void ase_file_prepare_header(FILE* f,
|
||||
header->grid_y = sprite->gridBounds().y;
|
||||
header->grid_width = sprite->gridBounds().w;
|
||||
header->grid_height = sprite->gridBounds().h;
|
||||
header->grid_type = uint16_t(sprite->gridType());
|
||||
}
|
||||
|
||||
static void ase_file_write_header(FILE* f, dio::AsepriteHeader* header)
|
||||
@ -586,6 +587,7 @@ static void ase_file_write_header(FILE* f, dio::AsepriteHeader* header)
|
||||
fputw(header->grid_y, f);
|
||||
fputw(header->grid_width, f);
|
||||
fputw(header->grid_height, f);
|
||||
fputw(header->grid_type, f);
|
||||
|
||||
fseek(f, header->pos + 128, SEEK_SET);
|
||||
}
|
||||
|
@ -397,6 +397,7 @@ FOR_ENUM(doc::BrushPattern)
|
||||
FOR_ENUM(doc::ColorMode)
|
||||
FOR_ENUM(doc::FitCriteria)
|
||||
FOR_ENUM(doc::RgbMapAlgorithm)
|
||||
FOR_ENUM(doc::Grid::Type)
|
||||
FOR_ENUM(filters::HueSaturationFilter::Mode)
|
||||
FOR_ENUM(filters::TiledMode)
|
||||
FOR_ENUM(render::OnionskinPosition)
|
||||
|
@ -129,6 +129,14 @@ gfx::Rect Site::gridBounds() const
|
||||
return doc::Sprite::DefaultGridBounds();
|
||||
}
|
||||
|
||||
doc::Grid::Type Site::gridType() const
|
||||
{
|
||||
if (m_sprite)
|
||||
return m_sprite->gridType();
|
||||
|
||||
return doc::Sprite::DefaultGridType();
|
||||
}
|
||||
|
||||
bool Site::shouldTrimCel(Cel* cel) const
|
||||
{
|
||||
return (cel && cel->layer() && cel->layer()->isTransparent() &&
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "app/tileset_mode.h"
|
||||
#include "doc/cel_list.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/grid.h"
|
||||
#include "doc/palette_picks.h"
|
||||
#include "doc/selected_objects.h"
|
||||
#include "gfx/fwd.h"
|
||||
@ -99,6 +100,7 @@ public:
|
||||
doc::Tileset* tileset() const;
|
||||
doc::Grid grid() const;
|
||||
gfx::Rect gridBounds() const;
|
||||
doc::Grid::Type gridType() const;
|
||||
|
||||
void tilemapMode(const TilemapMode mode) { m_tilemapMode = mode; }
|
||||
void tilesetMode(const TilesetMode mode) { m_tilesetMode = mode; }
|
||||
|
@ -11,6 +11,11 @@
|
||||
|
||||
#include "app/snap_to_grid.h"
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/context.h"
|
||||
#include "app/doc.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "doc/grid.h"
|
||||
#include "gfx/point.h"
|
||||
#include "gfx/rect.h"
|
||||
|
||||
@ -18,11 +23,106 @@
|
||||
|
||||
namespace app {
|
||||
|
||||
gfx::Point snap_to_isometric_grid(const gfx::Rect& grid,
|
||||
const gfx::Point& point,
|
||||
const PreferSnapTo prefer)
|
||||
{
|
||||
// Because we force unworkable grid sizes to share a pixel,
|
||||
// we need to account for that here
|
||||
auto guide = doc::Grid::IsometricGuide(grid.size());
|
||||
const int width = grid.w - int(!guide.evenWidth);
|
||||
const int height = grid.h - int(!guide.evenHeight);
|
||||
|
||||
// Convert point to grid space
|
||||
const gfx::PointF newPoint(int((point.x - grid.x) / width) * width,
|
||||
int((point.y - grid.y) / height) * height);
|
||||
// And then make it relative to the center of a cell
|
||||
const gfx::PointF vto((newPoint + gfx::Point(guide.end.x, guide.start.y)) - point);
|
||||
|
||||
// The following happens here:
|
||||
//
|
||||
// /\ /\
|
||||
// /A \/B \
|
||||
// \ /\ /
|
||||
// \/ \/
|
||||
// /\ /\
|
||||
// /C \/D \
|
||||
//
|
||||
// Only the origin for diamonds (A,B,C,D) can be found by dividing
|
||||
// the original point by grid size.
|
||||
//
|
||||
// In order to snap to a position relative to the "in-between" diamonds,
|
||||
// we need to determine whether the cell coords are outside the
|
||||
// bounds of the current grid cell.
|
||||
bool outside = false;
|
||||
|
||||
if (prefer != PreferSnapTo::ClosestGridVertex) {
|
||||
// We use the pixel-precise grid for this bounds-check
|
||||
const auto line = doc::Grid::getIsometricLine(grid.size());
|
||||
const int index = int(ABS(vto.y) - int(vto.y > 0)) + 1;
|
||||
const gfx::Point co(-vto.x + guide.end.x, -vto.y + guide.start.y);
|
||||
const gfx::Point& p = line[index];
|
||||
outside = !(p.x <= co.x) || !(co.x < width - p.x) || !(height - p.y <= co.y) || !(co.y < p.y);
|
||||
}
|
||||
|
||||
// Find which of the four corners of the current diamond
|
||||
// should be picked
|
||||
gfx::Point near(0, 0);
|
||||
const int offsetEvenX = (!guide.squareRatio ? int(guide.evenWidth) : 0);
|
||||
const int offsetOddY = (!guide.squareRatio ? int(!guide.shareEdges || !guide.evenHeight) :
|
||||
int(!guide.evenHeight));
|
||||
const int offsetOddX = (!guide.squareRatio ? int(guide.oddSize) : 0);
|
||||
const gfx::Point candidates[] = { gfx::Point(guide.end.x + offsetEvenX, 0),
|
||||
gfx::Point(guide.end.x + offsetEvenX, height),
|
||||
gfx::Point(offsetOddX, guide.start.y - offsetOddY),
|
||||
gfx::Point(width + offsetOddX, guide.start.y - offsetOddY) };
|
||||
switch (prefer) {
|
||||
case PreferSnapTo::ClosestGridVertex:
|
||||
if (ABS(vto.x) > ABS(vto.y))
|
||||
near = (vto.x < 0 ? candidates[3] : candidates[2]);
|
||||
else
|
||||
near = (vto.y < 0 ? candidates[1] : candidates[0]);
|
||||
break;
|
||||
|
||||
// Pick topmost corner
|
||||
case PreferSnapTo::FloorGrid:
|
||||
case PreferSnapTo::BoxOrigin:
|
||||
if (outside) {
|
||||
near = (vto.x < 0 ? candidates[3] : candidates[2]);
|
||||
near.y -= (vto.y > 0 ? height : 0);
|
||||
}
|
||||
else {
|
||||
near = candidates[0];
|
||||
}
|
||||
break;
|
||||
|
||||
// Pick bottom-most corner
|
||||
case PreferSnapTo::CeilGrid:
|
||||
case PreferSnapTo::BoxEnd:
|
||||
if (outside) {
|
||||
near = (vto.x < 0 ? candidates[3] : candidates[2]);
|
||||
near.y += (vto.y < 0 ? height : 0);
|
||||
}
|
||||
else {
|
||||
near = candidates[1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Convert offset back to pixel space
|
||||
return gfx::Point(newPoint + near + grid.origin());
|
||||
}
|
||||
|
||||
gfx::Point snap_to_grid(const gfx::Rect& grid, const gfx::Point& point, const PreferSnapTo prefer)
|
||||
{
|
||||
if (grid.isEmpty())
|
||||
return point;
|
||||
|
||||
// Use different logic for isometric grid
|
||||
const doc::Grid::Type gridType =
|
||||
App::instance()->preferences().document(App::instance()->context()->documents()[0]).grid.type();
|
||||
if (gridType == doc::Grid::Type::Isometric)
|
||||
return snap_to_isometric_grid(grid, point, prefer);
|
||||
|
||||
div_t d, dx, dy;
|
||||
dx = std::div(grid.x, grid.w);
|
||||
dy = std::div(grid.y, grid.h);
|
||||
|
@ -17,6 +17,94 @@ namespace app { namespace tools {
|
||||
|
||||
using namespace gfx;
|
||||
|
||||
// Adjustment for snap to isometric grid
|
||||
static void snap_isometric_line(ToolLoop* loop, Stroke& stroke, bool lineCtl)
|
||||
{
|
||||
// Get last two points
|
||||
Stroke::Pt& a = stroke[stroke.size() - 2];
|
||||
Stroke::Pt& b = stroke[stroke.size() - 1];
|
||||
|
||||
// Get function invoked by line tool
|
||||
bool lineTool = (string_id_to_brush_type(loop->getTool()->getId()) == kLineBrushType);
|
||||
|
||||
// TODO: rectangles and ellipses
|
||||
if (lineCtl && !loop->getIntertwine()->snapByAngle())
|
||||
return;
|
||||
|
||||
// Get line angle
|
||||
PointF vto(b.x - a.x, b.y - a.y);
|
||||
double len = ABS(vto.x) + ABS(vto.y);
|
||||
vto /= len;
|
||||
|
||||
const gfx::Rect& grid = loop->getGridBounds();
|
||||
const auto line = doc::Grid::IsometricGuide(grid.size());
|
||||
|
||||
// Offset vertical lines/single point to the left for line tool.
|
||||
// Because pressing the angle snap key will bypass this function,
|
||||
// this makes it so one can selectively apply the offset.
|
||||
if ((std::isnan(vto.x) && std::isnan(vto.y)) || (int(vto.x) == 0 && int(vto.y) != 0)) {
|
||||
const int step = 1 + (line.oddSize * int(!line.squareRatio));
|
||||
a.x -= step * int(lineTool);
|
||||
b.x -= step * int(lineTool);
|
||||
}
|
||||
// Horizontal lines
|
||||
else if (int(vto.y) == 0 && int(vto.x) != 0) {
|
||||
if (vto.x > 0)
|
||||
b.x--;
|
||||
else
|
||||
a.x--;
|
||||
}
|
||||
// Diagonal lines
|
||||
else {
|
||||
// Adjust start/end point based on line direction and grid size
|
||||
if (!line.squareRatio) {
|
||||
if (vto.x < 0) {
|
||||
a.x -= line.evenWidth;
|
||||
b.x -= 2 * line.oddSize;
|
||||
}
|
||||
else {
|
||||
a.x -= 2 * line.oddSize;
|
||||
b.x -= line.evenWidth;
|
||||
}
|
||||
|
||||
// Unticking 'share borders' adds one pixel of distance between edges
|
||||
if (!line.shareEdges) {
|
||||
if (vto.y < 0)
|
||||
a.y--;
|
||||
else
|
||||
b.y--;
|
||||
}
|
||||
|
||||
// Some line angles do not intertwine in the exact same way
|
||||
// when the order of the two points is inverted, so we try to
|
||||
// detect this edge case and flip the points.
|
||||
//
|
||||
// TODO: this fix only works for two-point lines. Support
|
||||
// for freehand strokes would require changes to intertwiners,
|
||||
// not just the freehand controller itself.
|
||||
if (lineTool && vto.x < 0 && a.x % (grid.w - !line.evenWidth)) {
|
||||
auto tmp = a;
|
||||
a = b;
|
||||
b = tmp;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (vto.x < 0) {
|
||||
a.x -= line.evenWidth;
|
||||
b.y -= SGN(vto.y) * line.evenHeight;
|
||||
}
|
||||
else {
|
||||
b.x -= line.evenWidth;
|
||||
b.y -= SGN(vto.y) * line.evenHeight;
|
||||
}
|
||||
if (vto.y < 0) {
|
||||
a.y -= line.evenHeight;
|
||||
b.y -= line.evenHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shared logic between controllers that can move/displace all points
|
||||
// using the space bar.
|
||||
class MoveOriginCapability : public Controller {
|
||||
@ -70,6 +158,11 @@ public:
|
||||
{
|
||||
m_last = pt;
|
||||
stroke.addPoint(pt);
|
||||
if (loop->getController()->canSnapToGrid() && loop->getSnapToGrid() &&
|
||||
loop->sprite()->gridType() == doc::Grid::Type::Isometric) {
|
||||
snap_isometric_line(loop, stroke, false);
|
||||
m_last = stroke[stroke.size() - 1];
|
||||
}
|
||||
}
|
||||
|
||||
void getStrokeToInterwine(const Stroke& input, Stroke& output) override
|
||||
@ -120,8 +213,17 @@ public:
|
||||
stroke.addPoint(pt);
|
||||
stroke.addPoint(pt);
|
||||
|
||||
if (loop->isSelectingTiles())
|
||||
if (loop->isSelectingTiles()) {
|
||||
snapPointsToGridTiles(loop, stroke);
|
||||
}
|
||||
else if (
|
||||
// 'Angle Snap' key not pressed...
|
||||
!(int(loop->getModifiers()) & int(ToolLoopModifiers::kSquareAspect)) &&
|
||||
|
||||
// And snapping to isometric grid
|
||||
(loop->getSnapToGrid() && loop->sprite()->gridType() == doc::Grid::Type::Isometric)) {
|
||||
snap_isometric_line(loop, stroke, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool releaseButton(Stroke& stroke, const Stroke::Pt& pt) override { return false; }
|
||||
@ -153,6 +255,8 @@ public:
|
||||
stroke[1] = pt;
|
||||
|
||||
bool isoAngle = false;
|
||||
bool isoMode = loop->getController()->canSnapToGrid() && loop->getSnapToGrid() &&
|
||||
loop->sprite()->gridType() == doc::Grid::Type::Isometric;
|
||||
|
||||
if ((int(loop->getModifiers()) & int(ToolLoopModifiers::kSquareAspect))) {
|
||||
int dx = stroke[1].x - m_first.x;
|
||||
@ -197,6 +301,9 @@ public:
|
||||
stroke[1].y = m_first.y + SGN(dy) * minsize;
|
||||
}
|
||||
}
|
||||
else if (isoMode) {
|
||||
snap_isometric_line(loop, stroke, true);
|
||||
}
|
||||
|
||||
if (hasAngle()) {
|
||||
int rx = stroke[1].x - m_center.x;
|
||||
@ -222,7 +329,7 @@ public:
|
||||
if (loop->isSelectingTiles()) {
|
||||
snapPointsToGridTiles(loop, stroke);
|
||||
}
|
||||
else {
|
||||
else if (!isoMode) {
|
||||
if (stroke[0].x < stroke[1].x)
|
||||
stroke[1].x -= bounds.w;
|
||||
else if (stroke[0].x > stroke[1].x)
|
||||
|
@ -145,8 +145,10 @@ doc::AlgoLineWithAlgoPixel Intertwine::getLineAlgo(ToolLoop* loop,
|
||||
|
||||
if ( // When "Snap Angle" in being used or...
|
||||
(int(loop->getModifiers()) & int(ToolLoopModifiers::kSquareAspect)) ||
|
||||
// "Snap to Grid" is enabled
|
||||
(loop->getController()->canSnapToGrid() && loop->getSnapToGrid())) {
|
||||
// "Snap to Grid" is enabled...
|
||||
(loop->getController()->canSnapToGrid() && loop->getSnapToGrid() &&
|
||||
// And we are not in isometric mode
|
||||
loop->sprite()->gridType() != doc::Grid::Type::Isometric)) {
|
||||
// We prefer the perfect pixel lines that matches grid tiles
|
||||
return (needsFixForLineBrush ? algo_line_perfect_with_fix_for_line_brush : algo_line_perfect);
|
||||
}
|
||||
|
@ -57,6 +57,8 @@
|
||||
#include "app/util/tile_flags_utils.h"
|
||||
#include "base/chrono.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/pi.h"
|
||||
#include "doc/algo.h"
|
||||
#include "doc/doc.h"
|
||||
#include "doc/mask_boundaries.h"
|
||||
#include "doc/slice.h"
|
||||
@ -805,7 +807,7 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g,
|
||||
alpha = std::clamp(alpha, 0, 255);
|
||||
}
|
||||
|
||||
drawGrid(g, enclosingRect, Rect(0, 0, 1, 1), m_docPref.pixelGrid.color(), alpha);
|
||||
drawGrid(g, enclosingRect, Rect(0, 0, 1, 1), m_docPref.pixelGrid.color(), alpha, true);
|
||||
|
||||
// Save all pixel grid settings that are unset
|
||||
m_docPref.pixelGrid.forceSection();
|
||||
@ -828,7 +830,7 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g,
|
||||
}
|
||||
|
||||
if (alpha > 8) {
|
||||
drawGrid(g, enclosingRect, gridrc, m_docPref.grid.color(), alpha);
|
||||
drawGrid(g, enclosingRect, gridrc, m_docPref.grid.color(), alpha, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1103,7 +1105,8 @@ void Editor::drawGrid(Graphics* g,
|
||||
const gfx::Rect& spriteBounds,
|
||||
const Rect& gridBounds,
|
||||
const app::Color& color,
|
||||
int alpha)
|
||||
int alpha,
|
||||
bool isPixelGrid)
|
||||
{
|
||||
if ((m_flags & kShowGrid) == 0)
|
||||
return;
|
||||
@ -1146,21 +1149,156 @@ void Editor::drawGrid(Graphics* g,
|
||||
grid_color =
|
||||
gfx::rgba(gfx::getr(grid_color), gfx::getg(grid_color), gfx::getb(grid_color), alpha);
|
||||
|
||||
// Draw horizontal lines
|
||||
int x1 = spriteBounds.x;
|
||||
int y1 = gridF.y;
|
||||
int x2 = spriteBounds.x + spriteBounds.w;
|
||||
int y2 = spriteBounds.y + spriteBounds.h;
|
||||
// Orthogonal grid
|
||||
if (isPixelGrid || getSite().sprite()->gridType() == doc::Grid::Type::Orthogonal) {
|
||||
// Draw horizontal lines
|
||||
int x1 = spriteBounds.x;
|
||||
int y1 = gridF.y;
|
||||
int x2 = spriteBounds.x + spriteBounds.w;
|
||||
int y2 = spriteBounds.y + spriteBounds.h;
|
||||
|
||||
for (double c = y1; c <= y2; c += gridF.h)
|
||||
g->drawHLine(grid_color, x1, c, spriteBounds.w);
|
||||
for (double c = y1; c <= y2; c += gridF.h)
|
||||
g->drawHLine(grid_color, x1, c, spriteBounds.w);
|
||||
|
||||
// Draw vertical lines
|
||||
x1 = gridF.x;
|
||||
y1 = spriteBounds.y;
|
||||
// Draw vertical lines
|
||||
x1 = gridF.x;
|
||||
y1 = spriteBounds.y;
|
||||
|
||||
for (double c = x1; c <= x2; c += gridF.w)
|
||||
g->drawVLine(grid_color, c, y1, spriteBounds.h);
|
||||
for (double c = x1; c <= x2; c += gridF.w)
|
||||
g->drawVLine(grid_color, c, y1, spriteBounds.h);
|
||||
}
|
||||
// Isometric grid
|
||||
else {
|
||||
const RectF pix(editorToScreenF(RectF(0, 0, 1, 1)));
|
||||
int x1 = gridF.x;
|
||||
int y1 = gridF.y;
|
||||
const int x2 = spriteBounds.x + spriteBounds.w;
|
||||
const int y2 = spriteBounds.y + spriteBounds.h;
|
||||
int dx = std::round(grid.w * pix.w);
|
||||
int dy = std::round(grid.h * pix.h);
|
||||
|
||||
auto guide = doc::Grid::IsometricGuide(grid.size());
|
||||
|
||||
// Diamonds share a side when their size is uneven
|
||||
dx -= pix.w * int(!guide.evenWidth);
|
||||
dy -= pix.h * int(!guide.evenHeight);
|
||||
|
||||
if (dx < 2)
|
||||
dx = 2;
|
||||
if (dy < 2)
|
||||
dy = 2;
|
||||
|
||||
while (x1 > spriteBounds.x)
|
||||
x1 -= dx;
|
||||
while (y1 > spriteBounds.y)
|
||||
y1 -= dy;
|
||||
|
||||
// Draw pixel-precise isometric grid when zoomed in
|
||||
if (m_proj.zoom().scale() > 6.00) {
|
||||
ui::Paint paint;
|
||||
paint.style(ui::Paint::Stroke);
|
||||
paint.antialias(false);
|
||||
paint.color(grid_color);
|
||||
|
||||
// Move single cell across the screen
|
||||
// to draw entire grid
|
||||
Path& cell = getIsometricGridPath(grid);
|
||||
|
||||
for (int y = y1; y < y2; y += dy) {
|
||||
for (int x = x1; x < x2; x += dx) {
|
||||
cell.offset(x, y);
|
||||
g->drawPath(cell, paint);
|
||||
// Restore original position for later use
|
||||
cell.offset(-x, -y);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Draw straight isometric line grid
|
||||
else {
|
||||
// Single side of diamond is line (a, b)
|
||||
PointF a(0, dy * 0.5);
|
||||
PointF b(dx * 0.5, dy);
|
||||
|
||||
// Get length and direction of line (a, b)
|
||||
Point vto = Point(b - a);
|
||||
PointF ivto = PointF(-vto.x, vto.y);
|
||||
const double lenF = sqrt(vto.x * vto.x + vto.y * vto.y);
|
||||
|
||||
// Now displace point (b) to right edge of canvas
|
||||
b = a + PointF(dx * 0.5, -dy * 0.5);
|
||||
|
||||
// Offset line (a, b) by screen coords
|
||||
a += Point(x1, y1);
|
||||
b += Point(x1, y1);
|
||||
|
||||
// Calculate number of diamonds required to
|
||||
// fill canvas horizontally
|
||||
Point left(std::round(((x2 - x1) / dx) * dx), 0);
|
||||
while (left.x < (x2 - x1))
|
||||
left.x += dx;
|
||||
|
||||
// Calculate how much we need to stretch
|
||||
// line (a, b) to cover the whole canvas
|
||||
const double len = (x2 - x1) + (y2 - y1);
|
||||
vto.x = std::round(vto.x * len);
|
||||
vto.y = std::round(vto.y * len);
|
||||
ivto.x = std::round(ivto.x * len);
|
||||
ivto.y = std::round(ivto.y * len);
|
||||
|
||||
// Move these two points across the screen in
|
||||
// cell-sized steps to draw the entire grid
|
||||
for (int y = y1; y < y2; y += dy) {
|
||||
g->drawLine(grid_color, Point(a), Point(a + vto));
|
||||
g->drawLine(grid_color, Point(a + left), Point((a + left) + ivto));
|
||||
a.y += dy;
|
||||
}
|
||||
for (int x = x1; x < x2; x += dx) {
|
||||
g->drawLine(grid_color, Point(b), Point(b + vto));
|
||||
g->drawLine(grid_color, Point(b), Point(b + ivto));
|
||||
b.x += dx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gfx::Path& Editor::getIsometricGridPath(Rect& grid)
|
||||
{
|
||||
static Path path;
|
||||
static Size prevSize(0, 0);
|
||||
static double prevScale = 0.00;
|
||||
|
||||
// Regenerate bitmap on zoom or grid size change
|
||||
if (prevScale != m_proj.zoom().scale() || prevSize != grid.size()) {
|
||||
const RectF pix(editorToScreenF(RectF(0, 0, 1, 1)));
|
||||
doc::ImageRef imref(doc::Image::create(doc::PixelFormat::IMAGE_BITMAP,
|
||||
std::round(grid.w * pix.w),
|
||||
std::round((grid.h + 1) * pix.h)));
|
||||
|
||||
// Return previous path if image generation fails
|
||||
doc::Image* im = imref.get();
|
||||
if (!im)
|
||||
return path;
|
||||
|
||||
// Prepare bitmap from points of pixel precise line.
|
||||
// A single grid cell is calculated from these
|
||||
im->clear(0x00);
|
||||
for (const auto p : doc::Grid::getIsometricLine(grid.size()))
|
||||
im->fillRect(std::round(p.x * pix.w),
|
||||
std::round((grid.h - p.y) * pix.h),
|
||||
std::floor((grid.w - p.x) * pix.w),
|
||||
std::floor(p.y * pix.h),
|
||||
0x01);
|
||||
|
||||
doc::MaskBoundaries immask;
|
||||
immask.regen(im);
|
||||
immask.createPathIfNeeeded();
|
||||
path = immask.path();
|
||||
}
|
||||
// Remember scale used to generate the current path
|
||||
prevScale = m_proj.zoom().scale();
|
||||
prevSize.w = grid.w;
|
||||
prevSize.h = grid.h;
|
||||
return path;
|
||||
}
|
||||
|
||||
void Editor::drawSlices(ui::Graphics* g)
|
||||
|
@ -366,7 +366,9 @@ private:
|
||||
const gfx::Rect& spriteBounds,
|
||||
const gfx::Rect& gridBounds,
|
||||
const app::Color& color,
|
||||
int alpha);
|
||||
int alpha,
|
||||
bool isPixelGrid);
|
||||
gfx::Path& getIsometricGridPath(gfx::Rect& grid);
|
||||
void drawSlices(ui::Graphics* g);
|
||||
void drawTileNumbers(ui::Graphics* g, const Cel* cel);
|
||||
void drawCelBounds(ui::Graphics* g, const Cel* cel, const gfx::Color color);
|
||||
|
@ -101,6 +101,7 @@ struct AsepriteHeader {
|
||||
int16_t grid_y;
|
||||
uint16_t grid_width;
|
||||
uint16_t grid_height;
|
||||
uint16_t grid_type;
|
||||
};
|
||||
|
||||
struct AsepriteFrameHeader {
|
||||
|
@ -74,6 +74,7 @@ bool AsepriteDecoder::decode()
|
||||
// Set grid bounds
|
||||
sprite->setGridBounds(
|
||||
gfx::Rect(header.grid_x, header.grid_y, header.grid_width, header.grid_height));
|
||||
sprite->setGridType(static_cast<doc::Grid::Type>(header.grid_type));
|
||||
|
||||
sprite->setUuidsForLayers((header.flags & ASE_FILE_FLAG_LAYER_WITH_UUID) ==
|
||||
ASE_FILE_FLAG_LAYER_WITH_UUID);
|
||||
@ -339,6 +340,7 @@ bool AsepriteDecoder::readHeader(AsepriteHeader* header)
|
||||
header->grid_y = (int16_t)read16();
|
||||
header->grid_width = read16();
|
||||
header->grid_height = read16();
|
||||
header->grid_type = read16();
|
||||
|
||||
if (header->depth != 8) // Transparent index only valid for indexed images
|
||||
header->transparent_index = 0;
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include "doc/grid.h"
|
||||
|
||||
#include "doc/algo.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/primitives.h"
|
||||
@ -19,6 +20,7 @@
|
||||
#include "gfx/size.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
@ -177,4 +179,60 @@ std::vector<gfx::Point> Grid::tilesInCanvasRegion(const gfx::Region& rgn) const
|
||||
return result;
|
||||
}
|
||||
|
||||
Grid::IsometricGuide::IsometricGuide(const gfx::Size& sz)
|
||||
{
|
||||
evenWidth = sz.w % 2 == 0;
|
||||
evenHeight = sz.h % 2 == 0;
|
||||
evenHalfWidth = ((sz.w / 2) % 2) == 0;
|
||||
evenHalfHeight = ((sz.h / 2) % 2) == 0;
|
||||
squareRatio = ABS(sz.w - sz.h) <= 1;
|
||||
oddSize = !evenWidth && evenHalfWidth && !evenHeight && evenHalfHeight;
|
||||
|
||||
// TODO: add 'share edges' checkbox to UI.
|
||||
// For testing the option, set this 'false' to 'true'
|
||||
shareEdges = !(false && !squareRatio && evenHeight);
|
||||
|
||||
start.x = 0;
|
||||
start.y = std::round(sz.h * 0.5);
|
||||
end.x = sz.w / 2;
|
||||
end.y = sz.h;
|
||||
|
||||
if (!squareRatio) {
|
||||
if (evenWidth) {
|
||||
end.x--;
|
||||
}
|
||||
else if (oddSize) {
|
||||
start.x--;
|
||||
end.x++;
|
||||
}
|
||||
}
|
||||
if (!shareEdges) {
|
||||
start.y++;
|
||||
}
|
||||
}
|
||||
|
||||
static void push_isometric_line_point(int x, int y, std::vector<gfx::Point>* data)
|
||||
{
|
||||
if (data->empty() || data->back().y != y) {
|
||||
data->push_back(gfx::Point(x * int(x >= 0), y));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<gfx::Point> Grid::getIsometricLine(const gfx::Size& sz)
|
||||
{
|
||||
std::vector<gfx::Point> result;
|
||||
const auto guide = IsometricGuide(sz);
|
||||
|
||||
// We use the line drawing algorithm to find the points
|
||||
// for a single pixel-precise line
|
||||
doc::algo_line_continuous_with_fix_for_line_brush(guide.start.x,
|
||||
guide.start.y,
|
||||
guide.end.x,
|
||||
guide.end.y,
|
||||
&result,
|
||||
(doc::AlgoPixel)&push_isometric_line_point);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace doc
|
||||
|
@ -17,6 +17,11 @@ namespace doc {
|
||||
|
||||
class Grid {
|
||||
public:
|
||||
enum class Type {
|
||||
Orthogonal = 0x00,
|
||||
Isometric = 0x01,
|
||||
|
||||
};
|
||||
explicit Grid(const gfx::Size& sz = gfx::Size(16, 16))
|
||||
: m_tileSize(sz)
|
||||
, m_origin(0, 0)
|
||||
@ -79,6 +84,32 @@ public:
|
||||
// Returns an array of tile positions that are touching the given region in the canvas
|
||||
std::vector<gfx::Point> tilesInCanvasRegion(const gfx::Region& rgn) const;
|
||||
|
||||
// Helper structure for calculating both isometric grid cells
|
||||
// as well as point snapping
|
||||
struct IsometricGuide {
|
||||
gfx::Point start;
|
||||
gfx::Point end;
|
||||
|
||||
union {
|
||||
struct {
|
||||
bool evenWidth : 1;
|
||||
bool evenHeight : 1;
|
||||
bool evenHalfWidth : 1;
|
||||
bool evenHalfHeight : 1;
|
||||
bool squareRatio : 1;
|
||||
bool oddSize : 1;
|
||||
bool shareEdges : 1;
|
||||
};
|
||||
int flags;
|
||||
};
|
||||
|
||||
IsometricGuide(const gfx::Size& sz);
|
||||
};
|
||||
|
||||
// Returns an array of coordinates used for calculating the
|
||||
// pixel-precise bounds of an isometric grid cell
|
||||
static std::vector<gfx::Point> getIsometricLine(const gfx::Size& sz);
|
||||
|
||||
private:
|
||||
gfx::Size m_tileSize;
|
||||
gfx::Point m_origin;
|
||||
|
@ -37,6 +37,7 @@
|
||||
namespace doc {
|
||||
|
||||
static gfx::Rect g_defaultGridBounds(0, 0, 16, 16);
|
||||
static Grid::Type g_defaultGridType = Grid::Type::Orthogonal;
|
||||
|
||||
// static
|
||||
gfx::Rect Sprite::DefaultGridBounds()
|
||||
@ -55,6 +56,18 @@ void Sprite::SetDefaultGridBounds(const gfx::Rect& defGridBounds)
|
||||
g_defaultGridBounds.h = 1;
|
||||
}
|
||||
|
||||
// static
|
||||
Grid::Type Sprite::DefaultGridType()
|
||||
{
|
||||
return g_defaultGridType;
|
||||
}
|
||||
|
||||
// static
|
||||
void Sprite::SetDefaultGridType(const Grid::Type type)
|
||||
{
|
||||
g_defaultGridType = type;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Constructors/Destructor
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "doc/color.h"
|
||||
#include "doc/fit_criteria.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/grid.h"
|
||||
#include "doc/image_buffer.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/image_spec.h"
|
||||
@ -122,6 +123,8 @@ public:
|
||||
// Defaults
|
||||
static gfx::Rect DefaultGridBounds();
|
||||
static void SetDefaultGridBounds(const gfx::Rect& defGridBounds);
|
||||
static Grid::Type DefaultGridType();
|
||||
static void SetDefaultGridType(Grid::Type type);
|
||||
static RgbMapAlgorithm DefaultRgbMapAlgorithm();
|
||||
static void SetDefaultRgbMapAlgorithm(const RgbMapAlgorithm mapAlgo);
|
||||
|
||||
@ -136,6 +139,9 @@ public:
|
||||
m_gridBounds.h = 1;
|
||||
}
|
||||
|
||||
void setGridType(const Grid::Type type) { m_gridType = type; }
|
||||
Grid::Type gridType() const { return m_gridType; }
|
||||
|
||||
virtual int getMemSize() const override;
|
||||
|
||||
////////////////////////////////////////
|
||||
@ -256,6 +262,7 @@ private:
|
||||
PalettesList m_palettes; // list of palettes
|
||||
LayerGroup* m_root; // main group of layers
|
||||
gfx::Rect m_gridBounds; // grid settings
|
||||
Grid::Type m_gridType;
|
||||
|
||||
// Current rgb map
|
||||
mutable std::unique_ptr<RgbMap> m_rgbMap;
|
||||
|
Loading…
x
Reference in New Issue
Block a user