From ad1a39714eef2143f0efe29ca72fca2d4859d298 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 13 Aug 2019 18:16:30 -0300 Subject: [PATCH] [lua] Add Image:resize() function Closes: https://community.aseprite.org/t/3633 --- src/app/CMakeLists.txt | 1 + src/app/commands/cmd_sprite_size.cpp | 40 ++++------- src/app/script/dialog_class.cpp | 3 +- src/app/script/image_class.cpp | 99 ++++++++++++++++++++++++++++ src/app/script/luacpp.h | 10 ++- src/app/site.cpp | 5 ++ src/app/site.h | 2 + src/app/util/resize_image.cpp | 99 ++++++++++++++++++++++++++++ src/app/util/resize_image.h | 41 ++++++++++++ src/doc/algorithm/resize_image.cpp | 19 +++++- src/doc/algorithm/resize_image.h | 9 ++- src/doc/image_spec.h | 30 ++++----- 12 files changed, 306 insertions(+), 52 deletions(-) create mode 100644 src/app/util/resize_image.cpp create mode 100644 src/app/util/resize_image.h diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index b1db6806f..3b4be8646 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -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 diff --git a/src/app/commands/cmd_sprite_size.cpp b/src/app/commands/cmd_sprite_size.cpp index 4d24241e5..e583b9399 100644 --- a/src/app/commands/cmd_sprite_size.cpp +++ b/src/app/commands/cmd_sprite_size.cpp @@ -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(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; diff --git a/src/app/script/dialog_class.cpp b/src/app/script/dialog_class.cpp index b21b49c92..d85edbcc1 100644 --- a/src/app/script/dialog_class.cpp +++ b/src/app/script/dialog_class.cpp @@ -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(); diff --git a/src/app/script/image_class.cpp b/src/app/script/image_class.cpp index 3458d38b3..a0fb28d44 100644 --- a/src/app/script/image_class.cpp +++ b/src/app/script/image_class.cpp @@ -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 +#include + 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(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 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 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(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 } diff --git a/src/app/script/luacpp.h b/src/app/script/luacpp.h index 2a96a8b44..b1c6b34b4 100644 --- a/src/app/script/luacpp.h +++ b/src/app/script/luacpp.h @@ -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. diff --git a/src/app/site.cpp b/src/app/site.cpp index 2bbdc027a..650ee4aef 100644 --- a/src/app/site.cpp +++ b/src/app/site.cpp @@ -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) diff --git a/src/app/site.h b/src/app/site.h index f9f2b781a..eab14a666 100644 --- a/src/app/site.h +++ b/src/app/site.h @@ -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; diff --git a/src/app/util/resize_image.cpp b/src/app/util/resize_image.cpp new file mode 100644 index 000000000..abcd3e56f --- /dev/null +++ b/src/app/util/resize_image.cpp @@ -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 + +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 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 diff --git a/src/app/util/resize_image.h b/src/app/util/resize_image.h new file mode 100644 index 000000000..35a75eda0 --- /dev/null +++ b/src/app/util/resize_image.h @@ -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 diff --git a/src/doc/algorithm/resize_image.cpp b/src/doc/algorithm/resize_image.cpp index 917845101..1bde006a3 100644 --- a/src/doc/algorithm/resize_image.cpp +++ b/src/doc/algorithm/resize_image.cpp @@ -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); diff --git a/src/doc/algorithm/resize_image.h b/src/doc/algorithm/resize_image.h index 0a9809b0c..0c2a44225 100644 --- a/src/doc/algorithm/resize_image.h +++ b/src/doc/algorithm/resize_image.h @@ -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 diff --git a/src/doc/image_spec.h b/src/doc/image_spec.h index 2332a1c48..c25be4082 100644 --- a/src/doc/image_spec.h +++ b/src/doc/image_spec.h @@ -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; };