diff --git a/laf b/laf index c23da3b05..26994fe6c 160000 --- a/laf +++ b/laf @@ -1 +1 @@ -Subproject commit c23da3b0516540036ee501e6a35de9afc0356ab4 +Subproject commit 26994fe6c1210e0989eaddd4b2bdc00422e1ac8a diff --git a/src/app/modules/palettes.cpp b/src/app/modules/palettes.cpp index f15d3429a..b3296eafb 100644 --- a/src/app/modules/palettes.cpp +++ b/src/app/modules/palettes.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2022 Igara Studio S.A. +// Copyright (C) 2022-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -12,9 +12,11 @@ #include "app/modules/palettes.h" #include "app/app.h" +#include "app/context.h" #include "app/extensions.h" #include "app/file/palette_file.h" #include "app/resource_finder.h" +#include "app/site.h" #include "base/fs.h" #include "doc/image.h" #include "doc/palette.h" @@ -125,6 +127,15 @@ void load_default_palette() // function and use the active Site palette. Palette* get_current_palette() { +#if !ENABLE_UI + if (auto* app = App::instance()) { + if (auto* ctx = app->context()) { + Site site = ctx->activeSite(); + if (site.sprite()) + return site.palette(); + } + } +#endif return ase_current_palette; } diff --git a/src/app/script/image_class.cpp b/src/app/script/image_class.cpp index 651d847fa..f6e6be0ca 100644 --- a/src/app/script/image_class.cpp +++ b/src/app/script/image_class.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2023 Igara Studio S.A. +// Copyright (C) 2018-2024 Igara Studio S.A. // Copyright (C) 2015-2018 David Capello // // This program is distributed under the terms of @@ -16,6 +16,7 @@ #include "app/context.h" #include "app/doc.h" #include "app/file/file.h" +#include "app/modules/palettes.h" #include "app/script/blend_mode.h" #include "app/script/docobj.h" #include "app/script/engine.h" @@ -29,6 +30,7 @@ #include "doc/algorithm/flip_image.h" #include "doc/algorithm/flip_type.h" #include "doc/algorithm/shrink_bounds.h" +#include "doc/blend_image.h" #include "doc/cel.h" #include "doc/image.h" #include "doc/image_ref.h" @@ -312,13 +314,18 @@ int Image_drawImage(lua_State* L) const Image* src = sprite->image(L); if (auto cel = obj->cel(L)) { - 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); + gfx::Rect bounds(src->size()); + + // Create the ImageBuffer only when it doesn't exist so we can + // cache the allocated buffer. + if (!buf) + buf = std::make_shared(); + + ImageRef tmp_src(doc::crop_image(dst, gfx::Rect(pos, src->size()), 0, buf)); + doc::blend_image(tmp_src.get(), src, + gfx::Clip(src->size()), + cel->sprite()->palette(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. @@ -332,7 +339,8 @@ int Image_drawImage(lua_State* L) // the source image without undo information. else { doc::blend_image(dst, src, - pos.x, pos.y, + gfx::Clip(pos, src->bounds()), + get_current_palette(), opacity, blendMode); } return 0; diff --git a/src/doc/CMakeLists.txt b/src/doc/CMakeLists.txt index caaeb9232..621dfc7db 100644 --- a/src/doc/CMakeLists.txt +++ b/src/doc/CMakeLists.txt @@ -1,5 +1,5 @@ # Aseprite Document Library -# Copyright (C) 2019-2023 Igara Studio S.A. +# Copyright (C) 2019-2024 Igara Studio S.A. # Copyright (C) 2001-2018 David Capello if(WIN32) @@ -22,6 +22,7 @@ add_library(doc-lib algorithm/stroke_selection.cpp anidir.cpp blend_funcs.cpp + blend_image.cpp blend_mode.cpp brush.cpp brush_type.cpp diff --git a/src/doc/blend_image.cpp b/src/doc/blend_image.cpp new file mode 100644 index 000000000..aa967e78d --- /dev/null +++ b/src/doc/blend_image.cpp @@ -0,0 +1,84 @@ +// Aseprite Document Library +// Copyright (c) 2024 Igara Studio S.A. +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "doc/blend_image.h" + +#include "doc/blend_internals.h" +#include "doc/image_impl.h" + +namespace doc { + +template +void blend_image_templ(Image* dst, + const Image* src, + const gfx::Clip& area, + const Palette* pal, + const int opacity, + const BlendMode blendMode) +{ + if constexpr (DstTraits::color_mode == ColorMode::INDEXED || + SrcTraits::color_mode == ColorMode::INDEXED) { + ASSERT(pal != nullptr); + if (pal == nullptr) + return; + } + BlenderHelper blender(dst, src, pal, blendMode, true); + 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, + gfx::Clip area, + const Palette* pal, + const int opacity, + const BlendMode blendMode) +{ + if (!area.clip(dst->width(), dst->height(), src->width(), src->height())) + return; + + switch (dst->pixelFormat()) { + + case IMAGE_RGB: + switch (src->pixelFormat()) { + case IMAGE_RGB: return blend_image_templ(dst, src, area, pal, opacity, blendMode); + case IMAGE_GRAYSCALE: return blend_image_templ(dst, src, area, pal, opacity, blendMode); + case IMAGE_INDEXED: return blend_image_templ(dst, src, area, pal, opacity, blendMode); + } + break; + + case IMAGE_GRAYSCALE: + switch (src->pixelFormat()) { + case IMAGE_RGB: return blend_image_templ(dst, src, area, pal, opacity, blendMode); + case IMAGE_GRAYSCALE: return blend_image_templ(dst, src, area, pal, opacity, blendMode); + case IMAGE_INDEXED: return blend_image_templ(dst, src, area, pal, opacity, blendMode); + } + break; + + case IMAGE_INDEXED: + switch (src->pixelFormat()) { + case IMAGE_RGB: return blend_image_templ(dst, src, area, pal, opacity, blendMode); + case IMAGE_GRAYSCALE: return blend_image_templ(dst, src, area, pal, opacity, blendMode); + case IMAGE_INDEXED: return blend_image_templ(dst, src, area, pal, opacity, blendMode); + } + break; + + case IMAGE_TILEMAP: + return dst->copy(src, area); + } +} + +} // namespace doc diff --git a/src/doc/blend_image.h b/src/doc/blend_image.h new file mode 100644 index 000000000..a0badff65 --- /dev/null +++ b/src/doc/blend_image.h @@ -0,0 +1,30 @@ +// Aseprite Document Library +// Copyright (C) 2019-2024 Igara Studio S.A. +// Copyright (C) 2001-2018 David Capello +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. + +#ifndef DOC_BLEND_IMAGE_H_INCLUDED +#define DOC_BLEND_IMAGE_H_INCLUDED +#pragma once + +#include "doc/blend_mode.h" +#include "gfx/fwd.h" + +namespace doc { + class Image; + class Palette; + + void blend_image(Image* dst, + const Image* src, + gfx::Clip area, + // For indexed color mode + const Palette* pal, + // For grayscale/RGB color modes + const int opacity, + const doc::BlendMode blendMode); + +} // namespace doc + +#endif diff --git a/src/doc/blend_internals.h b/src/doc/blend_internals.h index 435526cfe..8b9db7124 100644 --- a/src/doc/blend_internals.h +++ b/src/doc/blend_internals.h @@ -1,4 +1,5 @@ // Aseprite Document Library +// Copyright (c) 2024 Igara Studio S.A. // Copyright (c) 2001-2015 David Capello // // This file is released under the terms of the MIT license. @@ -14,4 +15,201 @@ #error Invalid Pixman library #endif +#include "doc/blend_funcs.h" +#include "doc/blend_mode.h" +#include "doc/color.h" +#include "doc/image.h" +#include "doc/image_traits.h" +#include "doc/palette.h" + +namespace doc { + + template + class BlenderHelper { + BlendFunc m_blendFunc; + color_t m_maskColor; + public: + BlenderHelper(Image* dst, const Image* src, const Palette* pal, + const BlendMode blendMode, const bool newBlend) + { + m_blendFunc = SrcTraits::get_blender(blendMode, newBlend); + m_maskColor = src->maskColor(); + } + + inline typename DstTraits::pixel_t + operator()(typename DstTraits::pixel_t dst, + typename SrcTraits::pixel_t src, + int opacity) + { + if (src != m_maskColor) + return (*m_blendFunc)(dst, src, opacity); + else + return dst; + } + }; + + ////////////////////////////////////////////////////////////////////// + // X -> Rgb + + template<> + class BlenderHelper { + BlendFunc m_blendFunc; + color_t m_maskColor; + public: + BlenderHelper(Image* dst, const Image* src, const Palette* pal, + const BlendMode blendMode, const bool newBlend) + { + m_blendFunc = RgbTraits::get_blender(blendMode, newBlend); + m_maskColor = src->maskColor(); + } + + inline RgbTraits::pixel_t + operator()(RgbTraits::pixel_t dst, + GrayscaleTraits::pixel_t src, + int opacity) + { + if (src != m_maskColor) { + int v = graya_getv(src); + return (*m_blendFunc)(dst, rgba(v, v, v, graya_geta(src)), opacity); + } + else + return dst; + } + }; + + template<> + class BlenderHelper { + const Palette* m_pal; + BlendMode m_blendMode; + BlendFunc m_blendFunc; + color_t m_maskColor; + public: + BlenderHelper(Image* dst, const Image* src, const Palette* pal, + const BlendMode blendMode, const bool newBlend) + { + m_blendMode = blendMode; + m_blendFunc = RgbTraits::get_blender(blendMode, newBlend); + m_maskColor = src->maskColor(); + m_pal = pal; + } + + inline RgbTraits::pixel_t + operator()(RgbTraits::pixel_t dst, + IndexedTraits::pixel_t src, + int opacity) + { + if (m_blendMode == BlendMode::SRC) { + return m_pal->getEntry(src); + } + else { + if (src != m_maskColor) { + return (*m_blendFunc)(dst, m_pal->getEntry(src), opacity); + } + else + return dst; + } + } + }; + + ////////////////////////////////////////////////////////////////////// + // X -> Grayscale + + template<> + class BlenderHelper { + BlendFunc m_blendFunc; + public: + BlenderHelper(Image* dst, const Image* src, const Palette* pal, + const BlendMode blendMode, const bool newBlend) + { + m_blendFunc = RgbTraits::get_blender(blendMode, newBlend); + } + + inline GrayscaleTraits::pixel_t + operator()(GrayscaleTraits::pixel_t dst, + RgbTraits::pixel_t src, + int opacity) + { + // TODO we should be able to configure this function + return rgba_to_graya_using_luma(src); + } + }; + + ////////////////////////////////////////////////////////////////////// + // X -> Indexed + + template<> + class BlenderHelper { + const Palette* m_pal; + BlendMode m_blendMode; + BlendFunc m_blendFunc; + color_t m_maskColor; + public: + BlenderHelper(Image* dst, const Image* src, const Palette* pal, + const BlendMode blendMode, const bool newBlend) + { + m_blendMode = blendMode; + m_blendFunc = RgbTraits::get_blender(blendMode, newBlend); + m_pal = pal; + + if (m_blendMode == BlendMode::SRC) + m_maskColor = -1; + else + m_maskColor = dst->maskColor(); + } + + inline IndexedTraits::pixel_t + operator()(IndexedTraits::pixel_t dst, + RgbTraits::pixel_t src, + int opacity) + { + if (dst != m_maskColor) { + src = (*m_blendFunc)(m_pal->getEntry(dst), src, opacity); + } + return m_pal->findBestfit(rgba_getr(src), + rgba_getg(src), + rgba_getb(src), + rgba_geta(src), + m_maskColor); + } + }; + + template<> + class BlenderHelper { + BlendMode m_blendMode; + color_t m_maskColor; + int m_paletteSize; + public: + BlenderHelper(Image* dst, const Image* src, const Palette* pal, + const BlendMode blendMode, const bool newBlend) + { + m_blendMode = blendMode; + m_maskColor = src->maskColor(); + m_paletteSize = pal->size(); + } + + inline IndexedTraits::pixel_t + operator()(IndexedTraits::pixel_t dst, + IndexedTraits::pixel_t src, + int opacity) + { + if (m_blendMode == BlendMode::SRC) { + return src; + } + else if (m_blendMode == BlendMode::DST_OVER) { + if (dst != m_maskColor) + return dst; + else + return src; + } + else { + if (src != m_maskColor && src < m_paletteSize) + return src; + else + return dst; + } + } + }; + +} // namespace doc + #endif diff --git a/src/doc/primitives.cpp b/src/doc/primitives.cpp index aa4ce6432..e1731ceed 100644 --- a/src/doc/primitives.cpp +++ b/src/doc/primitives.cpp @@ -72,46 +72,6 @@ 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 29a0b19b0..fd367a812 100644 --- a/src/doc/primitives.h +++ b/src/doc/primitives.h @@ -10,7 +10,6 @@ #pragma once #include "base/ints.h" -#include "doc/blend_mode.h" #include "doc/color.h" #include "doc/image_buffer.h" #include "gfx/fwd.h" @@ -18,7 +17,6 @@ namespace doc { class Brush; class Image; - class Palette; class Remap; color_t get_pixel(const Image* image, int x, int y); @@ -28,8 +26,6 @@ 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/src/render/render.cpp b/src/render/render.cpp index e46554d77..1c634c206 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -1,5 +1,5 @@ // Aseprite Render Library -// Copyright (C) 2019-2023 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This file is released under the terms of the MIT license. @@ -34,119 +34,6 @@ namespace { ////////////////////////////////////////////////////////////////////// // Scaled composite -template -class BlenderHelper { - BlendFunc m_blendFunc; - color_t m_mask_color; -public: - BlenderHelper(const Image* src, const Palette* pal, BlendMode blendMode, const bool newBlend) - { - m_blendFunc = SrcTraits::get_blender(blendMode, newBlend); - m_mask_color = src->maskColor(); - } - inline typename DstTraits::pixel_t - operator()(const typename DstTraits::pixel_t& dst, - const typename SrcTraits::pixel_t& src, - const int opacity) - { - if (src != m_mask_color) - return (*m_blendFunc)(dst, src, opacity); - else - return dst; - } -}; - -template<> -class BlenderHelper { - BlendFunc m_blendFunc; - color_t m_mask_color; -public: - BlenderHelper(const Image* src, const Palette* pal, BlendMode blendMode, const bool newBlend) - { - m_blendFunc = RgbTraits::get_blender(blendMode, newBlend); - m_mask_color = src->maskColor(); - } - inline RgbTraits::pixel_t - operator()(const RgbTraits::pixel_t& dst, - const GrayscaleTraits::pixel_t& src, - const int opacity) - { - if (src != m_mask_color) { - int v = graya_getv(src); - return (*m_blendFunc)(dst, rgba(v, v, v, graya_geta(src)), opacity); - } - else - return dst; - } -}; - -template<> -class BlenderHelper { - const Palette* m_pal; - BlendMode m_blendMode; - BlendFunc m_blendFunc; - color_t m_mask_color; -public: - BlenderHelper(const Image* src, const Palette* pal, BlendMode blendMode, const bool newBlend) - { - m_blendMode = blendMode; - m_blendFunc = RgbTraits::get_blender(blendMode, newBlend); - m_mask_color = src->maskColor(); - m_pal = pal; - } - inline RgbTraits::pixel_t - operator()(const RgbTraits::pixel_t& dst, - const IndexedTraits::pixel_t& src, - const int opacity) - { - if (m_blendMode == BlendMode::SRC) { - return m_pal->getEntry(src); - } - else { - if (src != m_mask_color) { - return (*m_blendFunc)(dst, m_pal->getEntry(src), opacity); - } - else - return dst; - } - } -}; - -template<> -class BlenderHelper { - BlendMode m_blendMode; - color_t m_maskColor; - int m_paletteSize; -public: - BlenderHelper(const Image* src, const Palette* pal, BlendMode blendMode, const bool newBlend) - { - m_blendMode = blendMode; - m_maskColor = src->maskColor(); - m_paletteSize = pal->size(); - } - inline IndexedTraits::pixel_t - operator()(const IndexedTraits::pixel_t& dst, - const IndexedTraits::pixel_t& src, - const int opacity) - { - if (m_blendMode == BlendMode::SRC) { - return src; - } - else if (m_blendMode == BlendMode::DST_OVER) { - if (dst != m_maskColor) - return dst; - else - return src; - } - else { - if (src != m_maskColor && src < m_paletteSize) - return src; - else - return dst; - } - } -}; - template void composite_image_without_scale( Image* dst, const Image* src, const Palette* pal, @@ -163,7 +50,7 @@ void composite_image_without_scale( ASSERT(DstTraits::pixel_format == dst->pixelFormat()); ASSERT(SrcTraits::pixel_format == src->pixelFormat()); - BlenderHelper blender(src, pal, blendMode, newBlend); + BlenderHelper blender(dst, src, pal, blendMode, newBlend); gfx::Clip area(areaF); if (!area.clip(dst->width(), dst->height(), @@ -226,7 +113,7 @@ void composite_image_scale_up( int(sy*double(src->height())))) return; - BlenderHelper blender(src, pal, blendMode, newBlend); + BlenderHelper blender(dst, src, pal, blendMode, newBlend); int px_x, px_y; int px_w = int(sx); int px_h = int(sy); @@ -379,7 +266,7 @@ void composite_image_scale_down( int(sy*double(src->height())))) return; - BlenderHelper blender(src, pal, blendMode, newBlend); + BlenderHelper blender(dst, src, pal, blendMode, newBlend); int step_w = int(1.0 / sx); int step_h = int(1.0 / sy); if (step_w < 1 || step_h < 1) @@ -447,7 +334,7 @@ void composite_image_general( sx*src->width(), sy*src->height())) return; - BlenderHelper blender(src, pal, blendMode, newBlend); + BlenderHelper blender(dst, src, pal, blendMode, newBlend); gfx::Rect dstBounds( area.dstBounds().x, area.dstBounds().y, @@ -522,7 +409,7 @@ void composite_image_general_with_tile_flags( sx*src->width(), sy*src->height())) return; - BlenderHelper blender(src, pal, blendMode, newBlend); + BlenderHelper blender(dst, src, pal, blendMode, newBlend); gfx::Rect dstBounds( area.dstBounds().x, area.dstBounds().y, diff --git a/tests/scripts/image.lua b/tests/scripts/image.lua index 0fa000982..6fb2dc054 100644 --- a/tests/scripts/image.lua +++ b/tests/scripts/image.lua @@ -1,4 +1,4 @@ --- Copyright (C) 2019-2023 Igara Studio S.A. +-- Copyright (C) 2019-2024 Igara Studio S.A. -- Copyright (C) 2018 David Capello -- -- This file is released under the terms of the MIT license. @@ -330,13 +330,14 @@ do 1, 2, 4, 3, 4, 3, 4, 0, 0, 0 }) + -- BlendMode.NORMAL by default, so mask color (color=0) is skipped b:drawImage(a, Point(0, 3)) expect_img(b, { 0, 0, 0, 0, 0, 0, 1, 2, 1, 2, 1, 2, 4, 3, 4, - 0, 1, 2, 0, 0 }) + 3, 1, 2, 0, 0 }) - b:drawImage(a, Point(0, 3)) -- Do nothing + b:drawImage(a, Point(0, 3), 255, BlendMode.SRC) expect_img(b, { 0, 0, 0, 0, 0, 0, 1, 2, 1, 2, 1, 2, 4, 3, 4, @@ -476,3 +477,60 @@ local spr = Sprite(3, 3) -- Test with sprite (with transactions & undo/redo) test_image_flip(app.image) app.sprite = nil -- Test without sprite (without transactions) test_image_flip(Image(3, 3)) + +---------------------------------------------------------------------- +-- Test crash using Image:drawImage() with different color modes + +do + local tmp = Sprite(3, 3) + local pal = Palette(4) + pal:setColor(0, Color(0, 0, 0)) + pal:setColor(1, Color(255, 0, 0)) + pal:setColor(2, Color(0, 255, 0)) + pal:setColor(3, Color(0, 0, 255)) + tmp:setPalette(pal) + + local rgb = Image{ width=2, height=2, colorMode=ColorMode.RGB } + local idx = Image{ width=2, height=2, colorMode=ColorMode.INDEXED } + + -- Draw INDEXED -> RGB + + array_to_pixels({ 0, 1, + 2, 3 }, idx) + rgb:drawImage(idx) + + local k = pal:getColor(0).rgbaPixel + local r = pal:getColor(1).rgbaPixel + local g = pal:getColor(2).rgbaPixel + local b = pal:getColor(3).rgbaPixel + expect_img(rgb, { 0, r, + g, b }) + + rgb:drawImage(idx, 0, 0, 255, BlendMode.SRC) + expect_img(rgb, { k, r, + g, b }) + + rgb:drawImage(idx, 1, 0) + expect_img(rgb, { k, r, + g, g }) + + rgb:drawImage(idx, 1, 0, 255, BlendMode.SRC) + expect_img(rgb, { k, k, + g, g }) + + -- Draw RGB -> INDEXED + + array_to_pixels({ 0, r, + g, b }, rgb) + + idx:clear(1) + idx:drawImage(rgb, 0, 0, 255, BlendMode.SRC) + expect_img(idx, { 0, 1, + 2, 3 }) + + idx:clear(1) + idx:drawImage(rgb) + expect_img(idx, { 1, 1, + 2, 3 }) + +end diff --git a/tests/scripts/test_utils.lua b/tests/scripts/test_utils.lua index f3c36a11d..3cada9af7 100644 --- a/tests/scripts/test_utils.lua +++ b/tests/scripts/test_utils.lua @@ -20,7 +20,22 @@ local function dump_img(image) for v=0,h-1 do local lineStr = ' ' for u=0,w-1 do - lineStr = lineStr .. image:getPixel(u, v) .. ',' + local pix = image:getPixel(u, v) + local pixStr + if image.colorMode == ColorMode.RGB then + pixStr = string.format('rgba(%d,%d,%d,%d)', + app.pixelColor.rgbaR(pix), + app.pixelColor.rgbaG(pix), + app.pixelColor.rgbaB(pix), + app.pixelColor.rgbaA(pix)) + elseif image.colorMode == ColorMode.GRAY then + pixStr = string.format('gray(%d,%d)', + app.pixelColor.grayaV(pix), + app.pixelColor.grayaA(pix)) + else + pixStr = tostring(pix) + end + lineStr = lineStr .. pixStr .. ',' end print(lineStr) end