Merge branch 'main' into beta

This commit is contained in:
David Capello 2025-02-26 10:26:09 -03:00
commit be6d2251aa
11 changed files with 143 additions and 65 deletions

View File

@ -10,6 +10,6 @@
// Increment this value if the scripting API is modified between two
// released Aseprite versions.
#define API_VERSION 31
#define API_VERSION 32
#endif

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2015-2018 David Capello
//
// This program is distributed under the terms of
@ -54,6 +54,7 @@
#include "app/ui/doc_view.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "base/gcd.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/mask.h"
@ -973,7 +974,13 @@ int Sprite_get_pixelRatio(lua_State* L)
int Sprite_set_pixelRatio(lua_State* L)
{
auto sprite = get_docobj<Sprite>(L, 1);
const gfx::Size pixelRatio = convert_args_into_size(L, 2);
gfx::Size pixelRatio = convert_args_into_size(L, 2);
if (pixelRatio.w < 1 || pixelRatio.h < 1)
return luaL_error(L, "invalid pixel ratio = %d:%d", pixelRatio.w, pixelRatio.h);
double gcd = base::gcd(double(pixelRatio.w), double(pixelRatio.h));
pixelRatio.w = int(double(pixelRatio.w) / gcd);
pixelRatio.h = int(double(pixelRatio.h) / gcd);
Tx tx(sprite);
tx(new cmd::SetPixelRatio(sprite, pixelRatio));
tx.commit();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2018 David Capello
// Copyright (C) 2016 Carlo Caputo
//
@ -23,13 +23,7 @@ namespace app { namespace thumb {
os::SurfaceRef get_cel_thumbnail(const doc::Cel* cel, const gfx::Size& fitInSize)
{
gfx::Size newSize;
if (cel->bounds().w > fitInSize.w || cel->bounds().h > fitInSize.h)
newSize = gfx::Rect(cel->bounds()).fitIn(gfx::Rect(fitInSize)).size();
else
newSize = cel->bounds().size();
gfx::Size newSize(gfx::Rect(cel->bounds()).fitIn(gfx::Rect(fitInSize)).size());
if (newSize.w < 1 || newSize.h < 1)
return nullptr;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -74,12 +74,6 @@ public:
};
class IntertwineAsLines : public Intertwine {
// It was introduced to know if joinStroke function
// was executed inmediatelly after a "Last" trace policy (i.e. after the
// user confirms a line draw while he is holding down the SHIFT key), so
// we have to ignore printing the first pixel of the line.
bool m_retainedTracePolicyLast = false;
// In freehand-like tools, on each mouse movement we draw only the
// line between the last two mouse points in the stroke (the
// complete stroke is not re-painted again), so we want to indicate
@ -90,22 +84,10 @@ class IntertwineAsLines : public Intertwine {
public:
bool snapByAngle() override { return true; }
void prepareIntertwine(ToolLoop* loop) override
{
m_retainedTracePolicyLast = false;
m_firstStroke = true;
}
void prepareIntertwine(ToolLoop* loop) override { m_firstStroke = true; }
void joinStroke(ToolLoop* loop, const Stroke& stroke) override
{
// Required for LineFreehand controller in the first stage, when
// we are drawing the line and the trace policy is "Last". Each
// new joinStroke() is like a fresh start. Without this fix, the
// first stage on LineFreehand will draw a "star" like pattern
// with lines from the first point to the last point.
if (loop->getTracePolicy() == TracePolicy::Last)
m_retainedTracePolicyLast = true;
if (stroke.size() == 0)
return;
else if (stroke.size() == 1) {
@ -129,9 +111,10 @@ public:
// when we use Shift+click in the Pencil tool to continue the
// old stroke).
// TODO useful only in the case when brush size = 1px
const int start =
(loop->getController()->isFreehand() && (m_retainedTracePolicyLast || !m_firstStroke) ? 1 :
0);
const int start = (loop->getController()->isFreehand() &&
((loop->getTracePolicy() == TracePolicy::Last) || !m_firstStroke) ?
1 :
0);
for (int c = start; c < pts.size(); ++c)
doPointshapeStrokePt(pts[c], loop);
@ -442,11 +425,6 @@ public:
};
class IntertwineAsPixelPerfect : public Intertwine {
// It was introduced to know if joinStroke function
// was executed inmediatelly after a "Last" trace policy (i.e. after the
// user confirms a line draw while he is holding down the SHIFT key), so
// we have to ignore printing the first pixel of the line.
bool m_retainedTracePolicyLast = false;
Stroke m_pts;
bool m_saveStrokeArea = false;
@ -483,7 +461,6 @@ public:
void prepareIntertwine(ToolLoop* loop) override
{
m_pts.reset();
m_retainedTracePolicyLast = false;
m_grid = m_dstGrid = m_celGrid = loop->getGrid();
m_restoredRegion.clear();
@ -509,10 +486,8 @@ public:
// new joinStroke() is like a fresh start. Without this fix, the
// first stage on LineFreehand will draw a "star" like pattern
// with lines from the first point to the last point.
if (loop->getTracePolicy() == TracePolicy::Last) {
m_retainedTracePolicyLast = true;
if (loop->getTracePolicy() == TracePolicy::Last)
m_pts.reset();
}
int thirdFromLastPt = 0, nextPt = 0;
@ -572,14 +547,17 @@ public:
// a joinStroke pass with a retained "Last" trace policy
// (i.e. the user confirms draw a line while he is holding
// the SHIFT key))
if (c == 0 && m_retainedTracePolicyLast)
if (c == 0 && loop->getTracePolicy() == TracePolicy::Last)
continue;
// For the last point we need to store the source image content at that
// point so we can restore it when erasing a point because of
// pixel-perfect. So we set the following flag to indicate this, and
// use it in doTransformPoint.
m_saveStrokeArea = (c == m_pts.size() - 1);
// The previous behavior isn't required for LineFreehand controller and
// the trace policy is "Last" (i.e. the user is drawing a line while
// he is holding the SHIFT key)
m_saveStrokeArea = ((c == m_pts.size() - 1) && loop->getTracePolicy() != TracePolicy::Last);
if (m_saveStrokeArea) {
clearPointshapeStrokePtAreas();
setLastPtIndex(c);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -782,7 +782,7 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g,
}
}
// Draw grids
// Draw Slices and Grids
{
gfx::Rect enclosingRect(m_padding.x + dx,
m_padding.y + dy,
@ -791,6 +791,11 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g,
IntersectClip clip(g, dest);
if (clip) {
// Draw slices
if (m_docPref.show.slices() && dx == m_proj.applyX(mainTilePosition().x) &&
dy == m_proj.applyY(mainTilePosition().y))
drawSlices(g);
// Draw the pixel grid
if ((m_proj.zoom().scale() > 2.0) && m_docPref.show.pixelGrid()) {
int alpha = m_docPref.pixelGrid.opacity();
@ -903,10 +908,6 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
enclosingRect = gfx::Rect(spriteRect.x, spriteRect.y, spriteRect.w * 3, spriteRect.h * 3);
}
// Draw slices
if (m_docPref.show.slices())
drawSlices(g);
// Symmetry mode
if (isActive() && (m_flags & Editor::kShowSymmetryLine) &&
Preferences::instance().symmetryMode.enabled()) {
@ -2450,6 +2451,10 @@ void Editor::onTiledModeChange()
spritePos += mainTilePosition();
screenPos = editorToScreen(spritePos);
auto lastPoint = document()->lastDrawingPoint();
lastPoint += mainTilePosition() - m_oldMainTilePos;
document()->setLastDrawingPoint(lastPoint);
centerInSpritePoint(spritePos);
}

View File

@ -2008,6 +2008,8 @@ void Timeline::onAfterLayerVisibilityChange(DocEvent& ev)
layer_t layerIdx = getLayerIndex(ev.layer());
if (layerIdx >= 0)
invalidateRect(getPartBounds(Hit(PART_ROW_EYE_ICON, layerIdx)).offset(origin()));
if (docPref().onionskin.active())
m_document->notifyGeneralUpdate();
}
void Timeline::onStateChanged(Editor* editor)

View File

@ -397,6 +397,43 @@ static void set_lum(double& r, double& g, double& b, double l)
clip_color(r, g, b);
}
static inline uint8_t get_imin_channel(const double r, const double g, const double b)
{
// We use '<=' to get min so as to catch two channels being equal
if (r <= g && r <= b)
return 0b001;
if (g <= r && g <= b)
return 0b010;
return 0b100;
}
static inline uint8_t get_imax_channel(const double r, const double g, const double b)
{
if (r > g && r > b)
return 0b001;
if (g > r && g > b)
return 0b010;
return 0b100;
}
static inline uint8_t get_imid_channel(uint8_t imin, uint8_t imax)
{
// Getting the remaining channel through exclusion guarantees that it is neither min nor max
return (~(imax | imin)) & 0b111;
}
static inline double& index_to_ref(double& r, double& g, double& b, uint8_t i)
{
if (i == 0b001)
return r;
if (i == 0b010)
return g;
return b;
}
// TODO replace this with a better impl (and test this, not sure if it's correct)
static void set_sat(double& r, double& g, double& b, double s)
{
@ -409,9 +446,15 @@ static void set_sat(double& r, double& g, double& b, double s)
((x) > (y) ? ((y) > (z) ? (y) : ((x) > (z) ? (z) : (x))) : \
((y) > (z) ? ((z) > (x) ? (z) : (x)) : (y)))
double& min = MIN(r, MIN(g, b));
double& mid = MID(r, g, b);
double& max = MAX(r, MAX(g, b));
// Fetch channel indices
const uint8_t imin = get_imin_channel(r, g, b);
const uint8_t imax = get_imax_channel(r, g, b);
const uint8_t imid = get_imid_channel(imin, imax);
// Map the indices for each channel to references
double& min = index_to_ref(r, g, b, imin);
double& max = index_to_ref(r, g, b, imax);
double& mid = index_to_ref(r, g, b, imid);
if (max > min) {
mid = ((mid - min) * s) / (max - min);

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2024 Igara Studio S.A.
// Copyright (c) 2024-2025 Igara Studio S.A.
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
@ -26,6 +26,7 @@ namespace doc {
template<class DstTraits, class SrcTraits>
class BlenderHelper {
BlendMode m_blendMode;
BlendFunc m_blendFunc;
color_t m_maskColor;
@ -36,6 +37,7 @@ public:
const BlendMode blendMode,
const bool newBlend)
{
m_blendMode = blendMode;
m_blendFunc = SrcTraits::get_blender(blendMode, newBlend);
m_maskColor = src->maskColor();
}
@ -44,6 +46,8 @@ public:
typename SrcTraits::pixel_t src,
int opacity)
{
if (m_blendMode == BlendMode::SRC)
return src;
if (src != m_maskColor)
return (*m_blendFunc)(dst, src, opacity);
else
@ -56,6 +60,7 @@ public:
template<>
class BlenderHelper<RgbTraits, GrayscaleTraits> {
BlendMode m_blendMode;
BlendFunc m_blendFunc;
color_t m_maskColor;
@ -66,6 +71,7 @@ public:
const BlendMode blendMode,
const bool newBlend)
{
m_blendMode = blendMode;
m_blendFunc = RgbTraits::get_blender(blendMode, newBlend);
m_maskColor = src->maskColor();
}
@ -74,6 +80,8 @@ public:
GrayscaleTraits::pixel_t src,
int opacity)
{
if (m_blendMode == BlendMode::SRC)
return src;
if (src != m_maskColor) {
int v = graya_getv(src);
return (*m_blendFunc)(dst, rgba(v, v, v, graya_geta(src)), opacity);

View File

@ -1,5 +1,5 @@
// Aseprite Render Library
// Copyright (c) 2020 Igara Studio S.A.
// Copyright (c) 2020-2025 Igara Studio S.A.
// Copyright (c) 2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -31,6 +31,20 @@ public:
void setPixelRatio(const doc::PixelRatio& pixelRatio) { m_pixelRatio = pixelRatio; }
void setZoom(const Zoom& zoom) { m_zoom = zoom; }
// To identify simplest composite scale up cases'
bool isSimpleScaleUpCase() const
{
return scaleX() >= 1.0 && scaleY() >= 1.0 && (m_pixelRatio.w == 1 || m_pixelRatio.w % 2 == 0) &&
(m_pixelRatio.h == 1 || m_pixelRatio.h % 2 == 0);
}
// To identify simplest composite scale down cases
bool isSimpleScaleDownCase() const
{
return scaleX() <= 1.0 && scaleY() <= 1.0 && std::fmod(1.0 / scaleX(), 1.0) == 0.0 &&
std::fmod(1.0 / scaleY(), 1.0) == 0.0;
}
double scaleX() const { return m_zoom.scale() * m_pixelRatio.w; }
double scaleY() const { return m_zoom.scale() * m_pixelRatio.h; }

View File

@ -1,5 +1,5 @@
// Aseprite Render Library
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -11,6 +11,7 @@
#include "render/render.h"
#include "base/gcd.h"
#include "doc/blend_internals.h"
#include "doc/blend_mode.h"
#include "doc/doc.h"
@ -497,19 +498,18 @@ CompositeImageFunc get_fastest_composition_path(const Projection& proj,
else if (finegrain || !proj.zoom().isSimpleZoomLevel()) {
return composite_image_general<DstTraits, SrcTraits>;
}
else if (proj.applyX(1) == 1 && proj.applyY(1) == 1) {
else if (proj.scaleX() == 1.0 && proj.scaleY() == 1.0) {
return composite_image_without_scale<DstTraits, SrcTraits>;
}
else if (proj.scaleX() >= 1.0 && proj.scaleY() >= 1.0) {
else if (proj.isSimpleScaleUpCase()) {
return composite_image_scale_up<DstTraits, SrcTraits>;
}
// Slower composite function for special cases with odd zoom and non-square pixel ratio
else if (((proj.removeX(1) > 1) && (proj.removeX(1) & 1)) ||
((proj.removeY(1) > 1) && (proj.removeY(1) & 1))) {
return composite_image_general<DstTraits, SrcTraits>;
else if (proj.isSimpleScaleDownCase()) {
return composite_image_scale_down<DstTraits, SrcTraits>;
}
else {
return composite_image_scale_down<DstTraits, SrcTraits>;
// Slower composite function for remaining cases not considered
return composite_image_general<DstTraits, SrcTraits>;
}
}

View File

@ -1,4 +1,4 @@
-- Copyright (C) 2019-2024 Igara Studio S.A.
-- Copyright (C) 2019-2025 Igara Studio S.A.
-- Copyright (C) 2018 David Capello
--
-- This file is released under the terms of the MIT license.
@ -352,6 +352,33 @@ do
end
-- Tests using Image:drawImage() with BlendMode::SRC + ColorMode.RGBA
do
local __ = Color(0, 0, 0, 0).rgbaPixel
local oo = Color(255, 255, 255, 0).rgbaPixel
local xx = Color(127, 127, 127, 127).rgbaPixel
local rr = Color(255, 0, 0).rgbaPixel
local BG = Color(127, 127, 127).rgbaPixel
local a_rgb = Image(3, 2, ColorMode.RGBA)
array_to_pixels({ __, rr, xx,
oo, rr, __ }, a_rgb)
local b_rgb = Image(4, 4, ColorMode.RGBA)
b_rgb:clear(BG)
b_rgb:drawImage(a_rgb, Point(0, 1), 255, BlendMode.SRC)
expect_img(b_rgb, { BG, BG, BG, BG,
__, rr, xx, BG,
oo, rr, __, BG,
BG, BG, BG, BG })
b_rgb:clear(BG)
b_rgb:drawImage(a_rgb, Point(-1, 2), 255, BlendMode.SRC)
expect_img(b_rgb, { BG, BG, BG, BG,
BG, BG, BG, BG,
rr, xx, BG, BG,
rr, __, BG, BG })
end
-- Tests using Image:drawImage() with opacity and blend modes
do
local spr = Sprite(3, 3, ColorMode.RGB)