Android, мгновенное размытие растрового изображения?

Итак, я пытаюсь размыть изображение как можно быстрее (мгновенно), так как активность должна обновляться, когда я нажимаю кнопку «Размытие».

Проблема, с которой я столкнулся, заключается в том, что я не могу найти размытие, которое работает достаточно быстро... Примечание. Размытие, предпочтительно размытие по Гауссу, вовсе не обязательно должно быть наилучшего качества...

Я попробовал следующее, но это заняло несколько секунд, можно ли как-то заставить этот код работать быстрее в ущерб качеству? Или есть другие альтернативы? Я бы посмотрел на вещи с графическим процессором, но это размытие на самом деле просто эффект, связанный с пользовательским интерфейсом, и происходит только тогда, когда я нажимаю открыть прозрачную активность размером с небольшую коробку...

Есть идеи?

static Bitmap fastblur(Bitmap sentBitmap, int radius, int fromX, int fromY,
    int width, int height) {

// Stack Blur v1.0 from
// http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
//
// Java Author: Mario Klingemann <mario at quasimondo.com>
// http://incubator.quasimondo.com
// created Feburary 29, 2004
// Android port : Yahel Bouaziz <yahel at kayenko.com>
// http://www.kayenko.com
// ported april 5th, 2012

// This is a compromise between Gaussian Blur and Box blur
// It creates much better looking blurs than Box Blur, but is
// 7x faster than my Gaussian Blur implementation.
//
// I called it Stack Blur because this describes best how this
// filter works internally: it creates a kind of moving stack
// of colors whilst scanning through the image. Thereby it
// just has to add one new block of color to the right side
// of the stack and remove the leftmost color. The remaining
// colors on the topmost layer of the stack are either added on
// or reduced by one, depending on if they are on the right or
// on the left side of the stack.
//
// If you are using this algorithm in your code please add
// the following line:
//
// Stack Blur Algorithm by Mario Klingemann <[email protected]>

Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);

if (radius < 1) {
    return (null);
}

int w = width;
int h = height;

int[] pix = new int[w * h];

bitmap.getPixels(pix, 0, w, fromX, fromY, w, h);

int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int div = radius + radius + 1;

int r[] = new int[wh];
int g[] = new int[wh];
int b[] = new int[wh];
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
int vmin[] = new int[Math.max(w, h)];

int divsum = (div + 1) >> 1;
divsum *= divsum;
int dv[] = new int[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
    dv[i] = (i / divsum);
}

yw = yi = 0;

int[][] stack = new int[div][3];
int stackpointer;
int stackstart;
int[] sir;
int rbs;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;

int originRadius = radius;
for (y = 0; y < h; y++) {
    rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
    for (i = -radius; i <= radius; i++) {
        p = pix[yi + Math.min(wm, Math.max(i, 0))];
        sir = stack[i + radius];
        sir[0] = (p & 0xff0000) >> 16;
        sir[1] = (p & 0x00ff00) >> 8;
        sir[2] = (p & 0x0000ff);
        rbs = r1 - Math.abs(i);
        rsum += sir[0] * rbs;
        gsum += sir[1] * rbs;
        bsum += sir[2] * rbs;
        if (i > 0) {
            rinsum += sir[0];
            ginsum += sir[1];
            binsum += sir[2];
        } else {
            routsum += sir[0];
            goutsum += sir[1];
            boutsum += sir[2];
        }
    }
    stackpointer = radius;

    for (x = 0; x < w; x++) {

        r[yi] = dv[rsum];
        g[yi] = dv[gsum];
        b[yi] = dv[bsum];

        rsum -= routsum;
        gsum -= goutsum;
        bsum -= boutsum;

        stackstart = stackpointer - radius + div;
        sir = stack[stackstart % div];

        routsum -= sir[0];
        goutsum -= sir[1];
        boutsum -= sir[2];

        if (y == 0) {
            vmin[x] = Math.min(x + radius + 1, wm);
        }
        p = pix[yw + vmin[x]];

        sir[0] = (p & 0xff0000) >> 16;
        sir[1] = (p & 0x00ff00) >> 8;
        sir[2] = (p & 0x0000ff);

        rinsum += sir[0];
        ginsum += sir[1];
        binsum += sir[2];

        rsum += rinsum;
        gsum += ginsum;
        bsum += binsum;

        stackpointer = (stackpointer + 1) % div;
        sir = stack[(stackpointer) % div];

        routsum += sir[0];
        goutsum += sir[1];
        boutsum += sir[2];

        rinsum -= sir[0];
        ginsum -= sir[1];
        binsum -= sir[2];

        yi++;
    }
    yw += w;
}

