Add API to enable mipmaps on Bitmap
Bug #7353771

This API can be used when scaling large images down to a small size
to get nicer looking results.

Change-Id: If09087eed36077eee5355f6047a3ca67747d7d9e
diff --git a/api/current.txt b/api/current.txt
index ee9a973..c45706e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8263,6 +8263,7 @@
     method public int getScaledWidth(int);
     method public final int getWidth();
     method public final boolean hasAlpha();
+    method public final boolean hasMipMap();
     method public final boolean isMutable();
     method public final boolean isPremultiplied();
     method public final boolean isRecycled();
@@ -8271,6 +8272,7 @@
     method public boolean sameAs(android.graphics.Bitmap);
     method public void setDensity(int);
     method public void setHasAlpha(boolean);
+    method public final void setHasMipMap(boolean);
     method public void setPixel(int, int, int);
     method public void setPixels(int[], int, int, int, int, int, int);
     method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index fd7a6a7..f485e03 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -7,7 +7,7 @@
 #include "SkUnPreMultiply.h"

 

 #include <binder/Parcel.h>

-#include "android_os_Parcel.h"
+#include "android_os_Parcel.h"

 #include "android_util_Binder.h"

 #include "android_nio_utils.h"

 #include "CreateJavaOutputStreamAdaptor.h"

@@ -353,6 +353,15 @@
     bitmap->setIsOpaque(!hasAlpha);

 }

 

+static jboolean Bitmap_hasMipMap(JNIEnv* env, jobject, SkBitmap* bitmap) {

+    return bitmap->hasHardwareMipMap();

+}

+

+static void Bitmap_setHasMipMap(JNIEnv* env, jobject, SkBitmap* bitmap,

+                                jboolean hasMipMap) {

+    bitmap->setHasHardwareMipMap(hasMipMap);

+}

+

 ///////////////////////////////////////////////////////////////////////////////

 

 static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {

@@ -666,6 +675,8 @@
     {   "nativeConfig",             "(I)I", (void*)Bitmap_config },

     {   "nativeHasAlpha",           "(I)Z", (void*)Bitmap_hasAlpha },

     {   "nativeSetHasAlpha",        "(IZ)V", (void*)Bitmap_setHasAlpha },

+    {   "nativeHasMipMap",          "(I)Z", (void*)Bitmap_hasMipMap },

+    {   "nativeSetHasMipMap",       "(IZ)V", (void*)Bitmap_setHasMipMap },

     {   "nativeCreateFromParcel",

         "(Landroid/os/Parcel;)Landroid/graphics/Bitmap;",

         (void*)Bitmap_createFromParcel },

diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 6238edb..22ecc61 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1030,6 +1030,51 @@
     }
 
     /**
+     * Indicates whether the renderer responsible for drawing this
+     * bitmap should attempt to use mipmaps when this bitmap is drawn
+     * scaled down.
+     * 
+     * If you know that you are going to draw this bitmap at less than
+     * 50% of its original size, you may be able to obtain a higher
+     * quality
+     * 
+     * This property is only a suggestion that can be ignored by the
+     * renderer. It is not guaranteed to have any effect.
+     * 
+     * @return true if the renderer should attempt to use mipmaps,
+     *         false otherwise
+     * 
+     * @see #setHasMipMap(boolean)
+     */
+    public final boolean hasMipMap() {
+        return nativeHasMipMap(mNativeBitmap);
+    }
+
+    /**
+     * Set a hint for the renderer responsible for drawing this bitmap
+     * indicating that it should attempt to use mipmaps when this bitmap
+     * is drawn scaled down.
+     *
+     * If you know that you are going to draw this bitmap at less than
+     * 50% of its original size, you may be able to obtain a higher
+     * quality by turning this property on.
+     * 
+     * Note that if the renderer respects this hint it might have to
+     * allocate extra memory to hold the mipmap levels for this bitmap.
+     *
+     * This property is only a suggestion that can be ignored by the
+     * renderer. It is not guaranteed to have any effect.
+     *
+     * @param hasMipMap indicates whether the renderer should attempt
+     *                  to use mipmaps
+     *
+     * @see #hasMipMap()
+     */
+    public final void setHasMipMap(boolean hasMipMap) {
+        nativeSetHasMipMap(mNativeBitmap, hasMipMap);
+    }
+
+    /**
      * Fills the bitmap's pixels with the specified {@link Color}.
      *
      * @throws IllegalStateException if the bitmap is not mutable.
@@ -1356,7 +1401,6 @@
     private static native int nativeHeight(int nativeBitmap);
     private static native int nativeRowBytes(int nativeBitmap);
     private static native int nativeConfig(int nativeBitmap);
-    private static native boolean nativeHasAlpha(int nativeBitmap);
 
     private static native int nativeGetPixel(int nativeBitmap, int x, int y);
     private static native void nativeGetPixels(int nativeBitmap, int[] pixels,
@@ -1385,7 +1429,10 @@
                                                     int[] offsetXY);
 
     private static native void nativePrepareToDraw(int nativeBitmap);
+    private static native boolean nativeHasAlpha(int nativeBitmap);
     private static native void nativeSetHasAlpha(int nBitmap, boolean hasAlpha);
+    private static native boolean nativeHasMipMap(int nativeBitmap);
+    private static native void nativeSetHasMipMap(int nBitmap, boolean hasMipMap);
     private static native boolean nativeSameAs(int nb0, int nb1);
     
     /* package */ final int ni() {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 2b50091..62e62bc 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -49,7 +49,7 @@
 
 #define ALPHA_THRESHOLD 0
 
-#define FILTER(paint) (paint && paint->isFilterBitmap() ? GL_LINEAR : GL_NEAREST)
+#define FILTER(paint) (!paint || paint->isFilterBitmap() ? GL_LINEAR : GL_NEAREST)
 
 ///////////////////////////////////////////////////////////////////////////////
 // Globals
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 03e2172..8d88bdc 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -36,6 +36,8 @@
         minFilter = GL_NEAREST;
         magFilter = GL_NEAREST;
 
+        mipMap = false;
+
         firstFilter = true;
         firstWrap = true;
 
@@ -83,6 +85,8 @@
                 glBindTexture(renderTarget, id);
             }
 
