Libgdx - screenshot poor quality - java

I try to make a screenshot of my game according to this article:
https://github.com/libgdx/libgdx/wiki/Taking-a-Screenshot
It seams that there is problem with black color in PNG conversion.
My screenshots looks as follow:
should be:
is:
Here is detailed view:
There is a strange color instead of shadow around a leaf.
Did anyone have a similar problem?

I resolved this problem by implementing platform specific mechanism - it's different for android and desktop applications.
You can find more about platform specific code in libgdx here.
Here is the interface for android:
public interface ScreenshotPixmap {
public void saveScreenshot(FileHandle fileHandle);
}
And implementation:
public class AndroidScreenshotPixmap implements ScreenshotPixmap {
public Pixmap getScreenshot( int x, int y, int w, int h, boolean flipY ) {
Gdx.gl.glPixelStorei( GL20.GL_PACK_ALIGNMENT, 1 );
final Pixmap pixmap = new Pixmap( w, h, Pixmap.Format.RGBA8888 );
ByteBuffer pixels = pixmap.getPixels();
Gdx.gl.glReadPixels( x, y, w, h, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, pixels );
final int numBytes = w * h * 4;
byte[] lines = new byte[numBytes];
if ( flipY ) {
final int numBytesPerLine = w * 4;
for ( int i = 0; i < h; i++ ) {
pixels.position( (h - i - 1) * numBytesPerLine );
pixels.get( lines, i * numBytesPerLine, numBytesPerLine );
}
pixels.clear();
pixels.put( lines );
} else {
pixels.clear();
pixels.get( lines );
}
return pixmap;
}
public int[] pixmapToIntArray( Pixmap pixmap ) {
int w = pixmap.getWidth();
int h = pixmap.getHeight();
int dest = 0;
int[] raw = new int[w * h];
for ( int y = 0; y < h; y++ ) {
for ( int x = 0; x < w; x++ ) {
int rgba = pixmap.getPixel( x, y );
raw[dest++] = 0xFF000000 | ( rgba >> 8 );
}
}
return raw;
}
public void savePNG( int[] colors, int width, int height, OutputStream stream ) {
Bitmap bitmap = Bitmap.createBitmap( colors, width, height, Bitmap.Config.ARGB_8888 );
bitmap.compress( Bitmap.CompressFormat.PNG, 100, stream );
}
#Override
public void saveScreenshot(FileHandle fileHandle) {
Pixmap pixmap = getScreenshot(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
OutputStream stream = fileHandle.write(false);
savePNG(pixmapToIntArray(pixmap), Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), stream);
}
}
Good luck.

Related

Java OpenCV Layer small image onto larger image with transparency

I am trying to write a function that overlays an image at a rectangle with transparency over top of another image, However it doesn't layer the images it just erases the section that I overlay and the transparency cuts through the entire image. Here is my code.
public static void overlayImage(String imagePath, String overlayPath, int x, int y, int width, int height) {
Mat overlay = Imgcodecs.imread(overlayPath, Imgcodecs.IMREAD_UNCHANGED);
Mat image = Imgcodecs.imread(imagePath, Imgcodecs.IMREAD_UNCHANGED);
Rectangle rect = new Rectangle(x, y, width, height);
Imgproc.resize(overlay, overlay, rect.size());
Mat submat = image.submat(new Rect(rect.x, rect.y, overlay.cols(), overlay.rows()));
overlay.copyTo(submat);
Imgcodecs.imwrite(imagePath, image);
}
EDIT: Here are some example pictures:
Before:
After:
Found this function that does exactly what I needed.
public static void overlayImage(Mat background,Mat foreground,Mat output, Point location){
background.copyTo(output);
for(int y = (int) Math.max(location.y , 0); y < background.rows(); ++y){
int fY = (int) (y - location.y);
if(fY >= foreground.rows())
break;
for(int x = (int) Math.max(location.x, 0); x < background.cols(); ++x){
int fX = (int) (x - location.x);
if(fX >= foreground.cols()){
break;
}
double opacity;
double[] finalPixelValue = new double[4];
opacity = foreground.get(fY , fX)[3];
finalPixelValue[0] = background.get(y, x)[0];
finalPixelValue[1] = background.get(y, x)[1];
finalPixelValue[2] = background.get(y, x)[2];
finalPixelValue[3] = background.get(y, x)[3];
for(int c = 0; c < output.channels(); ++c){
if(opacity > 0){
double foregroundPx = foreground.get(fY, fX)[c];
double backgroundPx = background.get(y, x)[c];
float fOpacity = (float) (opacity / 255);
finalPixelValue[c] = ((backgroundPx * ( 1.0 - fOpacity)) + (foregroundPx * fOpacity));
if(c==3){
finalPixelValue[c] = foreground.get(fY,fX)[3];
}
}
}
output.put(y, x,finalPixelValue);
}
}
}

