/* * Copyright 2014 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkColorSpaceXform_Base.h" #include "SkColorSpaceXformPriv.h" #include "SkColorTable.h" #include "SkConvertPixels.h" #include "SkHalf.h" #include "SkImageInfoPriv.h" #include "SkOpts.h" #include "SkPM4fPriv.h" #include "SkRasterPipeline.h" #include "SkUnPreMultiply.h" #include "SkUnPreMultiplyPriv.h" // Fast Path 1: The memcpy() case. static inline bool can_memcpy(const SkImageInfo& dstInfo, const SkImageInfo& srcInfo) { if (dstInfo.colorType() != srcInfo.colorType()) { return false; } if (kAlpha_8_SkColorType == dstInfo.colorType()) { return true; } if (dstInfo.alphaType() != srcInfo.alphaType() && kOpaque_SkAlphaType != dstInfo.alphaType() && kOpaque_SkAlphaType != srcInfo.alphaType()) { // We need to premultiply or unpremultiply. return false; } return !dstInfo.colorSpace() || SkColorSpace::Equals(dstInfo.colorSpace(), srcInfo.colorSpace()); } // Fast Path 2: Simple swizzles and premuls. enum AlphaVerb { kNothing_AlphaVerb, kPremul_AlphaVerb, kUnpremul_AlphaVerb, }; template static void wrap_unpremultiply(uint32_t* dst, const void* src, int count) { SkUnpremultiplyRow(dst, (const uint32_t*) src, count); } void swizzle_and_multiply(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, const SkImageInfo& srcInfo, const void* srcPixels, size_t srcRB) { void (*proc)(uint32_t* dst, const void* src, int count); const bool swapRB = dstInfo.colorType() != srcInfo.colorType(); AlphaVerb alphaVerb = kNothing_AlphaVerb; if (kPremul_SkAlphaType == dstInfo.alphaType() && kUnpremul_SkAlphaType == srcInfo.alphaType()) { alphaVerb = kPremul_AlphaVerb; } else if (kUnpremul_SkAlphaType == dstInfo.alphaType() && kPremul_SkAlphaType == srcInfo.alphaType()) { alphaVerb = kUnpremul_AlphaVerb; } switch (alphaVerb) { case kNothing_AlphaVerb: // If we do not need to swap or multiply, we should hit the memcpy case. SkASSERT(swapRB); proc = SkOpts::RGBA_to_BGRA; break; case kPremul_AlphaVerb: proc = swapRB ? SkOpts::RGBA_to_bgrA : SkOpts::RGBA_to_rgbA; break; case kUnpremul_AlphaVerb: proc = swapRB ? wrap_unpremultiply : wrap_unpremultiply; break; } for (int y = 0; y < dstInfo.height(); y++) { proc((uint32_t*) dstPixels, srcPixels, dstInfo.width()); dstPixels = SkTAddOffset(dstPixels, dstRB); srcPixels = SkTAddOffset(srcPixels, srcRB); } } // Fast Path 3: Color space xform. static inline bool optimized_color_xform(const SkImageInfo& dstInfo, const SkImageInfo& srcInfo, SkTransferFunctionBehavior behavior) { // Unpremultiplication is unsupported by SkColorSpaceXform. Note that if |src| is non-linearly // premultiplied, we're always going to have to unpremultiply before doing anything. if (kPremul_SkAlphaType == srcInfo.alphaType() && (kUnpremul_SkAlphaType == dstInfo.alphaType() || SkTransferFunctionBehavior::kIgnore == behavior)) { return false; } switch (dstInfo.colorType()) { case kRGBA_8888_SkColorType: case kBGRA_8888_SkColorType: case kRGBA_F16_SkColorType: break; default: return false; } switch (srcInfo.colorType()) { case kRGBA_8888_SkColorType: case kBGRA_8888_SkColorType: break; default: return false; } return true; } static inline void apply_color_xform(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, const SkImageInfo& srcInfo, const void* srcPixels, size_t srcRB, SkTransferFunctionBehavior behavior) { SkColorSpaceXform::ColorFormat dstFormat = select_xform_format(dstInfo.colorType()); SkColorSpaceXform::ColorFormat srcFormat = select_xform_format(srcInfo.colorType()); SkAlphaType xformAlpha; switch (srcInfo.alphaType()) { case kOpaque_SkAlphaType: xformAlpha = kOpaque_SkAlphaType; break; case kPremul_SkAlphaType: SkASSERT(kPremul_SkAlphaType == dstInfo.alphaType()); // This signal means: copy the src alpha to the dst, do not premultiply (in this // case because the pixels are already premultiplied). xformAlpha = kUnpremul_SkAlphaType; break; case kUnpremul_SkAlphaType: SkASSERT(kPremul_SkAlphaType == dstInfo.alphaType() || kUnpremul_SkAlphaType == dstInfo.alphaType()); xformAlpha = dstInfo.alphaType(); break; default: SkASSERT(false); xformAlpha = kUnpremul_SkAlphaType; break; } std::unique_ptr xform = SkColorSpaceXform_Base::New(srcInfo.colorSpace(), dstInfo.colorSpace(), behavior); SkASSERT(xform); for (int y = 0; y < dstInfo.height(); y++) { SkAssertResult(xform->apply(dstFormat, dstPixels, srcFormat, srcPixels, dstInfo.width(), xformAlpha)); dstPixels = SkTAddOffset(dstPixels, dstRB); srcPixels = SkTAddOffset(srcPixels, srcRB); } } // Fast Path 4: Index 8 sources. template void do_index8(const SkImageInfo& dstInfo, T* dstPixels, size_t dstRB, const SkImageInfo& srcInfo, const uint8_t* srcPixels, size_t srcRB, SkColorTable* ctable, SkTransferFunctionBehavior behavior) { T dstCTable[256]; int count = ctable->count(); SkImageInfo srcInfo8888 = srcInfo.makeColorType(kN32_SkColorType).makeWH(count, 1); SkImageInfo dstInfoCT = dstInfo.makeWH(count, 1); size_t rowBytes = count * sizeof(T); SkConvertPixels(dstInfoCT, dstCTable, rowBytes, srcInfo8888, ctable->readColors(), rowBytes, nullptr, behavior); for (int y = 0; y < dstInfo.height(); y++) { for (int x = 0; x < dstInfo.width(); x++) { dstPixels[x] = dstCTable[srcPixels[x]]; } dstPixels = SkTAddOffset(dstPixels, dstRB); srcPixels = SkTAddOffset(srcPixels, srcRB); } } void convert_from_index8(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, const SkImageInfo& srcInfo, const uint8_t* srcPixels, size_t srcRB, SkColorTable* ctable, SkTransferFunctionBehavior behavior) { switch (dstInfo.colorType()) { case kAlpha_8_SkColorType: do_index8(dstInfo, (uint8_t*) dstPixels, dstRB, srcInfo, srcPixels, srcRB, ctable, behavior); break; case kRGB_565_SkColorType: case kARGB_4444_SkColorType: do_index8(dstInfo, (uint16_t*) dstPixels, dstRB, srcInfo, srcPixels, srcRB, ctable, behavior); break; case kRGBA_8888_SkColorType: case kBGRA_8888_SkColorType: do_index8(dstInfo, (uint32_t*) dstPixels, dstRB, srcInfo, srcPixels, srcRB, ctable, behavior); break; case kRGBA_F16_SkColorType: do_index8(dstInfo, (uint64_t*) dstPixels, dstRB, srcInfo, srcPixels, srcRB, ctable, behavior); break; default: SkASSERT(false); } } // Fast Path 5: Alpha 8 dsts. static void convert_to_alpha8(uint8_t* dst, size_t dstRB, const SkImageInfo& srcInfo, const void* src, size_t srcRB, SkColorTable* ctable) { if (srcInfo.isOpaque()) { for (int y = 0; y < srcInfo.height(); ++y) { memset(dst, 0xFF, srcInfo.width()); dst = SkTAddOffset(dst, dstRB); } return; } switch (srcInfo.colorType()) { case kBGRA_8888_SkColorType: case kRGBA_8888_SkColorType: { auto src32 = (const uint32_t*) src; for (int y = 0; y < srcInfo.height(); y++) { for (int x = 0; x < srcInfo.width(); x++) { dst[x] = src32[x] >> 24; } dst = SkTAddOffset(dst, dstRB); src32 = SkTAddOffset(src32, srcRB); } break; } case kARGB_4444_SkColorType: { auto src16 = (const uint16_t*) src; for (int y = 0; y < srcInfo.height(); y++) { for (int x = 0; x < srcInfo.width(); x++) { dst[x] = SkPacked4444ToA32(src16[x]); } dst = SkTAddOffset(dst, dstRB); src16 = SkTAddOffset(src16, srcRB); } break; } case kIndex_8_SkColorType: { SkASSERT(ctable); const uint32_t* table = ctable->readColors(); auto src8 = (const uint8_t*)src; for (int y = 0; y < srcInfo.height(); y++) { for (int x = 0; x < srcInfo.width(); x++) { dst[x] = table[src8[x]] >> 24; } dst = SkTAddOffset(dst, dstRB); src8 = SkTAddOffset(src8, srcRB); } break; } case kRGBA_F16_SkColorType: { auto src64 = (const uint64_t*) src; for (int y = 0; y < srcInfo.height(); y++) { for (int x = 0; x < srcInfo.width(); x++) { dst[x] = (uint8_t) (255.0f * SkHalfToFloat(src64[x] >> 48)); } dst = SkTAddOffset(dst, dstRB); src64 = SkTAddOffset(src64, srcRB); } break; } default: SkASSERT(false); break; } } // Default: Use the pipeline. static void convert_with_pipeline(const SkImageInfo& dstInfo, void* dstRow, size_t dstRB, const SkImageInfo& srcInfo, const void* srcRow, size_t srcRB, bool isColorAware, SkTransferFunctionBehavior behavior) { SkRasterPipeline pipeline; switch (srcInfo.colorType()) { case kRGBA_8888_SkColorType: pipeline.append(SkRasterPipeline::load_8888, &srcRow); break; case kBGRA_8888_SkColorType: pipeline.append(SkRasterPipeline::load_8888, &srcRow); pipeline.append(SkRasterPipeline::swap_rb); break; case kRGB_565_SkColorType: pipeline.append(SkRasterPipeline::load_565, &srcRow); break; case kRGBA_F16_SkColorType: pipeline.append(SkRasterPipeline::load_f16, &srcRow); break; case kGray_8_SkColorType: pipeline.append(SkRasterPipeline::load_g8, &srcRow); break; case kARGB_4444_SkColorType: pipeline.append(SkRasterPipeline::load_4444, &srcRow); break; default: SkASSERT(false); break; } SkAlphaType premulState = srcInfo.alphaType(); if (kPremul_SkAlphaType == premulState && SkTransferFunctionBehavior::kIgnore == behavior) { pipeline.append(SkRasterPipeline::unpremul); premulState = kUnpremul_SkAlphaType; } if (isColorAware && srcInfo.gammaCloseToSRGB()) { pipeline.append_from_srgb(srcInfo.alphaType()); } else if (isColorAware && !srcInfo.colorSpace()->gammaIsLinear()) { SkColorSpaceTransferFn fn; SkAssertResult(srcInfo.colorSpace()->isNumericalTransferFn(&fn)); pipeline.append(SkRasterPipeline::parametric_r, &fn); pipeline.append(SkRasterPipeline::parametric_g, &fn); pipeline.append(SkRasterPipeline::parametric_b, &fn); } float matrix[12]; if (isColorAware) { SkAssertResult(append_gamut_transform(&pipeline, matrix, srcInfo.colorSpace(), dstInfo.colorSpace())); } SkAlphaType dat = dstInfo.alphaType(); if (SkTransferFunctionBehavior::kRespect == behavior) { if (kPremul_SkAlphaType == premulState && kUnpremul_SkAlphaType == dat) { pipeline.append(SkRasterPipeline::unpremul); premulState = kUnpremul_SkAlphaType; } else if (kUnpremul_SkAlphaType == premulState && kPremul_SkAlphaType == dat) { pipeline.append(SkRasterPipeline::premul); premulState = kPremul_SkAlphaType; } } if (isColorAware && dstInfo.gammaCloseToSRGB()) { pipeline.append(SkRasterPipeline::to_srgb); } else if (isColorAware && !dstInfo.colorSpace()->gammaIsLinear()) { SkColorSpaceTransferFn fn; SkAssertResult(dstInfo.colorSpace()->isNumericalTransferFn(&fn)); fn = fn.invert(); pipeline.append(SkRasterPipeline::parametric_r, &fn); pipeline.append(SkRasterPipeline::parametric_g, &fn); pipeline.append(SkRasterPipeline::parametric_b, &fn); } if (kUnpremul_SkAlphaType == premulState && kPremul_SkAlphaType == dat && SkTransferFunctionBehavior::kIgnore == behavior) { pipeline.append(SkRasterPipeline::premul); premulState = kPremul_SkAlphaType; } // The final premul state must equal the dst alpha type. Note that if we are "converting" // opaque to another alpha type, there's no need to worry about multiplication. SkASSERT(premulState == dat || kOpaque_SkAlphaType == srcInfo.alphaType()); switch (dstInfo.colorType()) { case kRGBA_8888_SkColorType: pipeline.append(SkRasterPipeline::store_8888, &dstRow); break; case kBGRA_8888_SkColorType: pipeline.append(SkRasterPipeline::swap_rb); pipeline.append(SkRasterPipeline::store_8888, &dstRow); break; case kRGB_565_SkColorType: pipeline.append(SkRasterPipeline::store_565, &dstRow); break; case kRGBA_F16_SkColorType: pipeline.append(SkRasterPipeline::store_f16, &dstRow); break; case kARGB_4444_SkColorType: pipeline.append(SkRasterPipeline::store_4444, &dstRow); break; default: SkASSERT(false); break; } for (int y = 0; y < srcInfo.height(); ++y) { pipeline.run(0,srcInfo.width()); // The pipeline has pointers to srcRow and dstRow, so we just need to update them in the // loop to move between rows of src/dst. dstRow = SkTAddOffset(dstRow, dstRB); srcRow = SkTAddOffset(srcRow, srcRB); } } void SkConvertPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, const SkImageInfo& srcInfo, const void* srcPixels, size_t srcRB, SkColorTable* ctable, SkTransferFunctionBehavior behavior) { SkASSERT(dstInfo.dimensions() == srcInfo.dimensions()); SkASSERT(SkImageInfoValidConversion(dstInfo, srcInfo)); // Fast Path 1: The memcpy() case. if (can_memcpy(dstInfo, srcInfo)) { SkRectMemcpy(dstPixels, dstRB, srcPixels, srcRB, dstInfo.minRowBytes(), dstInfo.height()); return; } const bool isColorAware = dstInfo.colorSpace(); SkASSERT(srcInfo.colorSpace() || !isColorAware); // Fast Path 2: Simple swizzles and premuls. if (4 == srcInfo.bytesPerPixel() && 4 == dstInfo.bytesPerPixel() && !isColorAware) { swizzle_and_multiply(dstInfo, dstPixels, dstRB, srcInfo, srcPixels, srcRB); return; } // Fast Path 3: Color space xform. if (isColorAware && optimized_color_xform(dstInfo, srcInfo, behavior)) { apply_color_xform(dstInfo, dstPixels, dstRB, srcInfo, srcPixels, srcRB, behavior); return; } // Fast Path 4: Index 8 sources. if (kIndex_8_SkColorType == srcInfo.colorType()) { SkASSERT(ctable); convert_from_index8(dstInfo, dstPixels, dstRB, srcInfo, (const uint8_t*) srcPixels, srcRB, ctable, behavior); return; } // Fast Path 5: Alpha 8 dsts. if (kAlpha_8_SkColorType == dstInfo.colorType()) { convert_to_alpha8((uint8_t*) dstPixels, dstRB, srcInfo, srcPixels, srcRB, ctable); return; } // Default: Use the pipeline. convert_with_pipeline(dstInfo, dstPixels, dstRB, srcInfo, srcPixels, srcRB, isColorAware, behavior); }