[lua] Add blend modes to Image:drawImage()

This commit is contained in:
Gaspar Capello 2023-03-23 15:38:55 -03:00 committed by David Capello
parent ff9f60a8be
commit 637632eafb
6 changed files with 156 additions and 13 deletions

View File

@ -135,6 +135,7 @@ inline app::script::BlendMode convert_to(const os::BlendMode& from) {
template<>
inline doc::BlendMode convert_to(const app::script::BlendMode& 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::PLUS: return doc::BlendMode::ADDITION;
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;
// Default value
case app::script::BlendMode::CLEAR:
case app::script::BlendMode::SRC:
case app::script::BlendMode::DST:
case app::script::BlendMode::DST_OVER:
case app::script::BlendMode::SRC_IN:
@ -175,6 +175,7 @@ inline doc::BlendMode convert_to(const app::script::BlendMode& from) {
template<>
inline app::script::BlendMode convert_to(const doc::BlendMode& 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::MULTIPLY: return app::script::BlendMode::MULTIPLY;
case doc::BlendMode::SCREEN: return app::script::BlendMode::SCREEN;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2015-2018 David Capello
//
// This program is distributed under the terms of
@ -15,6 +15,7 @@
#include "app/context.h"
#include "app/doc.h"
#include "app/file/file.h"
#include "app/script/blend_mode.h"
#include "app/script/docobj.h"
#include "app/script/engine.h"
#include "app/script/luacpp.h"
@ -41,6 +42,8 @@ namespace script {
namespace {
static ImageBufferPtr buf; // TODO non-thread safe
struct ImageObj {
doc::ObjectId imageId = 0;
doc::ObjectId celId = 0;
@ -247,23 +250,41 @@ int Image_drawImage(lua_State* L)
auto obj = get_obj<ImageObj>(L, 1);
auto sprite = get_obj<ImageObj>(L, 2);
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);
const Image* src = sprite->image(L);
// If the destination image is not related to a sprite, we just draw
// the source image without undo information.
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 {
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()
// but we need something that does the render and compares
// the minimal modified area.
Tx tx;
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)));
tx.commit();
}

View File

@ -1,5 +1,5 @@
// 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
//
// 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()));
}
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)
{
for (const gfx::Rect& rc : rgn)

View File

@ -1,5 +1,5 @@
// 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
//
// This file is released under the terms of the MIT license.
@ -10,6 +10,7 @@
#pragma once
#include "base/ints.h"
#include "doc/blend_mode.h"
#include "doc/color.h"
#include "doc/image_buffer.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, 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);
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());

View File

@ -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.
-- 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 celImage2 = Image(fr2.frame.w, fr2.frame.h, sheet2.colorMode)
celImage1:drawSprite(sheet1, 1, -fr1.frame.x, -fr1.frame.y)
celImage2:drawSprite(sheet2, 1, -fr2.frame.x, -fr2.frame.y)
celImage1:drawSprite(sheet1, 1, Point(-fr1.frame.x, -fr1.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 frImage2 = Image(fr2.sourceSize.w, fr2.sourceSize.h, sheet2.colorMode)
frImage1:drawImage(celImage1, fr1.spriteSourceSize.x, fr1.spriteSourceSize.y)
frImage2:drawImage(celImage2, fr2.spriteSourceSize.x, fr2.spriteSourceSize.y)
frImage1:drawImage(celImage1, Point(fr1.spriteSourceSize.x, fr1.spriteSourceSize.y), 255, BlendMode.SRC)
frImage2:drawImage(celImage2, Point(fr2.spriteSourceSize.x, fr2.spriteSourceSize.y), 255, BlendMode.SRC)
-- To debug this function
--frImage1:saveAs(replace_filename(file1, k .. "-fr1.png"))

View File

@ -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
--
-- This file is released under the terms of the MIT license.
@ -304,3 +304,81 @@ do
test(app.activeCel.image)
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