More auto-dark stuff

Initial attempt at selective bitmap inverting
Use CIE_LAB colorspace for inverting instead of HSV

Test: Manually poking around
Change-Id: I014ff31eeae471ee7f6a40a6daa4e7099c2a7ff8
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index bac7a4d..1b15dbd 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -15,32 +15,38 @@
  */
 
 #include "CanvasTransform.h"
+#include "utils/Color.h"
 #include "Properties.h"
 
+#include <ui/ColorSpace.h>
 #include <SkColorFilter.h>
 #include <SkPaint.h>
-#include <log/log.h>
+
+#include <algorithm>
+#include <cmath>
 
 namespace android::uirenderer {
 
 static SkColor makeLight(SkColor color) {
-    SkScalar hsv[3];
-    SkColorToHSV(color, hsv);
-    if (hsv[1] > .2f) return color;
-    // hsv[1] *= .85f;
-    // hsv[2] = std::min(1.0f, std::max(hsv[2], 1 - hsv[2]) * 1.3f);
-    hsv[2] = std::max(hsv[2], 1.1f - hsv[2]);
-    return SkHSVToColor(SkColorGetA(color), hsv);
+    Lab lab = sRGBToLab(color);
+    float invertedL = std::min(110 - lab.L, 100.0f);
+    if (invertedL > lab.L) {
+        lab.L = invertedL;
+        return LabToSRGB(lab, SkColorGetA(color));
+    } else {
+        return color;
+    }
 }
 
 static SkColor makeDark(SkColor color) {
-    SkScalar hsv[3];
-    SkColorToHSV(color, hsv);
-    if (hsv[1] > .2f) return color;
-    // hsv[1] *= .85f;
-    // hsv[2] = std::max(0.0f, std::min(hsv[2], 1 - hsv[2]) * .7f);
-    hsv[2] = std::min(hsv[2], 1.1f - hsv[2]);
-    return SkHSVToColor(SkColorGetA(color), hsv);
+    Lab lab = sRGBToLab(color);
+    float invertedL = std::min(110 - lab.L, 100.0f);
+    if (invertedL < lab.L) {
+        lab.L = invertedL;
+        return LabToSRGB(lab, SkColorGetA(color));
+    } else {
+        return color;
+    }
 }
 
 static SkColor transformColor(ColorTransform transform, SkColor color) {
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 6408ce6..ab80d3d 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -286,7 +286,7 @@
         eglDestroySyncKHR(display, fence);
     }
 
-    return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info()));
+    return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info(), Bitmap::computePalette(bitmap)));
 }
 
 };  // namespace android::uirenderer
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index a401b3f..7a8d026 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -17,6 +17,7 @@
 
 #include "Caches.h"
 #include "HardwareBitmapUploader.h"
+#include "Properties.h"
 #include "renderthread/RenderProxy.h"
 #include "utils/Color.h"
 
@@ -34,6 +35,7 @@
 #include <SkToSRGBColorFilter.h>
 
 #include <limits>
+#include <SkHighContrastFilter.h>
 
 namespace android {
 
@@ -195,11 +197,13 @@
     mPixelStorage.ashmem.size = mappedSize;
 }
 
-Bitmap::Bitmap(GraphicBuffer* buffer, const SkImageInfo& info)
+Bitmap::Bitmap(GraphicBuffer* buffer, const SkImageInfo& info, BitmapPalette palette)
         : SkPixelRef(info.width(), info.height(), nullptr,
                      bytesPerPixel(buffer->getPixelFormat()) * buffer->getStride())
         , mInfo(validateAlpha(info))
