14

Im trying to create a background effect like this for my pause menu. My current idea is to take a screenshot on pause, save it, open it, Gaussian blur it, then render it to the screen and render the menu ontop. The only problem is I don't know how to save the screenshot effectively.

I've tried using batch.setColor(0,0,0,0.7f); to render a faded image ontop on the background but it didn't give me the blur effect i was looking for, rather just a tint as I assumed it would.

Examples/documentation greatly appreciated.

EDIT: found this code

package com.me.mygdxgame;

import java.awt.Point;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

import com.badlogic.gdx.Application.ApplicationType;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.ScreenUtils;

public class ScreenShot {

    private static final int[] RGBA_OFFSETS = { 0, 1, 2, 3 };
    private static final int[] RGB_OFFSETS = { 0, 1, 2 };

    public static void saveScreenshot(String baseName) throws IOException {
        File createTempFile = File.createTempFile(baseName, ".png");
        saveScreenshot(createTempFile);
    }

    public static void saveScreenshot(File file) throws IOException {
        saveScreenshot(file, false);
    }

    public static void saveScreenshot(File file, boolean hasAlpha) throws IOException {
        if (Gdx.app.getType() == ApplicationType.Android)
            return;

        byte[] screenshotPixels = ScreenUtils.getFrameBufferPixels(true);

        int width = Gdx.graphics.getWidth();
        int height = Gdx.graphics.getHeight();

        saveScreenshot(file, screenshotPixels, width, height, hasAlpha);
    }

    public static void saveScreenshot(File file, byte[] pixels, int width, int height, boolean hasAlpha) throws IOException {
        DataBufferByte dataBuffer = new DataBufferByte(pixels, pixels.length);

        PixelInterleavedSampleModel sampleModel = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, width, height, 4, 4 * width, getOffsets(hasAlpha));

        WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer, new Point(0, 0));

        BufferedImage img = new BufferedImage(getColorModel(hasAlpha), raster, false, null);

        ImageIO.write(img, "png", file);
    }

    private static ColorModel getColorModel(boolean alpha) {
        if (alpha)
            return new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] { 8, 8, 8, 8 }, true, false, ComponentColorModel.TRANSLUCENT, DataBuffer.TYPE_BYTE);
        return new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] { 8, 8, 8 }, false, false, ComponentColorModel.OPAQUE, DataBuffer.TYPE_BYTE);
    }

    private static int[] getOffsets(boolean alpha) {
        if (alpha)
            return RGBA_OFFSETS;
        return RGB_OFFSETS;
    }

}

but it gives me this error when I try saveScreenshot("output");(runnign desktop version, if that makes a difference. Haven't tested on an android)

Exception in thread "LWJGL Application" com.badlogic.gdx.utils.GdxRuntimeException: java.awt.image.RasterFormatException: Incorrect scanline stride: 3200
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:111)
Caused by: java.awt.image.RasterFormatException: Incorrect scanline stride: 3200

error line: ImageIO.write(img, "png", file);

Chris
  • 2,435
  • 6
  • 26
  • 49

2 Answers2

14

I'm late to the party but this is EXACTLY what you're looking for:

https://github.com/mattdesl/lwjgl-basics/wiki/ShaderLesson5

Render to a field buffer object using a shader to create the blur, then draw it from the frame buffer onto the screen.

SpacePrez
  • 1,086
  • 7
  • 15
  • no idea how i get this working with libgdx for me it just changes colors – BlakkM9 Jun 15 '19 at 03:08
  • @BlakkM9 check which shader programs you're running then, your pixel shader must be wrong – SpacePrez Jun 16 '19 at 16:55
  • Copy pasted the the fragment shader and it was using the fragment shader aswell because changing things in the shader changes what was rendered. Anyways I found a workaround by doing the blur with BlurUtils (even if it is propably less performant) – BlakkM9 Jun 17 '19 at 03:40
  • @BlakkM9 Hi. In the BlurUtils it says width and height must be square, by any chance you know how to use it on rectanglular textures? – Jay N Feb 03 '20 at 07:10
  • @JayN where did you read that it needs to be square? for clearification i'm using [this](https://gist.github.com/mattdesl/4383372) for blurring my image. currently i'm using it on a resolution of 480x271 and it works fine for me. – BlakkM9 Feb 03 '20 at 07:58
  • @BlakkM9 I'm using the generateBlurredMipmaps() function, in that function there's a line that if width != height then it simply returns, and if I comment out that line, it just draws blank white screen for me. – Jay N Feb 04 '20 at 08:17
  • @JayN as far as i know textures for mip mapping always needs to be square because the smallest image in the mip map is 1x1 or 2x2. maybe using the normal blur function fits better for your purpose. – BlakkM9 Feb 04 '20 at 08:41
  • @BlakkM9 I've tried the normal one too, but I can't find a way to do the transition from normal image to a blurred one with that method. – Jay N Feb 04 '20 at 08:58
