mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-01 18:00:26 +00:00
Add support to render indexed images
For this we need a new SkSL shader that receives the palette and the indexed image data and outputs the RGBA values for each fragment.
This commit is contained in:
parent
af074fd6bf
commit
9e08386a28
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2022 Igara Studio S.A.
|
||||
// Copyright (C) 2022-2023 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -33,6 +33,16 @@ half4 main(vec2 fragcoord) {
|
||||
}
|
||||
)";
|
||||
|
||||
const char* kIndexedShaderCode = R"(
|
||||
uniform shader iImg;
|
||||
uniform shader iPal;
|
||||
|
||||
half4 main(vec2 fragcoord) {
|
||||
int index = int(255.0 * iImg.eval(fragcoord).a);
|
||||
return iPal.eval(half2(index, 0));
|
||||
}
|
||||
)";
|
||||
|
||||
inline SkBlendMode to_skia(const doc::BlendMode bm) {
|
||||
switch (bm) {
|
||||
case doc::BlendMode::NORMAL: return SkBlendMode::kSrcOver;
|
||||
@ -65,13 +75,18 @@ ShaderRenderer::ShaderRenderer()
|
||||
m_properties.renderBgOnScreen = true;
|
||||
m_properties.requiresRgbaBackbuffer = true;
|
||||
|
||||
auto result = SkRuntimeEffect::MakeForShader(SkString(kBgShaderCode));
|
||||
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");
|
||||
}
|
||||
m_bgEffect = result.effect;
|
||||
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;
|
||||
}
|
||||
|
||||
ShaderRenderer::~ShaderRenderer() = default;
|
||||
@ -157,6 +172,25 @@ void ShaderRenderer::renderSprite(os::Surface* dstSurface,
|
||||
const doc::frame_t frame,
|
||||
const gfx::ClipF& area)
|
||||
{
|
||||
m_sprite = sprite;
|
||||
|
||||
// Copy the current color palette to a 256 palette (so all entries
|
||||
// outside the valid range will be transparent in the kIndexedShaderCode)
|
||||
if (m_sprite->pixelFormat() == IMAGE_INDEXED) {
|
||||
const auto srcPal = m_sprite->palette(frame);
|
||||
m_palette.resize(256, 0);
|
||||
for (int i=0; i<srcPal->size(); ++i)
|
||||
m_palette.setEntry(i, srcPal->entry(i));
|
||||
|
||||
m_bgLayer = sprite->backgroundLayer();
|
||||
if (!m_bgLayer || !m_bgLayer->isVisible()) {
|
||||
afterBackgroundLayerIsPainted();
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_bgLayer = nullptr;
|
||||
}
|
||||
|
||||
SkCanvas* canvas = &static_cast<os::SkiaSurface*>(dstSurface)->canvas();
|
||||
canvas->save();
|
||||
{
|
||||
@ -308,6 +342,10 @@ void ShaderRenderer::drawLayerGroup(SkCanvas* canvas,
|
||||
frame, area);
|
||||
break;
|
||||
}
|
||||
|
||||
if (layer == m_bgLayer) {
|
||||
afterBackgroundLayerIsPainted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -318,11 +356,11 @@ void ShaderRenderer::renderCheckeredBackground(os::Surface* dstSurface,
|
||||
SkRuntimeShaderBuilder builder(m_bgEffect);
|
||||
builder.uniform("iBg1") = gfxColor_to_SkV4(
|
||||
color_utils::color_for_ui(
|
||||
app::Color::fromImage(sprite->pixelFormat(),
|
||||
app::Color::fromImage(m_bgOptions.colorPixelFormat,
|
||||
m_bgOptions.color1)));
|
||||
builder.uniform("iBg2") = gfxColor_to_SkV4(
|
||||
color_utils::color_for_ui(
|
||||
app::Color::fromImage(sprite->pixelFormat(),
|
||||
app::Color::fromImage(m_bgOptions.colorPixelFormat,
|
||||
m_bgOptions.color2)));
|
||||
|
||||
float sx = (m_bgOptions.zoom ? m_proj.scaleX(): 1.0);
|
||||
@ -370,25 +408,75 @@ void ShaderRenderer::drawImage(SkCanvas* canvas,
|
||||
(const void*)srcImage->getPixelAddress(0, 0),
|
||||
srcImage->getMemSize());
|
||||
|
||||
// TODO support other color modes
|
||||
ASSERT(srcImage->colorMode() == doc::ColorMode::RGB);
|
||||
switch (srcImage->colorMode()) {
|
||||
|
||||
auto skImg = SkImage::MakeRasterData(
|
||||
SkImageInfo::Make(srcImage->width(),
|
||||
srcImage->height(),
|
||||
kRGBA_8888_SkColorType,
|
||||
kUnpremul_SkAlphaType),
|
||||
skData,
|
||||
srcImage->getRowStrideSize());
|
||||
case doc::ColorMode::RGB: {
|
||||
auto skImg = SkImage::MakeRasterData(
|
||||
SkImageInfo::Make(srcImage->width(),
|
||||
srcImage->height(),
|
||||
kRGBA_8888_SkColorType,
|
||||
kUnpremul_SkAlphaType),
|
||||
skData,
|
||||
srcImage->getRowStrideSize());
|
||||
|
||||
SkPaint p;
|
||||
p.setAlpha(opacity);
|
||||
p.setBlendMode(to_skia(blendMode));
|
||||
canvas->drawImage(skImg.get(),
|
||||
SkIntToScalar(x),
|
||||
SkIntToScalar(y),
|
||||
SkSamplingOptions(),
|
||||
&p);
|
||||
SkPaint p;
|
||||
p.setAlpha(opacity);
|
||||
p.setBlendMode(to_skia(blendMode));
|
||||
canvas->drawImage(skImg.get(),
|
||||
SkIntToScalar(x),
|
||||
SkIntToScalar(y),
|
||||
SkSamplingOptions(),
|
||||
&p);
|
||||
break;
|
||||
}
|
||||
|
||||
case doc::ColorMode::GRAYSCALE: {
|
||||
break;
|
||||
}
|
||||
|
||||
case doc::ColorMode::INDEXED: {
|
||||
// We use kAlpha_8_SkColorType to access to the index value through the alpha channel
|
||||
auto skImg = SkImage::MakeRasterData(
|
||||
SkImageInfo::Make(srcImage->width(),
|
||||
srcImage->height(),
|
||||
kAlpha_8_SkColorType,
|
||||
kUnpremul_SkAlphaType),
|
||||
skData,
|
||||
srcImage->getRowStrideSize());
|
||||
|
||||
// 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();
|
||||
auto skPalData = SkData::MakeWithoutCopy(
|
||||
(const void*)m_palette.rawColorsData(),
|
||||
palSize);
|
||||
auto skPal = SkImage::MakeRasterData(
|
||||
SkImageInfo::Make(m_palette.size(), 1,
|
||||
kRGBA_8888_SkColorType,
|
||||
kUnpremul_SkAlphaType),
|
||||
skPalData,
|
||||
palSize);
|
||||
|
||||
SkRuntimeShaderBuilder builder(m_indexedEffect);
|
||||
builder.child("iImg") = skImg->makeRawShader(SkSamplingOptions(SkFilterMode::kNearest));
|
||||
builder.child("iPal") = skPal->makeShader(SkSamplingOptions(SkFilterMode::kNearest));
|
||||
|
||||
SkPaint p;
|
||||
p.setAlpha(opacity);
|
||||
p.setBlendMode(to_skia(blendMode));
|
||||
p.setStyle(SkPaint::kFill_Style);
|
||||
p.setShader(builder.makeShader());
|
||||
|
||||
canvas->save();
|
||||
canvas->translate(
|
||||
SkIntToScalar(x),
|
||||
SkIntToScalar(y));
|
||||
canvas->drawRect(SkRect::MakeXYWH(0, 0, srcImage->width(), srcImage->height()), p);
|
||||
canvas->restore();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// TODO this is equal to Render::checkIfWeShouldUsePreview(const Cel*),
|
||||
@ -410,6 +498,17 @@ bool ShaderRenderer::checkIfWeShouldUsePreview(const doc::Cel* cel) const
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShaderRenderer::afterBackgroundLayerIsPainted()
|
||||
{
|
||||
if (m_sprite && m_sprite->pixelFormat() == IMAGE_INDEXED) {
|
||||
// Only after we draw the background layer we set the transparent
|
||||
// index of the sprite as transparent, so the shader can return
|
||||
// the transparent color for this specific index on transparent
|
||||
// layers.
|
||||
m_palette.setEntry(m_sprite->transparentColor(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif // SK_ENABLE_SKSL
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2022 Igara Studio S.A.
|
||||
// Copyright (C) 2022-2023 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -11,6 +11,7 @@
|
||||
#if SK_ENABLE_SKSL
|
||||
|
||||
#include "app/render/renderer.h"
|
||||
#include "doc/palette.h"
|
||||
|
||||
#include "include/core/SkRefCnt.h"
|
||||
|
||||
@ -81,11 +82,15 @@ namespace app {
|
||||
const doc::BlendMode blendMode);
|
||||
|
||||
bool checkIfWeShouldUsePreview(const doc::Cel* cel) const;
|
||||
void afterBackgroundLayerIsPainted();
|
||||
|
||||
Properties m_properties;
|
||||
render::BgOptions m_bgOptions;
|
||||
render::Projection m_proj;
|
||||
sk_sp<SkRuntimeEffect> m_bgEffect;
|
||||
sk_sp<SkRuntimeEffect> m_indexedEffect;
|
||||
const doc::Sprite* m_sprite = nullptr;
|
||||
const doc::LayerImage* m_bgLayer = nullptr;
|
||||
// TODO these members are the same as in render::Render, we should
|
||||
// see a way to merge both
|
||||
const doc::Layer* m_selectedLayerForOpacity = nullptr;
|
||||
@ -95,6 +100,10 @@ namespace app {
|
||||
const doc::Tileset* m_previewTileset = nullptr;
|
||||
gfx::Point m_previewPos;
|
||||
doc::BlendMode m_previewBlendMode = doc::BlendMode::NORMAL;
|
||||
|
||||
// Palette of 256 colors (useful for the indexed shader to set all
|
||||
// colors outside the valid range as transparent RGBA=0 values)
|
||||
doc::Palette m_palette;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -116,6 +116,7 @@ void EditorRender::setupBackground(Doc* doc, doc::PixelFormat pixelFormat)
|
||||
render::BgOptions bg;
|
||||
bg.type = bgType;
|
||||
bg.zoom = docPref.bg.zoom();
|
||||
bg.colorPixelFormat = pixelFormat;
|
||||
bg.color1 = color_utils::color_for_image_without_alpha(docPref.bg.color1(), pixelFormat);
|
||||
bg.color2 = color_utils::color_for_image_without_alpha(docPref.bg.color2(), pixelFormat);
|
||||
bg.stripeSize = tile;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2020-2022 Igara Studio S.A.
|
||||
// Copyright (c) 2020-2023 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -95,11 +95,11 @@ Palette* Palette::createGrayscale()
|
||||
return graypal;
|
||||
}
|
||||
|
||||
void Palette::resize(int ncolors)
|
||||
void Palette::resize(int ncolors, color_t color)
|
||||
{
|
||||
ASSERT(ncolors >= 0);
|
||||
|
||||
m_colors.resize(ncolors, doc::rgba(0, 0, 0, 255));
|
||||
m_colors.resize(ncolors, color);
|
||||
++m_modifications;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2020-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -37,7 +37,10 @@ namespace doc {
|
||||
static Palette* createGrayscale();
|
||||
|
||||
int size() const { return (int)m_colors.size(); }
|
||||
void resize(int ncolors);
|
||||
void resize(int ncolors, color_t color = doc::rgba(0, 0, 0, 255));
|
||||
|
||||
// Used to share the palette data with a SkSL shader
|
||||
const color_t* rawColorsData() const { return m_colors.data(); }
|
||||
|
||||
const std::string& filename() const { return m_filename; }
|
||||
const std::string& comment() const { return m_comment; }
|
||||
|
@ -18,6 +18,7 @@ namespace render {
|
||||
struct BgOptions {
|
||||
BgType type = BgType::TRANSPARENT;
|
||||
bool zoom = false;
|
||||
doc::PixelFormat colorPixelFormat = doc::PixelFormat::IMAGE_RGB;
|
||||
doc::color_t color1 = 0;
|
||||
doc::color_t color2 = 0;
|
||||
gfx::Size stripeSize{16, 16};
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Render Library
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -930,8 +930,9 @@ void Render::renderCheckeredBackground(
|
||||
|
||||
gfx::Rect dstBounds = area.dstBounds();
|
||||
|
||||
// Fix background color (make them opaque)
|
||||
switch (image->pixelFormat()) {
|
||||
// Fix background colors (make them opaque)
|
||||
ASSERT(m_bg.colorPixelFormat == image->pixelFormat());
|
||||
switch (m_bg.colorPixelFormat) {
|
||||
case IMAGE_RGB:
|
||||
m_bg.color1 |= doc::rgba_a_mask;
|
||||
m_bg.color2 |= doc::rgba_a_mask;
|
||||
|
Loading…
Reference in New Issue
Block a user