Skia---如何处理图像(2)

弃用的方法以及兼容措施

在上一示例中使用的 SkCodec::MakeFromStream(std::move(stream), &result) 方法和SkCodec::MakeFromData(data)方法, 都已经被 Skia 标记为 deprecated(弃用)了。

然而在 Skia 的示例代码、测试代码及内部类中都还在使用这个方法。

如果你希望自己的代码尽可能的保证向后兼容,那么可以使用如下代码替换上一示例代码:

// #include "include/codec/SkPngDecoder.h"
// #include "include/codec/SkJpegDecoder.h"
// #include "include/codec/SkWebpDecoder.h"

// 此处省略了一些代码
sk_sp<SkData> data{ SkData::MakeFromFileName(pathStr.data()) };
std::vector<SkCodecs::Decoder> decoders;
decoders.push_back(SkPngDecoder::Decoder());
decoders.push_back(SkJpegDecoder::Decoder());
decoders.push_back(SkWebpDecoder::Decoder());
std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data, decoders);

如你所见,Skia 要求你主动告诉它你能提供什么解码器,Skia 这样做可以让应用程序消耗的内存更少,运行速度更快(不必一次性注册那么多图像解码器)。

对于 SkCodec::MakeFromStream 方法来说,处理方式也一样,如下代码所示:

// 此处省略了一些代码
std::unique_ptr<SkFILEStream> stream = SkFILEStream::Make(pathStr.data());
SkCodec::Result result;
std::vector<SkCodecs::Decoder> decoders;
decoders.push_back(SkPngDecoder::Decoder());
std::unique_ptr<SkCodec> codec = SkCodec::MakeFromStream(std::move(stream), decoders, &result);

图像编码

其他编码方法的是如何工作的,如下代码所示:

// #include "include/encode/SkJpegEncoder.h"

void png2Jpg() {
    std::wstring srcPath = L"D:\\project\\SkiaInAction\\图像处理\\original.png";
    auto srcStr = wideStrToStr(srcPath);
    sk_sp<SkData> data{ SkData::MakeFromFileName(srcStr.data()) };
    auto codec = SkCodec::MakeFromData(data);
    auto imgInfo = codec->getInfo();
    SkColor* byteMem = new SkColor[imgInfo.width() * imgInfo.height()]();
    SkPixmap pixmap(imgInfo, byteMem,imgInfo.minRowBytes());
    codec->getPixels(pixmap);
    
    std::wstring targetPath = L"D:\\project\\SkiaInAction\\图像处理\\original.jpg";
    auto targetStr = wideStrToStr(targetPath);
    SkFILEWStream stream(targetStr.data());
    SkJpegEncoder::Options options;
    options.fQuality = 80; //90kb -> 36kb
    SkJpegEncoder::Encode(&stream, pixmap, options);
    stream.flush();
    delete[] byteMem;
}

这段代码把一个 PNG 格式的文件转型成了 JPEG 格式的文件。代码中有以下几点需要注意:

  1. 解码到像素数组

    以 byteMem 指向的内存为基础创建一个类型为 SkPixmap 的对象:pixmap 。

    这个对象并不会管理 byteMem 指向的像素内存数据,需要开发者自己管理这个内存数据。这也是为什么在方法的最后通过 delete[] byteMem 释放这部分空间的原因。

    解码器 SkCodec 的 getPixels 方法,把 PNG 图像数据解码到 pixmap 指向的内存空间,也就是byteMem 指向的内存空间。

    如果你没有像素数组,而是有一个 SkBitmap 对象中,那你只要调用 SkBitmap 对象的 pixmap 方法就能得到一个同样的 SkPixmap 对象了。

  2. 编码到文件

    使用 SkJpegEncoder 类型的静态方法 Encode 方法把 pixmap 对象指向的内存编码到 SkFILEWStream 文件流中。 

    值得注意的是,通过 SkJpegEncoder::Options 对象指定了 JPEG 图像的质量。

    options.fQuality 的取值范围是 0 到 100 ,默认值为 100,如果使用默认值的话,本示例生成的 jpg 图片体积为 90k 。此值设置为 80 后,生成的 jpg 图片体积为 36k 。 也就是说,质量越低,图像文件体积越小。

