aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Matt Sarett <msarett@google.com>2017-05-09 12:46:50 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-05-09 18:32:04 +0000
commit2e61b182dad86e592d12c8a73ffcc7555a6e11a2 (patch)
tree323281b163e71f8058eab694c9562a2c1b007b67
parentee2d9df087c5225c9f3ba0fb98d237905f2d650c (diff)
Add jpeg encoder alpha handling option
This instructs us on how to encode jpegs when the src image has alpha. The original behavior is to ignore the alpha channel. This CL adds the option to blend the pixels onto opaque black. Note that kBlendOnBlack and kIgnore are identical unless the input alpha type is kUnpremul. Bug: 713862 Bug: skia:1501 Change-Id: I4891c70bb0ccd83f7974c359bd40a2143b5c49ac Reviewed-on: https://skia-review.googlesource.com/15817 Reviewed-by: Leon Scroggins <scroggo@google.com> Commit-Queue: Matt Sarett <msarett@google.com>
-rw-r--r--gm/encode-alpha-jpeg.cpp105
-rw-r--r--gn/gm.gni1
-rw-r--r--resources/rainbow-gradient.pngbin0 -> 2592 bytes
-rw-r--r--src/images/SkImageEncoderFns.h38
-rw-r--r--src/images/SkJpegEncoder.cpp48
-rw-r--r--src/images/SkJpegEncoder.h22
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; )
+
+};
diff --git a/gn/gm.gni b/gn/gm.gni
index ae30ca80b3..5cfde5fe12 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -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
new file mode 100644
index 0000000000..4e18a32716
--- /dev/null
+++ b/resources/rainbow-gradient.png
Binary files differ
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;
};
/**