aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-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;
};
/**