diff --git a/laf b/laf index 1b5834cd5..946fdf956 160000 --- a/laf +++ b/laf @@ -1 +1 @@ -Subproject commit 1b5834cd52340f14408112b746c8c46a581c3488 +Subproject commit 946fdf956b8a41a33337c9e1f0046bf4a4471b48 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 708dc7c6d..b44d245bf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -157,17 +157,22 @@ set(DATA_OUTPUT_DIR ${CMAKE_BINARY_DIR}/bin/data) include(FetchContent) -FetchContent_Declare( - clone_strings - GIT_REPOSITORY https://github.com/aseprite/strings.git - GIT_TAG origin/main - SOURCE_DIR ${DATA_OUTPUT_DIR}/strings.git - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "") -FetchContent_MakeAvailable(clone_strings) -add_custom_target(clone_strings DEPENDS clone_strings) +find_package(Git) +if(GIT_FOUND) + FetchContent_Declare( + clone_strings + GIT_REPOSITORY https://github.com/aseprite/strings.git + GIT_TAG origin/main + SOURCE_DIR ${DATA_OUTPUT_DIR}/strings.git + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "") + FetchContent_MakeAvailable(clone_strings) + add_custom_target(clone_strings DEPENDS clone_strings) +else() + add_custom_target(clone_strings) +endif() ###################################################################### # Copy data/ directory target into bin/data/ @@ -182,14 +187,16 @@ foreach(fn ${src_data_files}) list(APPEND out_data_files ${DATA_OUTPUT_DIR}/${fn}) endforeach() -# Copy original en.ini to strings.git/en.ini to keep it updated. We -# have to manually sync the "en.ini" file in the "strings" repo from -# the "aseprite" repo. -add_custom_command( - OUTPUT ${DATA_OUTPUT_DIR}/strings.git/en.ini - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SOURCE_DATA_DIR}/strings/en.ini ${DATA_OUTPUT_DIR}/strings.git/en.ini - MAIN_DEPENDENCY ${SOURCE_DATA_DIR}/strings/en.ini) -list(APPEND out_data_files ${DATA_OUTPUT_DIR}/strings.git/en.ini) +if(GIT_FOUND) + # Copy original en.ini to strings.git/en.ini to keep it updated. We + # have to manually sync the "en.ini" file in the "strings" repo from + # the "aseprite" repo. + add_custom_command( + OUTPUT ${DATA_OUTPUT_DIR}/strings.git/en.ini + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SOURCE_DATA_DIR}/strings/en.ini ${DATA_OUTPUT_DIR}/strings.git/en.ini + MAIN_DEPENDENCY ${SOURCE_DATA_DIR}/strings/en.ini) + list(APPEND out_data_files ${DATA_OUTPUT_DIR}/strings.git/en.ini) +endif() add_custom_command( OUTPUT ${DATA_OUTPUT_DIR}/README.md diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 21d120042..370b3aaf4 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -699,6 +699,7 @@ add_library(app-lib util/range_utils.cpp util/readable_time.cpp util/resize_image.cpp + util/shader_helpers.cpp util/tile_flags_utils.cpp util/tileset_utils.cpp util/wrap_point.cpp diff --git a/src/app/commands/cmd_export_sprite_sheet.cpp b/src/app/commands/cmd_export_sprite_sheet.cpp index 5c5458f83..552c8e6a0 100644 --- a/src/app/commands/cmd_export_sprite_sheet.cpp +++ b/src/app/commands/cmd_export_sprite_sheet.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2022 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -11,6 +11,7 @@ #include "app/app.h" #include "app/commands/cmd_export_sprite_sheet.h" +#include "app/console.h" #include "app/context.h" #include "app/context_access.h" #include "app/doc.h" @@ -141,6 +142,17 @@ ConstraintType constraint_type_from_params(const ExportSpriteSheetParams& params #endif // ENABLE_UI +void destroy_doc(Context* ctx, Doc* doc) +{ + try { + DocDestroyer destroyer(ctx, doc, 500); + destroyer.destroyDocument(); + } + catch (const LockedDocException& ex) { + Console::showException(ex); + } +} + Doc* generate_sprite_sheet_from_params( DocExporter& exporter, Context* ctx, @@ -500,8 +512,7 @@ public: auto ctx = UIContext::instance(); ctx->setActiveDocument(m_site.document()); - DocDestroyer destroyer(ctx, m_spriteSheet.release(), 100); - destroyer.destroyDocument(); + destroy_doc(ctx, m_spriteSheet.release()); } } @@ -1014,8 +1025,7 @@ private: auto ctx = UIContext::instance(); ctx->setActiveDocument(m_site.document()); - DocDestroyer destroyer(ctx, m_spriteSheet.release(), 100); - destroyer.destroyDocument(); + destroy_doc(ctx, m_spriteSheet.release()); m_editor = nullptr; } return; @@ -1066,8 +1076,7 @@ private: return; if (token.canceled()) { - DocDestroyer destroyer(&tmpCtx, newDocument, 100); - destroyer.destroyDocument(); + destroy_doc(&tmpCtx, newDocument); return; } @@ -1090,8 +1099,7 @@ private: // old one. IN this case the newDocument contains a back // buffer (ImageBufferPtr) that will be discarded. m_executionID != executionID) { - DocDestroyer destroyer(context, newDocument, 100); - destroyer.destroyDocument(); + destroy_doc(context, newDocument); return; } @@ -1137,8 +1145,7 @@ private: m_spriteSheet->notifyGeneralUpdate(); - DocDestroyer destroyer(context, newDocument, 100); - destroyer.destroyDocument(); + destroy_doc(context, newDocument); } waitGenTaskAndDelete(); @@ -1407,8 +1414,7 @@ void ExportSpriteSheetCommand::onExecute(Context* context) newDocument.release(); } else { - DocDestroyer destroyer(context, newDocument.release(), 100); - destroyer.destroyDocument(); + destroy_doc(context, newDocument.release()); } } diff --git a/src/app/commands/cmd_import_sprite_sheet.cpp b/src/app/commands/cmd_import_sprite_sheet.cpp index 0ba752077..955ea442e 100644 --- a/src/app/commands/cmd_import_sprite_sheet.cpp +++ b/src/app/commands/cmd_import_sprite_sheet.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2022 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -13,6 +13,7 @@ #include "app/commands/command.h" #include "app/commands/commands.h" #include "app/commands/new_params.h" +#include "app/console.h" #include "app/context.h" #include "app/context_access.h" #include "app/doc_access.h" @@ -264,8 +265,13 @@ private: releaseEditor(); if (m_fileOpened) { - DocDestroyer destroyer(m_context, oldDocument, 100); - destroyer.destroyDocument(); + try { + DocDestroyer destroyer(m_context, oldDocument, 500); + destroyer.destroyDocument(); + } + catch (const LockedDocException& ex) { + Console::showException(ex); + } } } diff --git a/src/app/commands/cmd_mask_by_color.cpp b/src/app/commands/cmd_mask_by_color.cpp index 18fa656b3..71d6fad04 100644 --- a/src/app/commands/cmd_mask_by_color.cpp +++ b/src/app/commands/cmd_mask_by_color.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2023 Igara Studio S.A. +// Copyright (C) 2018-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -14,11 +14,12 @@ #include "app/color.h" #include "app/color_utils.h" #include "app/commands/command.h" +#include "app/console.h" #include "app/context.h" #include "app/context_access.h" #include "app/doc.h" -#include "app/ini_file.h" #include "app/i18n/strings.h" +#include "app/ini_file.h" #include "app/modules/gui.h" #include "app/tx.h" #include "app/ui/color_bar.h" @@ -260,22 +261,21 @@ void MaskByColorCommand::maskPreview(const ContextReader& reader) reader.sprite(), image, xpos, ypos, m_selMode->selectionMode())); - { - ContextWriter writer(reader); + + ContextWriter writer(reader); #ifdef SHOW_BOUNDARIES_GEN_PERFORMANCE - base::Chrono chrono; + base::Chrono chrono; #endif - writer.document()->generateMaskBoundaries(mask.get()); + writer.document()->generateMaskBoundaries(mask.get()); #ifdef SHOW_BOUNDARIES_GEN_PERFORMANCE - double time = chrono.elapsed(); - m_window->setText("Mask by Color (" + base::convert_to(time) + ")"); + double time = chrono.elapsed(); + m_window->setText("Mask by Color (" + base::convert_to(time) + ")"); #endif - update_screen_for_document(writer.document()); - } + update_screen_for_document(writer.document()); } } diff --git a/src/app/commands/cmd_options.cpp b/src/app/commands/cmd_options.cpp index 284b4ce53..711f9faca 100644 --- a/src/app/commands/cmd_options.cpp +++ b/src/app/commands/cmd_options.cpp @@ -784,10 +784,15 @@ public: m_context->activeDocument() && m_context->activeDocument()->sprite() && m_context->activeDocument()->sprite()->gridBounds() != gridBounds()) { - ContextWriter writer(m_context); - Tx tx(writer, Strings::commands_GridSettings(), ModifyDocument); - tx(new cmd::SetGridBounds(writer.sprite(), gridBounds())); - tx.commit(); + try { + ContextWriter writer(m_context, 1000); + Tx tx(writer, Strings::commands_GridSettings(), ModifyDocument); + tx(new cmd::SetGridBounds(writer.sprite(), gridBounds())); + tx.commit(); + } + catch (const std::exception& ex) { + Console::showException(ex); + } } m_curPref->show.grid(gridVisible()->isSelected()); diff --git a/src/app/commands/cmd_sprite_properties.cpp b/src/app/commands/cmd_sprite_properties.cpp index 51a2c133b..32a442ab4 100644 --- a/src/app/commands/cmd_sprite_properties.cpp +++ b/src/app/commands/cmd_sprite_properties.cpp @@ -9,31 +9,32 @@ #include "config.h" #endif +#include "app/cmd/add_tileset.h" #include "app/cmd/assign_color_profile.h" #include "app/cmd/convert_color_profile.h" -#include "app/cmd/add_tileset.h" #include "app/cmd/remove_tileset.h" #include "app/cmd/set_pixel_ratio.h" #include "app/cmd/set_user_data.h" #include "app/color.h" #include "app/commands/command.h" +#include "app/console.h" #include "app/context_access.h" #include "app/doc_api.h" #include "app/i18n/strings.h" #include "app/modules/gui.h" #include "app/pref/preferences.h" -#include "app/util/tileset_utils.h" #include "app/tx.h" #include "app/ui/color_button.h" -#include "app/ui/user_data_view.h" #include "app/ui/skin/skin_theme.h" +#include "app/ui/user_data_view.h" #include "app/util/pixel_ratio.h" +#include "app/util/tileset_utils.h" #include "base/mem_utils.h" #include "doc/image.h" #include "doc/palette.h" #include "doc/sprite.h" -#include "doc/user_data.h" #include "doc/tilesets.h" +#include "doc/user_data.h" #include "fmt/format.h" #include "os/color_space.h" #include "os/system.h" @@ -352,12 +353,17 @@ void SpritePropertiesCommand::onExecute(Context* context) [&](){ selectedColorProfile = window.colorProfile()->getSelectedItemIndex(); - ContextWriter writer(context); - Sprite* sprite(writer.sprite()); - Tx tx(writer, Strings::sprite_properties_assign_color_profile()); - tx(new cmd::AssignColorProfile( - sprite, colorSpaces[selectedColorProfile]->gfxColorSpace())); - tx.commit(); + try { + ContextWriter writer(context); + Sprite* sprite(writer.sprite()); + Tx tx(writer, Strings::sprite_properties_assign_color_profile()); + tx(new cmd::AssignColorProfile( + sprite, colorSpaces[selectedColorProfile]->gfxColorSpace())); + tx.commit(); + } + catch (const base::Exception& e) { + Console::showException(e); + } updateButtons(); }); @@ -365,12 +371,17 @@ void SpritePropertiesCommand::onExecute(Context* context) [&](){ selectedColorProfile = window.colorProfile()->getSelectedItemIndex(); - ContextWriter writer(context); - Sprite* sprite(writer.sprite()); - Tx tx(writer, Strings::sprite_properties_convert_color_profile()); - tx(new cmd::ConvertColorProfile( - sprite, colorSpaces[selectedColorProfile]->gfxColorSpace())); - tx.commit(); + try { + ContextWriter writer(context); + Sprite* sprite(writer.sprite()); + Tx tx(writer, Strings::sprite_properties_convert_color_profile()); + tx(new cmd::ConvertColorProfile( + sprite, colorSpaces[selectedColorProfile]->gfxColorSpace())); + tx.commit(); + } + catch (const base::Exception& e) { + Console::showException(e); + } updateButtons(); }); diff --git a/src/app/render/shader_renderer.cpp b/src/app/render/shader_renderer.cpp index a4abdae9d..5461719cd 100644 --- a/src/app/render/shader_renderer.cpp +++ b/src/app/render/shader_renderer.cpp @@ -88,19 +88,9 @@ ShaderRenderer::ShaderRenderer() m_properties.renderBgOnScreen = true; m_properties.requiresRgbaBackbuffer = true; - auto makeShader = [](const char* code) { - auto result = SkRuntimeEffect::MakeForShader(SkString(code)); - if (!result.errorText.isEmpty()) { - LOG(ERROR, "Shader error: %s\n", result.errorText.c_str()); - std::printf("Shader error: %s\n", result.errorText.c_str()); - throw std::runtime_error("Cannot compile shaders for ShaderRenderer"); - } - return result; - }; - - m_bgEffect = makeShader(kBgShaderCode).effect; - m_indexedEffect = makeShader(kIndexedShaderCode).effect; - m_grayscaleEffect = makeShader(kGrayscaleShaderCode).effect; + m_bgEffect = make_shader(kBgShaderCode); + m_indexedEffect = make_shader(kIndexedShaderCode); + m_grayscaleEffect = make_shader(kGrayscaleShaderCode); } ShaderRenderer::~ShaderRenderer() = default; @@ -417,21 +407,11 @@ void ShaderRenderer::drawImage(SkCanvas* canvas, const int opacity, const doc::BlendMode blendMode) { - auto skData = SkData::MakeWithoutCopy( - (const void*)srcImage->getPixelAddress(0, 0), - srcImage->rowBytes() * srcImage->height()); + auto skImg = make_skimage_for_docimage(srcImage); switch (srcImage->colorMode()) { case doc::ColorMode::RGB: { - auto skImg = SkImages::RasterFromData( - SkImageInfo::Make(srcImage->width(), - srcImage->height(), - kRGBA_8888_SkColorType, - kUnpremul_SkAlphaType), - skData, - srcImage->rowBytes()); - SkPaint p; p.setAlpha(opacity); p.setBlendMode(to_skia(blendMode)); @@ -444,15 +424,6 @@ void ShaderRenderer::drawImage(SkCanvas* canvas, } case doc::ColorMode::GRAYSCALE: { - // We use kR8G8_unorm_SkColorType to access gray and alpha - auto skImg = SkImages::RasterFromData( - SkImageInfo::Make(srcImage->width(), - srcImage->height(), - kR8G8_unorm_SkColorType, - kOpaque_SkAlphaType), - skData, - srcImage->rowBytes()); - SkRuntimeShaderBuilder builder(m_grayscaleEffect); builder.child("iImg") = skImg->makeRawShader(SkSamplingOptions(SkFilterMode::kNearest)); @@ -472,15 +443,6 @@ void ShaderRenderer::drawImage(SkCanvas* canvas, } case doc::ColorMode::INDEXED: { - // We use kAlpha_8_SkColorType to access to the index value through the alpha channel - auto skImg = SkImages::RasterFromData( - SkImageInfo::Make(srcImage->width(), - srcImage->height(), - kAlpha_8_SkColorType, - kUnpremul_SkAlphaType), - skData, - srcImage->rowBytes()); - // Use the palette data as an "width x height" image where // width=number of palette colors, and height=1 const size_t palSize = sizeof(color_t) * m_palette.size(); diff --git a/src/app/ui/color_selector.cpp b/src/app/ui/color_selector.cpp index bdbeeea27..8a92bc475 100644 --- a/src/app/ui/color_selector.cpp +++ b/src/app/ui/color_selector.cpp @@ -655,35 +655,22 @@ bool ColorSelector::buildEffects() if (!m_mainEffect) { if (const char* code = getMainAreaShader()) - m_mainEffect = buildEffect(code); + m_mainEffect = make_shader(code); } if (!m_bottomEffect) { if (const char* code = getBottomBarShader()) - m_bottomEffect = buildEffect(code); + m_bottomEffect = make_shader(code); } if (!m_alphaEffect) { if (const char* code = getAlphaBarShader()) - m_alphaEffect = buildEffect(code); + m_alphaEffect = make_shader(code); } return (m_mainEffect && m_bottomEffect && m_alphaEffect); } -sk_sp ColorSelector::buildEffect(const char* code) -{ - auto result = SkRuntimeEffect::MakeForShader(SkString(code)); - if (!result.errorText.isEmpty()) { - LOG(ERROR, "Shader error: %s\n", result.errorText.c_str()); - std::printf("Shader error: %s\n", result.errorText.c_str()); - return nullptr; - } - else { - return result.effect; - } -} - void ColorSelector::resetBottomEffect() { m_bottomEffect.reset(); diff --git a/src/app/ui/color_selector.h b/src/app/ui/color_selector.h index 438cd591b..0d2047459 100644 --- a/src/app/ui/color_selector.h +++ b/src/app/ui/color_selector.h @@ -121,7 +121,6 @@ namespace app { #if SK_ENABLE_SKSL static const char* getAlphaBarShader(); bool buildEffects(); - sk_sp buildEffect(const char* code); #endif // Internal flag used to lock the modification of m_color. diff --git a/src/app/ui/editor/moving_selection_state.cpp b/src/app/ui/editor/moving_selection_state.cpp index 56bc0b77e..ef829a56f 100644 --- a/src/app/ui/editor/moving_selection_state.cpp +++ b/src/app/ui/editor/moving_selection_state.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2022 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2017-2018 David Capello // // This program is distributed under the terms of @@ -14,6 +14,7 @@ #include "app/cmd/set_mask_position.h" #include "app/commands/command.h" #include "app/commands/commands.h" +#include "app/console.h" #include "app/context_access.h" #include "app/tx.h" #include "app/ui/editor/editor.h" @@ -80,12 +81,15 @@ EditorState::LeaveAction MovingSelectionState::onLeaveState(Editor* editor, Edit doc->generateMaskBoundaries(); } else { - { + try { ContextWriter writer(UIContext::instance(), 1000); Tx tx(writer, "Move Selection Edges", DoesntModifyDocument); tx(new cmd::SetMaskPosition(doc, newOrigin)); tx.commit(); } + catch (const base::Exception& e) { + Console::showException(e); + } doc->resetTransformation(); } doc->notifyGeneralUpdate(); diff --git a/src/app/ui/input_chain_element.h b/src/app/ui/input_chain_element.h index 3a96135a5..17df4bec2 100644 --- a/src/app/ui/input_chain_element.h +++ b/src/app/ui/input_chain_element.h @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -29,6 +30,8 @@ namespace app { virtual bool onCanPaste(Context* ctx) = 0; virtual bool onCanClear(Context* ctx) = 0; + // These commands are executed from Context::executeCommand() + // which catch any exception that is thrown. virtual bool onCut(Context* ctx) = 0; virtual bool onCopy(Context* ctx) = 0; virtual bool onPaste(Context* ctx) = 0; diff --git a/src/app/ui/timeline/timeline.cpp b/src/app/ui/timeline/timeline.cpp index 297edc02f..be25e7ae9 100644 --- a/src/app/ui/timeline/timeline.cpp +++ b/src/app/ui/timeline/timeline.cpp @@ -1347,7 +1347,7 @@ bool Timeline::onProcessMessage(Message* msg) else if (mouseMsg->left()) { Command* command = Commands::instance() ->byId(CommandId::FrameTagProperties()); - UIContext::instance()->executeCommand(command, params); + m_context->executeCommand(command, params); } } break; @@ -1389,13 +1389,18 @@ bool Timeline::onProcessMessage(Message* msg) if (tag) { if ((m_state == STATE_RESIZING_TAG_LEFT && tag->fromFrame() != m_resizeTagData.from) || (m_state == STATE_RESIZING_TAG_RIGHT && tag->toFrame() != m_resizeTagData.to)) { - ContextWriter writer(UIContext::instance()); - Tx tx(writer, Strings::commands_FrameTagProperties()); - tx(new cmd::SetTagRange( - tag, - (m_state == STATE_RESIZING_TAG_LEFT ? m_resizeTagData.from: tag->fromFrame()), - (m_state == STATE_RESIZING_TAG_RIGHT ? m_resizeTagData.to: tag->toFrame()))); - tx.commit(); + try { + ContextWriter writer(m_context); + Tx tx(writer, Strings::commands_FrameTagProperties()); + tx(new cmd::SetTagRange( + tag, + (m_state == STATE_RESIZING_TAG_LEFT ? m_resizeTagData.from: tag->fromFrame()), + (m_state == STATE_RESIZING_TAG_RIGHT ? m_resizeTagData.to: tag->toFrame()))); + tx.commit(); + } + catch (const base::Exception& e) { + Console::showException(e); + } regenerateRows(); } @@ -1431,7 +1436,7 @@ bool Timeline::onProcessMessage(Message* msg) Command* command = Commands::instance() ->byId(CommandId::LayerProperties()); - UIContext::instance()->executeCommand(command); + m_context->executeCommand(command); return true; } @@ -1441,7 +1446,7 @@ bool Timeline::onProcessMessage(Message* msg) Params params; params.set("frame", "current"); - UIContext::instance()->executeCommand(command, params); + m_context->executeCommand(command, params); return true; } @@ -1449,7 +1454,7 @@ bool Timeline::onProcessMessage(Message* msg) Command* command = Commands::instance() ->byId(CommandId::CelProperties()); - UIContext::instance()->executeCommand(command); + m_context->executeCommand(command); return true; } diff --git a/src/app/util/shader_helpers.cpp b/src/app/util/shader_helpers.cpp new file mode 100644 index 000000000..265b1dc48 --- /dev/null +++ b/src/app/util/shader_helpers.cpp @@ -0,0 +1,101 @@ +// Aseprite +// Copyright (C) 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 + +#if SK_ENABLE_SKSL + +#include "app/util/shader_helpers.h" + +#include "base/exception.h" +#include "doc/image.h" +#include "fmt/format.h" + +#include "include/effects/SkRuntimeEffect.h" +#include "src/core/SkRuntimeEffectPriv.h" + +namespace app { + +sk_sp make_shader(const char* code) +{ + SkRuntimeEffect::Options options; + + // Allow usage of private functions like $hsl_to_rgb without a SkSL + // compilation error at runtime. + SkRuntimeEffectPriv::AllowPrivateAccess(&options); + + auto result = SkRuntimeEffect::MakeForShader(SkString(code), options); + if (!result.errorText.isEmpty()) { + std::string error = fmt::format("Error compiling shader.\nError: {}\n", + result.errorText.c_str()); + LOG(ERROR, error.c_str()); + std::printf("%s", error.c_str()); + throw base::Exception(error); + } + return result.effect; +} + +SkImageInfo get_skimageinfo_for_docimage(const doc::Image* img) +{ + switch (img->colorMode()) { + + case doc::ColorMode::RGB: + return SkImageInfo::Make(img->width(), + img->height(), + kRGBA_8888_SkColorType, + kUnpremul_SkAlphaType); + + case doc::ColorMode::GRAYSCALE: + // We use kR8G8_unorm_SkColorType to access gray and alpha + return SkImageInfo::Make(img->width(), + img->height(), + kR8G8_unorm_SkColorType, + kOpaque_SkAlphaType); + + case doc::ColorMode::INDEXED: { + // We use kAlpha_8_SkColorType to access to the index value through the alpha channel + return SkImageInfo::Make(img->width(), + img->height(), + kAlpha_8_SkColorType, + kUnpremul_SkAlphaType); + + } + } + return SkImageInfo(); +} + +sk_sp make_skimage_for_docimage(const doc::Image* img) +{ + switch (img->colorMode()) { + case doc::ColorMode::RGB: + case doc::ColorMode::GRAYSCALE: + case doc::ColorMode::INDEXED: { + auto skData = SkData::MakeWithoutCopy( + (const void*)img->getPixelAddress(0, 0), + img->rowBytes() * img->height()); + + return SkImages::RasterFromData( + get_skimageinfo_for_docimage(img), + skData, + img->rowBytes()); + } + } + return nullptr; +} + +std::unique_ptr make_skcanvas_for_docimage(const doc::Image* img) +{ + return SkCanvas::MakeRasterDirect( + get_skimageinfo_for_docimage(img), + (void*)img->getPixelAddress(0, 0), + img->rowBytes()); +} + +} // namespace app + +#endif // SK_ENABLE_SKSL diff --git a/src/app/util/shader_helpers.h b/src/app/util/shader_helpers.h index 414ef6a28..7dd11a9f8 100644 --- a/src/app/util/shader_helpers.h +++ b/src/app/util/shader_helpers.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2022 Igara Studio S.A. +// Copyright (C) 2022-2024 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -8,15 +8,30 @@ #define APP_UTIL_SHADER_HELPERS_H_INCLUDED #pragma once -#if SK_ENABLE_SKSL +#if LAF_SKIA #include "app/color.h" #include "gfx/color.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkImage.h" #include "include/core/SkM44.h" +#include "include/core/SkRefCnt.h" + +#if SK_ENABLE_SKSL + #include "include/effects/SkRuntimeEffect.h" +#endif + +#include + +namespace doc { + class Image; +} namespace app { +#if SK_ENABLE_SKSL + // rgb_to_hsl() and hsv_to_hsl() functions by Sam Hocevar licensed // under WTFPL (https://en.wikipedia.org/wiki/WTFPL) // Source: @@ -71,8 +86,16 @@ inline SkV4 appColorHsl_to_SkV4(const app::Color& color) { float(color.getAlpha() / 255.0)}; } +sk_sp make_shader(const char* code); + +#endif // SK_ENABLE_SKSL + +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); + } // namespace app -#endif +#endif // LAF_SKIA #endif diff --git a/src/clip b/src/clip index 94693e241..835cd0f7e 160000 --- a/src/clip +++ b/src/clip @@ -1 +1 @@ -Subproject commit 94693e2414a2c69a8ca16f065240c80a94cc6221 +Subproject commit 835cd0f7e7a964bb969482117856bc56a0ac12bf