diff --git a/src/app/script/graphics_context.cpp b/src/app/script/graphics_context.cpp index 4b6e2244a..9cfc1af85 100644 --- a/src/app/script/graphics_context.cpp +++ b/src/app/script/graphics_context.cpp @@ -29,6 +29,36 @@ namespace app { namespace script { +void GraphicsContext::color(gfx::Color color) +{ + switch (m_formatHint) { + case doc::PixelFormat::IMAGE_GRAYSCALE: + // Underlying SkSurface's color type is kR8G8_unorm_SkColorType, then we + // must transform the color to set R to the gray level and G to the + // alpha level. + color = gfx::rgba(gfx::getr(color), gfx::geta(color), 0); + break; + case doc::PixelFormat::IMAGE_INDEXED: { + // Underlying SkSurface's color type is kAlpha_8_SkColorType, then we + // must transform the color to set Alpha to the corresponding index. + int i = get_current_palette()->findExactMatch(gfx::getr(color), + gfx::getg(color), + gfx::getb(color), + gfx::geta(color), -1); + if (i == -1) { + i = get_current_palette()->findBestfit(gfx::getr(color), + gfx::getg(color), + gfx::getb(color), + gfx::geta(color), -1); + } + + color = gfx::rgba(0, 0, 0, i); + break; + } + } + m_paint.color(color); +} + void GraphicsContext::fillText(const std::string& text, int x, int y) { if (auto theme = skin::SkinTheme::instance()) { @@ -59,7 +89,7 @@ void GraphicsContext::drawImage(const doc::Image* img, int x, int y) return; } - drawImage(img, img->bounds(), gfx::Rect(x, y, img->size().w, img->size().h)); + drawImage(img, img->bounds(), gfx::Rect(x, y, img->size().w, img->size().h)); } void GraphicsContext::drawImage(const doc::Image* img, @@ -429,7 +459,23 @@ int GraphicsContext_set_antialias(lua_State* L) int GraphicsContext_get_color(lua_State* L) { auto gc = get_obj(L, 1); - push_obj(L, color_utils::color_from_ui(gc->color())); + + const gfx::Color gcColor = gc->color(); + app::Color color; + switch (gc->formatHint()) { + case IMAGE_GRAYSCALE: + color = app::Color::fromGray(gfx::getr(gcColor), gfx::geta(gcColor)); + break; + case IMAGE_INDEXED: { + const int i = gfx::geta(gcColor); + color = app::Color::fromIndex(i); + break; + } + case IMAGE_RGB: + default: + color = color_utils::color_from_ui(gcColor); + } + push_obj(L, color); return 1; } diff --git a/src/app/script/graphics_context.h b/src/app/script/graphics_context.h index 3ef65526e..9037c04a3 100644 --- a/src/app/script/graphics_context.h +++ b/src/app/script/graphics_context.h @@ -9,6 +9,7 @@ #pragma once #include "doc/palette.h" +#include "doc/pixel_format.h" #include "gfx/path.h" #include "os/paint.h" #include "os/surface.h" @@ -31,12 +32,26 @@ private: }; public: - GraphicsContext(const os::SurfaceRef& surface, int uiscale) : m_surface(surface), m_uiscale(uiscale) { } - GraphicsContext(GraphicsContext&& gc) { + GraphicsContext(const os::SurfaceRef& surface, + int uiscale, + doc::PixelFormat formatHint = doc::PixelFormat::IMAGE_RGB) + : m_surface(surface), m_uiscale(uiscale), m_formatHint(formatHint) { } + GraphicsContext(const GraphicsContext& gc) { + m_surface = gc.m_surface; + m_font = gc.m_font; + m_paint = gc.m_paint; + m_palette = gc.m_palette; + m_path = gc.m_path; + m_saved = gc.m_saved; + m_formatHint = gc.m_formatHint; + m_uiscale = gc.m_uiscale; + } + GraphicsContext(GraphicsContext&& gc) noexcept { std::swap(m_surface, gc.m_surface); std::swap(m_paint, gc.m_paint); std::swap(m_font, gc.m_font); std::swap(m_path, gc.m_path); + m_formatHint = gc.m_formatHint; m_uiscale = gc.m_uiscale; } @@ -68,7 +83,7 @@ public: void antialias(bool value) { m_paint.antialias(value); } gfx::Color color() const { return m_paint.color(); } - void color(gfx::Color color) { m_paint.color(color); } + void color(gfx::Color color); float strokeWidth() const { return m_paint.strokeWidth(); } void strokeWidth(float value) { m_paint.strokeWidth(value); } @@ -133,6 +148,10 @@ public: return m_uiscale; } + doc::PixelFormat formatHint() const { + return m_formatHint; + } + private: os::SurfaceRef m_surface = nullptr; // Keeps the UI Scale currently in use when canvas autoScaling is enabled. @@ -142,6 +161,8 @@ private: gfx::Path m_path; std::stack m_saved; doc::Palette* m_palette = nullptr; + // Pixel format hint about the underlying pixels wrapped by m_surface. + doc::PixelFormat m_formatHint = doc::PixelFormat::IMAGE_RGB; }; } // namespace script diff --git a/src/app/script/image_class.cpp b/src/app/script/image_class.cpp index 3628e04a0..fdc955c16 100644 --- a/src/app/script/image_class.cpp +++ b/src/app/script/image_class.cpp @@ -20,12 +20,14 @@ #include "app/script/blend_mode.h" #include "app/script/docobj.h" #include "app/script/engine.h" +#include "app/script/graphics_context.h" #include "app/script/luacpp.h" #include "app/script/security.h" #include "app/site.h" #include "app/tx.h" #include "app/util/autocrop.h" #include "app/util/resize_image.h" +#include "app/util/shader_helpers.h" #include "base/fs.h" #include "doc/algorithm/flip_image.h" #include "doc/algorithm/flip_type.h" @@ -37,6 +39,7 @@ #include "doc/primitives.h" #include "doc/sprite.h" #include "render/render.h" +#include "ui/scale.h" #include #include @@ -54,6 +57,11 @@ struct ImageObj { doc::ObjectId celId = 0; doc::ObjectId tilesetId = 0; doc::tile_index ti = 0; + +#if LAF_SKIA + std::unique_ptr context = nullptr; +#endif + ImageObj(doc::Image* image) : imageId(image->id()) { } @@ -742,6 +750,23 @@ int Image_get_cel(lua_State* L) return 1; } +int Image_get_context(lua_State* L) +{ +#if LAF_SKIA + auto* obj = get_obj(L, 1); + + if (!obj->context) { + auto surface = wrap_docimage_in_surface(obj->image(L)); + obj->context = std::make_unique(surface, ui::guiscale(), obj->image(L)->pixelFormat()); + } + + push_obj(L, *obj->context); + return 1; +#else + return 0; +#endif +} + const luaL_Reg Image_methods[] = { { "clone", Image_clone }, { "clear", Image_clear }, @@ -774,6 +799,7 @@ const Property Image_properties[] = { { "colorMode", Image_get_colorMode, nullptr }, { "spec", Image_get_spec, nullptr }, { "cel", Image_get_cel, nullptr }, + { "context", Image_get_context, nullptr }, { nullptr, nullptr, nullptr } }; diff --git a/src/app/util/shader_helpers.cpp b/src/app/util/shader_helpers.cpp index 660a1b3f6..c003080d7 100644 --- a/src/app/util/shader_helpers.cpp +++ b/src/app/util/shader_helpers.cpp @@ -16,9 +16,10 @@ #if LAF_SKIA +#include "os/skia/skia_surface.h" + #include "include/core/SkSurface.h" #if SK_ENABLE_SKSL - #include "include/effects/SkRuntimeEffect.h" #include "src/core/SkRuntimeEffectPriv.h" #endif @@ -111,6 +112,11 @@ sk_sp wrap_docimage_in_sksurface(const doc::Image* img) img->rowBytes()); } +os::SurfaceRef wrap_docimage_in_surface(const doc::Image* img) +{ + return os::SurfaceRef(new os::SkiaSurface(wrap_docimage_in_sksurface(img))); +} + } // namespace app #endif // LAF_SKIA diff --git a/src/app/util/shader_helpers.h b/src/app/util/shader_helpers.h index 1ee9729de..23c0b5bc7 100644 --- a/src/app/util/shader_helpers.h +++ b/src/app/util/shader_helpers.h @@ -12,6 +12,7 @@ #include "app/color.h" #include "gfx/color.h" +#include "os/surface.h" #include "include/core/SkCanvas.h" #include "include/core/SkImage.h" @@ -94,6 +95,7 @@ SkImageInfo get_skimageinfo_for_docimage(const doc::Image* img); sk_sp make_skimage_for_docimage(const doc::Image* img); std::unique_ptr make_skcanvas_for_docimage(const doc::Image* img); sk_sp wrap_docimage_in_sksurface(const doc::Image* img); +os::SurfaceRef wrap_docimage_in_surface(const doc::Image* img); } // namespace app diff --git a/tests/scripts/image.lua b/tests/scripts/image.lua index 6fb2dc054..f94d5fb8a 100644 --- a/tests/scripts/image.lua +++ b/tests/scripts/image.lua @@ -534,3 +534,91 @@ do 2, 3 }) end + +-- Test Image.context +do + -- Draw onto an indexed image + do + local spec = ImageSpec{ + width=3, height=4, + colorMode=ColorMode.INDEXED, + transparentColor=1 } + + local img = Image(spec) + local gc = img.context + if gc then + expect_eq(gc.width, 3) + expect_eq(gc.height, 4) + + gc.color = Color{ index=2 } + local c = gc.color.index + gc.strokeWidth = 1 + -- Setting blendMode to BlendMode.SRC will make GraphicsContext's painting + -- behave correclty for indexed images. + gc.blendMode = BlendMode.SRC + gc:rect(Rectangle{0,0,1,1}) + gc:stroke() + + expect_img(img, { c, c, 1, + c, c, 1, + 1, 1, 1, + 1, 1, 1 }) + else + skipped("GraphicsContext unavailable") + end + end + + -- Draw onto a grayscale image + do + local spec = ImageSpec{ + width=3, height=4, + colorMode=ColorMode.GRAY } + + local img = Image(spec) + local gc = img.context + if gc then + expect_eq(gc.width, 3) + expect_eq(gc.height, 4) + + gc.color = Color{ gray=2, alpha=255 } + local c = gc.color.grayPixel + gc.strokeWidth = 1 + gc:rect(Rectangle{0,0,1,1}) + gc:stroke() + + expect_img(img, { c, c, 0, + c, c, 0, + 0, 0, 0, + 0, 0, 0 }) + else + skipped("GraphicsContext unavailable") + end + end + + -- Draw onto an RGB image + do + local spec = ImageSpec{ + width=3, height=4, + colorMode=ColorMode.RGB } + + local img = Image(spec) + local gc = img.context + if gc then + expect_eq(gc.width, 3) + expect_eq(gc.height, 4) + + gc.color = Color(2,0,0,255) + local c = gc.color.rgbaPixel + gc.strokeWidth = 1 + gc:rect(Rectangle{0,0,1,1}) + gc:stroke() + + expect_img(img, { c, c, 0, + c, c, 0, + 0, 0, 0, + 0, 0, 0 }) + else + skipped("GraphicsContext unavailable") + end + end +end \ No newline at end of file diff --git a/tests/scripts/test_utils.lua b/tests/scripts/test_utils.lua index 0a7c4c2c7..8a5c0b303 100644 --- a/tests/scripts/test_utils.lua +++ b/tests/scripts/test_utils.lua @@ -288,3 +288,13 @@ function assert_slices_eq(expectedSlices, slices) end end end + +-- Prints a message indicating that the current test was skipped along with +-- a level 2 debug traceback. +function skipped(reason) + local msg = "Test skipped" + if reason then + msg = msg .. ": " .. reason + end + print(debug.traceback(msg, 2)) +end