merge a png with transparency onto another image

I'm trying to merge a png with some transparency on a background:
Mat frg = Highgui.imread( "path/to/foreground/image.png" ), -1 );
Mat bkg = Highgui.imread( "path/to/background/image.png" ), -1 );
// Create mask from foreground.
Mat mask = new Mat( frg.width(), frg.height(), 24 );
double f[] = { 1, 1, 1, 0 };
double e[] = { 0, 0, 0, 0 };
for ( int y = 0; y < ( int )( frg.rows() ); ++y ) {
for ( int x = 0; x < ( int )( frg.cols() ); ++x ) {
double info[] = frg.get( y, x );
if ( info[3] > 0 ) {
mask.put( y, x, e );
} else {
mask.put( y, x, f );
}
}
}
// Copy foreground onto background using mask.
frg.copyTo( bkg, mask );
Highgui.imwrite( "path/to/result/image.png", bkg );
The resulting image is a kind of ghost showing the background image repeated various times and the foreground one in a corrupted way.
Any clues?
Thanks Rosa and for those interested, here is how I translated into Java the C++ snippet pointed out by Rosa:
private Mat overtrayImage( Mat background, Mat foreground ) {
// The background and the foreground are assumed to be of the same size.
Mat destination = new Mat( background.size(), background.type() );
for ( int y = 0; y < ( int )( background.rows() ); ++y ) {
for ( int x = 0; x < ( int )( background.cols() ); ++x ) {
double b[] = background.get( y, x );
double f[] = foreground.get( y, x );
double alpha = f[3] / 255.0;
double d[] = new double[3];
for ( int k = 0; k < 3; ++k ) {
d[k] = f[k] * alpha + b[k] * ( 1.0 - alpha );
}
destination.put( y, x, d );
}
}
return destination;
}

LibGDX - saving screenshot in write protected folder