转型完成后生成的JPEG文件如下图所示:

JPEG编码.jpg

值得注意的是输入的 PNG 文件是包含透明像素的,但 JPEG 格式的图像是不支持透明的。上面示例中 Skia 把 PNG 文件内对应的透明像素转型了成深绿色像素。

把图像格式化成 webp 文件与格式化成 jpg 文件的方式类似,如下代码所示:

// #include "include/encode/SkWebpEncoder.h"

SkWebpEncoder::Options options;
options.fQuality = 80;
SkWebpEncoder::Encode(&stream, pixmap, options);

Skia 源码库仅提供了PNGJPEGWEBP的编码支持,如果要对其他格式的图像进行编码,则需要开发者结合 Skia 提供的能力自行编码完成( Skia 有公开相关接口)。

编码图像到Base64字符串

看一下 Skia 如何把一个 PNG 图像文件编码为 Base64 字符串,如下所示:

// #include "src/base/SkBase64.h"

void png2Base64() {
    std::wstring imgPath = L"D:\\project\\SkiaInAction\\图像处理\\original.png";
    auto pathStr = wideStrToStr(imgPath);
    sk_sp<SkData> pngData{ SkData::MakeFromFileName(pathStr.data()) };
    size_t len = SkBase64::EncodedSize(pngData->size());
    SkString result(len);
    SkBase64::Encode(pngData->data(), pngData->size(), result.data());
    result.prepend("data:image/png;base64,");
    SkDebugf(result.data());
}

这段代码中,通过 SkBase64::EncodedSize 方法获得 Base64 字符串的大小(此方唯一一个输入参数是PNG 图像数据 SkData )。

接着使用 SkBase64::Encode 方法把 PNG 图像数据转码到字符串 result 中。

这样生成的字符串并不包含 Base64 信息头信息,最后把这个信息头补充上去了。

SkDebugf 方法负责把转码完成后的Base64字符串打印到输出窗口(此方法常用于输出调试信息),如下图所示:

base64编码输出.png

在指定区域内绘制图像(图像缩放)

如何绘制指定大小的图像呢?如代码所示:

void drawImgRect(SkCanvas* canvas)
{
    auto img = getImg();
    auto rect = SkRect::MakeXYWH(0, 0, w, h);
    SkSamplingOptions imgOption{};
    canvas->drawImageRect(img, rect, imgOption);
}

在这段代码中,创建了一个与窗口大小相同的 SkRect 矩形对象。用这个矩形对象来约束图像的位置和大小,让图像布满整个窗口。

接着创建一个类型为 SkSamplingOptions 配置对象,这个对象用于控制图像缩放时如何完成像素采样工作,使用这个对象的默认值。

最后使用 SkCanvas 对象的 drawImageRect 方法来绘制指定大小的图像。程序运行结果如下图所示:

图像缩放失真.png

程序确实按照窗口的大小绘制图像了,但绘制的图像失真了,就像图像没有执行抗锯齿处理一样。

这时如果让 SkPaint 对象来处理图像的锯齿,没任何效果:

SkPaint paint;
paint.setAntiAlias(true);
canvas->drawImageRect(img, rect, imgOption,&paint);

绘制图像时确实可以使用 SkPaint 影响图像渲染结果,但不能用它来处理图像缩放失真的问题。

图像缩放失真的问题需要通过配置 SkSamplingOptions 对象来解决,如下代码所示:

SkSamplingOptions imgOption{ SkFilterMode::kLinear, SkMipmapMode::kLinear };

SkFilterMode::kLinear 用于指定了图像在缩放时的过滤器模式,此处表示双线性插值( 2x2 采样点间的内插)。

SkMipmapMode::kLinear 用于指定如何预先计算多级详细程度,此处表示在两个最近的层级之间进行插值。

使用这个 SkSamplingOptions 配置对象,再次运行程序,执行结果如下图所示:

指定图像大小.png

图像失真的问题解决了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liulun

如果文章真帮到了你,谢谢您打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值