mirror of
https://github.com/aseprite/aseprite.git
synced 2024-09-03 13:39:02 +00:00
[lua] Add blend modes to Image:drawImage()
This commit is contained in:
parent
ff9f60a8be
commit
637632eafb
@ -135,6 +135,7 @@ inline app::script::BlendMode convert_to(const os::BlendMode& from) {
|
|||||||
template<>
|
template<>
|
||||||
inline doc::BlendMode convert_to(const app::script::BlendMode& from) {
|
inline doc::BlendMode convert_to(const app::script::BlendMode& from) {
|
||||||
switch (from) {
|
switch (from) {
|
||||||
|
case app::script::BlendMode::SRC: return doc::BlendMode::SRC;
|
||||||
case app::script::BlendMode::SRC_OVER: return doc::BlendMode::NORMAL;
|
case app::script::BlendMode::SRC_OVER: return doc::BlendMode::NORMAL;
|
||||||
case app::script::BlendMode::PLUS: return doc::BlendMode::ADDITION;
|
case app::script::BlendMode::PLUS: return doc::BlendMode::ADDITION;
|
||||||
case app::script::BlendMode::MULTIPLY: return doc::BlendMode::MULTIPLY;
|
case app::script::BlendMode::MULTIPLY: return doc::BlendMode::MULTIPLY;
|
||||||
@ -157,7 +158,6 @@ inline doc::BlendMode convert_to(const app::script::BlendMode& from) {
|
|||||||
case app::script::BlendMode::DIVIDE: return doc::BlendMode::DIVIDE;
|
case app::script::BlendMode::DIVIDE: return doc::BlendMode::DIVIDE;
|
||||||
// Default value
|
// Default value
|
||||||
case app::script::BlendMode::CLEAR:
|
case app::script::BlendMode::CLEAR:
|
||||||
case app::script::BlendMode::SRC:
|
|
||||||
case app::script::BlendMode::DST:
|
case app::script::BlendMode::DST:
|
||||||
case app::script::BlendMode::DST_OVER:
|
case app::script::BlendMode::DST_OVER:
|
||||||
case app::script::BlendMode::SRC_IN:
|
case app::script::BlendMode::SRC_IN:
|
||||||
@ -175,6 +175,7 @@ inline doc::BlendMode convert_to(const app::script::BlendMode& from) {
|
|||||||
template<>
|
template<>
|
||||||
inline app::script::BlendMode convert_to(const doc::BlendMode& from) {
|
inline app::script::BlendMode convert_to(const doc::BlendMode& from) {
|
||||||
switch (from) {
|
switch (from) {
|
||||||
|
case doc::BlendMode::SRC: return app::script::BlendMode::SRC;
|
||||||
case doc::BlendMode::NORMAL: return app::script::BlendMode::SRC_OVER;
|
case doc::BlendMode::NORMAL: return app::script::BlendMode::SRC_OVER;
|
||||||
case doc::BlendMode::MULTIPLY: return app::script::BlendMode::MULTIPLY;
|
case doc::BlendMode::MULTIPLY: return app::script::BlendMode::MULTIPLY;
|
||||||
case doc::BlendMode::SCREEN: return app::script::BlendMode::SCREEN;
|
case doc::BlendMode::SCREEN: return app::script::BlendMode::SCREEN;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2022 Igara Studio S.A.
|
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||||
// Copyright (C) 2015-2018 David Capello
|
// Copyright (C) 2015-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -15,6 +15,7 @@
|
|||||||
#include "app/context.h"
|
#include "app/context.h"
|
||||||
#include "app/doc.h"
|
#include "app/doc.h"
|
||||||
#include "app/file/file.h"
|
#include "app/file/file.h"
|
||||||
|
#include "app/script/blend_mode.h"
|
||||||
#include "app/script/docobj.h"
|
#include "app/script/docobj.h"
|
||||||
#include "app/script/engine.h"
|
#include "app/script/engine.h"
|
||||||
#include "app/script/luacpp.h"
|
#include "app/script/luacpp.h"
|
||||||
@ -41,6 +42,8 @@ namespace script {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
static ImageBufferPtr buf; // TODO non-thread safe
|
||||||
|
|
||||||
struct ImageObj {
|
struct ImageObj {
|
||||||
doc::ObjectId imageId = 0;
|
doc::ObjectId imageId = 0;
|
||||||
doc::ObjectId celId = 0;
|
doc::ObjectId celId = 0;
|
||||||
@ -247,23 +250,41 @@ int Image_drawImage(lua_State* L)
|
|||||||
auto obj = get_obj<ImageObj>(L, 1);
|
auto obj = get_obj<ImageObj>(L, 1);
|
||||||
auto sprite = get_obj<ImageObj>(L, 2);
|
auto sprite = get_obj<ImageObj>(L, 2);
|
||||||
gfx::Point pos = convert_args_into_point(L, 3);
|
gfx::Point pos = convert_args_into_point(L, 3);
|
||||||
|
|
||||||
|
int opacity = 255;
|
||||||
|
if (lua_isinteger(L, 4))
|
||||||
|
opacity = std::clamp(int(lua_tointeger(L, 4)), 0, 255);
|
||||||
|
|
||||||
|
doc::BlendMode blendMode = doc::BlendMode::NORMAL;
|
||||||
|
if (lua_isinteger(L, 5)) {
|
||||||
|
blendMode = base::convert_to<doc::BlendMode>(
|
||||||
|
app::script::BlendMode(lua_tointeger(L, 5)));
|
||||||
|
}
|
||||||
|
|
||||||
Image* dst = obj->image(L);
|
Image* dst = obj->image(L);
|
||||||
const Image* src = sprite->image(L);
|
const Image* src = sprite->image(L);
|
||||||
|
|
||||||
// If the destination image is not related to a sprite, we just draw
|
// If the destination image is not related to a sprite, we just draw
|
||||||
// the source image without undo information.
|
// the source image without undo information.
|
||||||
if (obj->cel(L) == nullptr) {
|
if (obj->cel(L) == nullptr) {
|
||||||
doc::copy_image(dst, src, pos.x, pos.y);
|
doc::blend_image(dst, src,
|
||||||
|
pos.x, pos.y,
|
||||||
|
opacity, blendMode);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
gfx::Rect bounds(0, 0, src->size().w, src->size().h);
|
gfx::Rect bounds(0, 0, src->size().w, src->size().h);
|
||||||
|
buf.reset(new doc::ImageBuffer);
|
||||||
|
ImageRef tmp_src(
|
||||||
|
doc::crop_image(dst,
|
||||||
|
gfx::Rect(pos.x, pos.y, src->size().w, src->size().h),
|
||||||
|
0, buf));
|
||||||
|
doc::blend_image(tmp_src.get(), src, 0, 0, opacity, blendMode);
|
||||||
// TODO Use something similar to doc::algorithm::shrink_bounds2()
|
// TODO Use something similar to doc::algorithm::shrink_bounds2()
|
||||||
// but we need something that does the render and compares
|
// but we need something that does the render and compares
|
||||||
// the minimal modified area.
|
// the minimal modified area.
|
||||||
Tx tx;
|
Tx tx;
|
||||||
tx(new cmd::CopyRegion(
|
tx(new cmd::CopyRegion(
|
||||||
dst, src, gfx::Region(bounds),
|
dst, tmp_src.get(), gfx::Region(bounds),
|
||||||
gfx::Point(pos.x + bounds.x, pos.y + bounds.y)));
|
gfx::Point(pos.x + bounds.x, pos.y + bounds.y)));
|
||||||
tx.commit();
|
tx.commit();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite Document Library
|
// Aseprite Document Library
|
||||||
// Copyright (c) 2018-2021 Igara Studio S.A.
|
// Copyright (c) 2018-2023 Igara Studio S.A.
|
||||||
// Copyright (c) 2001-2016 David Capello
|
// Copyright (c) 2001-2016 David Capello
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// This file is released under the terms of the MIT license.
|
||||||
@ -67,6 +67,46 @@ void copy_image(Image* dst, const Image* src, int x, int y)
|
|||||||
dst->copy(src, gfx::Clip(x, y, 0, 0, src->width(), src->height()));
|
dst->copy(src, gfx::Clip(x, y, 0, 0, src->width(), src->height()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename ImageTraits>
|
||||||
|
void blend_image_templ(Image* dst,
|
||||||
|
const Image* src,
|
||||||
|
const int x, const int y,
|
||||||
|
const int opacity,
|
||||||
|
BlendFunc& blender)
|
||||||
|
{
|
||||||
|
gfx::Clip area = gfx::Clip(x, y, 0, 0, src->width(), src->height());
|
||||||
|
if (!area.clip(dst->width(), dst->height(), src->width(), src->height()))
|
||||||
|
return;
|
||||||
|
LockImageBits<ImageTraits> dstBits(dst);
|
||||||
|
const LockImageBits<ImageTraits> srcBits(src);
|
||||||
|
auto dstIt = dstBits.begin_area(area.dstBounds());
|
||||||
|
auto srcIt = srcBits.begin_area(area.srcBounds());
|
||||||
|
auto dstEnd = dstBits.end_area(area.dstBounds());
|
||||||
|
for (; dstIt < dstEnd; ++dstIt, ++srcIt)
|
||||||
|
*dstIt = blender(*dstIt, *srcIt, opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void blend_image(Image* dst, const Image* src, const int x, const int y,
|
||||||
|
const int opacity,
|
||||||
|
const doc::BlendMode blendMode)
|
||||||
|
{
|
||||||
|
ASSERT(dst->pixelFormat() == src->pixelFormat());
|
||||||
|
BlendFunc blender;
|
||||||
|
switch (src->pixelFormat()) {
|
||||||
|
case IMAGE_RGB:
|
||||||
|
blender = get_rgba_blender(blendMode, true);
|
||||||
|
return blend_image_templ<RgbTraits>(dst, src, x, y, opacity, blender);
|
||||||
|
case IMAGE_GRAYSCALE:
|
||||||
|
blender = get_graya_blender(blendMode, true);
|
||||||
|
return blend_image_templ<GrayscaleTraits>(dst, src, x, y, opacity, blender);
|
||||||
|
case IMAGE_INDEXED:
|
||||||
|
blender = get_indexed_blender(blendMode, true);
|
||||||
|
return blend_image_templ<IndexedTraits>(dst, src, x, y, opacity, blender);
|
||||||
|
case IMAGE_TILEMAP:
|
||||||
|
return copy_image(dst, src, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void copy_image(Image* dst, const Image* src, const gfx::Region& rgn)
|
void copy_image(Image* dst, const Image* src, const gfx::Region& rgn)
|
||||||
{
|
{
|
||||||
for (const gfx::Rect& rc : rgn)
|
for (const gfx::Rect& rc : rgn)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite Document Library
|
// Aseprite Document Library
|
||||||
// Copyright (c) 2018-2021 Igara Studio S.A.
|
// Copyright (c) 2018-2023 Igara Studio S.A.
|
||||||
// Copyright (c) 2001-2016 David Capello
|
// Copyright (c) 2001-2016 David Capello
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// This file is released under the terms of the MIT license.
|
||||||
@ -10,6 +10,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "base/ints.h"
|
#include "base/ints.h"
|
||||||
|
#include "doc/blend_mode.h"
|
||||||
#include "doc/color.h"
|
#include "doc/color.h"
|
||||||
#include "doc/image_buffer.h"
|
#include "doc/image_buffer.h"
|
||||||
#include "gfx/fwd.h"
|
#include "gfx/fwd.h"
|
||||||
@ -27,6 +28,8 @@ namespace doc {
|
|||||||
|
|
||||||
void copy_image(Image* dst, const Image* src);
|
void copy_image(Image* dst, const Image* src);
|
||||||
void copy_image(Image* dst, const Image* src, int x, int y);
|
void copy_image(Image* dst, const Image* src, int x, int y);
|
||||||
|
void blend_image(Image* dst, const Image* src, int x, int y,
|
||||||
|
const int opacity, const doc::BlendMode blendMode);
|
||||||
void copy_image(Image* dst, const Image* src, const gfx::Region& rgn);
|
void copy_image(Image* dst, const Image* src, const gfx::Region& rgn);
|
||||||
Image* crop_image(const Image* image, int x, int y, int w, int h, color_t bg, const ImageBufferPtr& buffer = ImageBufferPtr());
|
Image* crop_image(const Image* image, int x, int y, int w, int h, color_t bg, const ImageBufferPtr& buffer = ImageBufferPtr());
|
||||||
Image* crop_image(const Image* image, const gfx::Rect& bounds, color_t bg, const ImageBufferPtr& buffer = ImageBufferPtr());
|
Image* crop_image(const Image* image, const gfx::Rect& bounds, color_t bg, const ImageBufferPtr& buffer = ImageBufferPtr());
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
-- Copyright (C) 2019 Igara Studio S.A.
|
-- Copyright (C) 2019-2023 Igara Studio S.A.
|
||||||
--
|
--
|
||||||
-- This file is released under the terms of the MIT license.
|
-- This file is released under the terms of the MIT license.
|
||||||
-- Read LICENSE.txt for more information.
|
-- Read LICENSE.txt for more information.
|
||||||
@ -43,13 +43,13 @@ for k,v in pairs(data1.frames) do
|
|||||||
|
|
||||||
local celImage1 = Image(fr1.frame.w, fr1.frame.h, sheet1.colorMode)
|
local celImage1 = Image(fr1.frame.w, fr1.frame.h, sheet1.colorMode)
|
||||||
local celImage2 = Image(fr2.frame.w, fr2.frame.h, sheet2.colorMode)
|
local celImage2 = Image(fr2.frame.w, fr2.frame.h, sheet2.colorMode)
|
||||||
celImage1:drawSprite(sheet1, 1, -fr1.frame.x, -fr1.frame.y)
|
celImage1:drawSprite(sheet1, 1, Point(-fr1.frame.x, -fr1.frame.y))
|
||||||
celImage2:drawSprite(sheet2, 1, -fr2.frame.x, -fr2.frame.y)
|
celImage2:drawSprite(sheet2, 1, Point(-fr2.frame.x, -fr2.frame.y))
|
||||||
|
|
||||||
local frImage1 = Image(fr1.sourceSize.w, fr1.sourceSize.h, sheet1.colorMode)
|
local frImage1 = Image(fr1.sourceSize.w, fr1.sourceSize.h, sheet1.colorMode)
|
||||||
local frImage2 = Image(fr2.sourceSize.w, fr2.sourceSize.h, sheet2.colorMode)
|
local frImage2 = Image(fr2.sourceSize.w, fr2.sourceSize.h, sheet2.colorMode)
|
||||||
frImage1:drawImage(celImage1, fr1.spriteSourceSize.x, fr1.spriteSourceSize.y)
|
frImage1:drawImage(celImage1, Point(fr1.spriteSourceSize.x, fr1.spriteSourceSize.y), 255, BlendMode.SRC)
|
||||||
frImage2:drawImage(celImage2, fr2.spriteSourceSize.x, fr2.spriteSourceSize.y)
|
frImage2:drawImage(celImage2, Point(fr2.spriteSourceSize.x, fr2.spriteSourceSize.y), 255, BlendMode.SRC)
|
||||||
|
|
||||||
-- To debug this function
|
-- To debug this function
|
||||||
--frImage1:saveAs(replace_filename(file1, k .. "-fr1.png"))
|
--frImage1:saveAs(replace_filename(file1, k .. "-fr1.png"))
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
-- Copyright (C) 2019-2022 Igara Studio S.A.
|
-- Copyright (C) 2019-2023 Igara Studio S.A.
|
||||||
-- Copyright (C) 2018 David Capello
|
-- Copyright (C) 2018 David Capello
|
||||||
--
|
--
|
||||||
-- This file is released under the terms of the MIT license.
|
-- This file is released under the terms of the MIT license.
|
||||||
@ -304,3 +304,81 @@ do
|
|||||||
test(app.activeCel.image)
|
test(app.activeCel.image)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Tests using Image:drawImage() with opacity and blend modes
|
||||||
|
do
|
||||||
|
local spr = Sprite(3, 3, ColorMode.RGB)
|
||||||
|
local back = Image(3, 3)
|
||||||
|
local r_255 = Color(255, 0, 0).rgbaPixel
|
||||||
|
local g_255 = Color(0, 255, 0).rgbaPixel
|
||||||
|
local g_127 = Color(0, 255, 0, 127).rgbaPixel
|
||||||
|
local g_064 = Color(0, 255, 0, 64).rgbaPixel
|
||||||
|
local r_g127 = Color(128, 127, 0, 255).rgbaPixel -- result of g_127 over r_255 (NORMAL blend mode)
|
||||||
|
local r_g064 = Color(191, 64, 0, 255).rgbaPixel -- result of g_064 over r_255 (NORMAL blend mode)
|
||||||
|
local mask = Color(0, 0, 0, 0).rgbaPixel
|
||||||
|
back:clear(r_255)
|
||||||
|
spr:newCel(spr.layers[1], 1, back, Point(0, 0))
|
||||||
|
|
||||||
|
local image = Image(2, 2)
|
||||||
|
image:drawPixel(0, 0, g_127)
|
||||||
|
image:drawPixel(1, 0, g_064)
|
||||||
|
image:drawPixel(0, 1, mask)
|
||||||
|
image:drawPixel(1, 1, g_255)
|
||||||
|
|
||||||
|
local c = spr.layers[1]:cel(1).image
|
||||||
|
|
||||||
|
expect_img(c, { r_255, r_255, r_255,
|
||||||
|
r_255, r_255, r_255,
|
||||||
|
r_255, r_255, r_255 })
|
||||||
|
|
||||||
|
spr.layers[1]:cel(1).image:drawImage(image, Point(1, 1), 255, BlendMode.NORMAL)
|
||||||
|
|
||||||
|
c = spr.layers[1]:cel(1).image
|
||||||
|
|
||||||
|
expect_img(c, { r_255, r_255, r_255,
|
||||||
|
r_255, r_g127, r_g064,
|
||||||
|
r_255, r_255, g_255 })
|
||||||
|
undo()
|
||||||
|
c = spr.layers[1]:cel(1).image
|
||||||
|
expect_img(c, { r_255, r_255, r_255,
|
||||||
|
r_255, r_255, r_255,
|
||||||
|
r_255, r_255, r_255 })
|
||||||
|
|
||||||
|
-- Image:drawImage() with 50% opacity
|
||||||
|
spr.layers[1]:cel(1).image:drawImage(image, Point(1, 1), 128, BlendMode.NORMAL)
|
||||||
|
local r_g032 = Color(223, 32, 0, 255).rgbaPixel -- result of g_064 @oopacity=128 over r_255 (NORMAL blend mode)
|
||||||
|
local r_g128 = Color(127, 128, 0, 255).rgbaPixel -- result of g_255 o@oopacity=128 ver r_255 (NORMAL blend mode)
|
||||||
|
c = spr.layers[1]:cel(1).image
|
||||||
|
|
||||||
|
expect_img(c, { r_255, r_255, r_255,
|
||||||
|
r_255, r_g064, r_g032,
|
||||||
|
r_255, r_255, r_g128 })
|
||||||
|
undo()
|
||||||
|
c = spr.layers[1]:cel(1).image
|
||||||
|
expect_img(c, { r_255, r_255, r_255,
|
||||||
|
r_255, r_255, r_255,
|
||||||
|
r_255, r_255, r_255 })
|
||||||
|
|
||||||
|
-- Image:drawImage() without undo information (destination image
|
||||||
|
-- not related with a cel on a sprite)
|
||||||
|
local back2 = Image(3, 3, ColorMode.RGB)
|
||||||
|
back2:clear(r_255)
|
||||||
|
local image2 = Image(2, 2, ColorMode.RGB)
|
||||||
|
image2:drawPixel(0, 0, g_127)
|
||||||
|
image2:drawPixel(1, 0, g_064)
|
||||||
|
image2:drawPixel(0, 1, mask)
|
||||||
|
image2:drawPixel(1, 1, g_255)
|
||||||
|
|
||||||
|
expect_img(back2, { r_255, r_255, r_255,
|
||||||
|
r_255, r_255, r_255,
|
||||||
|
r_255, r_255, r_255 })
|
||||||
|
|
||||||
|
back2:drawImage(image2, Point(1, 1), 255, BlendMode.NORMAL)
|
||||||
|
expect_img(back2, { r_255, r_255, r_255,
|
||||||
|
r_255, r_g127, r_g064,
|
||||||
|
r_255, r_255, g_255 })
|
||||||
|
undo()
|
||||||
|
expect_img(back2, { r_255, r_255, r_255,
|
||||||
|
r_255, r_g127, r_g064,
|
||||||
|
r_255, r_255, g_255 })
|
||||||
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user