All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.blastedstudios.gdxworld.util.BlurUtil Maven / Gradle / Ivy

The newest version!
package com.blastedstudios.gdxworld.util;
import java.nio.ByteBuffer;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Blending;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.utils.BufferUtils;
import com.badlogic.gdx.utils.GdxRuntimeException;
 
/**
 * A simple set of software blur utilities for mobile applications.
 * 
 * @author davedes, blur algorithm by Romain Guy
 * https://gist.github.com/mattdesl/4383372
 */
public class BlurUtil {
	/*
	 * Copyright (c) 2007, Romain Guy All rights reserved.
	 * 
	 * Redistribution and use in source and binary forms, with or without
	 * modification, are permitted provided that the following conditions are
	 * met:
	 * 
	 * * Redistributions of source code must retain the above copyright notice,
	 * this list of conditions and the following disclaimer. * Redistributions
	 * in binary form must reproduce the above copyright notice, this list of
	 * conditions and the following disclaimer in the documentation and/or other
	 * materials provided with the distribution. * Neither the name of the
	 * TimingFramework project nor the names of its contributors may be used to
	 * endorse or promote products derived from this software without specific
	 * prior written permission.
	 * 
	 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
	 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
	 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
	 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
	 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
	 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
	 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
	 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
	 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
	 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
	 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
	 */
	/**
	 * 

* Blurs the source pixels into the destination pixels. The force of the * blur is specified by the radius which must be greater than 0. *

*

* The source and destination pixels arrays are expected to be in the RGBA * format. *

* * @param srcPixels * the source pixels * @param dstPixels * the destination pixels * @param width * the width of the source picture * @param height * the height of the source picture * @param radius * the radius of the blur effect * @author Romain Guy */ public static void blurPass(int[] srcPixels, int[] dstPixels, int width, int height, int radius) { final int windowSize = radius * 2 + 1; final int radiusPlusOne = radius + 1; int sumRed; int sumGreen; int sumBlue; int sumAlpha; int srcIndex = 0; int dstIndex; int pixel; int[] sumLookupTable = new int[256 * windowSize]; for (int i = 0; i < sumLookupTable.length; i++) { sumLookupTable[i] = i / windowSize; } int[] indexLookupTable = new int[radiusPlusOne]; if (radius < width) { for (int i = 0; i < indexLookupTable.length; i++) { indexLookupTable[i] = i; } } else { for (int i = 0; i < width; i++) { indexLookupTable[i] = i; } for (int i = width; i < indexLookupTable.length; i++) { indexLookupTable[i] = width - 1; } } for (int y = 0; y < height; y++) { sumAlpha = sumRed = sumGreen = sumBlue = 0; dstIndex = y; pixel = srcPixels[srcIndex]; sumRed += radiusPlusOne * ((pixel >> 24) & 0xFF); sumGreen += radiusPlusOne * ((pixel >> 16) & 0xFF); sumBlue += radiusPlusOne * ((pixel >> 8) & 0xFF); sumAlpha += radiusPlusOne * (pixel & 0xFF); for (int i = 1; i <= radius; i++) { pixel = srcPixels[srcIndex + indexLookupTable[i]]; sumRed += (pixel >> 24) & 0xFF; sumGreen += (pixel >> 16) & 0xFF; sumBlue += (pixel >> 8) & 0xFF; sumAlpha += pixel & 0xFF; } for (int x = 0; x < width; x++) { dstPixels[dstIndex] = sumLookupTable[sumRed] << 24 | sumLookupTable[sumGreen] << 16 | sumLookupTable[sumBlue] << 8 | sumLookupTable[sumAlpha]; dstIndex += height; int nextPixelIndex = x + radiusPlusOne; if (nextPixelIndex >= width) { nextPixelIndex = width - 1; } int previousPixelIndex = x - radius; if (previousPixelIndex < 0) { previousPixelIndex = 0; } int nextPixel = srcPixels[srcIndex + nextPixelIndex]; int previousPixel = srcPixels[srcIndex + previousPixelIndex]; sumRed += (nextPixel >> 24) & 0xFF; sumRed -= (previousPixel >> 24) & 0xFF; sumGreen += (nextPixel >> 16) & 0xFF; sumGreen -= (previousPixel >> 16) & 0xFF; sumBlue += (nextPixel >> 8) & 0xFF; sumBlue -= (previousPixel >> 8) & 0xFF; sumAlpha += nextPixel & 0xFF; sumAlpha -= previousPixel & 0xFF; } srcIndex += width; } } /** * Blurs (in both horizontal and vertical directions) the specified RGBA * image with the given radius and iterations. * * @param inputRGBA * the image pixels, in RGBA format * @param width * the width of the image in pixels * @param height * the height of the image in pixels * @param radius * the radius of the blur effect * @param iterations * the number of times to perform the blur; i.e. to increase * quality * @return the blurred pixels */ public static int[] blur(int[] inputRGBA, int width, int height, int radius, int iterations) { int[] srcPixels = new int[width * height]; int[] dstPixels = new int[width * height]; // copy input into srcPixels System.arraycopy(inputRGBA, 0, srcPixels, 0, srcPixels.length); for (int i = 0; i < iterations; i++) { // horizontal pass blurPass(srcPixels, dstPixels, width, height, radius); // vertical pass blurPass(dstPixels, srcPixels, height, width, radius); } // the result is now stored in srcPixels due to the 2nd pass return srcPixels; } /** * Convenience method to blur using ByteBuffers instead of arrays. * Note that this requires unnecessary copies of data and is only * for convenience; a proper solution would be * to re-write the blur algorithm using a ByteBuffer. * * @param inputRGBA * @param width * @param height * @param radius * @param iterations * @return */ public static ByteBuffer blur(ByteBuffer inputRGBA, int width, int height, int radius, int iterations) { if (inputRGBA.limit() != (width * height * 4)) throw new IllegalArgumentException( "inputRGBA must be in RGBA format"); int[] pixels = pack(inputRGBA); int[] out = blur(pixels, width, height, radius, iterations); return unpack(out); } /** * Converts an RGBA byte buffer into an array of RGBA packed ints. * * @param rgba * @return */ public static int[] pack(ByteBuffer rgba) { int[] pixels = new int[rgba.limit() / 4]; for (int i = 0; i < pixels.length; i++) { int r = rgba.get() & 0xFF; int g = rgba.get() & 0xFF; int b = rgba.get() & 0xFF; int a = rgba.get() & 0xFF; pixels[i] = (r << 24) | (g << 16) | (b << 8) | a; } return pixels; } /** * Unpacks the RGBA pixels array into a ByteBuffer with red, green, blue, * and alpha bytes in order; it is then flipped to "read mode" before being * returned. * * @param pixels * the pixels to use * @return the new byte buffer using RGBA bytes */ public static ByteBuffer unpack(int[] pixels) { ByteBuffer buf = BufferUtils.newByteBuffer(pixels.length * 4); for (int src = 0; src < pixels.length; src++) { int value = pixels[src]; buf.put((byte) ((value & 0xff000000) >>> 24)) .put((byte) ((value & 0x00ff0000) >>> 16)) .put((byte) ((value & 0x0000ff00) >>> 8)) .put((byte) ((value & 0x000000ff))); } buf.flip(); return buf; } /** * A convenience method to apply the blur to the entire Pixmap. * * @param pixmap * the pixmap to blur * @param radius * the radius of the blur effect * @param iterations * the number of iterations to blur * @param disposePixmap * whether to dispose the given pixmap after blurring * @return a new Pixmap containing the blurred image */ public static Pixmap blur(Pixmap pixmap, int radius, int iterations, boolean disposePixmap) { return blur(pixmap, 0, 0, pixmap.getWidth(), pixmap.getHeight(), 0, 0, pixmap.getWidth(), pixmap.getHeight(), radius, iterations, disposePixmap); } /** * Blurs the specified pixmap with the given source and destination regions. * * The pixmap does not need to be in RGBA8888 format, however, it is * recommended for better performance. * * A new pixmap will be returned containing the blurred image. The old * pixmap will only be disposed of if disposePixmap returns true. * * @param pixmap * the pixmap to blur * @param srcx * the x of the pixmap region to blur * @param srcy * the y of the pixmap region to blur * @param srcwidth * the width of the pixmap region to blur * @param srcheight * the height of the pixmap region to blur * @param dstx * the destination x to place the blurred image on the resulting * pixmap * @param dsty * the destination y to place the blurred image on the resulting * pixmap * @param dstwidth * the desired width of the resulting pixmap * @param dstheight * the desired height of the resulting pixmap * @param radius * the radius of the blur effect, in pixels * @param iterations * the number of iterations to apply the blur * @param disposePixmap * whether to dispose the specified pixmap after applying the * blur * @return a new RGBA8888 Pixmap containing the blurred image */ public static Pixmap blur(Pixmap pixmap, int srcx, int srcy, int srcwidth, int srcheight, int dstx, int dsty, int dstwidth, int dstheight, int radius, int iterations, boolean disposePixmap) { boolean srcEq = srcx == 0 && srcy == 0 && srcwidth == pixmap.getWidth() && srcheight == pixmap.getHeight(); boolean dstEq = dstx == 0 && dsty == 0 && dstwidth == pixmap.getWidth() && dstheight == pixmap.getHeight(); // we may need to re-draw the pixmap if a different region or format is // passed if (pixmap.getFormat() != Format.RGBA8888 || !srcEq || !dstEq) { Pixmap tmp = new Pixmap(dstwidth, dstheight, Format.RGBA8888); tmp.drawPixmap(pixmap, srcx, srcy, srcwidth, srcheight, dstx, dsty, dstwidth, dstheight); if (disposePixmap) { pixmap.dispose(); // discard old pixmap disposePixmap = false; } pixmap = tmp; } // blur the pixmap ByteBuffer blurred = blur(pixmap.getPixels(), dstwidth, dstheight, radius, iterations); Pixmap newPixmap = new Pixmap(dstwidth, dstheight, Format.RGBA8888); ByteBuffer newRGBA = newPixmap.getPixels(); newRGBA.clear(); newRGBA.put(blurred); newRGBA.flip(); if (disposePixmap) pixmap.dispose(); return newPixmap; } /** * Blurs the mipmaps of the currently bound texture with the given settings. * * For each mipmap level, the image will be scaled to half (using * nearest-neighbour scaling) and then blurred in software, before sending * the bytes to GL. * * The first mipmap level should already be uploaded to GL, i.e. through the * Texture constructor. No blur will be applied to it. * * The texture needs to have been created with format RGBA8888 to work * correctly on all devices. * * @param pixmap * the original pixmap to work with * @param textureWidth * the width of the texture * @param textureHeight * the height of the texture * @param radius * the radius of the blur to use at each level * @param iterations * the number of iterations to blur at each level * @param disposePixmap * whether to dispose the specified pixmap after building the * mipmaps */ public static void generateBlurredMipmaps(Pixmap pixmap, int textureWidth, int textureHeight, int radius, int iterations, boolean disposePixmap) { if (textureWidth != textureHeight) throw new GdxRuntimeException( "texture width and height must be square when using mipmapping."); Pixmap origPixmap = pixmap; int width = pixmap.getWidth() / 2; int height = pixmap.getHeight() / 2; int level = 1; Blending blending = Pixmap.getBlending(); Pixmap.setBlending(Blending.None); // for each mipmap level > 0 ... while (width > 0 && height > 0) { // apply blur pixmap = blur(origPixmap, 0, 0, origPixmap.getWidth(), origPixmap.getHeight(), 0, 0, width, height, radius, iterations, false); // upload pixels Gdx.gl.glTexImage2D(GL20.GL_TEXTURE_2D, level, pixmap.getGLInternalFormat(), pixmap.getWidth(), pixmap.getHeight(), 0, pixmap.getGLFormat(), pixmap.getGLType(), pixmap.getPixels()); // reduce size for next level width = pixmap.getWidth() / 2; height = pixmap.getHeight() / 2; level++; //dispose pixmap at this level pixmap.dispose(); // NOTE: We can play with the radius and iterations here, e.g. // increment them for // each level. // radius++; } Pixmap.setBlending(blending); if (disposePixmap) { origPixmap.dispose(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy