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:
parent
ee2d9df087
commit
2e61b182da
105
gm/encode-alpha-jpeg.cpp
Normal file
105
gm/encode-alpha-jpeg.cpp
Normal 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; )
|
||||
|
||||
};
|
@ -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",
|
||||
|
BIN
resources/rainbow-gradient.png
Normal file
BIN
resources/rainbow-gradient.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user