mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-28 16:11:35 +00:00
Merge branch 'main' into beta
This commit is contained in:
commit
be6d2251aa
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user