radius = originRadius;

for (x = 0; x < w; x++) {
    rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
    yp = -radius * w;
    for (i = -radius; i <= radius; i++) {
        yi = Math.max(0, yp) + x;

        sir = stack[i + radius];

        sir[0] = r[yi];
        sir[1] = g[yi];
        sir[2] = b[yi];

        rbs = r1 - Math.abs(i);

        rsum += r[yi] * rbs;
        gsum += g[yi] * rbs;
        bsum += b[yi] * rbs;

        if (i > 0) {
            rinsum += sir[0];
            ginsum += sir[1];
            binsum += sir[2];
        } else {
            routsum += sir[0];
            goutsum += sir[1];
            boutsum += sir[2];
        }

        if (i < hm) {
            yp += w;
        }
    }
    yi = x;
    stackpointer = radius;
    for (y = 0; y < h; y++) {
        pix[yi] = 0xff000000 | (dv[rsum] << 16) | (dv[gsum] << 8)
                | dv[bsum];

        rsum -= routsum;
        gsum -= goutsum;
        bsum -= boutsum;

        stackstart = stackpointer - radius + div;
        sir = stack[stackstart % div];

        routsum -= sir[0];
        goutsum -= sir[1];
        boutsum -= sir[2];

        if (x == 0) {
            vmin[y] = Math.min(y + r1, hm) * w;
        }
        p = x + vmin[y];

        sir[0] = r[p];
        sir[1] = g[p];
        sir[2] = b[p];

        rinsum += sir[0];
        ginsum += sir[1];
        binsum += sir[2];

        rsum += rinsum;
        gsum += ginsum;
        bsum += binsum;

        stackpointer = (stackpointer + 1) % div;
        sir = stack[stackpointer];

        routsum += sir[0];
        goutsum += sir[1];
        boutsum += sir[2];

        rinsum -= sir[0];
        ginsum -= sir[1];
        binsum -= sir[2];

        yi += w;
    }
}

bitmap.setPixels(pix, 0, w, fromX, fromY, w, h);

return (bitmap);

}


person rennoDeniro    schedule 17.11.2012    source источник
comment
futurestud.io/tutorials/ Это сработало для меня.   -  person Velu    schedule 21.04.2020


Ответы (2)


Попробуйте уменьшить изображение в 2, 4, 8, ... раз, а затем снова увеличить. Это быстро. В противном случае реализуйте его в рендерскрипте.

Если вам нужно больше, чем масштабирование, вы можете посмотреть этот фрагмент кода в скрипте рендеринга. Он делает то же размытие, что и в другом ответе. Тот же алгоритм может быть реализован на Java и является оптимизацией другого ответа. Этот код размывает одну строку. Чтобы размыть растровое изображение, вы должны вызвать это для всех строк, а затем то же самое для всех столбцов (вам нужно переопределить его для обработки столбцов). Чтобы получить быстрое размытие, просто сделайте это один раз. Если вы хотите, чтобы размытие выглядело лучше, сделайте это несколько раз. Обычно я делаю это только два раза.

