Improve gradients

Avoid using textures for common gradients (two stops from 0.0 to 1.0)

Change-Id: Iff55d21b126c8cfc4cfb701669f2339c8f6b131a
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index 7026eead..b1c4dfe 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -16,9 +16,6 @@
 
 #define LOG_TAG "OpenGLRenderer"
 
-#include <SkCanvas.h>
-#include <SkGradientShader.h>
-
 #include <utils/threads.h>
 
 #include "Debug.h"
@@ -29,6 +26,22 @@
 namespace uirenderer {
 
 ///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+#define GRADIENT_TEXTURE_HEIGHT 2
+#define GRADIENT_BYTES_PER_PIXEL 4
+
+///////////////////////////////////////////////////////////////////////////////
+// Functions
+///////////////////////////////////////////////////////////////////////////////
+
+template<typename T>
+static inline T min(T a, T b) {
+    return a < b ? a : b;
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // Constructors/destructor
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -83,7 +96,7 @@
 
 void GradientCache::operator()(GradientCacheEntry& shader, Texture*& texture) {
     if (texture) {
-        const uint32_t size = texture->width * texture->height * 4;
+        const uint32_t size = texture->width * texture->height * GRADIENT_BYTES_PER_PIXEL;
         mSize -= size;
     }
 
@@ -97,14 +110,13 @@
 // Caching
 ///////////////////////////////////////////////////////////////////////////////
 
-Texture* GradientCache::get(uint32_t* colors, float* positions,
-        int count, SkShader::TileMode tileMode) {
+Texture* GradientCache::get(uint32_t* colors, float* positions, int count) {
 
-    GradientCacheEntry gradient(colors, positions, count, tileMode);
+    GradientCacheEntry gradient(colors, positions, count);
     Texture* texture = mCache.get(gradient);
 
     if (!texture) {
-        texture = addLinearGradient(gradient, colors, positions, count, tileMode);
+        texture = addLinearGradient(gradient, colors, positions, count);
     }
 
     return texture;
@@ -114,39 +126,41 @@
     mCache.clear();
 }
 
+void GradientCache::getGradientInfo(const uint32_t* colors, const int count,
+        GradientInfo& info) {
+    uint32_t width = 1 << (31 - __builtin_clz(256 * (count - 1)));
+    bool hasAlpha = false;
+
+    for (int i = 0; i < count; i++) {
+        if (((colors[i] >> 24) & 0xff) < 255) {
+            hasAlpha = true;
+            break;
+        }
+    }
+
+    info.width = min(width, uint32_t(mMaxTextureSize));
+    info.hasAlpha = hasAlpha;
+}
+
 Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient,
-        uint32_t* colors, float* positions, int count, SkShader::TileMode tileMode) {
-    int width = 256 * (count - 1);
-    width = width < mMaxTextureSize ? width : mMaxTextureSize;
+        uint32_t* colors, float* positions, int count) {
 
-    SkBitmap bitmap;
-    bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, 4);
-    bitmap.allocPixels();
-    bitmap.eraseColor(0);
+    GradientInfo info;
+    getGradientInfo(colors, count, info);
 
-    SkCanvas canvas(bitmap);
-
-    SkPoint points[2];
-    points[0].set(0.0f, 0.0f);
-    points[1].set(bitmap.width(), 0.0f);
-
-    SkShader* localShader = SkGradientShader::CreateLinear(points,
-            reinterpret_cast<const SkColor*>(colors), positions, count, tileMode);
-
-    SkPaint p;
-    p.setStyle(SkPaint::kStrokeAndFill_Style);
-    p.setShader(localShader)->unref();
-
-    canvas.drawRectCoords(0.0f, 0.0f, bitmap.width(), 4.0f, p);
+    Texture* texture = new Texture;
+    texture->width = info.width;
+    texture->height = GRADIENT_TEXTURE_HEIGHT;
+    texture->blend = info.hasAlpha;
+    texture->generation = 1;
 
     // Asume the cache is always big enough
-    const uint32_t size = bitmap.rowBytes() * bitmap.height();
+    const uint32_t size = texture->width * texture->height * GRADIENT_BYTES_PER_PIXEL;
     while (mSize + size > mMaxSize) {
         mCache.removeOldest();
     }
 
-    Texture* texture = new Texture;
-    generateTexture(&bitmap, texture);
+    generateTexture(colors, positions, count, texture);
 
     mSize += size;
     mCache.put(gradient, texture);
@@ -154,25 +168,67 @@
     return texture;
 }
 
-void GradientCache::generateTexture(SkBitmap* bitmap, Texture* texture) {
-    SkAutoLockPixels autoLock(*bitmap);
-    if (!bitmap->readyToDraw()) {
-        ALOGE("Cannot generate texture from shader");
-        return;
+void GradientCache::generateTexture(uint32_t* colors, float* positions,
+        int count, Texture* texture) {
+
+    const uint32_t width = texture->width;
+    const GLsizei rowBytes = width * GRADIENT_BYTES_PER_PIXEL;
+    uint32_t pixels[width * texture->height];
+
+    int currentPos = 1;
+
+    float startA = (colors[0] >> 24) & 0xff;
+    float startR = (colors[0] >> 16) & 0xff;
+    float startG = (colors[0] >>  8) & 0xff;
+    float startB = (colors[0] >>  0) & 0xff;
+
+    float endA = (colors[1] >> 24) & 0xff;
+    float endR = (colors[1] >> 16) & 0xff;
+    float endG = (colors[1] >>  8) & 0xff;
+    float endB = (colors[1] >>  0) & 0xff;
+
+    float start = positions[0];
+    float distance = positions[1] - start;
+
+    uint8_t* p = (uint8_t*) pixels;
+    for (uint32_t x = 0; x < width; x++) {
+        float pos = x / float(width - 1);
+        if (pos > positions[currentPos]) {
+            startA = endA;
+            startR = endR;
+            startG = endG;
+            startB = endB;
+            start = positions[currentPos];
+
+            currentPos++;
+
+            endA = (colors[currentPos] >> 24) & 0xff;
+            endR = (colors[currentPos] >> 16) & 0xff;
+            endG = (colors[currentPos] >>  8) & 0xff;
+            endB = (colors[currentPos] >>  0) & 0xff;
+            distance = positions[currentPos] - start;
+        }
+
+        float amount = (pos - start) / distance;
+        float oppAmount = 1.0f - amount;
+
+        *p++ = uint8_t(startR * oppAmount + endR * amount);
+        *p++ = uint8_t(startG * oppAmount + endG * amount);
+        *p++ = uint8_t(startB * oppAmount + endB * amount);
+        *p++ = uint8_t(startA * oppAmount + endA * amount);
     }
 
-    texture->generation = bitmap->getGenerationID();
-    texture->width = bitmap->width();
-    texture->height = bitmap->height();
+    for (int i = 1; i < GRADIENT_TEXTURE_HEIGHT; i++) {
+        memcpy(pixels + width * i, pixels, rowBytes);
+    }
 
     glGenTextures(1, &texture->id);
 
     glBindTexture(GL_TEXTURE_2D, texture->id);
-    glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
+    glPixelStorei(GL_UNPACK_ALIGNMENT, GRADIENT_BYTES_PER_PIXEL);
 
-    texture->blend = !bitmap->isOpaque();
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap->rowBytesAsPixels(), texture->height, 0,
-            GL_RGBA, GL_UNSIGNED_BYTE, bitmap->getPixels());
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, texture->height, 0,
+            GL_RGBA, GL_UNSIGNED_BYTE, pixels);
 
     texture->setFilter(GL_LINEAR);
     texture->setWrap(GL_CLAMP_TO_EDGE);