Fix doc::blend_image() to support different color modes (fix #4530, fix #4531)

The code was refactored moving the BlenderHelper class from "render"
to "doc", and now doc::blend_image() supports blending different color
modes.

Some work is still needed to work with grayscale images correctly.
This commit is contained in:
David Capello 2024-06-11 20:25:42 -03:00
parent 31f3c79566
commit 8f7bf09263
12 changed files with 427 additions and 179 deletions

2
laf

@ -1 +1 @@
Subproject commit c23da3b0516540036ee501e6a35de9afc0356ab4
Subproject commit 26994fe6c1210e0989eaddd4b2bdc00422e1ac8a

View File

@ -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;
}

View File

@ -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<doc::ImageBuffer>();
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;

View File

@ -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

84
src/doc/blend_image.cpp Normal file
View File

@ -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<typename DstTraits,
typename SrcTraits>
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<DstTraits, SrcTraits> blender(dst, src, pal, blendMode, true);
LockImageBits<DstTraits> dstBits(dst);
const LockImageBits<SrcTraits> 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<RgbTraits, RgbTraits >(dst, src, area, pal, opacity, blendMode);
case IMAGE_GRAYSCALE: return blend_image_templ<RgbTraits, GrayscaleTraits>(dst, src, area, pal, opacity, blendMode);
case IMAGE_INDEXED: return blend_image_templ<RgbTraits, IndexedTraits >(dst, src, area, pal, opacity, blendMode);
}
break;
case IMAGE_GRAYSCALE:
switch (src->pixelFormat()) {
case IMAGE_RGB: return blend_image_templ<GrayscaleTraits, RgbTraits >(dst, src, area, pal, opacity, blendMode);
case IMAGE_GRAYSCALE: return blend_image_templ<GrayscaleTraits, GrayscaleTraits>(dst, src, area, pal, opacity, blendMode);
case IMAGE_INDEXED: return blend_image_templ<GrayscaleTraits, IndexedTraits >(dst, src, area, pal, opacity, blendMode);
}
break;
case IMAGE_INDEXED:
switch (src->pixelFormat()) {
case IMAGE_RGB: return blend_image_templ<IndexedTraits, RgbTraits >(dst, src, area, pal, opacity, blendMode);
case IMAGE_GRAYSCALE: return blend_image_templ<IndexedTraits, GrayscaleTraits>(dst, src, area, pal, opacity, blendMode);
case IMAGE_INDEXED: return blend_image_templ<IndexedTraits, IndexedTraits >(dst, src, area, pal, opacity, blendMode);
}
break;
case IMAGE_TILEMAP:
return dst->copy(src, area);
}
}
} // namespace doc

30
src/doc/blend_image.h Normal file
View File

@ -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

View File

@ -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 DstTraits, class SrcTraits>
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<RgbTraits, GrayscaleTraits> {
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<RgbTraits, IndexedTraits> {
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<GrayscaleTraits, RgbTraits> {
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<IndexedTraits, RgbTraits> {
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<IndexedTraits, IndexedTraits> {
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

View File

@ -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<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

@ -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());

View File

@ -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 DstTraits, class SrcTraits>
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<RgbTraits, GrayscaleTraits> {
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<RgbTraits, IndexedTraits> {
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<IndexedTraits, IndexedTraits> {
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<class DstTraits, class SrcTraits>
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<DstTraits, SrcTraits> blender(src, pal, blendMode, newBlend);
BlenderHelper<DstTraits, SrcTraits> 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<DstTraits, SrcTraits> blender(src, pal, blendMode, newBlend);
BlenderHelper<DstTraits, SrcTraits> 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<DstTraits, SrcTraits> blender(src, pal, blendMode, newBlend);
BlenderHelper<DstTraits, SrcTraits> 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<DstTraits, SrcTraits> blender(src, pal, blendMode, newBlend);
BlenderHelper<DstTraits, SrcTraits> 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<DstTraits, SrcTraits> blender(src, pal, blendMode, newBlend);
BlenderHelper<DstTraits, SrcTraits> blender(dst, src, pal, blendMode, newBlend);
gfx::Rect dstBounds(
area.dstBounds().x, area.dstBounds().y,

View File

@ -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

View File

@ -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