Причина, по которой я сделал одну строку, заключается в том, что я пытался распараллелить алгоритм, который дал некоторое улучшение и действительно прост в рендерскрипте. Я вызвал приведенный ниже код для всех строк параллельно, а затем то же самое для всех столбцов.

int W = 8;
uchar4 *in;
uchar4 *out;    

int N;
float invN;    

uint32_t nx;
uint32_t ny;    

void init_calc() {
  N = 2*W+1;
  invN = 1.0f/N;    

  nx = rsAllocationGetDimX(rsGetAllocation(in));
  ny = rsAllocationGetDimY(rsGetAllocation(in));
}    

void root(const ushort *v_in) {
  float4 sum = 0;    

  uchar4 *head = in + *v_in * nx;
  uchar4 *tail = head;
  uchar4 *p = out + *v_in * nx;    

  uchar4 *hpw = head + W;
  uchar4 *hpn = head + N;
  uchar4 *hpx = head + nx;
  uchar4 *hpxmw = head + nx - W - 1;    

  while (head < hpw) {
      sum += rsUnpackColor8888(*head++);
  }    

  while (head < hpn) {
      sum += rsUnpackColor8888(*head++);
      *p++ = rsPackColorTo8888(sum*invN);
  }    

  while (head < hpx) {
    sum += rsUnpackColor8888(*head++);
    sum -= rsUnpackColor8888(*tail++);
    *p++ = rsPackColorTo8888(sum*invN);
  }    

  while (tail < hpxmw) {
      sum -= rsUnpackColor8888(*tail++);
      *p++ = rsPackColorTo8888(sum*invN);
  }
}

Вот для вертикального размытия:

int W = 8;
uchar4 *in;
uchar4 *out;    

int N;
float invN;    

uint32_t nx;
uint32_t ny;    

void init_calc() {
  N = 2*W+1;
  invN = 1.0f/N;    

  nx = rsAllocationGetDimX(rsGetAllocation(in));
  ny = rsAllocationGetDimY(rsGetAllocation(in));
}    

void root(const ushort *v_in) {
  float4 sum = 0;    

  uchar4 *head = in + *v_in;
  uchar4 *tail = head;
  uchar4 *hpw = head + nx*W;
  uchar4 *hpn = head + nx*N;
  uchar4 *hpy = head + nx*ny;
  uchar4 *hpymw = head + nx*(ny-W-1);    

  uchar4 *p = out + *v_in;    

  while (head < hpw) {
      sum += rsUnpackColor8888(*head);
      head += nx;
  }    

  while (head < hpn) {
      sum += rsUnpackColor8888(*head);
      *p = rsPackColorTo8888(sum*invN);
      head += nx;
      p += nx;
  }    

  while (head < hpy) {
      sum += rsUnpackColor8888(*head);
      sum -= rsUnpackColor8888(*tail);
      *p = rsPackColorTo8888(sum*invN);
      head += nx;
      tail += nx;
      p += nx;
  }    

  while (tail < hpymw) {
      sum -= rsUnpackColor8888(*tail);
      *p = rsPackColorTo8888(sum*invN);
      tail += nx;
      p += nx;
  }
}

А вот код Java, который вызывает код rs:

private RenderScript mRS;
private ScriptC_horzblur mHorizontalScript;
private ScriptC_vertblur mVerticalScript;
private ScriptC_blur mBlurScript;

private Allocation alloc1;
private Allocation alloc2;

private void hblur(int radius, Allocation index, Allocation in, Allocation out) {
    mHorizontalScript.set_W(radius);
    mHorizontalScript.bind_in(in);
    mHorizontalScript.bind_out(out);
    mHorizontalScript.invoke_init_calc();
    mHorizontalScript.forEach_root(index);
}

private void vblur(int radius, Allocation index, Allocation in, Allocation out) {
    mHorizontalScript.set_W(radius);
    mVerticalScript.bind_in(in);
    mVerticalScript.bind_out(out);
    mVerticalScript.invoke_init_calc();
    mVerticalScript.forEach_root(index);
}

