[lua] Add Image:resize() function

Closes: https://community.aseprite.org/t/3633
This commit is contained in:
David Capello 2019-08-13 18:16:30 -03:00
parent b6de9d924b
commit ad1a39714e
12 changed files with 306 additions and 52 deletions

View File

@ -602,6 +602,7 @@ add_library(app-lib
util/pixel_ratio.cpp
util/range_utils.cpp
util/readable_time.cpp
util/resize_image.cpp
util/wrap_point.cpp
xml_document.cpp
xml_exception.cpp

View File

@ -19,6 +19,7 @@
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/sprite_job.h"
#include "app/util/resize_image.h"
#include "base/bind.h"
#include "base/convert_to.h"
#include "doc/algorithm/resize_image.h"
@ -93,38 +94,19 @@ protected:
++cels_count;
}
const gfx::SizeF scale(
double(m_new_width) / double(sprite()->width()),
double(m_new_height) / double(sprite()->height()));
// For each cel...
int progress = 0;
for (Cel* cel : sprite()->uniqueCels()) {
// Get cel's image
Image* image = cel->image();
if (image && !cel->link()) {
// Resize the cel bounds only if it's from a reference layer
if (cel->layer()->isReference()) {
gfx::RectF newBounds = scale_rect<double>(cel->boundsF());
tx()(new cmd::SetCelBoundsF(cel, newBounds));
}
else {
// Change its location
api.setCelPosition(sprite(), cel, scale_x(cel->x()), scale_y(cel->y()));
// Resize the image
int w = scale_x(image->width());
int h = scale_y(image->height());
ImageRef new_image(Image::create(image->pixelFormat(), MAX(1, w), MAX(1, h)));
new_image->setMaskColor(image->maskColor());
doc::algorithm::fixup_image_transparent_colors(image);
doc::algorithm::resize_image(
image, new_image.get(),
m_resize_method,
sprite()->palette(cel->frame()),
sprite()->rgbMap(cel->frame()),
(cel->layer()->isBackground() ? -1: sprite()->transparentColor()));
api.replaceImage(sprite(), cel->imageRef(), new_image);
}
}
resize_cel_image(
tx(), cel, scale,
m_resize_method,
cel->layer()->isReference() ?
-cel->boundsF().origin():
gfx::PointF(-cel->bounds().origin()));
jobProgress((float)progress / cels_count);
++progress;

View File

@ -181,8 +181,7 @@ int Dialog_show(lua_State* L)
lua_pop(L, 1);
type = lua_getfield(L, 2, "bounds");
if (type != LUA_TNONE &&
type != LUA_TNIL) {
if (VALID_LUATYPE(type)) {
const auto rc = convert_args_into_rect(L, -1);
if (!rc.isEmpty()) {
dlg->window.remapWindow();

View File

@ -11,14 +11,18 @@
#include "app/cmd/copy_rect.h"
#include "app/cmd/copy_region.h"
#include "app/commands/new_params.h" // Used for enum <-> Lua conversions
#include "app/context.h"
#include "app/doc.h"
#include "app/file/file.h"
#include "app/script/docobj.h"
#include "app/script/engine.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 "base/fs.h"
#include "doc/algorithm/shrink_bounds.h"
#include "doc/cel.h"
@ -28,6 +32,9 @@
#include "doc/sprite.h"
#include "render/render.h"
#include <algorithm>
#include <memory>
namespace app {
namespace script {
@ -335,6 +342,97 @@ int Image_saveAs(lua_State* L)
return 1;
}
int Image_resize(lua_State* L)
{
auto obj = get_obj<ImageObj>(L, 1);
doc::Image* img = obj->image(L);
Cel* cel = obj->cel(L);
ASSERT(img);
gfx::Size newSize = img->size();
auto method = doc::algorithm::ResizeMethod::RESIZE_METHOD_NEAREST_NEIGHBOR;
gfx::Point pivot(0, 0);
if (lua_istable(L, 2)) {
// Image:resize{ (size | width, height),
// method [, pivot] }
int type = lua_getfield(L, 2, "size");
if (VALID_LUATYPE(type)) {
newSize = convert_args_into_size(L, -1);
lua_pop(L, 1);
}
else {
lua_pop(L, 1);
type = lua_getfield(L, 2, "width");
if (VALID_LUATYPE(type))
newSize.w = lua_tointeger(L, -1);
lua_pop(L, 1);
type = lua_getfield(L, 2, "height");
if (VALID_LUATYPE(type))
newSize.h = lua_tointeger(L, -1);
lua_pop(L, 1);
}
type = lua_getfield(L, 2, "method");
if (VALID_LUATYPE(type)) {
// TODO improve these lua <-> enum conversions, a lot of useless
// work is done to create this dummy NewParams, etc.
NewParams dummyParams;
Param<doc::algorithm::ResizeMethod> param(&dummyParams, method, "method");
param.fromLua(L, -1);
method = param();
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "pivot");
if (VALID_LUATYPE(type))
pivot = convert_args_into_point(L, -1);
lua_pop(L, 1);
}
else {
newSize.w = lua_tointeger(L, 2);
newSize.h = lua_tointeger(L, 3);
}
newSize.w = std::max(1, newSize.w);
newSize.h = std::max(1, newSize.h);
const gfx::SizeF scale(
double(newSize.w) / double(img->width()),
double(newSize.h) / double(img->height()));
// If the destination image is not related to a sprite, we just draw
// the source image without undo information.
if (cel) {
Tx tx;
resize_cel_image(tx, cel, scale, method,
gfx::PointF(pivot));
tx.commit();
obj->imageId = cel->image()->id();
}
else {
Context* ctx = App::instance()->context();
ASSERT(ctx);
Site site = ctx->activeSite();
const doc::Palette* pal = site.palette();
const doc::RgbMap* rgbmap = site.rgbMap();
std::unique_ptr<doc::Image> newImg(
resize_image(img, scale, method,
pal, rgbmap));
// Delete old image, and we put the same ID of the old image into
// the new image so this userdata references the resized image.
delete img;
newImg->setId(obj->imageId);
// Release the image from the smart pointer because now it's owned
// by the ImageObj userdata.
newImg.release();
}
return 0;
}
int Image_get_width(lua_State* L)
{
const auto obj = get_obj<ImageObj>(L, 1);
@ -382,6 +480,7 @@ const luaL_Reg Image_methods[] = {
{ "isEmpty", Image_isEmpty },
{ "isPlain", Image_isPlain },
{ "saveAs", Image_saveAs },
{ "resize", Image_resize },
{ "__gc", Image_gc },
{ "__eq", Image_eq },
{ nullptr, nullptr }

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
@ -23,6 +23,14 @@ extern "C" {
namespace app {
namespace script {
#if LUA_TNONE != -1
#error Invalid LUA_TNONE value
#endif
#if LUA_TNIL != 0
#error Invalid LUA_TNIL value
#endif
#define VALID_LUATYPE(type) ((type) > 0)
// Some of these auxiliary methods are based on code from the Skia
// library (SkLua.cpp file) by Google Inc.

View File

@ -25,6 +25,11 @@ Palette* Site::palette()
return (m_sprite ? m_sprite->palette(m_frame): nullptr);
}
RgbMap* Site::rgbMap() const
{
return (m_sprite ? m_sprite->rgbMap(m_frame): nullptr);
}
const Cel* Site::cel() const
{
if (m_layer)

View File

@ -19,6 +19,7 @@ namespace doc {
class Image;
class Layer;
class Palette;
class RgbMap;
class Sprite;
} // namespace doc
@ -95,6 +96,7 @@ namespace app {
doc::Palette* palette();
doc::Image* image(int* x = nullptr, int* y = nullptr, int* opacity = nullptr) const;
doc::Palette* palette() const;
doc::RgbMap* rgbMap() const;
private:
Focus m_focus;

View File

@ -0,0 +1,99 @@
// Aseprite
// Copyright (c) 2019 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/util/resize_image.h"
#include "app/cmd/replace_image.h"
#include "app/cmd/set_cel_bounds.h"
#include "app/cmd/set_cel_position.h"
#include "app/tx.h"
#include "doc/cel.h"
#include "doc/image.h"
#include "doc/image_ref.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include <memory>
namespace app {
doc::Image* resize_image(
doc::Image* image,
const gfx::SizeF& scale,
const doc::algorithm::ResizeMethod method,
const Palette* pal,
const RgbMap* rgbmap)
{
doc::ImageSpec spec = image->spec();
spec.setWidth(std::max(1, int(scale.w*image->width())));
spec.setHeight(std::max(1, int(scale.h*image->height())));
std::unique_ptr<doc::Image> newImage(
doc::Image::create(spec));
newImage->setMaskColor(image->maskColor());
doc::algorithm::fixup_image_transparent_colors(newImage.get());
doc::algorithm::resize_image(
image, newImage.get(),
method,
pal,
rgbmap,
newImage->maskColor());
return newImage.release();
}
void resize_cel_image(
Tx& tx, doc::Cel* cel,
const gfx::SizeF& scale,
const doc::algorithm::ResizeMethod method,
const gfx::PointF& pivot)
{
// Get cel's image
doc::Image* image = cel->image();
if (image && !cel->link()) {
doc::Sprite* sprite = cel->sprite();
// Resize the cel bounds only if it's from a reference layer
if (cel->layer()->isReference()) {
gfx::RectF newBounds = cel->boundsF();
newBounds.offset(-pivot);
newBounds *= scale;
newBounds.offset(pivot);
tx(new cmd::SetCelBoundsF(cel, newBounds));
}
else {
// Change cel location
const int x = cel->x() + pivot.x - scale.w*pivot.x;
const int y = cel->y() + pivot.y - scale.h*pivot.y;
if (cel->x() != x || cel->y() != y)
tx(new cmd::SetCelPosition(cel, x, y));
// Resize the image
const int w = std::max(1, int(scale.w*image->width()));
const int h = std::max(1, int(scale.h*image->height()));
doc::ImageRef newImage(
doc::Image::create(
image->pixelFormat(), MAX(1, w), MAX(1, h)));
newImage->setMaskColor(image->maskColor());
doc::algorithm::fixup_image_transparent_colors(image);
doc::algorithm::resize_image(
image, newImage.get(),
method,
sprite->palette(cel->frame()),
sprite->rgbMap(cel->frame()),
(cel->layer()->isBackground() ? -1: sprite->transparentColor()));
tx(new cmd::ReplaceImage(sprite, cel->imageRef(), newImage));
}
}
}
} // namespace app

View File

@ -0,0 +1,41 @@
// Aseprite
// Copyright (c) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UTIL_RESIZE_CEL_IMAGE_H_INCLUDED
#define APP_UTIL_RESIZE_CEL_IMAGE_H_INCLUDED
#pragma once
#include "doc/algorithm/resize_image.h"
#include "doc/color.h"
#include "gfx/point.h"
#include "gfx/size.h"
namespace doc {
class Cel;
class Image;
class Palette;
class RgbMap;
}
namespace app {
class Tx;
doc::Image* resize_image(
doc::Image* image,
const gfx::SizeF& scale,
const doc::algorithm::ResizeMethod method,
const doc::Palette* pal,
const doc::RgbMap* rgbmap);
void resize_cel_image(
Tx& tx, doc::Cel* cel,
const gfx::SizeF& scale,
const doc::algorithm::ResizeMethod method,
const gfx::PointF& pivot);
} // namespace app
#endif

View File

@ -1,4 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2019 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -41,7 +42,12 @@ void resize_image_nearest(const Image* src, Image* dst)
}
}
void resize_image(const Image* src, Image* dst, ResizeMethod method, const Palette* pal, const RgbMap* rgbmap, color_t maskColor)
void resize_image(const Image* src,
Image* dst,
const ResizeMethod method,
const Palette* pal,
const RgbMap* rgbmap,
const color_t maskColor)
{
switch (method) {
@ -66,6 +72,17 @@ void resize_image(const Image* src, Image* dst, ResizeMethod method, const Palet
int v_floor, v_floor2;
int x, y;
// We cannot do interpolations between RGB values on indexed
// images without a palette/rgbmap.
if (dst->pixelFormat() == IMAGE_INDEXED &&
(!pal || !rgbmap)) {
resize_image(
src, dst,
RESIZE_METHOD_NEAREST_NEIGHBOR,
pal, rgbmap, maskColor);
return;
}
u = v = 0.0;
du = (src->width()-1) * 1.0 / (dst->width()-1);
dv = (src->height()-1) * 1.0 / (dst->height()-1);

View File

@ -1,4 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2019 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -29,8 +30,12 @@ namespace doc {
// Warning: If you are using the RESIZE_METHOD_BILINEAR, it is
// recommended to use 'fixup_image_transparent_colors' function
// over the source image 'src' BEFORE using this routine.
void resize_image(const Image* src, Image* dst, ResizeMethod method, const Palette* palette, const RgbMap* rgbmap,
color_t maskColor);
void resize_image(const Image* src,
Image* dst,
const ResizeMethod method,
const Palette* palette,
const RgbMap* rgbmap,
const color_t maskColor);
// It does not modify the image to the human eye, but internally
// tries to fixup all colors that are completely transparent

View File

@ -26,8 +26,7 @@ namespace doc {
const color_t maskColor = 0,
const gfx::ColorSpacePtr& colorSpace = gfx::ColorSpace::MakeNone())
: m_colorMode(colorMode),
m_width(width),
m_height(height),
m_size(width, height),
m_maskColor(maskColor),
m_colorSpace(colorSpace) {
ASSERT(width > 0);
@ -35,35 +34,33 @@ namespace doc {
}
ColorMode colorMode() const { return m_colorMode; }
int width() const { return m_width; }
int height() const { return m_height; }
gfx::Size size() const { return gfx::Size(m_width, m_height); }
gfx::Rect bounds() const { return gfx::Rect(0, 0, m_width, m_height); }
int width() const { return m_size.w; }
int height() const { return m_size.h; }
const gfx::Size& size() const { return m_size; }
gfx::Rect bounds() const { return gfx::Rect(m_size); }
const gfx::ColorSpacePtr& colorSpace() const { return m_colorSpace; }
// The transparent color for colored images (0 by default) or just 0 for RGBA and Grayscale
color_t maskColor() const { return m_maskColor; }
void setColorMode(const ColorMode colorMode) { m_colorMode = colorMode; }
void setWidth(const int width) { m_width = width; }
void setHeight(const int height) { m_height = height; }
void setWidth(const int width) { m_size.w = width; }
void setHeight(const int height) { m_size.h = height; }
void setMaskColor(const color_t color) { m_maskColor = color; }
void setColorSpace(const gfx::ColorSpacePtr& cs) { m_colorSpace = cs; }
void setSize(const int width, const int height) {
m_width = width;
m_height = height;
void setSize(const int width,
const int height) {
m_size = gfx::Size(width, height);
}
void setSize(const gfx::Size& sz) {
m_width = sz.w;
m_height = sz.h;
m_size = sz;
}
bool operator==(const ImageSpec& that) const {
return (m_colorMode == that.m_colorMode &&
m_width == that.m_width &&
m_height == that.m_height &&
m_size == that.m_size &&
m_maskColor == that.m_maskColor &&
((!m_colorSpace && !that.m_colorSpace) ||
(m_colorSpace && that.m_colorSpace &&
@ -75,8 +72,7 @@ namespace doc {
private:
ColorMode m_colorMode;
int m_width;
int m_height;
gfx::Size m_size;
color_t m_maskColor;
gfx::ColorSpacePtr m_colorSpace;
};