弃用的方法以及兼容措施
在上一示例中使用的 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
格式的文件。代码中有以下几点需要注意:
-
解码到像素数组
以
byteMem
指向的内存为基础创建一个类型为SkPixmap
的对象:pixmap
。这个对象并不会管理
byteMem
指向的像素内存数据,需要开发者自己管理这个内存数据。这也是为什么在方法的最后通过delete[] byteMem
释放这部分空间的原因。解码器
SkCodec
的getPixels
方法,把 PNG 图像数据解码到pixmap
指向的内存空间,也就是byteMem
指向的内存空间。如果你没有像素数组,而是有一个
SkBitmap
对象中,那你只要调用SkBitmap
对象的pixmap
方法就能得到一个同样的SkPixmap
对象了。 -
编码到文件
使用
SkJpegEncoder
类型的静态方法Encode
方法把pixmap
对象指向的内存编码到SkFILEWStream
文件流中。值得注意的是,通过
SkJpegEncoder::Options
对象指定了 JPEG 图像的质量。options.fQuality
的取值范围是 0 到 100 ,默认值为 100,如果使用默认值的话,本示例生成的 jpg 图片体积为90k
。此值设置为 80 后,生成的 jpg 图片体积为36k
。 也就是说,质量越低,图像文件体积越小。
转型完成后生成的JPEG文件如下图所示:
值得注意的是输入的 PNG
文件是包含透明像素的,但 JPEG
格式的图像是不支持透明的。上面示例中 Skia 把 PNG 文件内对应的透明像素转型了成深绿色像素。
把图像格式化成 webp
文件与格式化成 jpg
文件的方式类似,如下代码所示:
// #include "include/encode/SkWebpEncoder.h"
SkWebpEncoder::Options options;
options.fQuality = 80;
SkWebpEncoder::Encode(&stream, pixmap, options);
Skia 源码库仅提供了PNG
、JPEG
、WEBP
的编码支持,如果要对其他格式的图像进行编码,则需要开发者结合 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字符串打印到输出窗口(此方法常用于输出调试信息),如下图所示:
在指定区域内绘制图像(图像缩放)
如何绘制指定大小的图像呢?如代码所示:
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
方法来绘制指定大小的图像。程序运行结果如下图所示:
程序确实按照窗口的大小绘制图像了,但绘制的图像失真了,就像图像没有执行抗锯齿处理一样。
这时如果让 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
配置对象,再次运行程序,执行结果如下图所示:
图像失真的问题解决了。