Index: third_party/WebKit/Source/platform/graphics/ColorSpaceFilter.cpp |
diff --git a/third_party/WebKit/Source/platform/graphics/ColorSpaceFilter.cpp b/third_party/WebKit/Source/platform/graphics/ColorSpaceFilter.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..020adf631182b8a5182bae6cdbd6ed5b2f833960 |
--- /dev/null |
+++ b/third_party/WebKit/Source/platform/graphics/ColorSpaceFilter.cpp |
@@ -0,0 +1,438 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "platform/graphics/ColorSpaceFilter.h" |
+ |
+#include "platform/graphics/ColorSpaceProfile.h" |
+#include "platform/graphics/ColorSpaceTransform.h" |
+#include "platform/graphics/GraphicsScreen.h" |
+#include "third_party/skia/include/core/SkColorFilter.h" |
+#include "third_party/skia/include/core/SkColorPriv.h" |
+#include "third_party/skia/include/core/SkData.h" |
+#include "third_party/skia/include/core/SkString.h" |
+#include "third_party/skia/include/core/SkUnPreMultiply.h" |
+#include "third_party/skia/include/core/SkWriteBuffer.h" |
+#include "third_party/skia/include/core/SkWriter32.h" |
+#include "third_party/skia/include/effects/SkColorCubeFilter.h" |
+#ifdef HAVE_COLOR_CURVE_FILTER |
+#include "third_party/skia/include/effects/SkColorCurveFilter.h" |
+#endif |
+#include "third_party/skia/include/effects/SkColorFilterImageFilter.h" |
+#include "third_party/skia/include/gpu/GrPaint.h" |
+#include "wtf/MainThread.h" |
+#include "wtf/RefPtr.h" |
+ |
+#if USE(QCMSLIB) |
+ |
+class ColorSpaceTransformFilter : public SkColorFilter { |
+public: |
+ static PassRefPtr<ColorSpaceTransformFilter> create(qcms_transform* transform, blink::ColorSpaceProfile* source, blink::ColorSpaceProfile* target) |
+ { |
+ return adoptRef(new ColorSpaceTransformFilter(transform, source, target)); |
+ } |
+ |
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(ColorSpaceTransformFilter) |
+ |
+ // Color space transforms do not change pixel alpha. |
+ |
+ virtual uint32_t getFlags() const |
+ { |
+ return this->SkColorFilter::getFlags() | SkColorFilter::kAlphaUnchanged_Flag; |
+ } |
+ |
+ virtual void filterSpan(const SkPMColor src[], int count, SkPMColor dst[]) const |
+ { |
+ // 1) Copy |src| pixel values to |dst|, convert from native to RGBA format. |
+ |
+ int offset = count; // Record the offset to the first alpha pixel (if any). |
+ |
+ for (int i = 0; i < count; ++i) { |
+ SkPMColor pixel = src[i]; |
+ |
+ if (SkGetPackedA32(pixel) < 255) { |
+ offset = i; |
+ break; |
+ } |
+ |
+ dst[i] = SkSwizzle_RGBA_to_PMColor(pixel); |
+ } |
+ |
+ // 2) Unpremultiply remaining |src| pixel values into |dst| in RGBA format. |
+ |
+ static const SkUnPreMultiply::Scale* table = SkUnPreMultiply::GetScaleTable(); |
+ |
+ unsigned char* output = reinterpret_cast<unsigned char*>(&dst[offset]); |
+ |
+ for (int i = offset; i < count; ++i) { |
+ SkPMColor pixel = src[i]; |
+ |
+ unsigned b = SkGetPackedB32(pixel); |
+ unsigned g = SkGetPackedG32(pixel); |
+ unsigned r = SkGetPackedR32(pixel); |
+ unsigned a = SkGetPackedA32(pixel); |
+ |
+ if (a < 255) { |
+ SkUnPreMultiply::Scale scale = table[a]; |
+ b = SkUnPreMultiply::ApplyScale(scale, b); |
+ g = SkUnPreMultiply::ApplyScale(scale, g); |
+ r = SkUnPreMultiply::ApplyScale(scale, r); |
+ } |
+ |
+ ASSERT(b <= 255); |
+ ASSERT(g <= 255); |
+ ASSERT(r <= 255); |
+ ASSERT(a <= 255); |
+ |
+ *output++ = r; |
+ *output++ = g; |
+ *output++ = b; |
+ *output++ = a; |
+ } |
+ |
+ // 3) Color correct the |dst| RGBA pixels in-situ with native pixel output. |
+ |
+ qcms_transform_data_type(m_transform->transform(), dst, dst, count, SK_B32_SHIFT ? QCMS_OUTPUT_RGBX : QCMS_OUTPUT_BGRX); |
+ |
+ // 4) Premultiply the |dst| native pixels (starting from the alpha offset). |
+ |
+ unsigned char* input = reinterpret_cast<unsigned char*>(&dst[offset]); |
+ |
+ for (int i = offset; i < count; ++i, input += 4) { |
+ unsigned b = input[SK_B32_SHIFT ? 2 : 0]; |
+ unsigned g = input[1]; |
+ unsigned r = input[SK_B32_SHIFT ? 0 : 2]; |
+ unsigned a = input[3]; |
+ |
+ enum FractionControl { RoundFraction = 257 * 128 }; |
+ |
+ if (a < 255) { |
+ unsigned alpha = a * 257; |
+ b = (b * alpha + RoundFraction) >> 16; |
+ g = (g * alpha + RoundFraction) >> 16; |
+ r = (r * alpha + RoundFraction) >> 16; |
+ } |
+ |
+ dst[i] = SkPackARGB32(a, r, g, b); |
+ } |
+ } |
+ |
+ void showColorSpaceFilter(const char* prefix = (const char *) " ") const |
+ { |
+ fprintf(stderr, "%scolor transform %s\n", prefix, colorSpaceTransformFilterKey().ascii().data()); |
+ fflush(stderr); |
+ } |
+ |
+ PassRefPtr<SkColorFilter> asColorFilter() const |
+ { |
+ fprintf(stderr, "asColorFilter %p requested\n", this); |
+ |
+ if (!m_effect.get()) { |
+ const_cast<ColorSpaceTransformFilter*>(this)->createColorFilterEffect(); |
+ |
+ if (m_effect.get()) |
+ showColorSpaceFilter("asColorFilter created filter effect "); |
+ } |
+ |
+ if (!m_effect.get()) { |
+ showColorSpaceFilter("asColorFilter create filter effect: FAILED "); |
+ return nullptr; |
+ } |
+ |
+ showColorSpaceFilter("asColorFilter return filter SUCCESS "); |
+ return m_effect; |
+ } |
+ |
+ String colorSpaceTransformFilterKey() const |
+ { |
+ return transformFilterKey(m_source->profile(), m_target->profile()); |
+ } |
+ |
+ ~ColorSpaceTransformFilter() |
+ { |
+ } |
+ |
+protected: |
+ ColorSpaceTransformFilter(qcms_transform* transform, blink::ColorSpaceProfile* source, blink::ColorSpaceProfile* target) |
+ : m_transform(blink::ColorSpaceTransform::create(transform)) |
+ , m_source(source) |
+ , m_target(target) |
+ { |
+ fprintf(stderr, "creating ColorSpaceFilter %p transform-is-matrix: %d\n", this, qcms_transform_is_matrix(transform)); |
+ |
+ RELEASE_ASSERT(m_transform && m_source->profile() && m_target->profile()); |
+ } |
+ |
+ |
+ virtual const GrFragmentProcessor* asFragmentProcessor(GrContext* context) const |
+ { |
+ fprintf(stderr, "GPU asFragmentProcessor %p requested\n", this); |
+ |
+ if (!m_effect.get()) { |
+ const_cast<ColorSpaceTransformFilter*>(this)->createColorFilterEffect(); |
+ |
+ if (m_effect.get()) |
+ showColorSpaceFilter("GPU asFragmentProcessor created filter effect "); |
+ } |
+ |
+ if (!m_effect.get()) { |
+ showColorSpaceFilter("GPU asFragmentProcessor create filter effect: FAILED "); |
+ return nullptr; |
+ } |
+ |
+ const GrFragmentProcessor* fragment = m_effect->asFragmentProcessor(context); |
+ |
+ if (fragment) { |
+ showColorSpaceFilter("GPU asFragmentProcessor return fragment SUCCESS "); |
+ } |
+ |
+ return fragment; |
+ } |
+ |
+ void createColorFilterEffect() |
+ { |
+ RELEASE_ASSERT(!m_effect.get()); |
+ |
+ SkColorFilter* filter; |
+ |
+#ifdef HAVE_COLOR_CURVE_FILTER |
+ if (qcms_transform_is_matrix(m_transform->transform())) |
+ filter = this->createColorCurveFilter(); |
+ else |
+ filter = this->createColorCubeFilter(); |
+#else |
+ filter = this->createColorCubeFilter(); |
+#endif |
+ m_effect = adoptRef(filter); |
+ } |
+ |
+#ifdef HAVE_COLOR_CURVE_FILTER |
+ |
+ SkColorFilter* createColorCurveFilter() |
+ { |
+ uint32_t inputSize = inputToneCurveSize(m_transform->transform(), m_source->profile()); |
+ SkAutoDataUnref inputData( |
+ SkData::NewUninitialized(inputSize * 4 * sizeof(uint16_t))); |
+ if (!inputSize || !inputData) { |
+ fprintf(stderr, "filter input curve data creation failed\n"); |
+ return nullptr; |
+ } |
+ |
+ uint16_t* inputCurve = static_cast<uint16_t *>(inputData->writable_data()); |
+ readInputToneCurve(m_transform->transform(), m_source->profile(), inputCurve); |
+ fprintf(stderr, "input curve data created %p size=%d\n", this, (int) inputData->size()); |
+ |
+ SkAutoTUnref<SkToneCurve> input( |
+ SkToneCurve::Create(inputData, inputSize, SkToneCurve::kHalfType)); |
+ |
+ SkMatrix matrix; |
+ matrix[0] = qcms_transform_get_matrix(m_transform->transform(), 0, 0); |
+ matrix[1] = qcms_transform_get_matrix(m_transform->transform(), 0, 1); |
+ matrix[2] = qcms_transform_get_matrix(m_transform->transform(), 0, 2); |
+ matrix[3] = qcms_transform_get_matrix(m_transform->transform(), 1, 0); |
+ matrix[4] = qcms_transform_get_matrix(m_transform->transform(), 1, 1); |
+ matrix[5] = qcms_transform_get_matrix(m_transform->transform(), 1, 2); |
+ matrix[6] = qcms_transform_get_matrix(m_transform->transform(), 2, 0); |
+ matrix[7] = qcms_transform_get_matrix(m_transform->transform(), 2, 1); |
+ matrix[8] = qcms_transform_get_matrix(m_transform->transform(), 2, 2); |
+ |
+ fprintf(stderr, "[ %f, %f, %f ]\n", matrix[0], matrix[1], matrix[2]); |
+ fprintf(stderr, "[ %f, %f, %f ]\n", matrix[3], matrix[4], matrix[5]); |
+ fprintf(stderr, "[ %f, %f, %f ]\n", matrix[6], matrix[7], matrix[8]); |
+ |
+ uint32_t outputSize = outputToneCurveSize(m_transform->transform(), m_target->profile()); |
+ SkAutoDataUnref outputData( |
+ SkData::NewUninitialized(outputSize * 4 * sizeof(uint16_t))); |
+ if (!outputSize || !outputData) { |
+ fprintf(stderr, "filter output curve data creation failed\n"); |
+ return nullptr; |
+ } |
+ |
+ uint16_t* outputCurve = static_cast<uint16_t *>(outputData->writable_data()); |
+ readOutputToneCurve(m_transform->transform(), m_target->profile(), outputCurve); |
+ fprintf(stderr, "output curve data created %p size=%d\n", this, (int) outputData->size()); |
+ |
+ SkAutoTUnref<SkToneCurve> output( |
+ SkToneCurve::Create(outputData, outputSize, SkToneCurve::kHalfType)); |
+ |
+ SkColorCurveFilter* filter = SkColorCurveFilter::Create(input, output); |
+ if (!filter) { |
+ fprintf(stderr, "cube filter creation failed\n"); |
+ return nullptr; |
+ } |
+ |
+ filter->setColorMatrix(matrix); |
+ return filter; |
+ } |
+ |
+ static uint16_t inputToneCurveSize(qcms_transform* transform, qcms_profile* input) |
+ { |
+ return qcms_transform_get_input_trc_rgba(transform, input, QCMS_TRC_HALF_FLOAT, 0); |
+ } |
+ |
+ static uint16_t readInputToneCurve(qcms_transform* transform, qcms_profile* input, uint16_t* rgba) |
+ { |
+ return qcms_transform_get_input_trc_rgba(transform, input, QCMS_TRC_HALF_FLOAT, rgba); |
+ } |
+ |
+ static uint16_t outputToneCurveSize(qcms_transform* transform, qcms_profile* output) |
+ { |
+ return qcms_transform_get_output_trc_rgba(transform, output, QCMS_TRC_HALF_FLOAT, 0); |
+ } |
+ |
+ static uint16_t readOutputToneCurve(qcms_transform* transform, qcms_profile* output, uint16_t* rgba) |
+ { |
+ return qcms_transform_get_output_trc_rgba(transform, output, QCMS_TRC_HALF_FLOAT, rgba); |
+ } |
+ |
+#endif // HAVE_COLOR_CURVE_FILTER |
+ |
+ SkColorFilter* createColorCubeFilter() |
+ { |
+ enum ColorCubeSize { RgbaBytes = 4, CubeSize = 32, CubeVolume = CubeSize * CubeSize * CubeSize }; |
+ |
+ SkAutoDataUnref data(SkData::NewUninitialized(CubeVolume * RgbaBytes)); |
+ |
+ // 0.000084877 (84 micro-secs) creating data block |
+ |
+ unsigned char* cube = static_cast<unsigned char*>(data->writable_data()); |
+ fprintf(stderr, "filter data created %p size=%d\n", this, (int) data->size()); |
+ RELEASE_ASSERT(cube); |
+ |
+ qcms_profile* source = m_source->profile(); |
+ qcms_profile* target = m_target->profile(); |
+ |
+ if (!qcms_transform_create_LUT_zyx_bgra(source, target, QCMS_INTENT_PERCEPTUAL, CubeSize, cube)) { |
+ fprintf(stderr, "filter color cube creation failed\n"); |
+ return nullptr; |
+ } |
+ |
+ // 0.00851917 (8.5 milliseconds!!) qcms_transform_create_LUT_zyx_bgra: that's too slow. |
+ |
+ SkColorFilter* filter = SkColorCubeFilter::Create(data, CubeSize); |
+ if (!filter) { |
+ fprintf(stderr, "cube filter creation failed\n"); |
+ return nullptr; |
+ } |
+ |
+ // 0.00000405312 (4 micro-secs) ColorCubeFilter::Create and assign |
+ |
+ return filter; |
+ } |
+ |
+ static String transformFilterKey(qcms_profile* source, qcms_profile* target) |
+ { |
+ return qcms_profile_get_description(source) + transformTargetDescription(target); |
+ } |
+ |
+ static String transformTargetDescription(qcms_profile* target) |
+ { |
+ return String(" -> ") + qcms_profile_get_description(target); |
+ } |
+ |
+#ifndef SK_IGNORE_TO_STRING |
+ |
+ void toString(SkString* str) const |
+ { |
+ str->append("ColorSpaceTransformFilter: ()"); |
+ } |
+ |
+#endif // SK_IGNORE_TO_STRING |
+ |
+ void flatten(SkWriteBuffer&) const |
+ { |
+ RELEASE_ASSERT_NOT_REACHED(); |
+ } |
+ |
+private: |
+ RefPtr<blink::ColorSpaceTransform> m_transform; |
+ RefPtr<blink::ColorSpaceProfile> m_source; |
+ RefPtr<blink::ColorSpaceProfile> m_target; |
+ RefPtr<SkColorFilter> m_effect; |
+}; |
+ |
+SkFlattenable* ColorSpaceTransformFilter::CreateProc(class SkReadBuffer&) |
+{ |
+ RELEASE_ASSERT_NOT_REACHED(); |
+ |
+ return nullptr; |
+} |
+ |
+namespace blink { |
+ |
+PassRefPtr<SkColorFilter> createColorSpaceFilter(ColorSpaceProfile* source, ColorSpaceProfile* target) |
+{ |
+ if (!source || !target || !source->profile() || !target->profile()) |
+ return nullptr; |
+ |
+ if (ColorSpaceProfile::equal(source->profile(), target->profile())) |
+ return nullptr; |
+ |
+ RELEASE_ASSERT(imageColorProfilesEnabled()); |
+ |
+ // 5 micro-secs to cache lookup. |
+ |
+ if (RefPtr<SkColorFilter> filter = findColorTransform(source, target)) { |
+ // fprintf(stderr, " found color transform in cache %p\n", filter.get()); |
+ return filter; |
+ } |
+ |
+ // 20 micro-secs to qcms_transform_create. |
+ |
+ qcms_transform* transform = qcms_transform_create(source->profile(), QCMS_DATA_RGBA_8, target->profile(), QCMS_DATA_RGBA_8, QCMS_INTENT_PERCEPTUAL); |
+ if (!transform) |
+ return nullptr; |
+ |
+ // 5 micro-secs to create and cache ColorSpaceTransformFilter. |
+ |
+ RefPtr<SkColorFilter> filter = ColorSpaceTransformFilter::create(transform, source, target); |
+ fprintf(stderr, " created color transform filter %s -> %s\n", qcms_profile_get_description(source->profile()), qcms_profile_get_description(target->profile())); |
+ cacheColorTransform(source, target, filter.get()); |
+ |
+ return filter; |
+} |
+ |
+PassRefPtr<SkColorFilter> colorSpaceFlattenableFilter(SkColorFilter* filter) |
+{ |
+ RELEASE_ASSERT(imageColorProfilesEnabled()); |
+ |
+ if (ColorSpaceTransformFilter* transform = static_cast<ColorSpaceTransformFilter*>(filter)) |
+ return transform->asColorFilter(); |
+ |
+ return nullptr; |
+} |
+ |
+String colorSpaceFilterKey(SkColorFilter* filter) |
+{ |
+ RELEASE_ASSERT(imageColorProfilesEnabled()); |
+ |
+ if (ColorSpaceTransformFilter* transform = static_cast<ColorSpaceTransformFilter*>(filter)) |
+ return transform->colorSpaceTransformFilterKey(); |
+ |
+ return String(); |
+} |
+ |
+} // namespace blink |
+ |
+#else |
+ |
+namespace blink { |
+ |
+PassRefPtr<SkColorFilter> createColorSpaceFilter(ColorSpaceProfile*, ColorSpaceProfile*) |
+{ |
+ return nullptr; |
+} |
+ |
+PassRefPtr<SkColorFilter> colorSpaceFlattenableFilter(SkColorFilter*) |
+{ |
+ return nullptr; |
+} |
+ |
+String colorSpaceFilterKey(SkColorFilter*) |
+{ |
+ return String(); |
+} |
+ |
+} // namespace blink |
+ |
+#endif // USE(QCMSLIB) |