Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 339cf9b3 authored by John Reck's avatar John Reck
Browse files

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
parent 71fa53f8
Loading
Loading
Loading
Loading
+21 −15
Original line number Diff line number Diff line
@@ -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) {
+1 −1
Original line number Diff line number Diff line
@@ -286,7 +286,7 @@ sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sou
        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
+105 −2
Original line number Diff line number Diff line
@@ -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 @@ Bitmap::Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info
    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 @@ sk_sp<SkImage> Bitmap::makeImage(sk_sp<SkColorFilter>* outputColorFilter) {
    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
+24 −1
Original line number Diff line number Diff line
@@ -34,6 +34,12 @@ enum class PixelStorageType {
    Hardware,
};

enum class BitmapPalette {
    Unknown,
    Light,
    Dark,
};

namespace uirenderer {
namespace renderthread {
class RenderThread;
@@ -63,7 +69,7 @@ public:
    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 @@ public:
     */
    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 @@ private:

    const PixelStorageType mPixelStorageType;

    BitmapPalette mPalette = BitmapPalette::Unknown;
    uint32_t mPaletteGenerationId = -1;

    bool mHasHardwareMipMap = false;

    union {
+95 −1
Original line number Diff line number Diff line
@@ -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 @@ sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) {
    }
}

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
Loading