Bitmap blur(Bitmap org, int radius) {
    Bitmap out = Bitmap.createBitmap(org.getWidth(), org.getHeight(), org.getConfig());

    blur(org, out, radius);

    return out;
}

private Allocation createIndex(int size) {
    Element element = Element.U16(mRS);
    Allocation allocation = Allocation.createSized(mRS, element, size);
    short[] rows = new short[size];
    for (int i = 0; i < rows.length; i++) rows[i] = (short)i;
    allocation.copyFrom(rows);

    return allocation;
}

private void blur(Bitmap src, Bitmap dst, int r) {
    Allocation alloc1 = Allocation.createFromBitmap(mRS, src);
    Allocation alloc2 = Allocation.createTyped(mRS, alloc1.getType());

    Allocation hIndexAllocation = createIndex(alloc1.getType().getY());
    Allocation vIndexAllocation = createIndex(alloc1.getType().getX());

    // Iteration 1
    hblur(r, hIndexAllocation, alloc1, alloc2);
    vblur(r, vIndexAllocation, alloc2, alloc1);
    // Iteration 2
    hblur(r, hIndexAllocation, alloc1, alloc2);
    vblur(r, vIndexAllocation, alloc2, alloc1);
    // Add more iterations if you like or simply make a loop
    alloc1.copyTo(dst);
}
person Tobias Ritzau    schedule 17.11.2012
comment
как мне это закодировать? - person rennoDeniro; 18.11.2012
comment
посмотрите на это: developer.android.com/ ссылка/android/графика/, int, int, boolean) - person Tobias Ritzau; 18.11.2012
comment
это выглядит интересно, где я могу прочитать, чтобы реализовать это? я новичок в рендеринге скрипта и довольно простой в андроиде - person rennoDeniro; 19.11.2012
comment
У меня проблемы с пониманием того, как реализовать часть ScriptC_..., я действительно не понимаю, как это работает? И поиск вокруг тоже не особо помог :s - person rennoDeniro; 19.11.2012
comment
Эти классы создаются. Поместите файлы *.rs с вашими java-файлами. Дополнительные сведения см. здесь: developer.android.com/guide/topics. /graphics/renderscript/ Вы используете eclipse или другую IDE? - person Tobias Ritzau; 19.11.2012
comment
Эй, на данном этапе я компилировал im, выполняя: mScript.bind_in(mIN); mScript.bind_out(Моу); mScript.invoke_init_calc(); mScript.forEach_root(); mou.copyTo(viewBm); - person rennoDeniro; 20.11.2012
comment
Но на .forEach я получаю несоответствие типа u16: S? Любые идеи, что я делаю неправильно? - person rennoDeniro; 20.11.2012
comment
Должен признаться, что это было давно, и я просто играл с этим. Я надеюсь, что код работает. Я обновлю ответ вызовами с Java. Массив U16 — это индекс в растровом изображении, который решает, как разбить его на части. - person Tobias Ritzau; 20.11.2012
comment
Спасибо за вашу помощь. Вы пропустили следующие типы и где их нужно объявить: mHorizontalScript, mBitmapIn, alloc1,alloc2,r,dur,alpha. Какие типы и где они создаются? - person rennoDeniro; 20.11.2012
comment
В принципе сейчас все есть. - person Tobias Ritzau; 20.11.2012
comment
Здравствуйте, спасибо, что помогли мне понять основы рендерскрипта, но текущий метод размытия, похоже, не дает приличного желаемого размытия по Гауссу. Также copyToSrc вызывает сбои при повторной попытке размытия. - person rennoDeniro; 20.11.2012
comment
Я почистил код, больше всего я удалил тестовый код, который начинался с очень маленького радиуса размытия, а затем увеличивал его каждый раз, когда вызывался blur(). Вместо этого радиус является параметром. Взгляните на него и обновите его. Я также добавил комментарии, чтобы показать, как вы должны увеличить количество итераций. Я должен сказать, что я впечатлен тем, что вы запустили его :) - person Tobias Ritzau; 21.11.2012
comment
Только что проверил, действительно выглядит немного лучше, спасибо за вашу помощь! - person rennoDeniro; 21.11.2012
comment
Я думаю, вы можете взять это за ссылку на mBlurScript. - person StackOverflowed; 17.08.2013
comment
Попытка заставить этот образец работать. Все выглядит нормально, за исключением вызовов forEach_root(index) для каждого из сценариев. Компилятор жалуется, что не может найти эти методы... Есть идеи? - person Eagle11; 20.11.2014
comment
@ Eagle11, ты скопировал файлы как есть? В основном функции по-прежнему называются root? Убедились ли вы, что можете вызывать любой код рендерскрипта из Java? - person Tobias Ritzau; 02.12.2014