+            if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR;
+
             glTexParameteri(renderTarget, GL_TEXTURE_MIN_FILTER, min);
             glTexParameteri(renderTarget, GL_TEXTURE_MAG_FILTER, mag);
         }
@@ -116,7 +120,12 @@
      * Optional, size of the original bitmap.
      */
     uint32_t bitmapSize;
+    /**
+     * Indicates whether this texture will use trilinear filtering.
+     */
+    bool mipMap;
 
+private:
     /**
      * Last wrap modes set on this texture. Defaults to GL_CLAMP_TO_EDGE.
      */
@@ -129,7 +138,6 @@
     GLenum minFilter;
     GLenum magFilter;
 
-private:
     bool firstFilter;
     bool firstWrap;
 }; // struct Texture
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 4d16bb4..431367a 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -22,6 +22,7 @@
 
 #include <utils/threads.h>
 
+#include "Caches.h"
 #include "TextureCache.h"
 #include "Properties.h"
 
@@ -73,6 +74,8 @@
     INIT_LOGD("    Maximum texture dimension is %d pixels", mMaxTextureSize);
 
     mDebugEnabled = readDebugLevel() & kDebugCaches;
+
+    mHasNPot = Caches::getInstance().extensions.hasNPot();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -216,8 +219,11 @@
         return;
     }
 
+    // If the texture had mipmap enabled but not anymore,
+    // force a glTexImage2D to discard the mipmap levels
     const bool resize = !regenerate || bitmap->width() != int(texture->width) ||
-            bitmap->height() != int(texture->height);
+            bitmap->height() != int(texture->height) ||
+            (regenerate && mHasNPot && texture->mipMap && !bitmap->hasHardwareMipMap());
 
     if (!regenerate) {
         glGenTextures(1, &texture->id);
@@ -261,6 +267,13 @@
         break;
     }
 
+    if (mHasNPot) {
+        texture->mipMap = bitmap->hasHardwareMipMap();
+        if (texture->mipMap) {
+            glGenerateMipmap(GL_TEXTURE_2D);
+        }
+    }
+
     if (!regenerate) {
         texture->setFilter(GL_NEAREST);
         texture->setWrap(GL_CLAMP_TO_EDGE);
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index 31a2e3d..8e19092 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -138,6 +138,7 @@
 
     float mFlushRate;
 
+    bool mHasNPot;
     bool mDebugEnabled;
 
     Vector<SkBitmap*> mGarbage;
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 4cb409d..9118aea 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -33,6 +33,15 @@
         <meta-data android:name="android.graphics.renderThread" android:value="true" />
 
         <activity
+                android:name="MipMapActivity"
+                android:label="_MipMap">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
                 android:name="PathOffsetActivity"
                 android:label="_PathOffset">
             <intent-filter>
diff --git a/tests/HwAccelerationTest/res/drawable-nodpi/very_large_photo.jpg b/tests/HwAccelerationTest/res/drawable-nodpi/very_large_photo.jpg
new file mode 100644
index 0000000..7f047b1
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable-nodpi/very_large_photo.jpg
Binary files differ
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MipMapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MipMapActivity.java
new file mode 100644
index 0000000..1034649
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MipMapActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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 com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.ScaleAnimation;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class MipMapActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final BitmapsView view = new BitmapsView(this);
+        setContentView(view);
+    }
+
+    static class BitmapsView extends View {
+        private Paint mBitmapPaint;
+        private final Bitmap mBitmap1;
+        private final Bitmap mBitmap2;
+
+        BitmapsView(Context c) {
+            super(c);
+
+            mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.very_large_photo);
+            mBitmap2 = BitmapFactory.decodeResource(c.getResources(), R.drawable.very_large_photo);
+
+            mBitmap1.setHasMipMap(true);
+
+            mBitmapPaint = new Paint();
+            mBitmapPaint.setFilterBitmap(true);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            canvas.save();
+            canvas.scale(0.3f, 0.3f);
+            canvas.drawBitmap(mBitmap1, 0, 0, mBitmapPaint);
+            canvas.restore();
+
+            canvas.save();
+            canvas.translate(mBitmap1.getWidth() * 0.3f + 96.0f, 0.0f);
+            canvas.scale(0.3f, 0.3f);
+            canvas.drawBitmap(mBitmap2, 0, 0, mBitmapPaint);
+            canvas.restore();
+        }
+    }
+}