From 637632eafbfb052ea3b6e18b6764c75e88c00e9e Mon Sep 17 00:00:00 2001 From: Gaspar Capello Date: Thu, 23 Mar 2023 15:38:55 -0300 Subject: [PATCH] [lua] Add blend modes to Image:drawImage() --- src/app/script/blend_mode.h | 3 +- src/app/script/image_class.cpp | 29 +++++++-- src/doc/primitives.cpp | 42 ++++++++++++- src/doc/primitives.h | 5 +- tests/scripts/compare_sprite_sheets.lua | 10 ++-- tests/scripts/image.lua | 80 ++++++++++++++++++++++++- 6 files changed, 156 insertions(+), 13 deletions(-) diff --git a/src/app/script/blend_mode.h b/src/app/script/blend_mode.h index 0a2688f27..826eef2a1 100644 --- a/src/app/script/blend_mode.h +++ b/src/app/script/blend_mode.h @@ -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; diff --git a/src/app/script/image_class.cpp b/src/app/script/image_class.cpp index cf856b7a0..b2d716a38 100644 --- a/src/app/script/image_class.cpp +++ b/src/app/script/image_class.cpp @@ -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(L, 1); auto sprite = get_obj(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( + 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(); } diff --git a/src/doc/primitives.cpp b/src/doc/primitives.cpp index b5a07f94a..9e7387227 100644 --- a/src/doc/primitives.cpp +++ b/src/doc/primitives.cpp @@ -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 +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 dstBits(dst); + const LockImageBits 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(dst, src, x, y, opacity, blender); + case IMAGE_GRAYSCALE: + blender = get_graya_blender(blendMode, true); + return blend_image_templ(dst, src, x, y, opacity, blender); + case IMAGE_INDEXED: + blender = get_indexed_blender(blendMode, true); + return blend_image_templ(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) diff --git a/src/doc/primitives.h b/src/doc/primitives.h index 2a31a93be..fa1a64cb5 100644 --- a/src/doc/primitives.h +++ b/src/doc/primitives.h @@ -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()); diff --git a/tests/scripts/compare_sprite_sheets.lua b/tests/scripts/compare_sprite_sheets.lua index d786ceb7b..713c43899 100644 --- a/tests/scripts/compare_sprite_sheets.lua +++ b/tests/scripts/compare_sprite_sheets.lua @@ -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")) diff --git a/tests/scripts/image.lua b/tests/scripts/image.lua index 86875a0d2..aafd48172 100644 --- a/tests/scripts/image.lua +++ b/tests/scripts/image.lua @@ -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