diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7086aa5a0..6e4162c71 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -215,5 +215,6 @@ endif() if(ENABLE_BENCHMARKS) include(FindBenchmarks) find_benchmarks(doc doc-lib) + find_benchmarks(doc/algorithm doc-lib) find_benchmarks(render render-lib) endif() diff --git a/src/doc/algorithm/shrink_benchmark.cpp b/src/doc/algorithm/shrink_benchmark.cpp new file mode 100644 index 000000000..138505998 --- /dev/null +++ b/src/doc/algorithm/shrink_benchmark.cpp @@ -0,0 +1,59 @@ +// Aseprite Document Library +// Copyright (c) 2019 Igara Studio S.A. +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "doc/algorithm/shrink_bounds.h" + +#include "doc/color.h" +#include "doc/image.h" + +#include +#include + +using namespace doc; + +void BM_ShrinkBounds(benchmark::State& state) { + const PixelFormat pixelFormat = (PixelFormat)state.range(0); + const int w = state.range(1); + const int h = state.range(2); + + std::unique_ptr img(Image::create(pixelFormat, w, h)); + img->putPixel(w/2, h/2, rgba(1, 2, 3, 4)); + gfx::Rect rc; + while (state.KeepRunning()) { + doc::algorithm::shrink_bounds(img.get(), rc, 0); + } +} + +#define DEFARGS(MODE) \ + ->Args({ MODE, 100, 100 }) \ + ->Args({ MODE, 200, 200 }) \ + ->Args({ MODE, 300, 300 }) \ + ->Args({ MODE, 400, 400 }) \ + ->Args({ MODE, 499, 500 }) \ + ->Args({ MODE, 500, 500 }) \ + ->Args({ MODE, 600, 600 }) \ + ->Args({ MODE, 700, 700 }) \ + ->Args({ MODE, 799, 800 }) \ + ->Args({ MODE, 800, 800 }) \ + ->Args({ MODE, 900, 900 }) \ + ->Args({ MODE, 1000, 1000 }) \ + ->Args({ MODE, 1500, 1500 }) \ + ->Args({ MODE, 2000, 2000 }) \ + ->Args({ MODE, 4000, 4000 }) \ + ->Args({ MODE, 8000, 8000 }) + +BENCHMARK(BM_ShrinkBounds) + DEFARGS(IMAGE_RGB) + DEFARGS(IMAGE_GRAYSCALE) + DEFARGS(IMAGE_INDEXED) + ->Unit(benchmark::kMicrosecond) + ->UseRealTime(); + +BENCHMARK_MAIN(); diff --git a/src/doc/algorithm/shrink_bounds.cpp b/src/doc/algorithm/shrink_bounds.cpp index fde9f6ec8..6df00eec4 100644 --- a/src/doc/algorithm/shrink_bounds.cpp +++ b/src/doc/algorithm/shrink_bounds.cpp @@ -1,4 +1,5 @@ // Aseprite Document 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. @@ -14,6 +15,8 @@ #include "doc/image_impl.h" #include "doc/primitives_fast.h" +#include + namespace doc { namespace algorithm { @@ -44,81 +47,109 @@ bool is_same_pixel(color_t pixel1, color_t pixel2) return pixel1 == pixel2; } -template<> -bool is_same_pixel(color_t pixel1, color_t pixel2) +template +bool shrink_bounds_left_templ(const Image* image, gfx::Rect& bounds, color_t refpixel, int rowSize) { - return pixel1 == pixel2; + int u, v; + // Shrink left side + for (u=bounds.x; u(image, u, v=bounds.y); + for (; v(image, u, v)); + if (!is_same_pixel(*ptr, refpixel)) + return (!bounds.isEmpty()); + } + ++bounds.x; + --bounds.w; + } + return (!bounds.isEmpty()); +} + +template +bool shrink_bounds_right_templ(const Image* image, gfx::Rect& bounds, color_t refpixel, int rowSize) +{ + int u, v; + // Shrink right side + for (u=bounds.x2()-1; u>=bounds.x; --u) { + auto ptr = get_pixel_address_fast(image, u, v=bounds.y); + for (; v(image, u, v)); + if (!is_same_pixel(*ptr, refpixel)) + return (!bounds.isEmpty()); + } + --bounds.w; + } + return (!bounds.isEmpty()); +} + +template +bool shrink_bounds_top_templ(const Image* image, gfx::Rect& bounds, color_t refpixel) +{ + int u, v; + // Shrink top side + for (v=bounds.y; v(image, u=bounds.x, v); + for (; u(image, u, v)); + if (!is_same_pixel(*ptr, refpixel)) + return (!bounds.isEmpty()); + } + ++bounds.y; + --bounds.h; + } + return (!bounds.isEmpty()); +} + +template +bool shrink_bounds_bottom_templ(const Image* image, gfx::Rect& bounds, color_t refpixel) +{ + int u, v; + // Shrink bottom side + for (v=bounds.y2()-1; v>=bounds.y; --v) { + auto ptr = get_pixel_address_fast(image, u=bounds.x, v); + for (; u(image, u, v)); + if (!is_same_pixel(*ptr, refpixel)) + return (!bounds.isEmpty()); + } + --bounds.h; + } + return (!bounds.isEmpty()); } template bool shrink_bounds_templ(const Image* image, gfx::Rect& bounds, color_t refpixel) { - bool shrink; - int u, v; - - // Shrink left side - for (u=bounds.x; u( - get_pixel_fast(image, u, v), refpixel)) { - shrink = false; - break; - } - } - if (!shrink) - break; - ++bounds.x; - --bounds.w; + // Pixels per row + const int rowSize = image->getRowStrideSize() / image->getRowStrideSize(1); + const int canvasSize = image->width()*image->height(); + if ((std::thread::hardware_concurrency() >= 4) && + ((image->pixelFormat() == IMAGE_RGB && canvasSize >= 800*800) || + (image->pixelFormat() != IMAGE_RGB && canvasSize >= 500*500))) { + gfx::Rect + leftBounds(bounds), rightBounds(bounds), + topBounds(bounds), bottomBounds(bounds); + std::thread left ([&]{ shrink_bounds_left_templ (image, leftBounds, refpixel, rowSize); }); + std::thread right ([&]{ shrink_bounds_right_templ (image, rightBounds, refpixel, rowSize); }); + std::thread top ([&]{ shrink_bounds_top_templ (image, topBounds, refpixel); }); + std::thread bottom([&]{ shrink_bounds_bottom_templ(image, bottomBounds, refpixel); }); + left.join(); + right.join(); + top.join(); + bottom.join(); + bounds = leftBounds; + bounds &= rightBounds; + bounds &= topBounds; + bounds &= bottomBounds; + return !bounds.isEmpty(); } - - // Shrink right side - for (u=bounds.x+bounds.w-1; u>=bounds.x; --u) { - shrink = true; - for (v=bounds.y; v( - get_pixel_fast(image, u, v), refpixel)) { - shrink = false; - break; - } - } - if (!shrink) - break; - --bounds.w; + else { + return + shrink_bounds_left_templ(image, bounds, refpixel, rowSize) && + shrink_bounds_right_templ(image, bounds, refpixel, rowSize) && + shrink_bounds_top_templ(image, bounds, refpixel) && + shrink_bounds_bottom_templ(image, bounds, refpixel); } - - // Shrink top side - for (v=bounds.y; v( - get_pixel_fast(image, u, v), refpixel)) { - shrink = false; - break; - } - } - if (!shrink) - break; - ++bounds.y; - --bounds.h; - } - - // Shrink bottom side - for (v=bounds.y+bounds.h-1; v>=bounds.y; --v) { - shrink = true; - for (u=bounds.x; u( - get_pixel_fast(image, u, v), refpixel)) { - shrink = false; - break; - } - } - if (!shrink) - break; - --bounds.h; - } - - return (!bounds.isEmpty()); } template @@ -204,10 +235,13 @@ bool shrink_bounds(const Image* image, case IMAGE_RGB: return shrink_bounds_templ(image, bounds, refpixel); case IMAGE_GRAYSCALE: return shrink_bounds_templ(image, bounds, refpixel); case IMAGE_INDEXED: return shrink_bounds_templ(image, bounds, refpixel); - case IMAGE_BITMAP: return shrink_bounds_templ(image, bounds, refpixel); + case IMAGE_BITMAP: + // Not supported + break; } ASSERT(false); - return false; + bounds = start_bounds; + return true; } bool shrink_bounds(const Image* image, gfx::Rect& bounds, color_t refpixel)