diff options
author | 2018-07-18 16:32:27 -0700 | |
---|---|---|
committer | 2018-07-19 15:58:39 -0700 | |
commit | 339cf9b3272f8581aa4cae34be368d1ff78e85c8 (patch) | |
tree | c15bd2de1eedacf3348c7c1911bdd3c86b1dbaf0 | |
parent | 71fa53f8a548993d30d91343caee0e6269e8c0c5 (diff) |
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
-rw-r--r-- | libs/hwui/CanvasTransform.cpp | 36 | ||||
-rw-r--r-- | libs/hwui/HardwareBitmapUploader.cpp | 2 | ||||
-rw-r--r-- | libs/hwui/hwui/Bitmap.cpp | 107 | ||||
-rw-r--r-- | libs/hwui/hwui/Bitmap.h | 25 | ||||
-rw-r--r-- | libs/hwui/utils/Color.cpp | 96 | ||||
-rw-r--r-- | libs/hwui/utils/Color.h | 10 |
6 files changed, 256 insertions, 20 deletions
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp index bac7a4d17a49..1b15dbd526cc 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 6408ce6c04f4..ab80d3d98448 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -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 diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index a401b3f96ef2..7a8d026df3b4 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 @@ 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 diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index dbd445682bde..d2680429a1e9 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -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 { diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index 75740e8b5baf..a3e785957526 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 @@ 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 diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h index 2bec1f5f7852..3c13a548fe76 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -114,6 +114,16 @@ static constexpr float EOCF(float srgb) { 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 */ |