mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-09 18:44:46 +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<>
|
||||
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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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());
|
||||
|
@ -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"))
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user