-        , mPixelStorageType(PixelStorageType::Hardware) {
+        , mPixelStorageType(PixelStorageType::Hardware)
+        , mPalette(palette)
+        , mPaletteGenerationId(getGenerationID()) {
     mPixelStorage.hardware.buffer = buffer;
     buffer->incStrong(buffer);
     setImmutable();  // HW bitmaps are always immutable
@@ -326,7 +330,106 @@
     if (image->colorSpace() != nullptr && !image->colorSpace()->isSRGB()) {
         *outputColorFilter = SkToSRGBColorFilter::Make(image->refColorSpace());
     }
+
+    // TODO: Move this to the canvas (or other?) layer where we have the target lightness
+    // mode and can selectively do the right thing.
+    if (palette() != BitmapPalette::Unknown && uirenderer::Properties::forceDarkMode) {
+        SkHighContrastConfig config;
+        config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness;
+        *outputColorFilter = SkHighContrastFilter::Make(config)->makeComposed(*outputColorFilter);
+    }
     return image;
 }
 
+class MinMaxAverage {
+public:
+
+    void add(float sample) {
+        if (mCount == 0) {
+            mMin = sample;
+            mMax = sample;
+        } else {
+            mMin = std::min(mMin, sample);
+            mMax = std::max(mMax, sample);
+        }
+        mTotal += sample;
+        mCount++;
+    }
+
+    float average() {
+        return mTotal / mCount;
+    }
+
+    float min() {
+        return mMin;
+    }
+
+    float max() {
+        return mMax;
+    }
+
+    float delta() {
+        return mMax - mMin;
+    }
+
+private:
+    float mMin = 0.0f;
+    float mMax = 0.0f;
+    float mTotal = 0.0f;
+    int mCount = 0;
+};
+
+BitmapPalette Bitmap::computePalette(const SkImageInfo& info, const void* addr, size_t rowBytes) {
+    ATRACE_CALL();
+
+    SkPixmap pixmap{info, addr, rowBytes};
+
+    // TODO: This calculation of converting to HSV & tracking min/max is probably overkill
+    // Experiment with something simpler since we just want to figure out if it's "color-ful"
+    // and then the average perceptual lightness.
+
+    MinMaxAverage hue, saturation, value;
+    int sampledCount = 0;
+
+    // Sample a grid of 100 pixels to get an overall estimation of the colors in play
+    const int x_step = std::max(1, pixmap.width() / 10);
+    const int y_step = std::max(1, pixmap.height() / 10);
+    for (int x = 0; x < pixmap.width(); x += x_step) {
+        for (int y = 0; y < pixmap.height(); y += y_step) {
+            SkColor color = pixmap.getColor(x, y);
+            if (!info.isOpaque() && SkColorGetA(color) < 75) {
+                continue;
+            }
+
+            sampledCount++;
+            float hsv[3];
+            SkColorToHSV(color, hsv);
+            hue.add(hsv[0]);
+            saturation.add(hsv[1]);
+            value.add(hsv[2]);
+        }
+    }
+
+    // TODO: Tune the coverage threshold
+    if (sampledCount < 5) {
+        ALOGV("Not enough samples, only found %d for image sized %dx%d, format = %d, alpha = %d",
+              sampledCount, info.width(), info.height(), (int) info.colorType(), (int) info.alphaType());
+        return BitmapPalette::Unknown;
+    }
+
+    ALOGV("samples = %d, hue [min = %f, max = %f, avg = %f]; saturation [min = %f, max = %f, avg = %f]",
+          sampledCount,
+          hue.min(), hue.max(), hue.average(),
+          saturation.min(), saturation.max(), saturation.average());
+
+    if (hue.delta() <= 20 && saturation.delta() <= .1f) {
+        if (value.average() >= .5f) {
+            return BitmapPalette::Light;
+        } else {
+            return BitmapPalette::Dark;
+        }
+    }
+    return BitmapPalette::Unknown;
+}
+
 }  // namespace android
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index dbd4456..d268042 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -34,6 +34,12 @@
     Hardware,
 };
 
