diff options
-rw-r--r-- | gm/encode-alpha-jpeg.cpp | 105 | ||||
-rw-r--r-- | gn/gm.gni | 1 | ||||
-rw-r--r-- | resources/rainbow-gradient.png | bin | 0 -> 2592 bytes | |||
-rw-r--r-- | src/images/SkImageEncoderFns.h | 38 | ||||
-rw-r--r-- | src/images/SkJpegEncoder.cpp | 48 | ||||
-rw-r--r-- | src/images/SkJpegEncoder.h | 22 |
6 files changed, 202 insertions, 12 deletions
diff --git a/gm/encode-alpha-jpeg.cpp b/gm/encode-alpha-jpeg.cpp new file mode 100644 index 0000000000..6686e78044 --- /dev/null +++ b/gm/encode-alpha-jpeg.cpp @@ -0,0 +1,105 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "gm.h" +#include "SkImage.h" +#include "SkJpegEncoder.h" + +#include "Resources.h" + +namespace skiagm { + +static inline void read_into_pixmap(SkPixmap* dst, SkImageInfo dstInfo, void* dstPixels, + sk_sp<SkImage> src) { + dst->reset(dstInfo, dstPixels, dstInfo.minRowBytes()); + src->readPixels(*dst, 0, 0, SkImage::CachingHint::kDisallow_CachingHint); +} + +static inline sk_sp<SkImage> encode_pixmap_and_make_image(const SkPixmap& src, + SkJpegEncoder::AlphaOption alphaOption, SkTransferFunctionBehavior blendBehavior) { + SkDynamicMemoryWStream dst; + SkJpegEncoder::Options options; + options.fAlphaOption = alphaOption; + options.fBlendBehavior = blendBehavior; + SkJpegEncoder::Encode(&dst, src, options); + return SkImage::MakeFromEncoded(dst.detachAsData()); +} + +class EncodeJpegAlphaOptsGM : public GM { +public: + EncodeJpegAlphaOptsGM() {} + +protected: + SkString onShortName() override { + return SkString("encode-alpha-jpeg"); + } + + SkISize onISize() override { + return SkISize::Make(400, 200); + } + + void onDraw(SkCanvas* canvas) override { + sk_sp<SkImage> srcImg = GetResourceAsImage("rainbow-gradient.png"); + fStorage.reset(srcImg->width() * srcImg->height() * + SkColorTypeBytesPerPixel(kRGBA_F16_SkColorType)); + + SkPixmap src; + SkImageInfo info = SkImageInfo::MakeN32Premul(srcImg->width(), srcImg->height(), + canvas->imageInfo().colorSpace() ? SkColorSpace::MakeSRGB() : nullptr); + read_into_pixmap(&src, info, fStorage.get(), srcImg); + + SkTransferFunctionBehavior behavior = canvas->imageInfo().colorSpace() ? + SkTransferFunctionBehavior::kRespect : SkTransferFunctionBehavior::kIgnore; + + // Encode 8888 premul. + sk_sp<SkImage> img0 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kIgnore, + behavior); + sk_sp<SkImage> img1 = encode_pixmap_and_make_image(src, + SkJpegEncoder::AlphaOption::kBlendOnBlack, behavior); + canvas->drawImage(img0, 0.0f, 0.0f); + canvas->drawImage(img1, 0.0f, 100.0f); + + // Encode 8888 unpremul + info = info.makeAlphaType(kUnpremul_SkAlphaType); + read_into_pixmap(&src, info, fStorage.get(), srcImg); + img0 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kIgnore, behavior); + img1 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kBlendOnBlack, + behavior); + canvas->drawImage(img0, 100.0f, 0.0f); + canvas->drawImage(img1, 100.0f, 100.0f); + + if (canvas->imageInfo().colorSpace()) { + // Encode F16 premul + info = SkImageInfo::Make(srcImg->width(), srcImg->height(), kRGBA_F16_SkColorType, + kPremul_SkAlphaType, SkColorSpace::MakeSRGBLinear()); + read_into_pixmap(&src, info, fStorage.get(), srcImg); + img0 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kIgnore, behavior); + img1 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kBlendOnBlack, + behavior); + canvas->drawImage(img0, 200.0f, 0.0f); + canvas->drawImage(img1, 200.0f, 100.0f); + + // Encode F16 unpremul + info = info.makeAlphaType(kUnpremul_SkAlphaType); + read_into_pixmap(&src, info, fStorage.get(), srcImg); + img0 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kIgnore, behavior); + img1 = encode_pixmap_and_make_image(src, SkJpegEncoder::AlphaOption::kBlendOnBlack, + behavior); + canvas->drawImage(img0, 300.0f, 0.0f); + canvas->drawImage(img1, 300.0f, 100.0f); + } + } + +private: + SkAutoTMalloc<uint8_t> fStorage; + + typedef GM INHERITED; +}; + +DEF_GM( return new EncodeJpegAlphaOptsGM; ) + +}; @@ -112,6 +112,7 @@ gm_sources = [ "$_gm/emboss.cpp", "$_gm/emptypath.cpp", "$_gm/encode.cpp", + "$_gm/encode-alpha-jpeg.cpp", "$_gm/encode-platform.cpp", "$_gm/encode-srgb.cpp", "$_gm/etc1.cpp", diff --git a/resources/rainbow-gradient.png b/resources/rainbow-gradient.png Binary files differnew file mode 100644 index 0000000000..4e18a32716 --- /dev/null +++ b/resources/rainbow-gradient.png diff --git a/src/images/SkImageEncoderFns.h b/src/images/SkImageEncoderFns.h index 5120570c48..af75ac9fce 100644 --- a/src/images/SkImageEncoderFns.h +++ b/src/images/SkImageEncoderFns.h @@ -16,6 +16,7 @@ #include "SkColor.h" #include "SkColorPriv.h" #include "SkICC.h" +#include "SkOpts.h" #include "SkPreConfig.h" #include "SkRasterPipeline.h" #include "SkUnPreMultiply.h" @@ -165,6 +166,30 @@ static inline void transform_scanline_unpremultiply_sRGB(void* dst, const void* } /** + * Premultiply RGBA to rgbA. + */ +static inline void transform_scanline_to_premul_legacy(char* SK_RESTRICT dst, + const char* SK_RESTRICT src, + int width, int, const SkPMColor*) { + SkOpts::RGBA_to_rgbA((uint32_t*)dst, (const uint32_t*)src, width); +} + +/** + * Premultiply RGBA to rgbA linearly. + */ +static inline void transform_scanline_to_premul_linear(char* SK_RESTRICT dst, + const char* SK_RESTRICT src, + int width, int, const SkPMColor*) { + SkRasterPipeline p; + p.append(SkRasterPipeline::load_8888, (const void**) &src); + p.append_from_srgb(kUnpremul_SkAlphaType); + p.append(SkRasterPipeline::premul); + p.append(SkRasterPipeline::to_srgb); + p.append(SkRasterPipeline::store_8888, (void**) &dst); + p.run(0, width); +} + +/** * Transform from kPremul, kRGBA_8888_SkColorType to 4-bytes-per-pixel unpremultiplied RGBA. */ static inline void transform_scanline_srgbA(char* SK_RESTRICT dst, const char* SK_RESTRICT src, @@ -276,6 +301,19 @@ static inline void transform_scanline_F16_premul_to_8888(char* SK_RESTRICT dst, p.run(0, width); } +/** + * Transform from kUnpremul, kRGBA_F16 to premultiplied rgbA 8888. + */ +static inline void transform_scanline_F16_to_premul_8888(char* SK_RESTRICT dst, + const char* SK_RESTRICT src, int width, int, const SkPMColor*) { + SkRasterPipeline p; + p.append(SkRasterPipeline::load_f16, (const void**) &src); + p.append(SkRasterPipeline::premul); + p.append(SkRasterPipeline::to_srgb); + p.append(SkRasterPipeline::store_8888, (void**) &dst); + p.run(0, width); +} + static inline sk_sp<SkData> icc_from_color_space(const SkColorSpace& cs) { SkColorSpaceTransferFn fn; SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor); diff --git a/src/images/SkJpegEncoder.cpp b/src/images/SkJpegEncoder.cpp index fd88a5c8f6..d87fed81bc 100644 --- a/src/images/SkJpegEncoder.cpp +++ b/src/images/SkJpegEncoder.cpp @@ -35,7 +35,7 @@ public: return std::unique_ptr<SkJpegEncoderMgr>(new SkJpegEncoderMgr(stream)); } - bool setParams(const SkImageInfo& srcInfo); + bool setParams(const SkImageInfo& srcInfo, const SkJpegEncoder::Options& options); jpeg_compress_struct* cinfo() { return &fCInfo; } @@ -65,15 +65,36 @@ private: transform_scanline_proc fProc; }; -bool SkJpegEncoderMgr::setParams(const SkImageInfo& srcInfo) { +bool SkJpegEncoderMgr::setParams(const SkImageInfo& srcInfo, const SkJpegEncoder::Options& options) +{ + auto chooseProc8888 = [&]() { + if (kUnpremul_SkAlphaType != srcInfo.alphaType() || + SkJpegEncoder::AlphaOption::kIgnore == options.fAlphaOption) + { + return (transform_scanline_proc) nullptr; + } + + // Note that kRespect mode is only supported with sRGB or linear transfer functions. + // The legacy code path is incidentally correct when the transfer function is linear. + const bool isSRGBTransferFn = srcInfo.gammaCloseToSRGB() && + (SkTransferFunctionBehavior::kRespect == options.fBlendBehavior); + if (isSRGBTransferFn) { + return transform_scanline_to_premul_linear; + } else { + return transform_scanline_to_premul_legacy; + } + }; + J_COLOR_SPACE jpegColorType = JCS_EXT_RGBA; int numComponents = 0; switch (srcInfo.colorType()) { case kRGBA_8888_SkColorType: + fProc = chooseProc8888(); jpegColorType = JCS_EXT_RGBA; numComponents = 4; break; case kBGRA_8888_SkColorType: + fProc = chooseProc8888(); jpegColorType = JCS_EXT_BGRA; numComponents = 4; break; @@ -83,11 +104,19 @@ bool SkJpegEncoderMgr::setParams(const SkImageInfo& srcInfo) { numComponents = 3; break; case kARGB_4444_SkColorType: + if (SkJpegEncoder::AlphaOption::kBlendOnBlack == options.fAlphaOption) { + return false; + } + fProc = transform_scanline_444; jpegColorType = JCS_RGB; numComponents = 3; break; case kIndex_8_SkColorType: + if (SkJpegEncoder::AlphaOption::kBlendOnBlack == options.fAlphaOption) { + return false; + } + fProc = transform_scanline_index8_opaque; jpegColorType = JCS_RGB; numComponents = 3; @@ -98,11 +127,18 @@ bool SkJpegEncoderMgr::setParams(const SkImageInfo& srcInfo) { numComponents = 1; break; case kRGBA_F16_SkColorType: - if (!srcInfo.colorSpace() || !srcInfo.colorSpace()->gammaIsLinear()) { + if (!srcInfo.colorSpace() || !srcInfo.colorSpace()->gammaIsLinear() || + SkTransferFunctionBehavior::kRespect != options.fBlendBehavior) { return false; } - fProc = transform_scanline_F16_to_8888; + if (kUnpremul_SkAlphaType != srcInfo.alphaType() || + SkJpegEncoder::AlphaOption::kIgnore == options.fAlphaOption) + { + fProc = transform_scanline_F16_to_8888; + } else { + fProc = transform_scanline_F16_to_premul_8888; + } jpegColorType = JCS_EXT_RGBA; numComponents = 4; break; @@ -125,7 +161,7 @@ bool SkJpegEncoderMgr::setParams(const SkImageInfo& srcInfo) { std::unique_ptr<SkJpegEncoder> SkJpegEncoder::Make(SkWStream* dst, const SkPixmap& src, const Options& options) { - if (!SkPixmapIsValid(src, SkTransferFunctionBehavior::kIgnore)) { + if (!SkPixmapIsValid(src, options.fBlendBehavior)) { return nullptr; } @@ -134,7 +170,7 @@ std::unique_ptr<SkJpegEncoder> SkJpegEncoder::Make(SkWStream* dst, const SkPixma return nullptr; } - if (!encoderMgr->setParams(src.info())) { + if (!encoderMgr->setParams(src.info(), options)) { return nullptr; } diff --git a/src/images/SkJpegEncoder.h b/src/images/SkJpegEncoder.h index 49b49786c9..07290aeb9f 100644 --- a/src/images/SkJpegEncoder.h +++ b/src/images/SkJpegEncoder.h @@ -16,17 +16,27 @@ class SkWStream; class SkJpegEncoder : public SkEncoder { public: - // TODO (skbug.com/1501): - // Since jpegs are always opaque, this encoder ignores the alpha channel and treats the - // pixels as opaque. - // Another possible behavior is to blend the pixels onto opaque black. We'll need to add - // an option for this - and an SkTransferFunctionBehavior. + enum class AlphaOption { + kIgnore, + kBlendOnBlack, + }; struct Options { /** - * |fQuality| must be in [0, 100] where 0 corresponds to the lowest quality. + * |fQuality| must be in [0, 100] where 0 corresponds to the lowest quality. */ int fQuality = 100; + + /** + * Jpegs must be opaque. This instructs the encoder on how to handle input + * images with alpha. + * + * The default is to ignore the alpha channel and treat the image as opaque. + * Another option is to blend the pixels onto a black background before encoding. + * In the second case, the encoder supports linear or legacy blending. + */ + AlphaOption fAlphaOption = AlphaOption::kIgnore; + SkTransferFunctionBehavior fBlendBehavior = SkTransferFunctionBehavior::kRespect; }; /** |