My class for saving screenshots works fine:
public class ScreenshotSaver {
private static int counter = 1;
public static void saveScreenshot() {
FileHandle fh;
do {
fh = new FileHandle("screenshot" + counter++ + ".png");
} while (fh.exists());
Pixmap pixmap = getScreenshot(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
PixmapIO.writePNG(fh, pixmap);
pixmap.dispose();
}
private static Pixmap getScreenshot(int x, int y, int w, int h, boolean flipY) {
Gdx.gl.glPixelStorei(GL10.GL_PACK_ALIGNMENT, 1);
final Pixmap pixmap = new Pixmap(w, h, Format.RGBA8888);
ByteBuffer pixels = pixmap.getPixels();
Gdx.gl.glReadPixels(x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixels);
final int numBytes = w * h * 4;
byte[] lines = new byte[numBytes];
if (flipY) {
pixels.clear();
pixels.get(lines);
} else {
final int numBytesPerLine = w * 4;
for (int i = 0; i < h; i++) {
pixels.position((h - i - 1) * numBytesPerLine);
pixels.get(lines, i * numBytesPerLine, numBytesPerLine);
}
pixels.clear();
pixels.put(lines);
}
return pixmap;
}
}
The problem is, when I execute program in write protected area. Program simply turns off. I'd prefer if it just didn't save the screenshot instead. How to do that?
Why don't you just use a try/catch?
try{
PixmapIO.writePNG(fh, pixmap);
} catch (Exception e) {
// save it somewhere else
}
pixmap.dispose();
For runtime file writing you use Gdx.files.local(...). See file handling in the libgdx wiki. It works everywhere but with GWT/in the browser.

Color Dodge Blend Bitmap

I have two bitmaps topBitmap and bottomBitmap and I need to blend the two bitmaps using color dodge in android. I could not find color dodge in PorterDuffXfermode. Is there any way to do it with ot without the use of a Canvas?
Kindly let me know how to blend two bitmaps using color dodge mode in android.Thanks in advance.
Hi I found this method from somewhere in SO post. This method takes two image and create one image using color dodge
public Bitmap ColorDodgeBlend(Bitmap source, Bitmap layer) {
Bitmap base = source.copy(Config.ARGB_8888, true);
Bitmap blend = layer.copy(Config.ARGB_8888, false);
IntBuffer buffBase = IntBuffer.allocate(base.getWidth() * base.getHeight());
base.copyPixelsToBuffer(buffBase);
buffBase.rewind();
IntBuffer buffBlend = IntBuffer.allocate(blend.getWidth() * blend.getHeight());
blend.copyPixelsToBuffer(buffBlend);
buffBlend.rewind();
IntBuffer buffOut = IntBuffer.allocate(base.getWidth() * base.getHeight());
buffOut.rewind();
while (buffOut.position() < buffOut.limit()) {
int filterInt = buffBlend.get();
int srcInt = buffBase.get();
int redValueFilter = Color.red(filterInt);
int greenValueFilter = Color.green(filterInt);
int blueValueFilter = Color.blue(filterInt);
int redValueSrc = Color.red(srcInt);
int greenValueSrc = Color.green(srcInt);
int blueValueSrc = Color.blue(srcInt);
int redValueFinal = colordodge(redValueFilter, redValueSrc);
int greenValueFinal = colordodge(greenValueFilter, greenValueSrc);
int blueValueFinal = colordodge(blueValueFilter, blueValueSrc);
int pixel = Color.argb(255, redValueFinal, greenValueFinal, blueValueFinal);
buffOut.put(pixel);
}
buffOut.rewind();
base.copyPixelsFromBuffer(buffOut);
blend.recycle();
return base;
}
here is the method do get the color dodge for pixel to get the effect
private int colordodge(int in1, int in2) {
float image = (float)in2;
float mask = (float)in1;
return ((int) ((image == 255) ? image:Math.min(255, (((long)mask << 8 ) / (255 - image)))));
}
I am trying to create sketch image of original photo, using this method I am able to create cartoonifying the image in color full format but I need in black and white pencil sketch format. If you have any idea then please share it.
hope this methods are useful to you
Put this code for pencil sketch ,this work for me
public Bitmap Changetosketch(Bitmap bmp) {
Bitmap Copy, Invert, Result;
Copy = toGrayscale(bmp);
Invert = createInvertedBitmap(Copy);
Invert = Blur.blur(this, Invert);
Result = ColorDodgeBlend(Invert, Copy);
return Result;
}
public static Bitmap toGrayscale(Bitmap src)
{
final double GS_RED = 0.299;
final double GS_GREEN = 0.587;
final double GS_BLUE = 0.114;
// create output bitmap
Bitmap bmOut = Bitmap.createBitmap(src.getWidth(), src.getHeight(), src.getConfig());
// pixel information
int A, R, G, B;
int pixel;
// get image size
int width = src.getWidth();
int height = src.getHeight();
// scan through every single pixel
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
// get one pixel color
pixel = src.getPixel(x, y);
// retrieve color of all channels
A = Color.alpha(pixel);
R = Color.red(pixel);
G = Color.green(pixel);
B = Color.blue(pixel);
// take conversion up to one single value
R = G = B = (int) (GS_RED * R + GS_GREEN * G + GS_BLUE * B);
// set new pixel color to output bitmap
bmOut.setPixel(x, y, Color.argb(A, R, G, B));
}
}
// return final image
return bmOut;
}
public static Bitmap createInvertedBitmap(Bitmap src) {
Bitmap bmOut = Bitmap.createBitmap(src.getWidth(), src.getHeight(), src.getConfig());
// color info
int A, R, G, B;
int pixelColor;
// image size
int height = src.getHeight();
int width = src.getWidth();
// scan through every pixel
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// get one pixel
pixelColor = src.getPixel(x, y);
// saving alpha channel
A = Color.alpha(pixelColor);
// inverting byte for each R/G/B channel
R = 255 - Color.red(pixelColor);
G = 255 - Color.green(pixelColor);
B = 255 - Color.blue(pixelColor);
// set newly-inverted pixel to output image
bmOut.setPixel(x, y, Color.argb(A, R, G, B));
}
}
return bmOut;
}
public Bitmap ColorDodgeBlend(Bitmap source, Bitmap layer) {
Bitmap base = source.copy(Bitmap.Config.ARGB_8888, true);
Bitmap blend = layer.copy(Bitmap.Config.ARGB_8888, false);
IntBuffer buffBase = IntBuffer.allocate(base.getWidth() * base.getHeight());
base.copyPixelsToBuffer(buffBase);
buffBase.rewind();
IntBuffer buffBlend = IntBuffer.allocate(blend.getWidth() * blend.getHeight());
blend.copyPixelsToBuffer(buffBlend);
buffBlend.rewind();
IntBuffer buffOut = IntBuffer.allocate(base.getWidth() * base.getHeight());
buffOut.rewind();
while (buffOut.position() < buffOut.limit()) {
int filterInt = buffBlend.get();
int srcInt = buffBase.get();
int redValueFilter = Color.red(filterInt);
int greenValueFilter = Color.green(filterInt);
int blueValueFilter = Color.blue(filterInt);
int redValueSrc = Color.red(srcInt);
int greenValueSrc = Color.green(srcInt);
int blueValueSrc = Color.blue(srcInt);
int redValueFinal = colordodge(redValueFilter, redValueSrc);
int greenValueFinal = colordodge(greenValueFilter, greenValueSrc);
int blueValueFinal = colordodge(blueValueFilter, blueValueSrc);
int pixel = Color.argb(255, redValueFinal, greenValueFinal, blueValueFinal);
buffOut.put(pixel);
}
buffOut.rewind();
base.copyPixelsFromBuffer(buffOut);
blend.recycle();
return base;
}
private int colordodge(int in1, int in2) {
float image = (float)in2;
float mask = (float)in1;
return ((int) ((image == 255) ? image:Math.min(255, (((long)mask << 8 ) / (255 - image)))));
}