0

I have created my own implementation of gaussian blur for libgdx. Maybe it will be useful for someone. Just add the following class to your project:

package com.mygdx.game;

import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Color;
import java.lang.Math;

class Blur {
    private static double matrix [];
    private static int radius;

    public static void setMatrix(int radius, double sigma) {
        Blur.matrix = new double[radius+1];
        Blur.radius = radius;
        for(int i=0; i<=radius; i++)
            matrix[i] = gauss((double)i, sigma);
        double sum = 0;
        for(int i=-radius; i<=radius; i++)
            sum += matrix[Math.abs(i)];
        for(int i=0; i<=radius; i++)
            matrix[i]/=sum;
    }

    private static double gauss(double x, double sigma) {
        return 1/(sigma*Math.sqrt(2*Math.PI))*Math.exp(-(x*x)/(2*sigma*sigma));
    }

    public static Pixmap blur(Pixmap pixmap, boolean vertical) {
        int width = pixmap.getWidth();
        int height = pixmap.getHeight();
        Pixmap result = new Pixmap(width,height,Format.RGBA8888);

        for(int y=0; y<height; y++)
            for(int x=0; x<width; x++){
                float r=0,g=0,b=0,a=0;
                for(int i=-radius; i<=radius; i++) {
                    int px=x, py=y;
                    if(vertical)
                        py=mirrorTheEdge(py+i,height);
                    else 
                        px=mirrorTheEdge(px+i,width);
                    Color color = new Color(pixmap.getPixel(px,py));
                    float weight = (float)matrix[Math.abs(i)];
                    r+=color.r*weight;
                    g+=color.g*weight;
                    b+=color.b*weight;
                    a+=color.a*weight;
                }
                result.setColor(new Color(r,g,b,a));
                result.drawPixel(x,y);
            }

        return result;
    }

    private static int mirrorTheEdge(int p, int limit) {
        if(p<0) 
            p=p*-1-1;
        if(p>=limit) 
            p=limit*2-p+1;
        return p;
    }
}

And here is an example of use:

package com.mygdx.game;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.utils.ScreenUtils;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.files.FileHandle;

public class MyGdxGame extends ApplicationAdapter {
    SpriteBatch batch;
    Texture img;
    
    @Override
    public void create () {
        batch = new SpriteBatch();

        Pixmap pixmap = new Pixmap(new FileHandle("badlogic.jpg")); // Creating pixmap from file
        Blur.setMatrix(15,7.5); // Setting the blur strength
        pixmap = Blur.blur(pixmap,false); // Blurring the pixmap horizontally
        pixmap = Blur.blur(pixmap,true); // Blurring the pixmap vertically
        //pixmap = Blur.blur(Blur.blur(pixmap,false),true); // same in one line

        img = new Texture(pixmap);
    }

    @Override
    public void render () {
        ScreenUtils.clear(1, 0, 0, 1);
        batch.begin();
        batch.draw(img, 0, 0);
        batch.end();
    }
    
    @Override
    public void dispose () {
        batch.dispose();
        img.dispose();
    }
}

Basically, anything that can be transformed into a pixmap can be blurred. To set the strength of the blur, use Blur.setMatrix(radius,sigma). I don't understand it very well, but the higher these two values are, the blurrier the image. If you want to optimize this, remember that the lower the radius, the better. That's all.

pfx
  • 1