Размытие по Гауссу дорого делать точно. Гораздо более быструю аппроксимацию можно выполнить, просто итеративно усредняя пиксели. По-прежнему дорого сильно размывать изображение, но вы можете перерисовывать между каждой итерацией, чтобы, по крайней мере, дать мгновенную обратную связь и красивую анимацию размытия изображения.

static void blurfast(Bitmap bmp, int radius) {
  int w = bmp.getWidth();
  int h = bmp.getHeight();
  int[] pix = new int[w * h];
  bmp.getPixels(pix, 0, w, 0, 0, w, h);

  for(int r = radius; r >= 1; r /= 2) {
    for(int i = r; i < h - r; i++) {
      for(int j = r; j < w - r; j++) {
        int tl = pix[(i - r) * w + j - r];
        int tr = pix[(i - r) * w + j + r];
        int tc = pix[(i - r) * w + j];
        int bl = pix[(i + r) * w + j - r];
        int br = pix[(i + r) * w + j + r];
        int bc = pix[(i + r) * w + j];
        int cl = pix[i * w + j - r];
        int cr = pix[i * w + j + r];

        pix[(i * w) + j] = 0xFF000000 |
            (((tl & 0xFF) + (tr & 0xFF) + (tc & 0xFF) + (bl & 0xFF) + (br & 0xFF) + (bc & 0xFF) + (cl & 0xFF) + (cr & 0xFF)) >> 3) & 0xFF |
            (((tl & 0xFF00) + (tr & 0xFF00) + (tc & 0xFF00) + (bl & 0xFF00) + (br & 0xFF00) + (bc & 0xFF00) + (cl & 0xFF00) + (cr & 0xFF00)) >> 3) & 0xFF00 |
            (((tl & 0xFF0000) + (tr & 0xFF0000) + (tc & 0xFF0000) + (bl & 0xFF0000) + (br & 0xFF0000) + (bc & 0xFF0000) + (cl & 0xFF0000) + (cr & 0xFF0000)) >> 3) & 0xFF0000;
      }
    }
  }
  bmp.setPixels(pix, 0, w, 0, 0, w, h);
}
person gordy    schedule 18.11.2012
comment
Этот код несколько раз пересчитывает одни и те же суммы. Если вы хотите такого размытия, посмотрите мой ответ. - person Tobias Ritzau; 19.11.2012
comment
не забудьте указать радиус в степени 2 - например, 8, 16 или 32 - person gordy; 19.11.2012
comment
У меня 32 дает лучшие результаты, но когда я получаю изображение, размытие, кажется, применяется только к половине изображения: s, как я могу применить его ко всему изображению? - person rennoDeniro; 19.11.2012
comment
@rennoDeniro была ошибка, из-за которой не обрабатывалось все изображение. Я только что отредактировал ответ и исправил ошибку, дайте мне знать, если размытие достаточно быстрое и размытое. - person gordy; 20.11.2012
comment
Привет, К сожалению, это размытие недостаточно гауссовское и требует времени :( - person rennoDeniro; 20.11.2012