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)
include(FindBenchmarks)
find_benchmarks(doc doc-lib)
find_benchmarks(doc/algorithm doc-lib)
find_benchmarks(render render-lib)
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
// 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 <thread>
namespace doc {
namespace algorithm {
@ -44,81 +47,109 @@ bool is_same_pixel<IndexedTraits>(color_t pixel1, color_t pixel2)
return pixel1 == pixel2;
}
template<>
bool is_same_pixel<BitmapTraits>(color_t pixel1, color_t pixel2)
template<typename ImageTraits>
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>
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<bounds.x+bounds.w; ++u) {
shrink = true;
for (v=bounds.y; v<bounds.y+bounds.h; ++v) {
if (!is_same_pixel<ImageTraits>(
get_pixel_fast<ImageTraits>(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 <ImageTraits>(image, leftBounds, refpixel, rowSize); });
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); });
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<bounds.y+bounds.h; ++v) {
if (!is_same_pixel<ImageTraits>(
get_pixel_fast<ImageTraits>(image, u, v), refpixel)) {
shrink = false;
break;
}
}
if (!shrink)
break;
--bounds.w;
else {
return
shrink_bounds_left_templ<ImageTraits>(image, bounds, refpixel, rowSize) &&
shrink_bounds_right_templ<ImageTraits>(image, bounds, refpixel, rowSize) &&
shrink_bounds_top_templ<ImageTraits>(image, bounds, refpixel) &&
shrink_bounds_bottom_templ<ImageTraits>(image, bounds, refpixel);
}
// 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>
@ -204,10 +235,13 @@ bool shrink_bounds(const Image* image,
case IMAGE_RGB: return shrink_bounds_templ<RgbTraits>(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_BITMAP: return shrink_bounds_templ<BitmapTraits>(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)