+enum class BitmapPalette {
+    Unknown,
+    Light,
+    Dark,
+};
+
 namespace uirenderer {
 namespace renderthread {
 class RenderThread;
@@ -63,7 +69,7 @@
     Bitmap(void* address, void* context, FreeFunc freeFunc, const SkImageInfo& info,
            size_t rowBytes);
     Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes);
-    Bitmap(GraphicBuffer* buffer, const SkImageInfo& info);
+    Bitmap(GraphicBuffer* buffer, const SkImageInfo& info, BitmapPalette palette = BitmapPalette::Unknown);
 
     int rowBytesAsPixels() const { return rowBytes() >> mInfo.shiftPerPixel(); }
 
@@ -103,6 +109,20 @@
      */
     sk_sp<SkImage> makeImage(sk_sp<SkColorFilter>* outputColorFilter);
 
+    static BitmapPalette computePalette(const SkImageInfo& info, const void* addr, size_t rowBytes);
+
+    static BitmapPalette computePalette(const SkBitmap& bitmap) {
+        return computePalette(bitmap.info(), bitmap.getPixels(), bitmap.rowBytes());
+    }
+
+    BitmapPalette palette() {
+        if (!isHardware() && mPaletteGenerationId != getGenerationID()) {
+            mPalette = computePalette(info(), pixels(), rowBytes());
+            mPaletteGenerationId = getGenerationID();
+        }
+        return mPalette;
+    }
+
 private:
     virtual ~Bitmap();
     void* getStorage() const;
@@ -111,6 +131,9 @@
 
     const PixelStorageType mPixelStorageType;
 
+    BitmapPalette mPalette = BitmapPalette::Unknown;
+    uint32_t mPaletteGenerationId = -1;
+
     bool mHasHardwareMipMap = false;
 
     union {
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index 75740e8..a3e7859 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -16,8 +16,10 @@
 
 #include "Color.h"
 
-
 #include <utils/Log.h>
+#include <ui/ColorSpace.h>
+
+#include <algorithm>
 #include <cmath>
 
 namespace android {
@@ -107,5 +109,97 @@
     }
 }
 
+template<typename T>
+static constexpr T clamp(T x, T min, T max) {
+    return x < min ? min : x > max ? max : x;
+}
+
+//static const float2 ILLUMINANT_D50_XY = {0.34567f, 0.35850f};
+static const float3 ILLUMINANT_D50_XYZ = {0.964212f, 1.0f, 0.825188f};
+static const mat3 BRADFORD = mat3{
+        float3{ 0.8951f, -0.7502f,  0.0389f},
+        float3{ 0.2664f,  1.7135f, -0.0685f},
+        float3{-0.1614f,  0.0367f,  1.0296f}
+};
+
+static mat3 adaptation(const mat3& matrix, const float3& srcWhitePoint, const float3& dstWhitePoint) {
+    float3 srcLMS = matrix * srcWhitePoint;
+    float3 dstLMS = matrix * dstWhitePoint;
+    return inverse(matrix) * mat3{dstLMS / srcLMS} * matrix;
+}
+
+namespace LabColorSpace {
+
+static constexpr float A = 216.0f / 24389.0f;
+static constexpr float B = 841.0f / 108.0f;
+static constexpr float C = 4.0f / 29.0f;
+static constexpr float D = 6.0f / 29.0f;
+
+float3 toXyz(const Lab& lab) {
+    float3 v { lab.L, lab.a, lab.b };
+    v[0] = clamp(v[0], 0.0f, 100.0f);
+    v[1] = clamp(v[1], -128.0f, 128.0f);
+    v[2] = clamp(v[2], -128.0f, 128.0f);
+
+    float fy = (v[0] + 16.0f) / 116.0f;
+    float fx = fy + (v[1] * 0.002f);
+    float fz = fy - (v[2] * 0.005f);
+    float X = fx > D ? fx * fx * fx : (1.0f / B) * (fx - C);
+    float Y = fy > D ? fy * fy * fy : (1.0f / B) * (fy - C);
+    float Z = fz > D ? fz * fz * fz : (1.0f / B) * (fz - C);
+
+    v[0] = X * ILLUMINANT_D50_XYZ[0];
+    v[1] = Y * ILLUMINANT_D50_XYZ[1];
+    v[2] = Z * ILLUMINANT_D50_XYZ[2];
+
+    return v;
+}
+
+Lab fromXyz(const float3& v) {
+    float X = v[0] / ILLUMINANT_D50_XYZ[0];
+    float Y = v[1] / ILLUMINANT_D50_XYZ[1];
+    float Z = v[2] / ILLUMINANT_D50_XYZ[2];
+
+    float fx = X > A ? pow(X, 1.0f / 3.0f) : B * X + C;
+    float fy = Y > A ? pow(Y, 1.0f / 3.0f) : B * Y + C;
+    float fz = Z > A ? pow(Z, 1.0f / 3.0f) : B * Z + C;
+
+    float L = 116.0f * fy - 16.0f;
+    float a = 500.0f * (fx - fy);
+    float b = 200.0f * (fy - fz);
+
+    return Lab {
+            clamp(L, 0.0f, 100.0f),
+            clamp(a, -128.0f, 128.0f),
+            clamp(b, -128.0f, 128.0f)
+    };
+}
+
+};
+
+Lab sRGBToLab(SkColor color) {
+    auto colorSpace = ColorSpace::sRGB();
+    float3 rgb;
+    rgb.r = SkColorGetR(color) / 255.0f;
+    rgb.g = SkColorGetG(color) / 255.0f;
+    rgb.b = SkColorGetB(color) / 255.0f;
+    float3 xyz = colorSpace.rgbToXYZ(rgb);
+    float3 srcXYZ = ColorSpace::XYZ(float3{colorSpace.getWhitePoint(), 1});
+    xyz = adaptation(BRADFORD, srcXYZ, ILLUMINANT_D50_XYZ) * xyz;
+    return LabColorSpace::fromXyz(xyz);
+}
+
+SkColor LabToSRGB(const Lab& lab, SkAlpha alpha) {
+    auto colorSpace = ColorSpace::sRGB();
+    float3 xyz = LabColorSpace::toXyz(lab);
+    float3 dstXYZ = ColorSpace::XYZ(float3{colorSpace.getWhitePoint(), 1});
+    xyz = adaptation(BRADFORD, ILLUMINANT_D50_XYZ, dstXYZ) * xyz;
+    float3 rgb = colorSpace.xyzToRGB(xyz);
+    return SkColorSetARGB(alpha,
+            static_cast<uint8_t>(rgb.r * 255),
+            static_cast<uint8_t>(rgb.g * 255),
+            static_cast<uint8_t>(rgb.b * 255));
+}
+
 };  // namespace uirenderer
 };  // namespace android
diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h
index 2bec1f5..3c13a54 100644
--- a/libs/hwui/utils/Color.h
+++ b/libs/hwui/utils/Color.h
@@ -114,6 +114,16 @@
 bool transferFunctionCloseToSRGB(const SkColorSpace* colorSpace);
 
 sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace);
+
+struct Lab {
+    float L;
+    float a;
+    float b;
+};
+
+Lab sRGBToLab(SkColor color);
+SkColor LabToSRGB(const Lab& lab, SkAlpha alpha);
+
 } /* namespace uirenderer */
 } /* namespace android */