Use threads in shrink_bounds() when it's possible

Improves the performance when we edit big images (shrink + crop +
image allocations are the performance issues we have when we're
editing big images).

The real solution for image allocations would be to change the
internal representation of images to a tile-based images with a cache
of tiles. But that is not planned in the short-term.
This commit is contained in:
David Capello 2019-08-21 20:21:57 -03:00
parent 0812ea8224
commit 9c81ed46f2
3 changed files with 163 additions and 69 deletions

View File

@ -215,5 +215,6 @@ endif()
if(ENABLE_BENCHMARKS) if(ENABLE_BENCHMARKS)
include(FindBenchmarks) include(FindBenchmarks)
find_benchmarks(doc doc-lib) find_benchmarks(doc doc-lib)
find_benchmarks(doc/algorithm doc-lib)
find_benchmarks(render render-lib) find_benchmarks(render render-lib)
endif() endif()

View File

@ -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 <benchmark/benchmark.h>
#include <memory>
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<Image> 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();

View File

@ -1,4 +1,5 @@
// Aseprite Document Library // Aseprite Document Library
// Copyright (c) 2019 Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello // Copyright (c) 2001-2016 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -14,6 +15,8 @@
#include "doc/image_impl.h" #include "doc/image_impl.h"
#include "doc/primitives_fast.h" #include "doc/primitives_fast.h"
#include <thread>
namespace doc { namespace doc {
namespace algorithm { namespace algorithm {
@ -44,81 +47,109 @@ bool is_same_pixel<IndexedTraits>(color_t pixel1, color_t pixel2)
return pixel1 == pixel2; return pixel1 == pixel2;
} }
template<> template<typename ImageTraits>
bool is_same_pixel<BitmapTraits>(color_t pixel1, color_t pixel2) 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<bounds.x2(); ++u) {
auto ptr = get_pixel_address_fast<ImageTraits>(image, u, v=bounds.y);
for (; v<bounds.y2(); ++v, ptr+=rowSize) {
ASSERT(ptr == get_pixel_address_fast<ImageTraits>(image, u, v));
if (!is_same_pixel<ImageTraits>(*ptr, refpixel))
return (!bounds.isEmpty());
}
++bounds.x;
--bounds.w;
}
return (!bounds.isEmpty());
}
template<typename ImageTraits>
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<ImageTraits>(image, u, v=bounds.y);
for (; v<bounds.y2(); ++v, ptr+=rowSize) {
ASSERT(ptr == get_pixel_address_fast<ImageTraits>(image, u, v));
if (!is_same_pixel<ImageTraits>(*ptr, refpixel))
return (!bounds.isEmpty());
}
--bounds.w;
}
return (!bounds.isEmpty());
}
template<typename ImageTraits>
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<bounds.y2(); ++v) {
auto ptr = get_pixel_address_fast<ImageTraits>(image, u=bounds.x, v);
for (; u<bounds.x2(); ++u, ++ptr) {
ASSERT(ptr == get_pixel_address_fast<ImageTraits>(image, u, v));
if (!is_same_pixel<ImageTraits>(*ptr, refpixel))
return (!bounds.isEmpty());
}
++bounds.y;
--bounds.h;
}
return (!bounds.isEmpty());
}
template<typename ImageTraits>
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<ImageTraits>(image, u=bounds.x, v);
for (; u<bounds.x2(); ++u, ++ptr) {
ASSERT(ptr == get_pixel_address_fast<ImageTraits>(image, u, v));
if (!is_same_pixel<ImageTraits>(*ptr, refpixel))
return (!bounds.isEmpty());
}
--bounds.h;
}
return (!bounds.isEmpty());
} }
template<typename ImageTraits> template<typename ImageTraits>
bool shrink_bounds_templ(const Image* image, gfx::Rect& bounds, color_t refpixel) bool shrink_bounds_templ(const Image* image, gfx::Rect& bounds, color_t refpixel)
{ {
bool shrink; // Pixels per row
int u, v; const int rowSize = image->getRowStrideSize() / image->getRowStrideSize(1);
const int canvasSize = image->width()*image->height();
// Shrink left side if ((std::thread::hardware_concurrency() >= 4) &&
for (u=bounds.x; u<bounds.x+bounds.w; ++u) { ((image->pixelFormat() == IMAGE_RGB && canvasSize >= 800*800) ||
shrink = true; (image->pixelFormat() != IMAGE_RGB && canvasSize >= 500*500))) {
for (v=bounds.y; v<bounds.y+bounds.h; ++v) { gfx::Rect
if (!is_same_pixel<ImageTraits>( leftBounds(bounds), rightBounds(bounds),
get_pixel_fast<ImageTraits>(image, u, v), refpixel)) { topBounds(bounds), bottomBounds(bounds);
shrink = false; std::thread left ([&]{ shrink_bounds_left_templ <ImageTraits>(image, leftBounds, refpixel, rowSize); });
break; std::thread right ([&]{ shrink_bounds_right_templ <ImageTraits>(image, rightBounds, refpixel, rowSize); });
} std::thread top ([&]{ shrink_bounds_top_templ <ImageTraits>(image, topBounds, refpixel); });
} std::thread bottom([&]{ shrink_bounds_bottom_templ<ImageTraits>(image, bottomBounds, refpixel); });
if (!shrink) left.join();
break; right.join();
++bounds.x; top.join();
--bounds.w; bottom.join();
bounds = leftBounds;
bounds &= rightBounds;
bounds &= topBounds;
bounds &= bottomBounds;
return !bounds.isEmpty();
} }
else {
// Shrink right side return
for (u=bounds.x+bounds.w-1; u>=bounds.x; --u) { shrink_bounds_left_templ<ImageTraits>(image, bounds, refpixel, rowSize) &&
shrink = true; shrink_bounds_right_templ<ImageTraits>(image, bounds, refpixel, rowSize) &&
for (v=bounds.y; v<bounds.y+bounds.h; ++v) { shrink_bounds_top_templ<ImageTraits>(image, bounds, refpixel) &&
if (!is_same_pixel<ImageTraits>( shrink_bounds_bottom_templ<ImageTraits>(image, bounds, refpixel);
get_pixel_fast<ImageTraits>(image, u, v), refpixel)) {
shrink = false;
break;
}
}
if (!shrink)
break;
--bounds.w;
} }
// Shrink top side
for (v=bounds.y; v<bounds.y+bounds.h; ++v) {
shrink = true;
for (u=bounds.x; u<bounds.x+bounds.w; ++u) {
if (!is_same_pixel<ImageTraits>(
get_pixel_fast<ImageTraits>(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<bounds.x+bounds.w; ++u) {
if (!is_same_pixel<ImageTraits>(
get_pixel_fast<ImageTraits>(image, u, v), refpixel)) {
shrink = false;
break;
}
}
if (!shrink)
break;
--bounds.h;
}
return (!bounds.isEmpty());
} }
template<typename ImageTraits> template<typename ImageTraits>
@ -204,10 +235,13 @@ bool shrink_bounds(const Image* image,
case IMAGE_RGB: return shrink_bounds_templ<RgbTraits>(image, bounds, refpixel); case IMAGE_RGB: return shrink_bounds_templ<RgbTraits>(image, bounds, refpixel);
case IMAGE_GRAYSCALE: return shrink_bounds_templ<GrayscaleTraits>(image, bounds, refpixel); case IMAGE_GRAYSCALE: return shrink_bounds_templ<GrayscaleTraits>(image, bounds, refpixel);
case IMAGE_INDEXED: return shrink_bounds_templ<IndexedTraits>(image, bounds, refpixel); case IMAGE_INDEXED: return shrink_bounds_templ<IndexedTraits>(image, bounds, refpixel);
case IMAGE_BITMAP: return shrink_bounds_templ<BitmapTraits>(image, bounds, refpixel); case IMAGE_BITMAP:
// Not supported
break;
} }
ASSERT(false); ASSERT(false);
return false; bounds = start_bounds;
return true;
} }
bool shrink_bounds(const Image* image, gfx::Rect& bounds, color_t refpixel) bool shrink_bounds(const Image* image, gfx::Rect& bounds, color_t refpixel)