Динамическое Размытие На Android

Информации о том, как быстро размыть картинку на Андроиде, предостаточно.

Но можно ли сделать это настолько эффективно, чтобы размытое растровое изображение можно было перерисовывать без лагов при каждом изменении контента, как это реализовано в iOS? Итак, что я хотел бы сделать:

  1. ViewGroup, который сможет размывать контент, находящийся под ним, и отображать его в качестве фона.

    Назовем это BlurView

  2. Возможность добавлять к нему детей (любой вид)
  3. Не зависит от какой-либо конкретной реализации родительской ViewGroup.
  4. Перерисовать размытый фон, если содержимое под нашим представлением изменится.

  5. Выполните полное размытие и цикл рендеринга менее чем за 16 мс.

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

здесь .

гифка с примером (8мб)

Динамическое размытие на Android

Как получить растровое изображение с содержимым для BlurView? Давайте создадим для него Canvas, Bitmap и прикажем всей иерархии представлений рисовать на этом холсте.

  
  
  
  
  
   

internalBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888); internalCanvas = new Canvas(internalBitmap); .

rootView.draw(internalCanvas);

Теперь все виды, видимые на экране, рисуются во внутреннем битмапе.

Проблемы: Если у вашего rootView нет указанного фона, вы должны сначала нарисовать фон активности на холсте, иначе он будет прозрачным на растровом изображении.



final View decorView = getWindow().

getDecorView(); //Activity's root View. Can also be root View of your layout final View rootView = decorView.findViewById(android.R.id.content); final Drawable windowBackground = decorView.getBackground(); .

windowBackground.draw(internalCanvas); rootView.draw(internalCanvas);

Мне бы хотелось нарисовать только ту часть иерархии, которая нам нужна.

То есть под наш BlurView, с соответствующим размером.

Кроме того, было бы неплохо уменьшить Bitmap, на котором будет происходить рендеринг.

Это ускорит отрисовку иерархии представлений, размоет ее, а также уменьшит потребление памяти.

Добавьте поле ScaleFactor, желательно, чтобы этот коэффициент был степенью двойки.



float scaleFactor = 8;

Уменьшаем растровое изображение в 8 раз и настраиваем матрицу холста так, чтобы на нем можно было правильно рисовать с учетом положения, размера и масштабного коэффициента:

private void setupInternalCanvasMatrix() { float scaledLeftPosition = -blurView.getLeft() / scaleFactor; float scaledTopPosition = -blurView.getTop() / scaleFactor; float scaledTranslationX = blurView.getTranslationX() / scaleFactor; float scaledTranslationY = blurView.getTranslationY() / scaleFactor; internalCanvas.translate(scaledLeftPosition - scaledTranslationX, scaledTopPosition - scaledTranslationY); float scaleX = blurView.getScaleX() / scaleFactor; float scaleY = blurView.getScaleY() / scaleFactor; internalCanvas.scale(scaleX, scaleY); }

Теперь, когда мы вызываем rootView.draw(internalCanvas), у нас будет уменьшенная копия всей иерархии представлений во внутреннем битмапе.

Она будет выглядеть устрашающе, но меня это не особо беспокоит, потому что.

я все равно ее размою дальше.

Проблемы: rootView скажет всем своим дочерним элементам рисовать себя на холсте, включая BlurView. Я хотел бы избежать этого; BlurView не должен размываться.

Для этого вы можете переопределить метод draw(Canvas Canvas) в BlurView и проверить, на каком холсте его просят отрисовать.

Если это системный холст, мы разрешаем рендеринг; если это внутренний холст, мы отключаем его.

На самом деле, размытие Я сделал интерфейс BlurAlgorithm и возможность настройки алгоритма.

Добавлены две реализации: StackBlur (от Марио Клингеманна) и RenderScriptBlur. Опция RenderScript работает на графическом процессоре.

Для экономии памяти я записываю результат в тот же растровый рисунок, который пришел ко мне на вход. Это необязательно.

Вы также можете попробовать кэшировать outAllocation и повторно использовать его, если размер входного изображения не изменился.

РендерСкриптРазмытие

@Override public final Bitmap blur(Bitmap bitmap, float blurRadius) { Allocation inAllocation = Allocation.createFromBitmap(renderScript, bitmap); Bitmap outputBitmap; if (canModifyBitmap) { outputBitmap = bitmap; } else { outputBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig()); } //do not use inAllocation in forEach. it will cause visual artifacts on blurred Bitmap Allocation outAllocation = Allocation.createTyped(renderScript, inAllocation.getType()); blurScript.setRadius(blurRadius); blurScript.setInput(inAllocation); blurScript.forEach(outAllocation); outAllocation.copyTo(outputBitmap); inAllocation.destroy(); outAllocation.destroy(); return outputBitmap; }

Когда перерисовывать размытие? Есть несколько вариантов:

  1. Обновите вручную, если точно знаете, что контент изменился.

  2. Подождите, чтобы прокрутить события, если BlurView находится над контейнером прокрутки.

  3. Слушайте вызовы draw() через ViewTreeObserver
Я выбрал вариант 3, используя OnPreDrawListener. Стоит отметить, что его метод onPreDraw выполняет переход каждый раз, когда какое-либо представление в иерархии рисует себя.

Это, конечно, не идеально, потому что.

вам придется перерисовывать BlurView при каждом чихе, но никакого контроля со стороны программиста это не требует. Проблемы: onPreDraw также будет вызываться, когда вся иерархия рисуется на внутреннем холсте, а также когда рисуется BlurView. Чтобы избежать этой проблемы, я установил флаг isMeDrawingNow. Все кажется очевидным, кроме одного момента.

Вам нужно установить значение false через обработчик, т.к.

отправка рендеринга происходит асинхронно через очередь событий потока пользовательского интерфейса.

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



drawListener = new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { if (!isMeDrawingNow) { updateBlur(); } return true; } }; rootView.getViewTreeObserver().

addOnPreDrawListener(drawListener);

Производительность Статистики много не собирал, но на Nexus 4, Nexus 5 весь цикл рендеринга занимает 1-4мс на экранах из демо-проекта.

Галактика нексус - около 10мс.

Почему-то Sony Xperia Z3 Compact справляется хуже - 8-16мс.

Однако производительностью я доволен, FPS остается на хорошем уровне.

Заключение Весь код находится на GitHub (библиотека и демонстрационный проект) — РазмытиеПросмотр .

Идеи, комментарии, отчеты об ошибках и запросы на включение приветствуются.

Теги: #Android #blur #RenderScript #Разработка для Android

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.