提交 cacd50aa 编写于 作者: M Matt Pharr

Add support for qoi format images

https://qoiformat.org
上级 60e95c27
......@@ -30,3 +30,6 @@
[submodule "src/ext/utf8proc"]
path = src/ext/utf8proc
url = https://github.com/JuliaStrings/utf8proc.git
[submodule "src/ext/qoi"]
path = src/ext/qoi
url = https://github.com/phoboslab/qoi.git
......@@ -64,6 +64,7 @@ check_ext ("double-conversion" "double-conversion/cmake" cc1f75a114aca8d2af69f73
check_ext ("filesystem" "filesystem/filesystem" c5f9de30142453eb3c6fe991e82dfc2583373116)
check_ext ("libdeflate" "libdeflate/common" 1fd0bea6ca2073c68493632dafc4b1ddda1bcbc3)
check_ext ("lodepng" "lodepng/examples" 8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a)
check_ext ("qoi" "qoi" 028c75fd26e5e0758c7c711216c00404994c1ad3)
check_ext ("stb" "stb/tools" af1a5bc352164740c1cc1354942b1c6b72eacb8a)
check_ext ("utf8proc" "utf8proc/bench" 2484e2ed5e1d9c19edcccf392a7d9920ad90dfaf)
check_ext ("zlib" "zlib/doc" 54d591eabf9fe0e84c725638f8d5d8d202a093fa)
......@@ -829,6 +830,7 @@ target_include_directories (pbrt_lib PUBLIC
src
src/ext
${STB_INCLUDE}
${QOI_INCLUDE}
${OPENEXR_INCLUDE}
${ZLIB_INCLUDE_DIRS}
${LIBDEFLATE_INCLUDE_DIRS}
......
......@@ -146,3 +146,9 @@ set_property (TARGET flip_lib PROPERTY FOLDER "ext")
set (UTF8PROC_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/utf8proc PARENT_SCOPE)
add_subdirectory (utf8proc)
###########################################################################
# qoi
set (QOI_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/qoi PARENT_SCOPE)
Subproject commit 028c75fd26e5e0758c7c711216c00404994c1ad3
......@@ -46,6 +46,10 @@
#define STBI_WINDOWS_UTF8
#include <stb/stb_image.h>
#define QOI_NO_STDIO
#define QOI_IMPLEMENTATION
#include <qoi/qoi.h>
namespace pbrt {
std::string ToString(PixelFormat format) {
......@@ -867,6 +871,7 @@ static ImageAndMetadata ReadPNG(const std::string &name, Allocator alloc,
ColorEncoding encoding);
static ImageAndMetadata ReadPFM(const std::string &filename, Allocator alloc);
static ImageAndMetadata ReadHDR(const std::string &filename, Allocator alloc);
static ImageAndMetadata ReadQOI(const std::string &filename, Allocator alloc);
// ImageIO Function Definitions
ImageAndMetadata Image::Read(std::string name, Allocator alloc, ColorEncoding encoding) {
......@@ -878,6 +883,8 @@ ImageAndMetadata Image::Read(std::string name, Allocator alloc, ColorEncoding en
return ReadPFM(name, alloc);
else if (HasExtension(name, "hdr"))
return ReadHDR(name, alloc);
else if (HasExtension(name, "qoi"))
return ReadQOI(name, alloc);
else {
int x, y, n;
unsigned char *data = stbi_load(name.c_str(), &x, &y, &n, 0);
......@@ -1001,6 +1008,8 @@ bool Image::Write(std::string name, const ImageMetadata &metadata) const {
return outImage.WritePFM(name, outMetadata);
else if (HasExtension(name, "png"))
return outImage.WritePNG(name, outMetadata);
else if (HasExtension(name, "qoi"))
return outImage.WriteQOI(name, outMetadata);
else {
Error("%s: no support for writing images with this extension", name);
return false;
......@@ -1483,6 +1492,58 @@ bool Image::WritePNG(const std::string &filename, const ImageMetadata &metadata)
return true;
}
bool Image::WriteQOI(const std::string &filename, const ImageMetadata &metadata) const {
Image image = *this;
void *qoiPixels = nullptr;
int qoiSize = 0;
qoi_desc desc;
desc.width = resolution.x;
desc.height = resolution.y;
desc.channels = NChannels();
if (Encoding() && !Encoding().Is<LinearColorEncoding>() &&
!Encoding().Is<sRGBColorEncoding>()) {
Error("%s: only linear and sRGB encodings are supported by QOI.", Encoding().ToString()); //filename);
return false;
}
desc.colorspace = Encoding().Is<LinearColorEncoding>() ? QOI_LINEAR : QOI_SRGB;
if (NChannels() == 4) {
// Try to order it as QOI expects. Though continue on if we don't
// find these particular channel names.a
ImageChannelDesc desc = GetChannelDesc({"R", "G", "B", "A"});
if (desc)
image = SelectChannels(desc);
} else if (NChannels() == 3) {
// Similarly try to get the channels in order..
ImageChannelDesc desc = GetChannelDesc({"R", "G", "B"});
if (desc)
image = SelectChannels(desc);
} else {
Error("%s: only 3 and 4 channel images are supported for QOI", filename);
return false;
}
if (format == PixelFormat::U256)
qoiPixels = qoi_encode(image.RawPointer({0, 0}), &desc, &qoiSize);
else {
int nOutOfGamut = 0;
std::unique_ptr<uint8_t[]> rgba8 = QuantizePixelsToU256(&nOutOfGamut);
if (nOutOfGamut > 0)
Warning("%s: %d out of gamut pixel channels clamped to [0,1].", filename,
nOutOfGamut);
qoiPixels = qoi_encode(rgba8.get(), &desc, &qoiSize);
}
bool success = WriteFileContents(filename, std::string((const char *)qoiPixels, qoiSize));
if (!success)
Error("%s: error writing QOI file.", filename);
free(qoiPixels);
return success;
}
///////////////////////////////////////////////////////////////////////////
// PFM Function Definitions
......@@ -1660,6 +1721,34 @@ static ImageAndMetadata ReadHDR(const std::string &filename, Allocator alloc) {
}
}
static ImageAndMetadata ReadQOI(const std::string &filename, Allocator alloc) {
std::string contents = ReadFileContents(filename);
qoi_desc desc;
void *pixels = qoi_decode(contents.data(), contents.size(), &desc, 0 /* channels */);
CHECK(pixels != nullptr); // qoi failure
ImageMetadata metadata;
metadata.colorSpace = RGBColorSpace::sRGB;
std::vector<std::string> channelNames{"R", "G", "B"};
if (desc.channels == 4)
channelNames.push_back("A");
else
CHECK_EQ(3, desc.channels);
CHECK(desc.colorspace == QOI_SRGB || desc.colorspace == QOI_LINEAR);
ColorEncoding encoding = (desc.colorspace == QOI_SRGB) ? ColorEncoding::sRGB :
ColorEncoding::Linear;
Image image(PixelFormat::U256, Point2i(desc.width, desc.height), channelNames, encoding, alloc);
std::memcpy(image.RawPointer({0, 0}), pixels, desc.width * desc.height * desc.channels);
free(pixels);
return ImageAndMetadata{image, metadata};
}
bool Image::WritePFM(const std::string &filename, const ImageMetadata &metadata) const {
FILE *fp = FOpenWrite(filename);
if (!fp) {
......
......@@ -407,6 +407,7 @@ class Image {
bool WriteEXR(const std::string &name, const ImageMetadata &metadata) const;
bool WritePFM(const std::string &name, const ImageMetadata &metadata) const;
bool WritePNG(const std::string &name, const ImageMetadata &metadata) const;
bool WriteQOI(const std::string &name, const ImageMetadata &metadata) const;
std::unique_ptr<uint8_t[]> QuantizePixelsToU256(int *nOutOfGamut) const;
......
......@@ -546,6 +546,66 @@ TEST(Image, PngEmojiIO) {
EXPECT_TRUE(RemoveFile(filename.c_str()));
}
TEST(Image, QoiRgbIO) {
Point2i res(11, 50);
pstd::vector<uint8_t> rgbPixels = GetU8Pixels(res, 3);
Image image(rgbPixels, res, {"R", "G", "B"}, ColorEncoding::sRGB);
EXPECT_TRUE(image.Write("test.qoi"));
ImageAndMetadata read = Image::Read("test.qoi");
EXPECT_EQ(image.Resolution(), read.image.Resolution());
EXPECT_EQ(read.image.Format(), PixelFormat::U256);
ASSERT_TRUE(read.image.Encoding() != nullptr);
// EXPECT_EQ(*read.image.Encoding(), *ColorEncoding::sRGB);
ASSERT_TRUE((bool)read.metadata.colorSpace);
ASSERT_TRUE(*read.metadata.colorSpace != nullptr);
EXPECT_EQ(*RGBColorSpace::sRGB, *read.metadata.GetColorSpace());
for (int y = 0; y < res[1]; ++y)
for (int x = 0; x < res[0]; ++x)
for (int c = 0; c < 3; ++c) {
EXPECT_EQ(SRGB8ToLinear(rgbPixels[c + 3 * (y * res[0] + x)]),
image.GetChannel({x, y}, c))
<< " x " << x << ", y " << y << ", c " << c << ", orig "
<< rgbPixels[3 * y * res[0] + 3 * x + c];
}
EXPECT_TRUE(RemoveFile("test.qoi"));
}
TEST(Image, QoiRgbaIO) {
Point2i res(11, 50);
pstd::vector<uint8_t> rgbPixels = GetU8Pixels(res, 4);
// Intentionally out of normal order...
Image image(rgbPixels, res, {"A", "R", "G", "B"}, ColorEncoding::sRGB);
EXPECT_TRUE(image.Write("test-rgba.qoi"));
ImageAndMetadata read = Image::Read("test-rgba.qoi");
EXPECT_EQ(image.Resolution(), read.image.Resolution());
EXPECT_EQ(read.image.Format(), PixelFormat::U256);
ASSERT_TRUE(read.image.Encoding() != nullptr);
// EXPECT_EQ(*read.image.Encoding(), *ColorEncoding::sRGB);
ASSERT_TRUE((bool)read.metadata.colorSpace);
ASSERT_TRUE(*read.metadata.colorSpace != nullptr);
EXPECT_EQ(*RGBColorSpace::sRGB, *read.metadata.GetColorSpace());
ImageChannelDesc desc = image.GetChannelDesc({"A", "R", "G", "B"});
ASSERT_TRUE((bool)desc);
for (int y = 0; y < res[1]; ++y)
for (int x = 0; x < res[0]; ++x) {
ImageChannelValues v = image.GetChannels({x, y}, desc);
for (int c = 0; c < 4; ++c)
EXPECT_EQ(SRGB8ToLinear(rgbPixels[c + 4 * (y * res[0] + x)]), v[c])
<< " x " << x << ", y " << y << ", c " << c << ", orig "
<< rgbPixels[c + 4 * (y * res[0] + x)];
}
EXPECT_TRUE(RemoveFile("test-rgba.qoi"));
}
TEST(Image, SampleSimple) {
pstd::vector<float> texels = {Float(0), Float(1), Float(0), Float(0)};
Image zeroOne(texels, {2, 2}, {"Y"});
......@@ -751,3 +811,7 @@ TEST(ImageIO, RoundTripPFM) {
TEST(ImageIO, RoundTripPNG) {
TestRoundTrip("out.png");
}
TEST(ImageIO, RoundTripQOI) {
TestRoundTrip("out.qoi");
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册