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>
This commit is contained in:
Matt Sarett 2017-05-09 12:46:50 -04:00 committed by Skia Commit-Bot
parent ee2d9df087
commit 2e61b182da
6 changed files with 202 additions and 12 deletions

105
gm/encode-alpha-jpeg.cpp Normal file
View File

@ -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; )
};

View File

@ -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",

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -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"
@ -164,6 +165,30 @@ static inline void transform_scanline_unpremultiply_sRGB(void* dst, const void*
p.run(0, width);
}
/**
* 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.
*/
@ -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);

View File

@ -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;
}

View File

@ -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;
};
/**