Capture screen of GLSurfaceView to bitmap

I need to be able to capture an image of a GLSurfaceView at certain moment in time. I have the following code:
relative.setDrawingCacheEnabled(true);
screenshot = Bitmap.createBitmap(relative.getDrawingCache());
relative.setDrawingCacheEnabled(false);
Log.v(TAG, "Screenshot height: " + screenshot.getHeight());
image.setImageBitmap(screenshot);
The GLSurfaceView is contained within a RelativeLayout, but I have also tries it straight using the GLSurfaceView to try and capture the image. With this I think the screen captures a transparent image, i.e. nothing there. Any help will be appreciated.
SurfaceView and GLSurfaceView punch holes in their windows to allow their surfaces to be displayed. In other words, they have transparent areas.
So you cannot capture an image by calling GLSurfaceView.getDrawingCache().
If you want to get an image from GLSurfaceView, you should invoke gl.glReadPixels() in GLSurfaceView.onDrawFrame().
I patched createBitmapFromGLSurface method and call it in onDrawFrame().
(The original code might be from skuld's code.)
private Bitmap createBitmapFromGLSurface(int x, int y, int w, int h, GL10 gl)
throws OutOfMemoryError {
int bitmapBuffer[] = new int[w * h];
int bitmapSource[] = new int[w * h];
IntBuffer intBuffer = IntBuffer.wrap(bitmapBuffer);
intBuffer.position(0);
try {
gl.glReadPixels(x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, intBuffer);
int offset1, offset2;
for (int i = 0; i < h; i++) {
offset1 = i * w;
offset2 = (h - i - 1) * w;
for (int j = 0; j < w; j++) {
int texturePixel = bitmapBuffer[offset1 + j];
int blue = (texturePixel >> 16) & 0xff;
int red = (texturePixel << 16) & 0x00ff0000;
int pixel = (texturePixel & 0xff00ff00) | red | blue;
bitmapSource[offset2 + j] = pixel;
}
}
} catch (GLException e) {
return null;
}
return Bitmap.createBitmap(bitmapSource, w, h, Bitmap.Config.ARGB_8888);
}
Here is a complete solution if you are using a third party library that you just 'pass in' a GLSurfaceView defined in your layout. You won't have a handle on the onDrawFrame() of the renderer, this can be a problem...
To do this you need to queue it up for the GLSurfaceView to handle.
private GLSurfaceView glSurfaceView; // findById() in onCreate
private Bitmap snapshotBitmap;
private interface BitmapReadyCallbacks {
void onBitmapReady(Bitmap bitmap);
}
/* Usage code
captureBitmap(new BitmapReadyCallbacks() {
#Override
public void onBitmapReady(Bitmap bitmap) {
someImageView.setImageBitmap(bitmap);
}
});
*/
// supporting methods
private void captureBitmap(final BitmapReadyCallbacks bitmapReadyCallbacks) {
glSurfaceView.queueEvent(new Runnable() {
#Override
public void run() {
EGL10 egl = (EGL10) EGLContext.getEGL();
GL10 gl = (GL10)egl.eglGetCurrentContext().getGL();
snapshotBitmap = createBitmapFromGLSurface(0, 0, glSurfaceView.getWidth(), glSurfaceView.getHeight(), gl);
runOnUiThread(new Runnable() {
#Override
public void run() {
bitmapReadyCallbacks.onBitmapReady(snapshotBitmap);
}
});
}
});
}
// from other answer in this question
private Bitmap createBitmapFromGLSurface(int x, int y, int w, int h, GL10 gl) {
int bitmapBuffer[] = new int[w * h];
int bitmapSource[] = new int[w * h];
IntBuffer intBuffer = IntBuffer.wrap(bitmapBuffer);
intBuffer.position(0);
try {
gl.glReadPixels(x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, intBuffer);
int offset1, offset2;
for (int i = 0; i < h; i++) {
offset1 = i * w;
offset2 = (h - i - 1) * w;
for (int j = 0; j < w; j++) {
int texturePixel = bitmapBuffer[offset1 + j];
int blue = (texturePixel >> 16) & 0xff;
int red = (texturePixel << 16) & 0x00ff0000;
int pixel = (texturePixel & 0xff00ff00) | red | blue;
bitmapSource[offset2 + j] = pixel;
}
}
} catch (GLException e) {
Log.e(TAG, "createBitmapFromGLSurface: " + e.getMessage(), e);
return null;
}
return Bitmap.createBitmap(bitmapSource, w, h, Config.ARGB_8888);
}
Note: In this code, when I click the Button, it takes the screenshot as Image and saves it in sdcard location. I used boolean condition and an if statement in onDraw method, because the renderer class may call the onDraw method anytime and anyway, and without the if this code may save lots of images in the memory card.
MainActivity class:
protected boolean printOptionEnable = false;
saveImageButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Log.v("hari", "pan button clicked");
isSaveClick = true;
myRenderer.printOptionEnable = isSaveClick;
}
});
MyRenderer class:
int width_surface , height_surface ;
#Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
Log.i("JO", "onSurfaceChanged");
// Adjust the viewport based on geometry changes,
// such as screen rotation
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;
width_surface = width ;
height_surface = height ;
}
#Override
public void onDrawFrame(GL10 gl) {
try {
if (printOptionEnable) {
printOptionEnable = false ;
Log.i("hari", "printOptionEnable if condition:" + printOptionEnable);
int w = width_surface ;
int h = height_surface ;
Log.i("hari", "w:"+w+"-----h:"+h);
int b[]=new int[(int) (w*h)];
int bt[]=new int[(int) (w*h)];
IntBuffer buffer=IntBuffer.wrap(b);
buffer.position(0);
GLES20.glReadPixels(0, 0, w, h,GLES20.GL_RGBA,GLES20.GL_UNSIGNED_BYTE, buffer);
for(int i=0; i<h; i++)
{
//remember, that OpenGL bitmap is incompatible with Android bitmap
//and so, some correction need.
for(int j=0; j<w; j++)
{
int pix=b[i*w+j];
int pb=(pix>>16)&0xff;
int pr=(pix<<16)&0x00ff0000;
int pix1=(pix&0xff00ff00) | pr | pb;
bt[(h-i-1)*w+j]=pix1;
}
}
Bitmap inBitmap = null ;
if (inBitmap == null || !inBitmap.isMutable()
|| inBitmap.getWidth() != w || inBitmap.getHeight() != h) {
inBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
}
//Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
inBitmap.copyPixelsFromBuffer(buffer);
//return inBitmap ;
// return Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
inBitmap = Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
inBitmap.compress(CompressFormat.JPEG, 90, bos);
byte[] bitmapdata = bos.toByteArray();
ByteArrayInputStream fis = new ByteArrayInputStream(bitmapdata);
final Calendar c=Calendar.getInstance();
long mytimestamp=c.getTimeInMillis();
String timeStamp=String.valueOf(mytimestamp);
String myfile="hari"+timeStamp+".jpeg";
dir_image = new File(Environment.getExternalStorageDirectory()+File.separator+
"printerscreenshots"+File.separator+"image");
dir_image.mkdirs();
try {
File tmpFile = new File(dir_image,myfile);
FileOutputStream fos = new FileOutputStream(tmpFile);
byte[] buf = new byte[1024];
int len;
while ((len = fis.read(buf)) > 0) {
fos.write(buf, 0, len);
}
fis.close();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Log.v("hari", "screenshots:"+dir_image.toString());
}
} catch(Exception e) {
e.printStackTrace();
}
}
You can use a GLTextureView extending TextureView instead of GlsurfaceView to show you OpenGL data.
See: http://stackoverflow.com/questions/12061419/converting-from-glsurfaceview-to-textureview-via-gltextureview
As the GLTextureView extends from TextureView, it has a getBitmap function that should work.
myGlTextureView.getBitmap(int width, int height)

Categories