/* * Copyright (C) Winson Chiu * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cw.kop.autobackground; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.ThumbnailUtils; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.os.Handler; import android.support.v8.renderscript.Allocation; import android.support.v8.renderscript.Element; import android.support.v8.renderscript.RenderScript; import android.support.v8.renderscript.ScriptIntrinsicBlur; import android.util.Log; import android.widget.Toast; import java.io.File; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import cw.kop.autobackground.files.FileHandler; import cw.kop.autobackground.settings.AppSettings; /** * Created by TheKeeperOfPie on 11/13/2014. */ class WallpaperRenderer implements GLSurfaceView.Renderer { private static final String TAG = "Renderer"; private float[] matrixView = new float[16]; private float renderScreenWidth = 1; private float renderScreenHeight = 1; private long startTime; private long endTime; private long frameTime; private long targetFrameTime; private Context serviceContext; private float rawOffsetX = 0f; private boolean loadCurrent = false; private Callback callback; private volatile List<RenderImage> renderImagesTop; private volatile List<RenderImage> renderImagesBottom; private boolean isLastTop = false; private Handler handler; private RenderImage.EventListener eventListener = new RenderImage.EventListener() { @Override public void removeSelf(RenderImage image) { renderImagesTop.remove(image); renderImagesBottom.remove(image); if (!(AppSettings.useAnimation() || AppSettings.useVerticalAnimation())) { callback.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); } } @Override public void doneLoading() { if (renderImagesTop.size() > 1) { renderImagesTop.get(0).startFinish(); } if (renderImagesBottom.size() > 1) { renderImagesBottom.get(0).startFinish(); } } public Context getContext() { return serviceContext; } @Override public void toastEffect(final String effectName, final String effectValue) { handler.post(new Runnable() { @Override public void run() { Toast.makeText(serviceContext, "Effect applied: " + effectName + " " + effectValue, Toast.LENGTH_SHORT).show(); } }); } @Override public void requestRender() { callback.requestRender(); } }; public WallpaperRenderer(Context context, Callback callback) { serviceContext = context; this.callback = callback; startTime = System.currentTimeMillis(); renderImagesTop = new CopyOnWriteArrayList<>(); renderImagesBottom = new CopyOnWriteArrayList<>(); handler = new Handler(serviceContext.getMainLooper()); } @Override public void onDrawFrame(GL10 gl) { try { endTime = System.currentTimeMillis(); frameTime = endTime - startTime; if (frameTime < targetFrameTime) { Thread.sleep(targetFrameTime - frameTime); } startTime = System.currentTimeMillis(); } catch (InterruptedException e) { } if (renderImagesTop.size() > 2) { renderImagesTop.get(0).finishImmediately(); } if (renderImagesBottom.size() > 2) { renderImagesBottom.get(0).finishImmediately(); } GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); if (isLastTop) { for (RenderImage image : renderImagesBottom) { image.renderImage(); } for (RenderImage image : renderImagesTop) { image.renderImage(); } } else { for (RenderImage image : renderImagesTop) { image.renderImage(); } for (RenderImage image : renderImagesBottom) { image.renderImage(); } } } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { if (width != renderScreenWidth) { GLES20.glViewport(0, 0, width, height); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); for (RenderImage image : renderImagesTop) { image.setDimensions(width, height); } for (RenderImage image : renderImagesBottom) { image.setDimensions(width, height); } } if (loadCurrent) { callback.loadCurrent(); loadCurrent = false; } renderScreenWidth = width; renderScreenHeight = height; for (RenderImage image : renderImagesTop) { image.resetMatrices(); } for (RenderImage image : renderImagesBottom) { image.resetMatrices(); } for (int i = 0; i < 16; i++) { matrixView[i] = 0.0f; } // Set the camera position (View matrix) android.opengl.Matrix.setLookAtM(matrixView, 0, 0f, 0f, 1f, 0f, 0f, 0.0f, 0f, 1f, 0.0f); if (renderImagesTop.isEmpty()) { File nextImage = FileHandler.getNextImage(); if (nextImage != null && nextImage.exists()) { loadNext(nextImage); } } if (renderImagesBottom.isEmpty() && AppSettings.useDoubleImage()) { File nextImage = FileHandler.getNextImage(); if (nextImage != null && nextImage.exists()) { loadNext(nextImage); } } } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); RenderImage.setupRenderValues(); } private RenderImage getNewImage(Bitmap bitmap, float minRatioX, float maxRatioX, float minRatioY, float maxRatioY) { RenderImage image = new RenderImage(bitmap, getNewTextureId(), eventListener, minRatioX, maxRatioX, minRatioY, maxRatioY); image.setAnimated(AppSettings.useAnimation() || AppSettings.useVerticalAnimation()); image.setAnimationModifierX(AppSettings.getAnimationSpeed() / 10f); image.setAnimationModifierY(AppSettings.getVerticalAnimationSpeed() / 10f); frameTime = 1000 / AppSettings.getAnimationFrameRate(); return image; } public void onOffsetsChanged(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels) { rawOffsetX = AppSettings.forceParallax() ? 1.0f - xOffset : xOffset; for (RenderImage image : renderImagesTop) { image.onOffsetsChanged(xOffset, yOffset, xStep, yStep, xPixels, yPixels); } for (RenderImage image : renderImagesBottom) { image.onOffsetsChanged(xOffset, yOffset, xStep, yStep, xPixels, yPixels); } } public void onSwipe(float xMovement, float yMovement, float positionY) { if (positionY > renderScreenHeight / 2 && renderImagesBottom.size() > 0) { for (RenderImage image : renderImagesBottom) { image.onSwipe(xMovement, yMovement); } } else if (renderImagesTop.size() > 0) { for (RenderImage image : renderImagesTop) { image.onSwipe(xMovement, yMovement); } } } public void setScaleFactor(float factor, float positionY) { if (positionY > renderScreenHeight / 2 && renderImagesBottom.size() > 0) { for (RenderImage image : renderImagesBottom) { image.applyScaleFactor(factor); } } else if (renderImagesTop.size() > 0) { for (RenderImage image : renderImagesTop) { image.applyScaleFactor(factor); } } } public void resetPosition() { for (RenderImage image : renderImagesTop) { image.applyScaleFactor(0.0f); image.setRawOffsetX(rawOffsetX); image.setAnimated(AppSettings.useAnimation() || AppSettings.useVerticalAnimation()); } for (RenderImage image : renderImagesBottom) { image.applyScaleFactor(0.0f); image.setRawOffsetX(rawOffsetX); image.setAnimated(AppSettings.useAnimation() || AppSettings.useVerticalAnimation()); } callback.requestRender(); } private int getNewTextureId() { int[] tempIntArray = new int[1]; GLES20.glGenTextures(1, tempIntArray, 0); return tempIntArray[0]; } public void loadNext(File nextImage) { if (isLastTop) { loadNext(nextImage, renderScreenHeight); } else { loadNext(nextImage, 0); } } public void loadNext(File nextImage, float positionY) { if (nextImage == null) { return; } BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_8888; options.inPreferQualityOverSpeed = true; options.inDither = true; if (AppSettings.getBlurRadius() > 0) { options.inSampleSize = 2; } try { Bitmap bitmap = BitmapFactory.decodeFile(nextImage.getAbsolutePath(), options); if (bitmap == null) { return; } int maxTextureSize = RenderImage.maxTextureSize[0]; int width = maxTextureSize < bitmap.getWidth() ? maxTextureSize : bitmap.getWidth(); int height = maxTextureSize < bitmap.getHeight() ? maxTextureSize : bitmap.getHeight(); if (AppSettings.getBlurRadius() > 0) { float ratio = (float) height / width; width -= width % 4; height = (int) (width * ratio); } bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); if (AppSettings.getBlurRadius() > 0) { bitmap = blurBitmap(bitmap); } RenderImage newImage; if (AppSettings.useDoubleImage()) { if (positionY < renderScreenHeight / 2) { newImage = getNewImage(bitmap, 0.0f, 1.0f, 0.5f, 1.0f); } else { newImage = getNewImage(bitmap, 0.0f, 1.0f, 0.0f, 0.5f); } } else { newImage = getNewImage(bitmap, 0.0f, 1.0f, 0.0f, 1.0f); } newImage.setRawOffsetX(rawOffsetX); newImage.setDimensions(renderScreenWidth, renderScreenHeight); callback.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); if (AppSettings.useDoubleImage() && positionY > renderScreenHeight / 2) { renderImagesBottom.add(newImage); isLastTop = false; } else { renderImagesTop.add(newImage); isLastTop = true; if (!AppSettings.useDoubleImage()) { if (renderImagesBottom.size() > 0) { renderImagesBottom.get(0).startFinish(); } } } } catch (Exception e) { e.printStackTrace(); } } private Bitmap blurBitmap(Bitmap bitmap) { Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); RenderScript renderScript = RenderScript.create(serviceContext); ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)); Allocation inAlloc = Allocation.createFromBitmap(renderScript, bitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE); Allocation outAlloc = Allocation.createFromBitmap(renderScript, output); script.setRadius(AppSettings.getBlurRadius() / 10f); script.setInput(inAlloc); script.forEach(outAlloc); outAlloc.copyTo(output); renderScript.destroy(); bitmap.recycle(); return output; } public void setLoadCurrent(boolean load) { loadCurrent = load; } public void setTargetFrameTime(long newFrameTime) { this.targetFrameTime = newFrameTime; } public void release() { Log.i(TAG, "release"); for (RenderImage image : renderImagesTop) { image.finishImmediately(); } for (RenderImage image : renderImagesBottom) { image.finishImmediately(); } } public interface Callback { void setRenderMode(int mode); void loadCurrent(); void requestRender(); } }