mirror of https://github.com/aseprite/aseprite.git
601 lines
16 KiB
C++
601 lines
16 KiB
C++
// Aseprite
|
|
// Copyright (C) 2022-2024 Igara Studio S.A.
|
|
//
|
|
// This program is distributed under the terms of
|
|
// the End-User License Agreement for Aseprite.
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "app/script/graphics_context.h"
|
|
|
|
#include "app/color.h"
|
|
#include "app/color_utils.h"
|
|
#include "app/modules/palettes.h"
|
|
#include "app/script/blend_mode.h"
|
|
#include "app/script/engine.h"
|
|
#include "app/script/luacpp.h"
|
|
#include "app/ui/skin/skin_theme.h"
|
|
#include "app/util/conversion_to_surface.h"
|
|
#include "doc/cel.h"
|
|
#include "doc/tileset.h"
|
|
#include "os/surface.h"
|
|
#include "os/system.h"
|
|
#include "text/draw_text.h"
|
|
|
|
#include <algorithm>
|
|
|
|
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()) {
|
|
text::draw_text(m_surface.get(), theme->fontMgr(), m_font,
|
|
text, m_paint.color(), 0, x, y, nullptr);
|
|
}
|
|
}
|
|
|
|
gfx::Size GraphicsContext::measureText(const std::string& text) const
|
|
{
|
|
if (auto theme = skin::SkinTheme::instance()) {
|
|
return text::draw_text(nullptr, theme->fontMgr(), m_font, text,
|
|
0, 0, 0, 0, nullptr).size();
|
|
}
|
|
return gfx::Size();
|
|
}
|
|
|
|
void GraphicsContext::drawImage(const doc::Image* img, int x, int y)
|
|
{
|
|
if (m_paint.blendMode() == os::BlendMode::Src) {
|
|
convert_image_to_surface(
|
|
img,
|
|
m_palette ? m_palette : get_current_palette(),
|
|
m_surface.get(),
|
|
0, 0,
|
|
x, y,
|
|
img->width(), img->height());
|
|
return;
|
|
}
|
|
|
|
drawImage(img, img->bounds(), gfx::Rect(x, y, img->size().w, img->size().h));
|
|
}
|
|
|
|
void GraphicsContext::drawImage(const doc::Image* img,
|
|
const gfx::Rect& srcRc,
|
|
const gfx::Rect& dstRc)
|
|
{
|
|
if (srcRc.isEmpty() || dstRc.isEmpty())
|
|
return; // Do nothing for empty rectangles
|
|
|
|
static os::SurfaceRef tmpSurface = nullptr;
|
|
if (!tmpSurface ||
|
|
tmpSurface->width() < srcRc.w ||
|
|
tmpSurface->height() < srcRc.h) {
|
|
tmpSurface = os::System::instance()->makeRgbaSurface(
|
|
std::max(srcRc.w, (tmpSurface ? tmpSurface->width(): 0)),
|
|
std::max(srcRc.h, (tmpSurface ? tmpSurface->height(): 0)));
|
|
}
|
|
if (tmpSurface) {
|
|
convert_image_to_surface(
|
|
img,
|
|
m_palette ? m_palette : get_current_palette(),
|
|
tmpSurface.get(),
|
|
srcRc.x, srcRc.y,
|
|
0, 0,
|
|
srcRc.w, srcRc.h);
|
|
|
|
m_surface->drawSurface(tmpSurface.get(), gfx::Rect(0, 0, srcRc.w, srcRc.h),
|
|
dstRc, os::Sampling(), &m_paint);
|
|
}
|
|
}
|
|
|
|
void GraphicsContext::drawThemeImage(const std::string& partId, const gfx::Point& pt)
|
|
{
|
|
if (auto theme = skin::SkinTheme::instance()) {
|
|
skin::SkinPartPtr part = (m_uiscale > 1 ? theme->getUnscaledPartById(partId):
|
|
theme->getPartById(partId));
|
|
if (part && part->bitmap(0)) {
|
|
auto bmp = part->bitmap(0);
|
|
m_surface->drawRgbaSurface(bmp, pt.x, pt.y);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GraphicsContext::drawThemeRect(const std::string& partId, const gfx::Rect& rc)
|
|
{
|
|
if (auto theme = skin::SkinTheme::instance()) {
|
|
skin::SkinPartPtr part = (m_uiscale > 1 ? theme->getUnscaledPartById(partId):
|
|
theme->getPartById(partId));
|
|
if (part && part->bitmap(0)) {
|
|
ui::Graphics g(nullptr, m_surface, 0, 0);
|
|
|
|
// TODO Copy code from Theme::paintLayer()
|
|
|
|
// 9-slices
|
|
if (!part->slicesBounds().isEmpty()) {
|
|
if (m_uiscale > 1)
|
|
theme->drawRectUsingUnscaledSheet(&g, rc, part.get(), true);
|
|
else
|
|
theme->drawRect(&g, rc, part.get(), true);
|
|
}
|
|
else {
|
|
ui::IntersectClip clip(&g, rc);
|
|
if (clip) {
|
|
auto bmp = part->bitmap(0);
|
|
// Horizontal line
|
|
if (rc.w > part->spriteBounds().w) {
|
|
for (int x=rc.x; x<rc.x2(); x+=part->spriteBounds().w) {
|
|
g.drawRgbaSurface(
|
|
bmp,
|
|
x, rc.y+rc.h/2-part->spriteBounds().h/2);
|
|
}
|
|
}
|
|
// Vertical line
|
|
else {
|
|
for (int y=rc.y; y<rc.y2(); y+=part->spriteBounds().h) {
|
|
g.drawRgbaSurface(
|
|
bmp,
|
|
rc.x+rc.w/2-part->spriteBounds().w/2, y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GraphicsContext::stroke()
|
|
{
|
|
m_paint.style(os::Paint::Stroke);
|
|
m_surface->drawPath(m_path, m_paint);
|
|
}
|
|
|
|
void GraphicsContext::fill()
|
|
{
|
|
m_paint.style(os::Paint::Fill);
|
|
m_surface->drawPath(m_path, m_paint);
|
|
}
|
|
|
|
namespace {
|
|
|
|
int GraphicsContext_gc(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
gc->~GraphicsContext();
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_save(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
gc->save();
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_restore(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
gc->restore();
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_clip(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
gc->clip();
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_strokeRect(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
const gfx::Rect rc = convert_args_into_rect(L, 2);
|
|
gc->strokeRect(rc);
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_fillRect(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
const gfx::Rect rc = convert_args_into_rect(L, 2);
|
|
gc->fillRect(rc);
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_fillText(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
if (const char* text = lua_tostring(L, 2)) {
|
|
int x = lua_tointeger(L, 3);
|
|
int y = lua_tointeger(L, 4);
|
|
gc->fillText(text, x, y);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_measureText(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
if (const char* text = lua_tostring(L, 2)) {
|
|
push_obj(L, gc->measureText(text));
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
doc::Palette* get_image_palette(const doc::Image* img,
|
|
const doc::Palette* currentPal,
|
|
lua_State* L,
|
|
int index)
|
|
{
|
|
if (img->spec().colorMode() != ColorMode::INDEXED || currentPal)
|
|
return nullptr;
|
|
|
|
if (const doc::Cel* cel = get_image_cel_from_arg(L, index)) {
|
|
return cel->sprite()->palette(cel->frame());
|
|
}
|
|
else if (const doc::Tileset* tileset = get_image_tileset_from_arg(L, index)) {
|
|
return tileset->sprite()->palette(0);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int GraphicsContext_drawImage(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
if (const doc::Image* img = may_get_image_from_arg(L, 2)) {
|
|
doc::Palette* pal = get_image_palette(img, gc->palette(), L, 2);
|
|
if (pal) {
|
|
gc->save();
|
|
gc->palette(pal);
|
|
}
|
|
int x = lua_tointeger(L, 3);
|
|
int y = lua_tointeger(L, 4);
|
|
|
|
if (lua_gettop(L) >= 9) {
|
|
int w = lua_tointeger(L, 5);
|
|
int h = lua_tointeger(L, 6);
|
|
int dx = lua_tointeger(L, 7);
|
|
int dy = lua_tointeger(L, 8);
|
|
int dw = lua_tointeger(L, 9);
|
|
int dh = lua_tointeger(L, 10);
|
|
gc->drawImage(img, gfx::Rect(x, y, w, h), gfx::Rect(dx, dy, dw, dh));
|
|
}
|
|
else if (lua_gettop(L) >= 3) {
|
|
const auto srcRect = may_get_obj<gfx::Rect>(L, 3);
|
|
const auto dstRect = may_get_obj<gfx::Rect>(L, 4);
|
|
if (srcRect && dstRect)
|
|
gc->drawImage(img, *srcRect, *dstRect);
|
|
else {
|
|
gc->drawImage(img, x, y);
|
|
}
|
|
}
|
|
if (pal)
|
|
gc->restore();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_theme(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
push_app_theme(L, gc->uiscale());
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_drawThemeImage(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
if (const char* id = lua_tostring(L, 2)) {
|
|
const gfx::Point pt = convert_args_into_point(L, 3);
|
|
gc->drawThemeImage(id, pt);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_drawThemeRect(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
if (const char* id = lua_tostring(L, 2)) {
|
|
const gfx::Rect rc = convert_args_into_rect(L, 3);
|
|
gc->drawThemeRect(id, rc);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_beginPath(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
gc->beginPath();
|
|
lua_pushvalue(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_closePath(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
gc->closePath();
|
|
lua_pushvalue(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_moveTo(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
float x = lua_tonumber(L, 2);
|
|
float y = lua_tonumber(L, 3);
|
|
gc->moveTo(x, y);
|
|
lua_pushvalue(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_lineTo(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
float x = lua_tonumber(L, 2);
|
|
float y = lua_tonumber(L, 3);
|
|
gc->lineTo(x, y);
|
|
lua_pushvalue(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_cubicTo(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
float cp1x = lua_tonumber(L, 2);
|
|
float cp1y = lua_tonumber(L, 3);
|
|
float cp2x = lua_tonumber(L, 4);
|
|
float cp2y = lua_tonumber(L, 5);
|
|
float x = lua_tonumber(L, 6);
|
|
float y = lua_tonumber(L, 7);
|
|
gc->cubicTo(cp1x, cp1y, cp2x, cp2y, x, y);
|
|
lua_pushvalue(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_oval(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
const gfx::Rect rc = convert_args_into_rect(L, 2);
|
|
gc->oval(rc);
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_rect(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
const gfx::Rect rc = convert_args_into_rect(L, 2);
|
|
gc->rect(rc);
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_roundedRect(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
const gfx::Rect rc = convert_args_into_rect(L, 2);
|
|
const float rx = lua_tonumber(L, 3);
|
|
const float ry = (lua_gettop(L) >= 4 ? lua_tonumber(L, 4): rx);
|
|
gc->roundedRect(rc, rx, ry);
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_stroke(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
gc->stroke();
|
|
lua_pushvalue(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_fill(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
gc->fill();
|
|
lua_pushvalue(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_get_width(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
lua_pushinteger(L, gc->width());
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_get_height(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
lua_pushinteger(L, gc->height());
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_get_antialias(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
lua_pushboolean(L, gc->antialias());
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_set_antialias(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
const bool antialias = lua_toboolean(L, 2);
|
|
gc->antialias(antialias);
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_get_color(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
|
|
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;
|
|
}
|
|
|
|
int GraphicsContext_set_color(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
const app::Color color = convert_args_into_color(L, 2);
|
|
gc->color(color_utils::color_for_ui(color));
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_get_strokeWidth(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
lua_pushnumber(L, gc->strokeWidth());
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_set_strokeWidth(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
const float strokeWidth = lua_tonumber(L, 2);
|
|
gc->strokeWidth(strokeWidth);
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_get_blendMode(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
lua_pushinteger(
|
|
L, int(base::convert_to<app::script::BlendMode>(gc->blendMode())));
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_set_blendMode(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
gc->blendMode(base::convert_to<os::BlendMode>(
|
|
app::script::BlendMode(lua_tointeger(L, 2))));
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_get_opacity(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
lua_pushinteger(L, gc->opacity());
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_set_opacity(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
const int opacity = lua_tointeger(L, 2);
|
|
gc->opacity(std::clamp(opacity, 0, 255));
|
|
return 0;
|
|
}
|
|
|
|
int GraphicsContext_get_palette(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
push_palette(L, gc->palette());
|
|
return 1;
|
|
}
|
|
|
|
int GraphicsContext_set_palette(lua_State* L)
|
|
{
|
|
auto gc = get_obj<GraphicsContext>(L, 1);
|
|
doc::Palette* palette = get_palette_from_arg(L, 2);
|
|
gc->palette(palette);
|
|
return 1;
|
|
}
|
|
|
|
const luaL_Reg GraphicsContext_methods[] = {
|
|
{ "__gc", GraphicsContext_gc },
|
|
{ "save", GraphicsContext_save },
|
|
{ "restore", GraphicsContext_restore },
|
|
{ "clip", GraphicsContext_clip },
|
|
{ "strokeRect", GraphicsContext_strokeRect },
|
|
{ "fillRect", GraphicsContext_fillRect },
|
|
{ "fillText", GraphicsContext_fillText },
|
|
{ "measureText", GraphicsContext_measureText },
|
|
{ "drawImage", GraphicsContext_drawImage },
|
|
{ "drawThemeImage", GraphicsContext_drawThemeImage },
|
|
{ "drawThemeRect", GraphicsContext_drawThemeRect },
|
|
{ "beginPath", GraphicsContext_beginPath },
|
|
{ "closePath", GraphicsContext_closePath },
|
|
{ "moveTo", GraphicsContext_moveTo },
|
|
{ "lineTo", GraphicsContext_lineTo },
|
|
{ "cubicTo", GraphicsContext_cubicTo },
|
|
{ "oval", GraphicsContext_oval },
|
|
{ "rect", GraphicsContext_rect },
|
|
{ "roundedRect", GraphicsContext_roundedRect },
|
|
{ "stroke", GraphicsContext_stroke },
|
|
{ "fill", GraphicsContext_fill },
|
|
{ nullptr, nullptr }
|
|
};
|
|
|
|
const Property GraphicsContext_properties[] = {
|
|
{ "width", GraphicsContext_get_width, nullptr },
|
|
{ "height", GraphicsContext_get_height, nullptr },
|
|
{ "theme", GraphicsContext_theme, nullptr },
|
|
{ "antialias", GraphicsContext_get_antialias, GraphicsContext_set_antialias },
|
|
{ "color", GraphicsContext_get_color, GraphicsContext_set_color },
|
|
{ "strokeWidth", GraphicsContext_get_strokeWidth, GraphicsContext_set_strokeWidth },
|
|
{ "blendMode", GraphicsContext_get_blendMode, GraphicsContext_set_blendMode },
|
|
{ "opacity", GraphicsContext_get_opacity, GraphicsContext_set_opacity },
|
|
{ "palette", GraphicsContext_get_palette, GraphicsContext_set_palette },
|
|
{ nullptr, nullptr, nullptr }
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
DEF_MTNAME(GraphicsContext);
|
|
|
|
void register_graphics_context_class(lua_State* L)
|
|
{
|
|
REG_CLASS(L, GraphicsContext);
|
|
REG_CLASS_PROPERTIES(L, GraphicsContext);
|
|
}
|
|
|
|
} // namespace script
|
|
} // namespace app
|