Refactor the thumbnails generation of cels

A new Render::renderCel() method is used to render the specific cel
thumbnail, useful in the future to render thumbnails of different kind
of layers (e.g. future tilemaps). And now the background is rendered
in a different step (so the thumbnails doesn't contain the
background.)
This commit is contained in:
David Capello 2019-04-11 14:29:20 -03:00
parent 59361a3b6d
commit e3f09525db
9 changed files with 187 additions and 157 deletions

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -41,18 +41,17 @@ namespace {
gfx::Color gridColor1 = gfx::rgba(128, 128, 128);
gfx::Color gridColor2 = gfx::rgba(192, 192, 192);
} // anonymous namespace
static void rectgrid(ui::Graphics* g, const gfx::Rect& rc, const gfx::Size& tile)
void draw_checked_grid(ui::Graphics* g,
const gfx::Rect& rc,
const gfx::Size& tile,
const gfx::Color c1,
const gfx::Color c2)
{
if (tile.w < 1 || tile.h < 1)
return;
int x, y, u, v;
const gfx::Color c1 = gridColor1;
const gfx::Color c2 = gridColor2;
u = 0;
v = 0;
for (y=rc.y; y<rc.y2()-tile.h; y+=tile.h) {
@ -74,6 +73,26 @@ static void rectgrid(ui::Graphics* g, const gfx::Rect& rc, const gfx::Size& tile
}
}
} // anonymous namespace
void draw_checked_grid(ui::Graphics* g,
const gfx::Rect& rc,
const gfx::Size& tile)
{
draw_checked_grid(g, rc, tile,
gridColor1, gridColor2);
}
void draw_checked_grid(ui::Graphics* g,
const gfx::Rect& rc,
const gfx::Size& tile,
DocumentPreferences& docPref)
{
draw_checked_grid(g, rc, tile,
color_utils::color_for_ui(docPref.bg.color1()),
color_utils::color_for_ui(docPref.bg.color2()));
}
void draw_color(ui::Graphics* g,
const Rect& rc,
const app::Color& _color,
@ -90,9 +109,9 @@ void draw_color(ui::Graphics* g,
if (alpha < 255) {
if (rc.w == rc.h)
rectgrid(g, rc, gfx::Size(rc.w/2, rc.h/2));
draw_checked_grid(g, rc, gfx::Size(rc.w/2, rc.h/2));
else
rectgrid(g, rc, gfx::Size(rc.w/4, rc.h/2));
draw_checked_grid(g, rc, gfx::Size(rc.w/4, rc.h/2));
}
if (alpha > 0) {

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -9,9 +10,11 @@
#pragma once
#include "app/color.h"
#include "app/pref/preferences.h"
#include "doc/color_mode.h"
#include "gfx/color.h"
#include "gfx/rect.h"
#include "gfx/size.h"
#include "ui/base.h"
namespace os {
@ -23,7 +26,15 @@ namespace ui {
}
namespace app {
using namespace doc;
void draw_checked_grid(ui::Graphics* g,
const gfx::Rect& rc,
const gfx::Size& tile);
void draw_checked_grid(ui::Graphics* g,
const gfx::Rect& rc,
const gfx::Size& tile,
DocumentPreferences& docPref);
void draw_color(ui::Graphics* g,
const gfx::Rect& rc,

View File

@ -1,100 +1,72 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2018 David Capello
// Copyright (C) 2016 Carlo Caputo
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#include "app/color_utils.h"
#include "app/doc.h"
#include "app/pref/preferences.h"
#include "app/thumbnails.h"
#include "doc/algorithm/resize_image.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "doc/blend_mode.h"
#include "doc/cel.h"
#include "doc/conversion_to_surface.h"
#include "doc/doc.h"
#include "doc/frame.h"
#include "doc/object.h"
#include "doc/object_id.h"
#include "render/render.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include "os/surface.h"
#include "os/system.h"
#include "render/render.h"
namespace app {
namespace thumb {
os::Surface* get_cel_thumbnail(const doc::Cel* cel,
const gfx::Size& thumb_size,
gfx::Rect cel_image_on_thumb)
const gfx::Size& fitInSize)
{
Doc* document = static_cast<Doc*>(cel->sprite()->document());
doc::frame_t frame = cel->frame();
doc::Image* image = cel->image();
gfx::Size newSize;
DocumentPreferences& docPref = Preferences::instance().document(document);
if (cel->bounds().w > fitInSize.w ||
cel->bounds().h > fitInSize.h)
newSize = gfx::Rect(cel->bounds()).fitIn(gfx::Rect(fitInSize)).size();
else
newSize = cel->bounds().size();
doc::color_t bg1 = color_utils::color_for_image(docPref.bg.color1(), image->pixelFormat());
doc::color_t bg2 = color_utils::color_for_image(docPref.bg.color2(), image->pixelFormat());
if (newSize.w < 1 ||
newSize.h < 1)
return nullptr;
gfx::Size image_size = image->size();
doc::ImageRef thumbnailImage(
doc::Image::create(
doc::IMAGE_RGB, newSize.w, newSize.h));
if (cel_image_on_thumb.isEmpty()) {
double zw = thumb_size.w / (double)image_size.w;
double zh = thumb_size.h / (double)image_size.h;
double zoom = MIN(1.0, MIN(zw, zh));
render::Render render;
render::Projection proj(cel->sprite()->pixelRatio(),
render::Zoom(newSize.w, cel->image()->width()));
render.setProjection(proj);
cel_image_on_thumb = gfx::Rect(
(int)(thumb_size.w * 0.5 - image_size.w * zoom * 0.5),
(int)(thumb_size.h * 0.5 - image_size.h * zoom * 0.5),
MAX(1, (int)(image_size.w * zoom)),
MAX(1, (int)(image_size.h * zoom)));
const doc::Palette* palette = cel->sprite()->palette(cel->frame());
render.renderCel(
thumbnailImage.get(),
cel->sprite(),
cel->image(),
cel->layer(),
palette,
gfx::Rect(gfx::Point(0, 0), cel->bounds().size()),
gfx::Clip(gfx::Rect(gfx::Point(0, 0), newSize)),
255, doc::BlendMode::NORMAL);
if (os::Surface* thumbnail = os::instance()->createRgbaSurface(
thumbnailImage->width(),
thumbnailImage->height())) {
convert_image_to_surface(
thumbnailImage.get(), palette, thumbnail,
0, 0, 0, 0, thumbnailImage->width(), thumbnailImage->height());
return thumbnail;
}
const doc::Sprite* sprite = document->sprite();
// TODO doc::Image::createCopy() from pre-rendered checkered background
std::unique_ptr<doc::Image> thumb_img(doc::Image::create(
image->pixelFormat(), thumb_size.w, thumb_size.h));
int block_size = MID(4, thumb_size.w/8, 16);
for (int dst_y = 0; dst_y < thumb_size.h; dst_y++) {
for (int dst_x = 0; dst_x < thumb_size.w; dst_x++) {
thumb_img->putPixel(dst_x, dst_y,
(((dst_x / block_size) % 2) ^
((dst_y / block_size) % 2)) ? bg2: bg1);
}
}
std::unique_ptr<doc::Image> scale_img;
const doc::Image* source = image;
if (cel_image_on_thumb.w != image_size.w || cel_image_on_thumb.h != image_size.h) {
scale_img.reset(doc::Image::create(
image->pixelFormat(), cel_image_on_thumb.w, cel_image_on_thumb.h));
doc::algorithm::resize_image(
image, scale_img.get(),
doc::algorithm::ResizeMethod::RESIZE_METHOD_NEAREST_NEIGHBOR,
sprite->palette(frame),
sprite->rgbMap(frame),
sprite->transparentColor());
source = scale_img.get();
}
render::composite_image(
thumb_img.get(), source,
sprite->palette(frame),
cel_image_on_thumb.x,
cel_image_on_thumb.y,
255, BlendMode::NORMAL);
os::Surface* thumb_surf = os::instance()->createRgbaSurface(
thumb_img->width(), thumb_img->height());
convert_image_to_surface(thumb_img.get(), sprite->palette(frame), thumb_surf,
0, 0, 0, 0, thumb_img->width(), thumb_img->height());
return thumb_surf;
else
return nullptr;
}
} // thumb

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2016 Carlo Caputo
//
// This program is distributed under the terms of
@ -8,7 +9,6 @@
#define APP_THUMBNAILS_H_INCLUDED
#pragma once
#include "gfx/rect.h"
#include "gfx/size.h"
namespace doc {
@ -20,13 +20,12 @@ namespace os {
}
namespace app {
namespace thumb {
namespace thumb {
os::Surface* get_cel_thumbnail(const doc::Cel* cel,
const gfx::Size& thumb_size,
gfx::Rect cel_image_on_thumb = gfx::Rect());
os::Surface* get_cel_thumbnail(const doc::Cel* cel,
const gfx::Size& fitInSize);
} // thumb
} // thumb
} // app
#endif

View File

@ -44,6 +44,7 @@
#include "app/util/layer_boundaries.h"
#include "app/util/readable_time.h"
#include "base/bind.h"
#include "base/clamp.h"
#include "base/convert_to.h"
#include "base/memory.h"
#include "base/scoped_value.h"
@ -2236,10 +2237,14 @@ void Timeline::drawCel(ui::Graphics* g, layer_t layerIndex, frame_t frame, Cel*
skinTheme()->calcBorder(this, style));
if (!thumb_bounds.isEmpty()) {
os::Surface* thumb_surf = thumb::get_cel_thumbnail(cel, thumb_bounds.size());
if (thumb_surf) {
g->drawRgbaSurface(thumb_surf, thumb_bounds.x, thumb_bounds.y);
thumb_surf->dispose();
if (os::Surface* surface = thumb::get_cel_thumbnail(cel, thumb_bounds.size())) {
const int t = base::clamp(thumb_bounds.w/8, 4, 16);
draw_checked_grid(g, thumb_bounds, gfx::Size(t, t), docPref());
g->drawRgbaSurface(surface,
thumb_bounds.center().x-surface->width()/2,
thumb_bounds.center().y-surface->height()/2);
surface->dispose();
}
}
}
@ -2251,7 +2256,7 @@ void Timeline::drawCel(ui::Graphics* g, layer_t layerIndex, frame_t frame, Cel*
void Timeline::updateCelOverlayBounds(const Hit& hit)
{
gfx::Rect inner, outer;
gfx::Rect rc;
if (docPref().thumbnails.overlayEnabled() && hit.part == PART_CEL) {
m_thumbnailsOverlayHit = hit;
@ -2271,88 +2276,66 @@ void Timeline::updateCelOverlayBounds(const Hit& hit)
gfx::Point center = client_bounds.center();
gfx::Rect bounds_cel = getPartBounds(m_thumbnailsOverlayHit);
inner = gfx::Rect(
rc = gfx::Rect(
bounds_cel.x + m_thumbnailsOverlayDirection.x,
bounds_cel.y + m_thumbnailsOverlayDirection.y,
width,
height
);
height);
if (!client_bounds.contains(inner)) {
if (!client_bounds.contains(rc)) {
m_thumbnailsOverlayDirection = gfx::Point(
bounds_cel.x < center.x ? (int)(frameBoxWidth()*1.0) : -width,
bounds_cel.y < center.y ? (int)(frameBoxWidth()*0.5) : -height+(int)(frameBoxWidth()*0.5)
);
inner.setOrigin(gfx::Point(
bounds_cel.y < center.y ? (int)(frameBoxWidth()*0.5) : -height+(int)(frameBoxWidth()*0.5));
rc.setOrigin(gfx::Point(
bounds_cel.x + m_thumbnailsOverlayDirection.x,
bounds_cel.y + m_thumbnailsOverlayDirection.y
));
bounds_cel.y + m_thumbnailsOverlayDirection.y));
}
outer = gfx::Rect(inner).enlarge(1);
}
else {
outer = gfx::Rect(0, 0, 0, 0);
rc = gfx::Rect(0, 0, 0, 0);
}
if (outer != m_thumbnailsOverlayOuter) {
if (!m_thumbnailsOverlayOuter.isEmpty()) {
invalidateRect(gfx::Rect(m_thumbnailsOverlayOuter).offset(origin()));
}
if (!outer.isEmpty()) {
invalidateRect(gfx::Rect(outer).offset(origin()));
}
m_thumbnailsOverlayVisible = !outer.isEmpty();
m_thumbnailsOverlayOuter = outer;
m_thumbnailsOverlayInner = inner;
}
if (rc == m_thumbnailsOverlayBounds)
return;
if (!m_thumbnailsOverlayBounds.isEmpty())
invalidateRect(gfx::Rect(m_thumbnailsOverlayBounds).offset(origin()));
if (!rc.isEmpty())
invalidateRect(gfx::Rect(rc).offset(origin()));
m_thumbnailsOverlayVisible = !rc.isEmpty();
m_thumbnailsOverlayBounds = rc;
}
void Timeline::drawCelOverlay(ui::Graphics* g)
{
if (!m_thumbnailsOverlayVisible) {
if (!m_thumbnailsOverlayVisible)
return;
}
Layer* layer = m_rows[m_thumbnailsOverlayHit.layer].layer();
Cel* cel = layer->cel(m_thumbnailsOverlayHit.frame);
if (!cel) {
if (!cel)
return;
}
Image* image = cel->image();
if (!image) {
return;
}
IntersectClip clip(g, m_thumbnailsOverlayOuter);
Image* image = cel->image();
if (!image)
return;
IntersectClip clip(g, m_thumbnailsOverlayBounds);
if (!clip)
return;
double scale = (
m_sprite->width() > m_sprite->height() ?
m_thumbnailsOverlayInner.w / (double)m_sprite->width() :
m_thumbnailsOverlayInner.h / (double)m_sprite->height()
);
gfx::Rect rc = m_sprite->bounds().fitIn(
gfx::Rect(m_thumbnailsOverlayBounds).shrink(1));
if (os::Surface* surface = thumb::get_cel_thumbnail(cel, rc.size())) {
draw_checked_grid(g, rc, gfx::Size(8, 8)*ui::guiscale(), docPref());
gfx::Size overlay_size(
m_thumbnailsOverlayInner.w,
m_thumbnailsOverlayInner.h
);
gfx::Rect cel_image_on_overlay(
(int)(cel->x() * scale),
(int)(cel->y() * scale),
(int)(image->width() * scale),
(int)(image->height() * scale)
);
os::Surface* overlay_surf = thumb::get_cel_thumbnail(cel, overlay_size, cel_image_on_overlay);
g->drawRgbaSurface(overlay_surf,
m_thumbnailsOverlayInner.x, m_thumbnailsOverlayInner.y);
g->drawRect(gfx::rgba(0,0,0,255), m_thumbnailsOverlayOuter);
overlay_surf->dispose();
g->drawRgbaSurface(surface,
rc.center().x-surface->width()/2,
rc.center().y-surface->height()/2);
g->drawRect(gfx::rgba(0, 0, 0, 128), m_thumbnailsOverlayBounds);
surface->dispose();
}
}
void Timeline::drawCelLinkDecorators(ui::Graphics* g, const gfx::Rect& bounds,

View File

@ -407,8 +407,7 @@ namespace app {
// Data used for thumbnails.
bool m_thumbnailsOverlayVisible;
gfx::Rect m_thumbnailsOverlayInner;
gfx::Rect m_thumbnailsOverlayOuter;
gfx::Rect m_thumbnailsOverlayBounds;
Hit m_thumbnailsOverlayHit;
gfx::Point m_thumbnailsOverlayDirection;
obs::connection m_thumbnailsPrefConn;

View File

@ -479,7 +479,7 @@ template<class DstTraits, class SrcTraits>
CompositeImageFunc get_fastest_composition_path(const Projection& proj,
const bool finegrain)
{
if (finegrain) {
if (finegrain || !proj.zoom().isSimpleZoomLevel()) {
return composite_image_general<DstTraits, SrcTraits>;
}
else if (proj.applyX(1) == 1 && proj.applyY(1) == 1) {
@ -490,9 +490,7 @@ CompositeImageFunc get_fastest_composition_path(const Projection& proj,
}
// Slower composite function for special cases with odd zoom and non-square pixel ratio
else if (((proj.removeX(1) > 1) && (proj.removeX(1) & 1)) ||
((proj.removeY(1) > 1) && (proj.removeY(1) & 1)) ||
(proj.applyX(1.0) - proj.applyX(1) > 0.01) ||
(proj.applyY(1.0) - proj.applyY(1) > 0.01)) {
((proj.removeY(1) > 1) && (proj.removeY(1) & 1))) {
return composite_image_general<DstTraits, SrcTraits>;
}
else {
@ -1175,6 +1173,37 @@ void Render::renderLayer(
}
}
void Render::renderCel(
Image* dst_image,
const Sprite* sprite,
const Image* cel_image,
const Layer* cel_layer,
const Palette* pal,
const gfx::RectF& celBounds,
const gfx::Clip& area,
const int opacity,
const BlendMode blendMode)
{
m_sprite = sprite;
CompositeImageFunc compositeImage =
getImageComposition(
dst_image->pixelFormat(),
sprite->pixelFormat(), nullptr);
if (!compositeImage)
return;
renderCel(
dst_image,
cel_image,
pal,
celBounds,
area,
compositeImage,
opacity,
blendMode);
}
void Render::renderCel(
Image* dst_image,
const Image* cel_image,

View File

@ -131,6 +131,17 @@ namespace render {
const int opacity,
const BlendMode blendMode);
void renderCel(
Image* dst_image,
const Sprite* sprite,
const Image* cel_image,
const Layer* cel_layer,
const Palette* pal,
const gfx::RectF& celBounds,
const gfx::Clip& area,
const int opacity,
const BlendMode blendMode);
private:
void renderSpriteLayers(
Image* dstImage,

View File

@ -1,4 +1,5 @@
// Aseprite Render Library
// Copyright (c) 2019 Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -49,6 +50,12 @@ namespace render {
return !operator==(other);
}
// Returns true if this zoom level can be handled by simpler
// rendering techniques.
bool isSimpleZoomLevel() const {
return (m_num == 1 || m_den == 1);
}
static Zoom fromScale(double scale);
static Zoom fromLinearScale(int i);
static int linearValues();