mirror of
https://github.com/google/sandboxed-api.git
synced 2024-03-22 13:11:30 +08:00
Update after code review
This commit is contained in:
parent
47fd491e20
commit
b4aca05300
|
@ -12,6 +12,8 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
licenses(["unencumbered"]) # code authored by Google
|
||||
|
||||
load(
|
||||
"@com_google_sandboxed_api//sandboxed_api/bazel:sapi.bzl",
|
||||
"sapi_library",
|
||||
|
@ -25,7 +27,7 @@ cc_library(
|
|||
"@guetzli//:guetzli_lib",
|
||||
"@com_google_sandboxed_api//sandboxed_api:lenval_core",
|
||||
"@com_google_sandboxed_api//sandboxed_api:vars",
|
||||
"@png_archive//:png"
|
||||
"@png_archive//:png",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -36,19 +38,19 @@ sapi_library(
|
|||
functions = [
|
||||
"ProcessJpeg",
|
||||
"ProcessRgb",
|
||||
"WriteDataToFd"
|
||||
"WriteDataToFd",
|
||||
],
|
||||
input_files = ["guetzli_entry_points.h"],
|
||||
lib = ":guetzli_wrapper",
|
||||
lib_name = "Guetzli",
|
||||
visibility = ["//visibility:public"],
|
||||
namespace = "guetzli::sandbox"
|
||||
namespace = "guetzli::sandbox",
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name="guetzli_sandboxed",
|
||||
srcs=["guetzli_sandboxed.cc"],
|
||||
deps = [
|
||||
":guetzli_sapi"
|
||||
]
|
||||
)
|
||||
":guetzli_sapi",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# Guetzli Sandboxed
|
||||
This is an example implementation of a sandbox for the [Guetzli](https://github.com/google/guetzli) library using [Sandboxed API](https://github.com/google/sandboxed-api).
|
||||
Please read Guetzli's [documentation](https://github.com/google/guetzli#introduction) to learn more about it.
|
||||
|
||||
## Implementation details
|
||||
Because Guetzli provides C++ API and SAPI requires functions to be `extern "C"` a wrapper library has been written for the compatibility. SAPI provides a Transaction class, which is a convenient way to create a wrapper for your sandboxed API that handles internal errors. Original Guetzli has command-line utility to encode images, so fully compatible utility that uses sandboxed Guetzli is provided.
|
||||
|
||||
Wrapper around Guetzli uses file descriptors to pass data to the sandbox. This approach restricts the sandbox from using open() syscall and also helps to prevent making copies of data, because you need to synchronize it between processes.
|
||||
|
||||
## Build Guetzli Sandboxed
|
||||
Right now Sandboxed API support only Linux systems, so you need one to build it. Guetzli sandboxed uses [Bazel](https://bazel.build/) as a build system so you need to [install it](https://docs.bazel.build/versions/3.4.0/install.html) before building.
|
||||
|
||||
To build Guetzli sandboxed encoding utility you can use this command:
|
||||
`bazel build //:guetzli_sandboxed`
|
||||
|
||||
Than you can use it in this way:
|
||||
```
|
||||
guetzli_sandboxed [--quality Q] [--verbose] original.png output.jpg
|
||||
guetzli_sandboxed [--quality Q] [--verbose] original.jpg output.jpg
|
||||
```
|
||||
Refer to Guetzli's [documentation](https://github.com/google/guetzli#using) to read more about usage.
|
||||
|
||||
## Examples
|
||||
There are two different sets of unit tests which demonstrate how to use different parts of Guetzli sandboxed:
|
||||
* `tests/guetzli_sapi_test.cc` - example usage of Guetzli sandboxed API.
|
||||
* `tests/guetzli_transaction_test.cc` - example usage of Guetzli transaction.
|
||||
|
||||
Also, there is an example of custom security policy for your sandbox in
|
||||
`guetzli_sandbox.h`
|
|
@ -47,38 +47,49 @@ load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
|
|||
|
||||
protobuf_deps()
|
||||
|
||||
local_repository(
|
||||
name = "butteraugli",
|
||||
path = "third_party/butteraugli/"
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "guetzli",
|
||||
build_file = "guetzli.BUILD",
|
||||
sha256 = "39632357e49db83d9560bf0de560ad833352f36d23b109b0e995b01a37bddb57",
|
||||
strip_prefix = "guetzli-master",
|
||||
url = "https://github.com/google/guetzli/archive/master.zip"
|
||||
url = "https://github.com/google/guetzli/archive/master.zip",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "png_archive",
|
||||
build_file = "png.BUILD",
|
||||
sha256 = "a941dc09ca00148fe7aaf4ecdd6a67579c293678ed1e1cf633b5ffc02f4f8cf7",
|
||||
strip_prefix = "libpng-1.2.57",
|
||||
url = "http://github.com/glennrp/libpng/archive/v1.2.57.zip",
|
||||
name = "butteraugli",
|
||||
build_file = "butteraugli.BUILD",
|
||||
sha256 = "39632357e49db83d9560bf0de560ad833352f36d23b109b0e995b01a37bddb57",
|
||||
strip_prefix = "guetzli-master/third_party/butteraugli",
|
||||
url = "https://github.com/google/guetzli/archive/master.zip",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "zlib_archive",
|
||||
build_file = "zlib.BUILD",
|
||||
sha256 = "8d7e9f698ce48787b6e1c67e6bff79e487303e66077e25cb9784ac8835978017",
|
||||
strip_prefix = "zlib-1.2.10",
|
||||
url = "http://zlib.net/fossils/zlib-1.2.10.tar.gz",
|
||||
name = "png_archive",
|
||||
build_file = "png.BUILD",
|
||||
sha256 = "a941dc09ca00148fe7aaf4ecdd6a67579c293678ed1e1cf633b5ffc02f4f8cf7",
|
||||
strip_prefix = "libpng-1.2.57",
|
||||
url = "http://github.com/glennrp/libpng/archive/v1.2.57.zip",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "zlib_archive",
|
||||
build_file = "zlib.BUILD",
|
||||
sha256 = "8d7e9f698ce48787b6e1c67e6bff79e487303e66077e25cb9784ac8835978017",
|
||||
strip_prefix = "zlib-1.2.10",
|
||||
url = "http://zlib.net/fossils/zlib-1.2.10.tar.gz",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "jpeg_archive",
|
||||
url = "http://www.ijg.org/files/jpegsrc.v9b.tar.gz",
|
||||
sha256 = "240fd398da741669bf3c90366f58452ea59041cacc741a489b99f2f6a0bad052",
|
||||
strip_prefix = "jpeg-9b",
|
||||
build_file = "jpeg.BUILD",
|
||||
)
|
||||
|
||||
maybe(
|
||||
git_repository,
|
||||
name = "googletest",
|
||||
remote = "https://github.com/google/googletest",
|
||||
tag = "release-1.10.0"
|
||||
)
|
||||
tag = "release-1.10.0",
|
||||
)
|
||||
|
|
28
oss-internship-2020/guetzli/external/butteraugli.BUILD
vendored
Normal file
28
oss-internship-2020/guetzli/external/butteraugli.BUILD
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
licenses(["unencumbered"]) # code authored by Google
|
||||
|
||||
cc_library(
|
||||
name = "butteraugli_lib",
|
||||
srcs = [
|
||||
"butteraugli/butteraugli.cc",
|
||||
"butteraugli/butteraugli.h",
|
||||
],
|
||||
hdrs = [
|
||||
"butteraugli/butteraugli.h",
|
||||
],
|
||||
copts = ["-Wno-sign-compare"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
|
@ -1,4 +1,18 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
licenses(["unencumbered"]) # code authored by Google
|
||||
|
||||
cc_library(
|
||||
name = "guetzli_lib",
|
||||
|
@ -15,4 +29,4 @@ cc_library(
|
|||
deps = [
|
||||
"@butteraugli//:butteraugli_lib",
|
||||
],
|
||||
)
|
||||
)
|
||||
|
|
1
oss-internship-2020/guetzli/third_party/butteraugli/jpeg.BUILD → oss-internship-2020/guetzli/external/jpeg.BUILD
vendored
Executable file → Normal file
1
oss-internship-2020/guetzli/third_party/butteraugli/jpeg.BUILD → oss-internship-2020/guetzli/external/jpeg.BUILD
vendored
Executable file → Normal file
|
@ -1,3 +1,4 @@
|
|||
|
||||
# Description:
|
||||
# The Independent JPEG Group's JPEG runtime library.
|
||||
|
|
@ -12,6 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "guetzli_entry_points.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -22,7 +24,6 @@
|
|||
|
||||
#include "guetzli/jpeg_data_reader.h"
|
||||
#include "guetzli/quality.h"
|
||||
#include "guetzli_entry_points.h"
|
||||
#include "png.h"
|
||||
#include "sandboxed_api/sandbox2/util/fileops.h"
|
||||
#include "sandboxed_api/util/statusor.h"
|
||||
|
@ -38,19 +39,21 @@ struct GuetzliInitData {
|
|||
guetzli::ProcessStats stats;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
void CopyMemoryToLenVal(const T* data, size_t size,
|
||||
sapi::LenValStruct* out_data) {
|
||||
free(out_data->data); // Not sure about this
|
||||
out_data->size = size;
|
||||
T* new_out = static_cast<T*>(malloc(size));
|
||||
memcpy(new_out, data, size);
|
||||
out_data->data = new_out;
|
||||
struct ImageData {
|
||||
int xsize;
|
||||
int ysize;
|
||||
std::vector<uint8_t> rgb;
|
||||
};
|
||||
|
||||
sapi::LenValStruct CreateLenValFromData(const void* data, size_t size) {
|
||||
void* new_data = malloc(size);
|
||||
memcpy(new_data, data, size);
|
||||
return { size, new_data };
|
||||
}
|
||||
|
||||
sapi::StatusOr<std::string> ReadFromFd(int fd) {
|
||||
struct stat file_data;
|
||||
auto status = fstat(fd, &file_data);
|
||||
int status = fstat(fd, &file_data);
|
||||
|
||||
if (status < 0) {
|
||||
return absl::FailedPreconditionError(
|
||||
|
@ -58,10 +61,9 @@ sapi::StatusOr<std::string> ReadFromFd(int fd) {
|
|||
);
|
||||
}
|
||||
|
||||
auto fsize = file_data.st_size;
|
||||
|
||||
std::unique_ptr<char[]> buf(new char[fsize]);
|
||||
status = read(fd, buf.get(), fsize);
|
||||
std::string result;
|
||||
result.resize(file_data.st_size);
|
||||
status = read(fd, result.data(), result.size());
|
||||
|
||||
if (status < 0) {
|
||||
return absl::FailedPreconditionError(
|
||||
|
@ -69,15 +71,15 @@ sapi::StatusOr<std::string> ReadFromFd(int fd) {
|
|||
);
|
||||
}
|
||||
|
||||
return std::string(buf.get(), fsize);
|
||||
return result;
|
||||
}
|
||||
|
||||
sapi::StatusOr<GuetzliInitData> PrepareDataForProcessing(
|
||||
const ProcessingParams* processing_params) {
|
||||
auto input_status = ReadFromFd(processing_params->remote_fd);
|
||||
sapi::StatusOr<std::string> input = ReadFromFd(processing_params->remote_fd);
|
||||
|
||||
if (!input_status.ok()) {
|
||||
return input_status.status();
|
||||
if (!input.ok()) {
|
||||
return input.status();
|
||||
}
|
||||
|
||||
guetzli::Params guetzli_params;
|
||||
|
@ -91,7 +93,7 @@ sapi::StatusOr<GuetzliInitData> PrepareDataForProcessing(
|
|||
}
|
||||
|
||||
return GuetzliInitData{
|
||||
std::move(input_status.value()),
|
||||
std::move(input.value()),
|
||||
guetzli_params,
|
||||
stats
|
||||
};
|
||||
|
@ -101,29 +103,36 @@ inline uint8_t BlendOnBlack(const uint8_t val, const uint8_t alpha) {
|
|||
return (static_cast<int>(val) * static_cast<int>(alpha) + 128) / 255;
|
||||
}
|
||||
|
||||
bool ReadPNG(const std::string& data, int* xsize, int* ysize,
|
||||
std::vector<uint8_t>* rgb) {
|
||||
// Modified version of ReadPNG from original guetzli.cc
|
||||
sapi::StatusOr<ImageData> ReadPNG(const std::string& data) {
|
||||
std::vector<uint8_t> rgb;
|
||||
int xsize, ysize;
|
||||
png_structp png_ptr =
|
||||
png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if (!png_ptr) {
|
||||
return false;
|
||||
return absl::FailedPreconditionError(
|
||||
"Error reading PNG data from input file");
|
||||
}
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr) {
|
||||
png_destroy_read_struct(&png_ptr, nullptr, nullptr);
|
||||
return false;
|
||||
return absl::FailedPreconditionError(
|
||||
"Error reading PNG data from input file");
|
||||
}
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)) != 0) {
|
||||
// Ok we are here because of the setjmp.
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
|
||||
return false;
|
||||
return absl::FailedPreconditionError(
|
||||
"Error reading PNG data from input file");
|
||||
}
|
||||
|
||||
std::istringstream memstream(data, std::ios::in | std::ios::binary);
|
||||
png_set_read_fn(png_ptr, static_cast<void*>(&memstream), [](png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead) {
|
||||
std::istringstream& memstream = *static_cast<std::istringstream*>(png_get_io_ptr(png_ptr));
|
||||
png_set_read_fn(png_ptr, static_cast<void*>(&memstream),
|
||||
[](png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead) {
|
||||
std::istringstream& memstream =
|
||||
*static_cast<std::istringstream*>(png_get_io_ptr(png_ptr));
|
||||
|
||||
memstream.read(reinterpret_cast<char*>(outBytes), byteCountToRead);
|
||||
|
||||
|
@ -143,18 +152,18 @@ bool ReadPNG(const std::string& data, int* xsize, int* ysize,
|
|||
|
||||
png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr);
|
||||
|
||||
*xsize = png_get_image_width(png_ptr, info_ptr);
|
||||
*ysize = png_get_image_height(png_ptr, info_ptr);
|
||||
rgb->resize(3 * (*xsize) * (*ysize));
|
||||
xsize = png_get_image_width(png_ptr, info_ptr);
|
||||
ysize = png_get_image_height(png_ptr, info_ptr);
|
||||
rgb.resize(3 * (xsize) * (ysize));
|
||||
|
||||
const int components = png_get_channels(png_ptr, info_ptr);
|
||||
switch (components) {
|
||||
case 1: {
|
||||
// GRAYSCALE
|
||||
for (int y = 0; y < *ysize; ++y) {
|
||||
for (int y = 0; y < ysize; ++y) {
|
||||
const uint8_t* row_in = row_pointers[y];
|
||||
uint8_t* row_out = &(*rgb)[3 * y * (*xsize)];
|
||||
for (int x = 0; x < *xsize; ++x) {
|
||||
uint8_t* row_out = &(rgb)[3 * y * (xsize)];
|
||||
for (int x = 0; x < xsize; ++x) {
|
||||
const uint8_t gray = row_in[x];
|
||||
row_out[3 * x + 0] = gray;
|
||||
row_out[3 * x + 1] = gray;
|
||||
|
@ -165,10 +174,10 @@ bool ReadPNG(const std::string& data, int* xsize, int* ysize,
|
|||
}
|
||||
case 2: {
|
||||
// GRAYSCALE + ALPHA
|
||||
for (int y = 0; y < *ysize; ++y) {
|
||||
for (int y = 0; y < ysize; ++y) {
|
||||
const uint8_t* row_in = row_pointers[y];
|
||||
uint8_t* row_out = &(*rgb)[3 * y * (*xsize)];
|
||||
for (int x = 0; x < *xsize; ++x) {
|
||||
uint8_t* row_out = &(rgb)[3 * y * (xsize)];
|
||||
for (int x = 0; x < xsize; ++x) {
|
||||
const uint8_t gray = BlendOnBlack(row_in[2 * x], row_in[2 * x + 1]);
|
||||
row_out[3 * x + 0] = gray;
|
||||
row_out[3 * x + 1] = gray;
|
||||
|
@ -179,19 +188,19 @@ bool ReadPNG(const std::string& data, int* xsize, int* ysize,
|
|||
}
|
||||
case 3: {
|
||||
// RGB
|
||||
for (int y = 0; y < *ysize; ++y) {
|
||||
for (int y = 0; y < ysize; ++y) {
|
||||
const uint8_t* row_in = row_pointers[y];
|
||||
uint8_t* row_out = &(*rgb)[3 * y * (*xsize)];
|
||||
memcpy(row_out, row_in, 3 * (*xsize));
|
||||
uint8_t* row_out = &(rgb)[3 * y * (xsize)];
|
||||
memcpy(row_out, row_in, 3 * (xsize));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// RGBA
|
||||
for (int y = 0; y < *ysize; ++y) {
|
||||
for (int y = 0; y < ysize; ++y) {
|
||||
const uint8_t* row_in = row_pointers[y];
|
||||
uint8_t* row_out = &(*rgb)[3 * y * (*xsize)];
|
||||
for (int x = 0; x < *xsize; ++x) {
|
||||
uint8_t* row_out = &(rgb)[3 * y * (xsize)];
|
||||
for (int x = 0; x < xsize; ++x) {
|
||||
const uint8_t alpha = row_in[4 * x + 3];
|
||||
row_out[3 * x + 0] = BlendOnBlack(row_in[4 * x + 0], alpha);
|
||||
row_out[3 * x + 1] = BlendOnBlack(row_in[4 * x + 1], alpha);
|
||||
|
@ -202,10 +211,16 @@ bool ReadPNG(const std::string& data, int* xsize, int* ysize,
|
|||
}
|
||||
default:
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
|
||||
return false;
|
||||
return absl::FailedPreconditionError(
|
||||
"Error reading PNG data from input file");
|
||||
}
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
|
||||
return true;
|
||||
|
||||
return ImageData{
|
||||
xsize,
|
||||
ysize,
|
||||
std::move(rgb)
|
||||
};
|
||||
}
|
||||
|
||||
bool CheckMemoryLimitExceeded(int memlimit_mb, int xsize, int ysize) {
|
||||
|
@ -219,70 +234,72 @@ bool CheckMemoryLimitExceeded(int memlimit_mb, int xsize, int ysize) {
|
|||
|
||||
extern "C" bool ProcessJpeg(const ProcessingParams* processing_params,
|
||||
sapi::LenValStruct* output) {
|
||||
auto processing_data_status = PrepareDataForProcessing(processing_params);
|
||||
auto processing_data = PrepareDataForProcessing(processing_params);
|
||||
|
||||
if (!processing_data_status.status().ok()) {
|
||||
fprintf(stderr, "%s\n", processing_data_status.status().ToString().c_str());
|
||||
if (!processing_data.ok()) {
|
||||
std::cerr << processing_data.status().ToString() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
guetzli::JPEGData jpg_header;
|
||||
if (!guetzli::ReadJpeg(processing_data_status.value().in_data,
|
||||
if (!guetzli::ReadJpeg(processing_data->in_data,
|
||||
guetzli::JPEG_READ_HEADER, &jpg_header)) {
|
||||
fprintf(stderr, "Error reading JPG data from input file\n");
|
||||
std::cerr << "Error reading JPG data from input file" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CheckMemoryLimitExceeded(processing_params->memlimit_mb,
|
||||
jpg_header.width, jpg_header.height)) {
|
||||
fprintf(stderr, "Memory limit would be exceeded.\n");
|
||||
std::cerr << "Memory limit would be exceeded" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string out_data;
|
||||
if (!guetzli::Process(processing_data_status.value().params,
|
||||
&processing_data_status.value().stats,
|
||||
processing_data_status.value().in_data,
|
||||
if (!guetzli::Process(processing_data->params,
|
||||
&processing_data->stats,
|
||||
processing_data->in_data,
|
||||
&out_data)) {
|
||||
fprintf(stderr, "Guezli processing failed\n");
|
||||
std::cerr << "Guezli processing failed" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
CopyMemoryToLenVal(out_data.data(), out_data.size(), output);
|
||||
*output = CreateLenValFromData(out_data.data(), out_data.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" bool ProcessRgb(const ProcessingParams* processing_params,
|
||||
sapi::LenValStruct* output) {
|
||||
auto processing_data_status = PrepareDataForProcessing(processing_params);
|
||||
auto processing_data = PrepareDataForProcessing(processing_params);
|
||||
|
||||
if (!processing_data_status.status().ok()) {
|
||||
fprintf(stderr, "%s\n", processing_data_status.status().ToString().c_str());
|
||||
if (!processing_data.ok()) {
|
||||
std::cerr << processing_data.status().ToString() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
int xsize, ysize;
|
||||
std::vector<uint8_t> rgb;
|
||||
|
||||
if (!ReadPNG(processing_data_status.value().in_data, &xsize, &ysize, &rgb)) {
|
||||
fprintf(stderr, "Error reading PNG data from input file\n");
|
||||
auto png_data = ReadPNG(processing_data->in_data);
|
||||
if (!png_data.ok()) {
|
||||
std::cerr << "Error reading PNG data from input file" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CheckMemoryLimitExceeded(processing_params->memlimit_mb, xsize, ysize)) {
|
||||
fprintf(stderr, "Memory limit would be exceeded.\n");
|
||||
if (CheckMemoryLimitExceeded(processing_params->memlimit_mb,
|
||||
png_data->xsize, png_data->ysize)) {
|
||||
std::cerr << "Memory limit would be exceeded" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string out_data;
|
||||
if (!guetzli::Process(processing_data_status.value().params,
|
||||
&processing_data_status.value().stats,
|
||||
rgb, xsize, ysize, &out_data)) {
|
||||
fprintf(stderr, "Guetzli processing failed\n");
|
||||
if (!guetzli::Process(processing_data->params,
|
||||
&processing_data->stats,
|
||||
png_data->rgb,
|
||||
png_data->xsize,
|
||||
png_data->ysize,
|
||||
&out_data)) {
|
||||
std::cerr << "Guetzli processing failed" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
CopyMemoryToLenVal(out_data.data(), out_data.size(), output);
|
||||
*output = CreateLenValFromData(out_data.data(), out_data.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,13 +15,8 @@
|
|||
#ifndef GUETZLI_SANDBOXED_GUETZLI_SANDBOX_H_
|
||||
#define GUETZLI_SANDBOXED_GUETZLI_SANDBOX_H_
|
||||
|
||||
#include <libgen.h>
|
||||
#include <syscall.h>
|
||||
|
||||
#include "sandboxed_api/sandbox2/policy.h"
|
||||
#include "sandboxed_api/sandbox2/policybuilder.h"
|
||||
#include "sandboxed_api/util/flag.h"
|
||||
|
||||
#include "guetzli_sapi.sapi.h"
|
||||
|
||||
namespace guetzli {
|
||||
|
@ -42,7 +37,7 @@ class GuetzliSapiSandbox : public GuetzliSandbox {
|
|||
.AllowSyscalls({
|
||||
__NR_futex,
|
||||
__NR_close,
|
||||
__NR_recvmsg // Seems like this one needed to work with remote file descriptors
|
||||
__NR_recvmsg // To work with remote fd
|
||||
})
|
||||
.BuildOrDie();
|
||||
}
|
||||
|
|
|
@ -30,12 +30,6 @@ namespace {
|
|||
constexpr int kDefaultJPEGQuality = 95;
|
||||
constexpr int kDefaultMemlimitMB = 6000;
|
||||
|
||||
void TerminateHandler() {
|
||||
fprintf(stderr, "Unhandled exception. Most likely insufficient memory available.\n"
|
||||
"Make sure that there is 300MB/MPix of memory available.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void Usage() {
|
||||
fprintf(stderr,
|
||||
"Guetzli JPEG compressor. Usage: \n"
|
||||
|
@ -54,8 +48,6 @@ void Usage() {
|
|||
} // namespace
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
std::set_terminate(TerminateHandler);
|
||||
|
||||
int verbose = 0;
|
||||
int quality = kDefaultJPEGQuality;
|
||||
int memlimit_mb = kDefaultMemlimitMB;
|
||||
|
@ -92,25 +84,9 @@ int main(int argc, const char** argv) {
|
|||
Usage();
|
||||
}
|
||||
|
||||
sandbox2::file_util::fileops::FDCloser in_fd_closer(
|
||||
open(argv[opt_idx], O_RDONLY));
|
||||
|
||||
if (in_fd_closer.get() < 0) {
|
||||
fprintf(stderr, "Can't open input file: %s\n", argv[opt_idx]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sandbox2::file_util::fileops::FDCloser out_fd_closer(
|
||||
open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR));
|
||||
|
||||
if (out_fd_closer.get() < 0) {
|
||||
fprintf(stderr, "Can't create temporary output file: %s\n", argv[opt_idx]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
guetzli::sandbox::TransactionParams params = {
|
||||
in_fd_closer.get(),
|
||||
out_fd_closer.get(),
|
||||
argv[opt_idx],
|
||||
argv[opt_idx + 1],
|
||||
verbose,
|
||||
quality,
|
||||
memlimit_mb
|
||||
|
@ -119,29 +95,10 @@ int main(int argc, const char** argv) {
|
|||
guetzli::sandbox::GuetzliTransaction transaction(std::move(params));
|
||||
auto result = transaction.Run();
|
||||
|
||||
if (result.ok()) {
|
||||
if (access(argv[opt_idx + 1], F_OK) != -1) {
|
||||
if (remove(argv[opt_idx + 1]) < 0) {
|
||||
fprintf(stderr, "Error deleting existing output file: %s\n",
|
||||
argv[opt_idx + 1]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::stringstream path;
|
||||
path << "/proc/self/fd/" << out_fd_closer.get();
|
||||
|
||||
if (linkat(AT_FDCWD, path.str().c_str(), AT_FDCWD, argv[opt_idx + 1],
|
||||
AT_SYMLINK_FOLLOW) < 0) {
|
||||
fprintf(stderr, "Error linking %s\n",
|
||||
argv[opt_idx + 1]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "%s\n", result.ToString().c_str()); // Use cerr instead ?
|
||||
return 1;
|
||||
if (!result.ok()) {
|
||||
std::cerr << result.ToString() << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
|
@ -14,73 +14,49 @@
|
|||
|
||||
#include "guetzli_transaction.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
namespace guetzli {
|
||||
namespace sandbox {
|
||||
|
||||
absl::Status GuetzliTransaction::Init() {
|
||||
// Close remote fd if transaction is repeated
|
||||
if (in_fd_.GetRemoteFd() != -1) {
|
||||
SAPI_RETURN_IF_ERROR(in_fd_.CloseRemoteFd(sandbox()->GetRpcChannel()));
|
||||
}
|
||||
if (out_fd_.GetRemoteFd() != -1) {
|
||||
SAPI_RETURN_IF_ERROR(out_fd_.CloseRemoteFd(sandbox()->GetRpcChannel()));
|
||||
absl::Status GuetzliTransaction::Main() {
|
||||
sapi::v::Fd in_fd(open(params_.in_file, O_RDONLY));
|
||||
|
||||
if (in_fd.GetValue() < 0) {
|
||||
return absl::FailedPreconditionError(
|
||||
"Error opening input file"
|
||||
);
|
||||
}
|
||||
|
||||
// Reposition back to the beginning of file
|
||||
if (lseek(in_fd_.GetValue(), 0, SEEK_CUR) != 0) {
|
||||
if (lseek(in_fd_.GetValue(), 0, SEEK_SET) != 0) {
|
||||
return absl::FailedPreconditionError(
|
||||
"Error returnig cursor to the beginning"
|
||||
);
|
||||
}
|
||||
}
|
||||
SAPI_ASSIGN_OR_RETURN(image_type_, GetImageTypeFromFd(in_fd.GetValue()));
|
||||
SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&in_fd));
|
||||
|
||||
// Choosing between jpg and png modes
|
||||
sapi::StatusOr<ImageType> image_type = GetImageTypeFromFd(in_fd_.GetValue());
|
||||
|
||||
if (!image_type.ok()) {
|
||||
return image_type.status();
|
||||
}
|
||||
|
||||
image_type_ = image_type.value();
|
||||
|
||||
SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&in_fd_));
|
||||
SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&out_fd_));
|
||||
|
||||
if (in_fd_.GetRemoteFd() < 0) {
|
||||
if (in_fd.GetRemoteFd() < 0) {
|
||||
return absl::FailedPreconditionError(
|
||||
"Error receiving remote FD: remote input fd is set to -1");
|
||||
}
|
||||
if (out_fd_.GetRemoteFd() < 0) {
|
||||
return absl::FailedPreconditionError(
|
||||
"Error receiving remote FD: remote output fd is set to -1");
|
||||
}
|
||||
|
||||
in_fd_.OwnLocalFd(false); // FDCloser will close local fd
|
||||
out_fd_.OwnLocalFd(false); // FDCloser will close local fd
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GuetzliTransaction::Main() {
|
||||
GuetzliApi api(sandbox());
|
||||
sapi::v::LenVal output(0);
|
||||
|
||||
sapi::v::Struct<ProcessingParams> processing_params;
|
||||
*processing_params.mutable_data() = {in_fd_.GetRemoteFd(),
|
||||
*processing_params.mutable_data() = {in_fd.GetRemoteFd(),
|
||||
params_.verbose,
|
||||
params_.quality,
|
||||
params_.memlimit_mb
|
||||
};
|
||||
|
||||
auto result_status = image_type_ == ImageType::kJpeg ?
|
||||
auto result = image_type_ == ImageType::kJpeg ?
|
||||
api.ProcessJpeg(processing_params.PtrBefore(), output.PtrBoth()) :
|
||||
api.ProcessRgb(processing_params.PtrBefore(), output.PtrBoth());
|
||||
|
||||
if (!result_status.value_or(false)) {
|
||||
if (!result.value_or(false)) {
|
||||
std::stringstream error_stream;
|
||||
error_stream << "Error processing "
|
||||
<< (image_type_ == ImageType::kJpeg ? "jpeg" : "rgb") << " data"
|
||||
|
@ -91,7 +67,22 @@ absl::Status GuetzliTransaction::Main() {
|
|||
);
|
||||
}
|
||||
|
||||
auto write_result = api.WriteDataToFd(out_fd_.GetRemoteFd(),
|
||||
sapi::v::Fd out_fd(open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR));
|
||||
|
||||
if (out_fd.GetValue() < 0) {
|
||||
return absl::FailedPreconditionError(
|
||||
"Error creating temp output file"
|
||||
);
|
||||
}
|
||||
|
||||
SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&out_fd));
|
||||
|
||||
if (out_fd.GetRemoteFd() < 0) {
|
||||
return absl::FailedPreconditionError(
|
||||
"Error receiving remote FD: remote output fd is set to -1");
|
||||
}
|
||||
|
||||
auto write_result = api.WriteDataToFd(out_fd.GetRemoteFd(),
|
||||
output.PtrBefore());
|
||||
|
||||
if (!write_result.value_or(false)) {
|
||||
|
@ -100,21 +91,39 @@ absl::Status GuetzliTransaction::Main() {
|
|||
);
|
||||
}
|
||||
|
||||
SAPI_RETURN_IF_ERROR(LinkOutFile(out_fd.GetValue()));
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
time_t GuetzliTransaction::CalculateTimeLimitFromImageSize(
|
||||
uint64_t pixels) const {
|
||||
return (pixels / kMpixPixels + 5) * 60;
|
||||
absl::Status GuetzliTransaction::LinkOutFile(int out_fd) const {
|
||||
if (access(params_.out_file, F_OK) != -1) {
|
||||
if (remove(params_.out_file) < 0) {
|
||||
std::stringstream error;
|
||||
error << "Error deleting existing output file: " << params_.out_file;
|
||||
return absl::FailedPreconditionError(error.str());
|
||||
}
|
||||
}
|
||||
|
||||
std::stringstream path;
|
||||
path << "/proc/self/fd/" << out_fd;
|
||||
if (linkat(AT_FDCWD, path.str().c_str(), AT_FDCWD, params_.out_file,
|
||||
AT_SYMLINK_FOLLOW) < 0) {
|
||||
std::stringstream error;
|
||||
error << "Error linking: " << params_.out_file;
|
||||
return absl::FailedPreconditionError(error.str());
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
sapi::StatusOr<ImageType> GuetzliTransaction::GetImageTypeFromFd(int fd) const {
|
||||
static const unsigned char kPNGMagicBytes[] = {
|
||||
0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n',
|
||||
};
|
||||
char read_buf[8];
|
||||
char read_buf[sizeof(kPNGMagicBytes)];
|
||||
|
||||
if (read(fd, read_buf, 8) != 8) {
|
||||
if (read(fd, read_buf, sizeof(kPNGMagicBytes)) != sizeof(kPNGMagicBytes)) {
|
||||
return absl::FailedPreconditionError(
|
||||
"Error determining type of the input file"
|
||||
);
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#ifndef GUETZLI_SANDBOXED_GUETZLI_TRANSACTION_H_
|
||||
#define GUETZLI_SANDBOXED_GUETZLI_TRANSACTION_H_
|
||||
|
||||
#include <libgen.h>
|
||||
#include <syscall.h>
|
||||
|
||||
#include "sandboxed_api/transaction.h"
|
||||
|
@ -32,8 +31,8 @@ enum class ImageType {
|
|||
};
|
||||
|
||||
struct TransactionParams {
|
||||
int in_fd = -1;
|
||||
int out_fd = -1;
|
||||
const char* in_file = nullptr;
|
||||
const char* out_file = nullptr;
|
||||
int verbose = 0;
|
||||
int quality = 0;
|
||||
int memlimit_mb = 0;
|
||||
|
@ -43,35 +42,26 @@ struct TransactionParams {
|
|||
// Create a new one for each processing operation
|
||||
class GuetzliTransaction : public sapi::Transaction {
|
||||
public:
|
||||
GuetzliTransaction(TransactionParams&& params)
|
||||
GuetzliTransaction(TransactionParams params)
|
||||
: sapi::Transaction(std::make_unique<GuetzliSapiSandbox>())
|
||||
, params_(std::move(params))
|
||||
, in_fd_(params_.in_fd)
|
||||
, out_fd_(params_.out_fd)
|
||||
{
|
||||
//TODO: Add retry count as a parameter
|
||||
sapi::Transaction::set_retry_count(kDefaultTransactionRetryCount);
|
||||
//TODO: Try to use sandbox().set_wall_limit instead of infinite time limit
|
||||
sapi::Transaction::SetTimeLimit(0);
|
||||
sapi::Transaction::SetTimeLimit(0); // Infinite time limit
|
||||
}
|
||||
|
||||
private:
|
||||
absl::Status Init() override;
|
||||
//absl::Status Init() override;
|
||||
absl::Status Main() final;
|
||||
|
||||
absl::Status LinkOutFile(int out_fd) const;
|
||||
sapi::StatusOr<ImageType> GetImageTypeFromFd(int fd) const;
|
||||
|
||||
// As guetzli takes roughly 1 minute of CPU per 1 MPix we need to calculate
|
||||
// approximate time for transaction to complete
|
||||
time_t CalculateTimeLimitFromImageSize(uint64_t pixels) const;
|
||||
|
||||
const TransactionParams params_;
|
||||
sapi::v::Fd in_fd_;
|
||||
sapi::v::Fd out_fd_;
|
||||
ImageType image_type_ = ImageType::kJpeg;
|
||||
|
||||
static const int kDefaultTransactionRetryCount = 0;
|
||||
static const uint64_t kMpixPixels = 1'000'000;
|
||||
};
|
||||
|
||||
} // namespace sandbox
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
licenses(["unencumbered"]) # code authored by Google
|
||||
|
||||
cc_test(
|
||||
name = "transaction_tests",
|
||||
srcs = ["guetzli_transaction_test.cc"],
|
||||
|
@ -34,4 +36,4 @@ cc_test(
|
|||
],
|
||||
size = "large",
|
||||
data = glob(["testdata/*"])
|
||||
)
|
||||
)
|
||||
|
|
|
@ -39,9 +39,6 @@ constexpr const char* kInJpegFilename = "nature.jpg";
|
|||
constexpr const char* kPngReferenceFilename = "bees_reference.jpg";
|
||||
constexpr const char* kJpegReferenceFIlename = "nature_reference.jpg";
|
||||
|
||||
constexpr int kPngExpectedSize = 38'625;
|
||||
constexpr int kJpegExpectedSize = 10'816;
|
||||
|
||||
constexpr int kDefaultQualityTarget = 95;
|
||||
constexpr int kDefaultMemlimitMb = 6000;
|
||||
|
||||
|
@ -49,9 +46,7 @@ constexpr const char* kRelativePathToTestdata =
|
|||
"/guetzli_sandboxed/tests/testdata/";
|
||||
|
||||
std::string GetPathToInputFile(const char* filename) {
|
||||
return std::string(getenv("TEST_SRCDIR"))
|
||||
+ std::string(kRelativePathToTestdata)
|
||||
+ std::string(filename);
|
||||
return absl::StrCat(getenv("TEST_SRCDIR"), kRelativePathToTestdata, filename);
|
||||
}
|
||||
|
||||
std::string ReadFromFile(const std::string& filename) {
|
||||
|
@ -66,18 +61,6 @@ std::string ReadFromFile(const std::string& filename) {
|
|||
return result.str();
|
||||
}
|
||||
|
||||
template<typename Container>
|
||||
bool CompareBytesInLenValAndContainer(const sapi::v::LenVal& lenval,
|
||||
const Container& container) {
|
||||
return std::equal(
|
||||
lenval.GetData(), lenval.GetData() + lenval.GetDataSize(),
|
||||
container.begin(),
|
||||
[](const uint8_t lhs, const auto rhs) {
|
||||
return lhs == static_cast<uint8_t>(rhs);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class GuetzliSapiTest : public ::testing::Test {
|
||||
|
@ -110,14 +93,13 @@ TEST_F(GuetzliSapiTest, ProcessRGB) {
|
|||
auto processing_result = api_->ProcessRgb(processing_params.PtrBefore(),
|
||||
output.PtrBoth());
|
||||
ASSERT_TRUE(processing_result.value_or(false)) << "Error processing rgb data";
|
||||
ASSERT_EQ(output.GetDataSize(), kPngExpectedSize)
|
||||
<< "Incorrect result data size";
|
||||
std::string reference_data =
|
||||
ReadFromFile(GetPathToInputFile(kPngReferenceFilename));
|
||||
ASSERT_EQ(output.GetDataSize(), reference_data.size())
|
||||
<< "Incorrect result data size";
|
||||
ASSERT_TRUE(CompareBytesInLenValAndContainer(output, reference_data))
|
||||
<< "Processed data doesn't match reference output";
|
||||
ASSERT_EQ(std::string(output.GetData(),
|
||||
output.GetData() + output.GetDataSize()), reference_data)
|
||||
<< "Processed data doesn't match reference output";
|
||||
}
|
||||
|
||||
// This test can take up to few minutes depending on your hardware
|
||||
|
@ -138,14 +120,13 @@ TEST_F(GuetzliSapiTest, ProcessJpeg) {
|
|||
auto processing_result = api_->ProcessJpeg(processing_params.PtrBefore(),
|
||||
output.PtrBoth());
|
||||
ASSERT_TRUE(processing_result.value_or(false)) << "Error processing jpg data";
|
||||
ASSERT_EQ(output.GetDataSize(), kJpegExpectedSize)
|
||||
<< "Incorrect result data size";
|
||||
std::string reference_data =
|
||||
ReadFromFile(GetPathToInputFile(kJpegReferenceFIlename));
|
||||
ASSERT_EQ(output.GetDataSize(), reference_data.size())
|
||||
<< "Incorrect result data size";
|
||||
ASSERT_TRUE(CompareBytesInLenValAndContainer(output, reference_data))
|
||||
<< "Processed data doesn't match reference output";
|
||||
ASSERT_EQ(std::string(output.GetData(),
|
||||
output.GetData() + output.GetDataSize()), reference_data)
|
||||
<< "Processed data doesn't match reference output";
|
||||
}
|
||||
|
||||
} // namespace tests
|
||||
|
|
|
@ -34,6 +34,8 @@ namespace {
|
|||
|
||||
constexpr const char* kInPngFilename = "bees.png";
|
||||
constexpr const char* kInJpegFilename = "nature.jpg";
|
||||
constexpr const char* kOutJpegFilename = "out_jpeg.jpg";
|
||||
constexpr const char* kOutPngFilename = "out_png.png";
|
||||
constexpr const char* kPngReferenceFilename = "bees_reference.jpg";
|
||||
constexpr const char* kJpegReferenceFIlename = "nature_reference.jpg";
|
||||
|
||||
|
@ -46,10 +48,8 @@ constexpr int kDefaultMemlimitMb = 6000;
|
|||
constexpr const char* kRelativePathToTestdata =
|
||||
"/guetzli_sandboxed/tests/testdata/";
|
||||
|
||||
std::string GetPathToInputFile(const char* filename) {
|
||||
return std::string(getenv("TEST_SRCDIR"))
|
||||
+ std::string(kRelativePathToTestdata)
|
||||
+ std::string(filename);
|
||||
std::string GetPathToFile(const char* filename) {
|
||||
return absl::StrCat(getenv("TEST_SRCDIR"), kRelativePathToTestdata, filename);
|
||||
}
|
||||
|
||||
std::string ReadFromFile(const std::string& filename) {
|
||||
|
@ -64,18 +64,35 @@ std::string ReadFromFile(const std::string& filename) {
|
|||
return result.str();
|
||||
}
|
||||
|
||||
// Helper class to delete file after opening
|
||||
class FileRemover {
|
||||
public:
|
||||
explicit FileRemover(const char* path)
|
||||
: path_(path)
|
||||
, fd_(open(path, O_RDONLY))
|
||||
{}
|
||||
|
||||
~FileRemover() {
|
||||
close(fd_);
|
||||
remove(path_);
|
||||
}
|
||||
|
||||
int get() const { return fd_; }
|
||||
|
||||
private:
|
||||
const char* path_;
|
||||
int fd_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(GuetzliTransactionTest, TestTransactionJpg) {
|
||||
sandbox2::file_util::fileops::FDCloser in_fd_closer(
|
||||
open(GetPathToInputFile(kInJpegFilename).c_str(), O_RDONLY));
|
||||
ASSERT_TRUE(in_fd_closer.get() != -1) << "Error opening input jpg file";
|
||||
sandbox2::file_util::fileops::FDCloser out_fd_closer(
|
||||
open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR));
|
||||
ASSERT_TRUE(out_fd_closer.get() != -1) << "Error creating temp output file";
|
||||
std::string in_path = GetPathToFile(kInJpegFilename);
|
||||
std::string out_path = GetPathToFile(kOutJpegFilename);
|
||||
|
||||
TransactionParams params = {
|
||||
in_fd_closer.get(),
|
||||
out_fd_closer.get(),
|
||||
in_path.c_str(),
|
||||
out_path.c_str(),
|
||||
0,
|
||||
kDefaultQualityTarget,
|
||||
kDefaultMemlimitMb
|
||||
|
@ -86,17 +103,17 @@ TEST(GuetzliTransactionTest, TestTransactionJpg) {
|
|||
|
||||
ASSERT_TRUE(result.ok()) << result.ToString();
|
||||
}
|
||||
ASSERT_TRUE(fcntl(out_fd_closer.get(), F_GETFD) != -1 || errno != EBADF)
|
||||
<< "Local output fd closed";
|
||||
auto reference_data = ReadFromFile(GetPathToInputFile(kJpegReferenceFIlename));
|
||||
auto output_size = lseek(out_fd_closer.get(), 0, SEEK_END);
|
||||
auto reference_data = ReadFromFile(GetPathToFile(kJpegReferenceFIlename));
|
||||
FileRemover file_remover(out_path.c_str());
|
||||
ASSERT_TRUE(file_remover.get() != -1) << "Error opening output file";
|
||||
auto output_size = lseek(file_remover.get(), 0, SEEK_END);
|
||||
ASSERT_EQ(reference_data.size(), output_size)
|
||||
<< "Different sizes of reference and returned data";
|
||||
ASSERT_EQ(lseek(out_fd_closer.get(), 0, SEEK_SET), 0)
|
||||
ASSERT_EQ(lseek(file_remover.get(), 0, SEEK_SET), 0)
|
||||
<< "Error repositioning out file";
|
||||
|
||||
std::unique_ptr<char[]> buf(new char[output_size]);
|
||||
auto status = read(out_fd_closer.get(), buf.get(), output_size);
|
||||
auto status = read(file_remover.get(), buf.get(), output_size);
|
||||
ASSERT_EQ(status, output_size) << "Error reading data from temp output file";
|
||||
|
||||
ASSERT_TRUE(
|
||||
|
@ -105,16 +122,13 @@ TEST(GuetzliTransactionTest, TestTransactionJpg) {
|
|||
}
|
||||
|
||||
TEST(GuetzliTransactionTest, TestTransactionPng) {
|
||||
sandbox2::file_util::fileops::FDCloser in_fd_closer(
|
||||
open(GetPathToInputFile(kInPngFilename).c_str(), O_RDONLY));
|
||||
ASSERT_TRUE(in_fd_closer.get() != -1) << "Error opening input png file";
|
||||
sandbox2::file_util::fileops::FDCloser out_fd_closer(
|
||||
open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR));
|
||||
ASSERT_TRUE(out_fd_closer.get() != -1) << "Error creating temp output file";
|
||||
std::string in_path = GetPathToFile(kInPngFilename);
|
||||
std::string out_path = GetPathToFile(kOutPngFilename);
|
||||
|
||||
TransactionParams params = {
|
||||
in_fd_closer.get(),
|
||||
out_fd_closer.get(),
|
||||
0,
|
||||
in_path.c_str(),
|
||||
out_path.c_str(),
|
||||
0,
|
||||
kDefaultQualityTarget,
|
||||
kDefaultMemlimitMb
|
||||
};
|
||||
|
@ -124,17 +138,17 @@ TEST(GuetzliTransactionTest, TestTransactionPng) {
|
|||
|
||||
ASSERT_TRUE(result.ok()) << result.ToString();
|
||||
}
|
||||
ASSERT_TRUE(fcntl(out_fd_closer.get(), F_GETFD) != -1 || errno != EBADF)
|
||||
<< "Local output fd closed";
|
||||
auto reference_data = ReadFromFile(GetPathToInputFile(kPngReferenceFilename));
|
||||
auto output_size = lseek(out_fd_closer.get(), 0, SEEK_END);
|
||||
auto reference_data = ReadFromFile(GetPathToFile(kPngReferenceFilename));
|
||||
FileRemover file_remover(out_path.c_str());
|
||||
ASSERT_TRUE(file_remover.get() != -1) << "Error opening output file";
|
||||
auto output_size = lseek(file_remover.get(), 0, SEEK_END);
|
||||
ASSERT_EQ(reference_data.size(), output_size)
|
||||
<< "Different sizes of reference and returned data";
|
||||
ASSERT_EQ(lseek(out_fd_closer.get(), 0, SEEK_SET), 0)
|
||||
ASSERT_EQ(lseek(file_remover.get(), 0, SEEK_SET), 0)
|
||||
<< "Error repositioning out file";
|
||||
|
||||
std::unique_ptr<char[]> buf(new char[output_size]);
|
||||
auto status = read(out_fd_closer.get(), buf.get(), output_size);
|
||||
auto status = read(file_remover.get(), buf.get(), output_size);
|
||||
ASSERT_EQ(status, output_size) << "Error reading data from temp output file";
|
||||
|
||||
ASSERT_TRUE(
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
cc_library(
|
||||
name = "butteraugli_lib",
|
||||
srcs = [
|
||||
"butteraugli/butteraugli.cc",
|
||||
"butteraugli/butteraugli.h",
|
||||
],
|
||||
hdrs = [
|
||||
"butteraugli/butteraugli.h",
|
||||
],
|
||||
copts = ["-Wno-sign-compare"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "butteraugli",
|
||||
srcs = ["butteraugli/butteraugli_main.cc"],
|
||||
copts = ["-Wno-sign-compare"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":butteraugli_lib",
|
||||
"@jpeg_archive//:jpeg",
|
||||
"@png_archive//:png",
|
||||
],
|
||||
)
|
|
@ -1,201 +0,0 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -1,68 +0,0 @@
|
|||
# butteraugli
|
||||
|
||||
> A tool for measuring perceived differences between images
|
||||
|
||||
## Introduction
|
||||
|
||||
Butteraugli is a project that estimates the psychovisual similarity of two
|
||||
images. It gives a score for the images that is reliable in the domain of barely
|
||||
noticeable differences. Butteraugli not only gives a scalar score, but also
|
||||
computes a spatial map of the level of differences.
|
||||
|
||||
One of the main motivations for this project is the statistical differences in
|
||||
location and density of different color receptors, particularly the low density
|
||||
of blue cones in the fovea. Another motivation comes from more accurate modeling
|
||||
of ganglion cells, particularly the frequency space inhibition.
|
||||
|
||||
## Use
|
||||
|
||||
Butteraugli can work as a quality metric for lossy image and video compression.
|
||||
On our small test corpus butteraugli performs better than our implementations of
|
||||
the reference methods, psnrhsv-m, ssim, and our yuv-color-space variant of ssim.
|
||||
One possible use is to define the quality level setting used in a jpeg
|
||||
compressor, or to compare two or more compression methods at the same level of
|
||||
psychovisual differences.
|
||||
|
||||
Butteraugli is intended to be a research tool more than a practical tool for
|
||||
choosing compression formats. We don't know how well butteraugli performs with
|
||||
major deformations -- we have mostly tuned it within a small range of quality,
|
||||
roughly corresponding to jpeg qualities 90 to 95.
|
||||
|
||||
## Interface
|
||||
|
||||
Only a C++ interface is provided. The interface takes two images and outputs a
|
||||
map together with a scalar value defining the difference. The scalar value can
|
||||
be compared to two reference values that divide the value space into three
|
||||
experience classes: 'great', 'acceptable' and 'not acceptable'.
|
||||
|
||||
## Build instructions
|
||||
|
||||
Install [Bazel](http://bazel.build) by following the
|
||||
[instructions](https://www.bazel.build/docs/install.html). Run `bazel build -c opt
|
||||
//:butteraugli` in the directory that contains this README file to build the
|
||||
[command-line utility](#cmdline-tool). If you want to use Butteraugli as a
|
||||
library, depend on the `//:butteraugli_lib` target.
|
||||
|
||||
Alternatively, you can use the Makefile provided in the `butteraugli` directory,
|
||||
after ensuring that [libpng](http://www.libpng.org/) and
|
||||
[libjpeg](http://ijg.org/) are installed. On some systems you might need to also
|
||||
install corresponding `-dev` packages.
|
||||
|
||||
The code is portable and also compiles on Windows after defining
|
||||
`_CRT_SECURE_NO_WARNINGS` in the project settings.
|
||||
|
||||
## Command-line utility {#cmdline-tool}
|
||||
|
||||
Butteraugli, apart from the library, comes bundled with a comparison tool. The
|
||||
comparison tool supports PNG and JPG images as inputs. To compare images, run:
|
||||
|
||||
```
|
||||
butteraugli image1.{png|jpg} image2.{png|jpg}
|
||||
```
|
||||
|
||||
The tool can also produce a heatmap of differences between images. The heatmap
|
||||
will be output as a PNM image. To produce one, run:
|
||||
|
||||
```
|
||||
butteraugli image1.{png|jpg} image2.{png|jpg} heatmap.pnm
|
||||
```
|
|
@ -1,25 +0,0 @@
|
|||
workspace(name = "butteraugli")
|
||||
|
||||
new_http_archive(
|
||||
name = "png_archive",
|
||||
url = "http://github.com/glennrp/libpng/archive/v1.2.57.zip",
|
||||
sha256 = "a941dc09ca00148fe7aaf4ecdd6a67579c293678ed1e1cf633b5ffc02f4f8cf7",
|
||||
strip_prefix = "libpng-1.2.57",
|
||||
build_file = "png.BUILD",
|
||||
)
|
||||
|
||||
new_http_archive(
|
||||
name = "zlib_archive",
|
||||
url = "http://zlib.net/fossils/zlib-1.2.10.tar.gz",
|
||||
sha256 = "8d7e9f698ce48787b6e1c67e6bff79e487303e66077e25cb9784ac8835978017",
|
||||
strip_prefix = "zlib-1.2.10",
|
||||
build_file = "zlib.BUILD",
|
||||
)
|
||||
|
||||
new_http_archive(
|
||||
name = "jpeg_archive",
|
||||
url = "http://www.ijg.org/files/jpegsrc.v9b.tar.gz",
|
||||
sha256 = "240fd398da741669bf3c90366f58452ea59041cacc741a489b99f2f6a0bad052",
|
||||
strip_prefix = "jpeg-9b",
|
||||
build_file = "jpeg.BUILD",
|
||||
)
|
|
@ -1,7 +0,0 @@
|
|||
LDLIBS += -lpng -ljpeg
|
||||
CXXFLAGS += -std=c++11 -I..
|
||||
LINK.o = $(LINK.cc)
|
||||
|
||||
all: butteraugli.o butteraugli_main.o butteraugli
|
||||
|
||||
butteraugli: butteraugli.o butteraugli_main.o
|
File diff suppressed because it is too large
Load Diff
|
@ -1,619 +0,0 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Disclaimer: This is not an official Google product.
|
||||
//
|
||||
// Author: Jyrki Alakuijala (jyrki.alakuijala@gmail.com)
|
||||
|
||||
#ifndef BUTTERAUGLI_BUTTERAUGLI_H_
|
||||
#define BUTTERAUGLI_BUTTERAUGLI_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#define BUTTERAUGLI_ENABLE_CHECKS 0
|
||||
|
||||
// This is the main interface to butteraugli image similarity
|
||||
// analysis function.
|
||||
|
||||
namespace butteraugli {
|
||||
|
||||
template<typename T>
|
||||
class Image;
|
||||
|
||||
using Image8 = Image<uint8_t>;
|
||||
using ImageF = Image<float>;
|
||||
|
||||
// ButteraugliInterface defines the public interface for butteraugli.
|
||||
//
|
||||
// It calculates the difference between rgb0 and rgb1.
|
||||
//
|
||||
// rgb0 and rgb1 contain the images. rgb0[c][px] and rgb1[c][px] contains
|
||||
// the red image for c == 0, green for c == 1, blue for c == 2. Location index
|
||||
// px is calculated as y * xsize + x.
|
||||
//
|
||||
// Value of pixels of images rgb0 and rgb1 need to be represented as raw
|
||||
// intensity. Most image formats store gamma corrected intensity in pixel
|
||||
// values. This gamma correction has to be removed, by applying the following
|
||||
// function:
|
||||
// butteraugli_val = 255.0 * pow(png_val / 255.0, gamma);
|
||||
// A typical value of gamma is 2.2. It is usually stored in the image header.
|
||||
// Take care not to confuse that value with its inverse. The gamma value should
|
||||
// be always greater than one.
|
||||
// Butteraugli does not work as intended if the caller does not perform
|
||||
// gamma correction.
|
||||
//
|
||||
// diffmap will contain an image of the size xsize * ysize, containing
|
||||
// localized differences for values px (indexed with the px the same as rgb0
|
||||
// and rgb1). diffvalue will give a global score of similarity.
|
||||
//
|
||||
// A diffvalue smaller than kButteraugliGood indicates that images can be
|
||||
// observed as the same image.
|
||||
// diffvalue larger than kButteraugliBad indicates that a difference between
|
||||
// the images can be observed.
|
||||
// A diffvalue between kButteraugliGood and kButteraugliBad indicates that
|
||||
// a subtle difference can be observed between the images.
|
||||
//
|
||||
// Returns true on success.
|
||||
|
||||
bool ButteraugliInterface(const std::vector<ImageF> &rgb0,
|
||||
const std::vector<ImageF> &rgb1,
|
||||
ImageF &diffmap,
|
||||
double &diffvalue);
|
||||
|
||||
const double kButteraugliQuantLow = 0.26;
|
||||
const double kButteraugliQuantHigh = 1.454;
|
||||
|
||||
// Converts the butteraugli score into fuzzy class values that are continuous
|
||||
// at the class boundary. The class boundary location is based on human
|
||||
// raters, but the slope is arbitrary. Particularly, it does not reflect
|
||||
// the expectation value of probabilities of the human raters. It is just
|
||||
// expected that a smoother class boundary will allow for higher-level
|
||||
// optimization algorithms to work faster.
|
||||
//
|
||||
// Returns 2.0 for a perfect match, and 1.0 for 'ok', 0.0 for bad. Because the
|
||||
// scoring is fuzzy, a butteraugli score of 0.96 would return a class of
|
||||
// around 1.9.
|
||||
double ButteraugliFuzzyClass(double score);
|
||||
|
||||
// Input values should be in range 0 (bad) to 2 (good). Use
|
||||
// kButteraugliNormalization as normalization.
|
||||
double ButteraugliFuzzyInverse(double seek);
|
||||
|
||||
// Returns a map which can be used for adaptive quantization. Values can
|
||||
// typically range from kButteraugliQuantLow to kButteraugliQuantHigh. Low
|
||||
// values require coarse quantization (e.g. near random noise), high values
|
||||
// require fine quantization (e.g. in smooth bright areas).
|
||||
bool ButteraugliAdaptiveQuantization(size_t xsize, size_t ysize,
|
||||
const std::vector<std::vector<float> > &rgb, std::vector<float> &quant);
|
||||
|
||||
// Implementation details, don't use anything below or your code will
|
||||
// break in the future.
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define BUTTERAUGLI_RESTRICT __restrict
|
||||
#else
|
||||
#define BUTTERAUGLI_RESTRICT __restrict__
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define BUTTERAUGLI_INLINE __forceinline
|
||||
#else
|
||||
#define BUTTERAUGLI_INLINE inline
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
// Early versions of Clang did not support __builtin_assume_aligned.
|
||||
#define BUTTERAUGLI_HAS_ASSUME_ALIGNED __has_builtin(__builtin_assume_aligned)
|
||||
#elif defined(__GNUC__)
|
||||
#define BUTTERAUGLI_HAS_ASSUME_ALIGNED 1
|
||||
#else
|
||||
#define BUTTERAUGLI_HAS_ASSUME_ALIGNED 0
|
||||
#endif
|
||||
|
||||
// Returns a void* pointer which the compiler then assumes is N-byte aligned.
|
||||
// Example: float* PIK_RESTRICT aligned = (float*)PIK_ASSUME_ALIGNED(in, 32);
|
||||
//
|
||||
// The assignment semantics are required by GCC/Clang. ICC provides an in-place
|
||||
// __assume_aligned, whereas MSVC's __assume appears unsuitable.
|
||||
#if BUTTERAUGLI_HAS_ASSUME_ALIGNED
|
||||
#define BUTTERAUGLI_ASSUME_ALIGNED(ptr, align) __builtin_assume_aligned((ptr), (align))
|
||||
#else
|
||||
#define BUTTERAUGLI_ASSUME_ALIGNED(ptr, align) (ptr)
|
||||
#endif // BUTTERAUGLI_HAS_ASSUME_ALIGNED
|
||||
|
||||
// Functions that depend on the cache line size.
|
||||
class CacheAligned {
|
||||
public:
|
||||
static constexpr size_t kPointerSize = sizeof(void *);
|
||||
static constexpr size_t kCacheLineSize = 64;
|
||||
|
||||
// The aligned-return annotation is only allowed on function declarations.
|
||||
static void *Allocate(const size_t bytes);
|
||||
static void Free(void *aligned_pointer);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using CacheAlignedUniquePtrT = std::unique_ptr<T[], void (*)(void *)>;
|
||||
|
||||
using CacheAlignedUniquePtr = CacheAlignedUniquePtrT<uint8_t>;
|
||||
|
||||
template <typename T = uint8_t>
|
||||
static inline CacheAlignedUniquePtrT<T> Allocate(const size_t entries) {
|
||||
return CacheAlignedUniquePtrT<T>(
|
||||
static_cast<T * const BUTTERAUGLI_RESTRICT>(
|
||||
CacheAligned::Allocate(entries * sizeof(T))),
|
||||
CacheAligned::Free);
|
||||
}
|
||||
|
||||
// Returns the smallest integer not less than "amount" that is divisible by
|
||||
// "multiple", which must be a power of two.
|
||||
template <size_t multiple>
|
||||
static inline size_t Align(const size_t amount) {
|
||||
static_assert(multiple != 0 && ((multiple & (multiple - 1)) == 0),
|
||||
"Align<> argument must be a power of two");
|
||||
return (amount + multiple - 1) & ~(multiple - 1);
|
||||
}
|
||||
|
||||
// Single channel, contiguous (cache-aligned) rows separated by padding.
|
||||
// T must be POD.
|
||||
//
|
||||
// Rationale: vectorization benefits from aligned operands - unaligned loads and
|
||||
// especially stores are expensive when the address crosses cache line
|
||||
// boundaries. Introducing padding after each row ensures the start of a row is
|
||||
// aligned, and that row loops can process entire vectors (writes to the padding
|
||||
// are allowed and ignored).
|
||||
//
|
||||
// We prefer a planar representation, where channels are stored as separate
|
||||
// 2D arrays, because that simplifies vectorization (repeating the same
|
||||
// operation on multiple adjacent components) without the complexity of a
|
||||
// hybrid layout (8 R, 8 G, 8 B, ...). In particular, clients can easily iterate
|
||||
// over all components in a row and Image requires no knowledge of the pixel
|
||||
// format beyond the component type "T". The downside is that we duplicate the
|
||||
// xsize/ysize members for each channel.
|
||||
//
|
||||
// This image layout could also be achieved with a vector and a row accessor
|
||||
// function, but a class wrapper with support for "deleter" allows wrapping
|
||||
// existing memory allocated by clients without copying the pixels. It also
|
||||
// provides convenient accessors for xsize/ysize, which shortens function
|
||||
// argument lists. Supports move-construction so it can be stored in containers.
|
||||
template <typename ComponentType>
|
||||
class Image {
|
||||
// Returns cache-aligned row stride, being careful to avoid 2K aliasing.
|
||||
static size_t BytesPerRow(const size_t xsize) {
|
||||
// Allow reading one extra AVX-2 vector on the right margin.
|
||||
const size_t row_size = xsize * sizeof(T) + 32;
|
||||
const size_t align = CacheAligned::kCacheLineSize;
|
||||
size_t bytes_per_row = (row_size + align - 1) & ~(align - 1);
|
||||
// During the lengthy window before writes are committed to memory, CPUs
|
||||
// guard against read after write hazards by checking the address, but
|
||||
// only the lower 11 bits. We avoid a false dependency between writes to
|
||||
// consecutive rows by ensuring their sizes are not multiples of 2 KiB.
|
||||
if (bytes_per_row % 2048 == 0) {
|
||||
bytes_per_row += align;
|
||||
}
|
||||
return bytes_per_row;
|
||||
}
|
||||
|
||||
public:
|
||||
using T = ComponentType;
|
||||
|
||||
Image() : xsize_(0), ysize_(0), bytes_per_row_(0),
|
||||
bytes_(static_cast<uint8_t*>(nullptr), Ignore) {}
|
||||
|
||||
Image(const size_t xsize, const size_t ysize)
|
||||
: xsize_(xsize),
|
||||
ysize_(ysize),
|
||||
bytes_per_row_(BytesPerRow(xsize)),
|
||||
bytes_(Allocate(bytes_per_row_ * ysize)) {}
|
||||
|
||||
Image(const size_t xsize, const size_t ysize, T val)
|
||||
: xsize_(xsize),
|
||||
ysize_(ysize),
|
||||
bytes_per_row_(BytesPerRow(xsize)),
|
||||
bytes_(Allocate(bytes_per_row_ * ysize)) {
|
||||
for (size_t y = 0; y < ysize_; ++y) {
|
||||
T* const BUTTERAUGLI_RESTRICT row = Row(y);
|
||||
for (int x = 0; x < xsize_; ++x) {
|
||||
row[x] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image(const size_t xsize, const size_t ysize,
|
||||
uint8_t * const BUTTERAUGLI_RESTRICT bytes,
|
||||
const size_t bytes_per_row)
|
||||
: xsize_(xsize),
|
||||
ysize_(ysize),
|
||||
bytes_per_row_(bytes_per_row),
|
||||
bytes_(bytes, Ignore) {}
|
||||
|
||||
// Move constructor (required for returning Image from function)
|
||||
Image(Image &&other)
|
||||
: xsize_(other.xsize_),
|
||||
ysize_(other.ysize_),
|
||||
bytes_per_row_(other.bytes_per_row_),
|
||||
bytes_(std::move(other.bytes_)) {}
|
||||
|
||||
// Move assignment (required for std::vector)
|
||||
Image &operator=(Image &&other) {
|
||||
xsize_ = other.xsize_;
|
||||
ysize_ = other.ysize_;
|
||||
bytes_per_row_ = other.bytes_per_row_;
|
||||
bytes_ = std::move(other.bytes_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Swap(Image &other) {
|
||||
std::swap(xsize_, other.xsize_);
|
||||
std::swap(ysize_, other.ysize_);
|
||||
std::swap(bytes_per_row_, other.bytes_per_row_);
|
||||
std::swap(bytes_, other.bytes_);
|
||||
}
|
||||
|
||||
// How many pixels.
|
||||
size_t xsize() const { return xsize_; }
|
||||
size_t ysize() const { return ysize_; }
|
||||
|
||||
T *const BUTTERAUGLI_RESTRICT Row(const size_t y) {
|
||||
#ifdef BUTTERAUGLI_ENABLE_CHECKS
|
||||
if (y >= ysize_) {
|
||||
printf("Row %zu out of bounds (ysize=%zu)\n", y, ysize_);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
void *row = bytes_.get() + y * bytes_per_row_;
|
||||
return reinterpret_cast<T *>(BUTTERAUGLI_ASSUME_ALIGNED(row, 64));
|
||||
}
|
||||
|
||||
const T *const BUTTERAUGLI_RESTRICT Row(const size_t y) const {
|
||||
#ifdef BUTTERAUGLI_ENABLE_CHECKS
|
||||
if (y >= ysize_) {
|
||||
printf("Const row %zu out of bounds (ysize=%zu)\n", y, ysize_);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
void *row = bytes_.get() + y * bytes_per_row_;
|
||||
return reinterpret_cast<const T *>(BUTTERAUGLI_ASSUME_ALIGNED(row, 64));
|
||||
}
|
||||
|
||||
// Raw access to byte contents, for interfacing with other libraries.
|
||||
// Unsigned char instead of char to avoid surprises (sign extension).
|
||||
uint8_t * const BUTTERAUGLI_RESTRICT bytes() { return bytes_.get(); }
|
||||
const uint8_t * const BUTTERAUGLI_RESTRICT bytes() const {
|
||||
return bytes_.get();
|
||||
}
|
||||
size_t bytes_per_row() const { return bytes_per_row_; }
|
||||
|
||||
// Returns number of pixels (some of which are padding) per row. Useful for
|
||||
// computing other rows via pointer arithmetic.
|
||||
intptr_t PixelsPerRow() const {
|
||||
static_assert(CacheAligned::kCacheLineSize % sizeof(T) == 0,
|
||||
"Padding must be divisible by the pixel size.");
|
||||
return static_cast<intptr_t>(bytes_per_row_ / sizeof(T));
|
||||
}
|
||||
|
||||
private:
|
||||
// Deleter used when bytes are not owned.
|
||||
static void Ignore(void *ptr) {}
|
||||
|
||||
// (Members are non-const to enable assignment during move-assignment.)
|
||||
size_t xsize_; // original intended pixels, not including any padding.
|
||||
size_t ysize_;
|
||||
size_t bytes_per_row_; // [bytes] including padding.
|
||||
CacheAlignedUniquePtr bytes_;
|
||||
};
|
||||
|
||||
// Returns newly allocated planes of the given dimensions.
|
||||
template <typename T>
|
||||
static inline std::vector<Image<T>> CreatePlanes(const size_t xsize,
|
||||
const size_t ysize,
|
||||
const size_t num_planes) {
|
||||
std::vector<Image<T>> planes;
|
||||
planes.reserve(num_planes);
|
||||
for (size_t i = 0; i < num_planes; ++i) {
|
||||
planes.emplace_back(xsize, ysize);
|
||||
}
|
||||
return planes;
|
||||
}
|
||||
|
||||
// Returns a new image with the same dimensions and pixel values.
|
||||
template <typename T>
|
||||
static inline Image<T> CopyPixels(const Image<T> &other) {
|
||||
Image<T> copy(other.xsize(), other.ysize());
|
||||
const void *BUTTERAUGLI_RESTRICT from = other.bytes();
|
||||
void *BUTTERAUGLI_RESTRICT to = copy.bytes();
|
||||
memcpy(to, from, other.ysize() * other.bytes_per_row());
|
||||
return copy;
|
||||
}
|
||||
|
||||
// Returns new planes with the same dimensions and pixel values.
|
||||
template <typename T>
|
||||
static inline std::vector<Image<T>> CopyPlanes(
|
||||
const std::vector<Image<T>> &planes) {
|
||||
std::vector<Image<T>> copy;
|
||||
copy.reserve(planes.size());
|
||||
for (const Image<T> &plane : planes) {
|
||||
copy.push_back(CopyPixels(plane));
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
// Compacts a padded image into a preallocated packed vector.
|
||||
template <typename T>
|
||||
static inline void CopyToPacked(const Image<T> &from, std::vector<T> *to) {
|
||||
const size_t xsize = from.xsize();
|
||||
const size_t ysize = from.ysize();
|
||||
#if BUTTERAUGLI_ENABLE_CHECKS
|
||||
if (to->size() < xsize * ysize) {
|
||||
printf("%zu x %zu exceeds %zu capacity\n", xsize, ysize, to->size());
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
for (size_t y = 0; y < ysize; ++y) {
|
||||
const float* const BUTTERAUGLI_RESTRICT row_from = from.Row(y);
|
||||
float* const BUTTERAUGLI_RESTRICT row_to = to->data() + y * xsize;
|
||||
memcpy(row_to, row_from, xsize * sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
// Expands a packed vector into a preallocated padded image.
|
||||
template <typename T>
|
||||
static inline void CopyFromPacked(const std::vector<T> &from, Image<T> *to) {
|
||||
const size_t xsize = to->xsize();
|
||||
const size_t ysize = to->ysize();
|
||||
assert(from.size() == xsize * ysize);
|
||||
for (size_t y = 0; y < ysize; ++y) {
|
||||
const float* const BUTTERAUGLI_RESTRICT row_from =
|
||||
from.data() + y * xsize;
|
||||
float* const BUTTERAUGLI_RESTRICT row_to = to->Row(y);
|
||||
memcpy(row_to, row_from, xsize * sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline std::vector<Image<T>> PlanesFromPacked(
|
||||
const size_t xsize, const size_t ysize,
|
||||
const std::vector<std::vector<T>> &packed) {
|
||||
std::vector<Image<T>> planes;
|
||||
planes.reserve(packed.size());
|
||||
for (const std::vector<T> &p : packed) {
|
||||
planes.push_back(Image<T>(xsize, ysize));
|
||||
CopyFromPacked(p, &planes.back());
|
||||
}
|
||||
return planes;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline std::vector<std::vector<T>> PackedFromPlanes(
|
||||
const std::vector<Image<T>> &planes) {
|
||||
assert(!planes.empty());
|
||||
const size_t num_pixels = planes[0].xsize() * planes[0].ysize();
|
||||
std::vector<std::vector<T>> packed;
|
||||
packed.reserve(planes.size());
|
||||
for (const Image<T> &image : planes) {
|
||||
packed.push_back(std::vector<T>(num_pixels));
|
||||
CopyToPacked(image, &packed.back());
|
||||
}
|
||||
return packed;
|
||||
}
|
||||
|
||||
struct PsychoImage {
|
||||
std::vector<ImageF> uhf;
|
||||
std::vector<ImageF> hf;
|
||||
std::vector<ImageF> mf;
|
||||
std::vector<ImageF> lf;
|
||||
};
|
||||
|
||||
class ButteraugliComparator {
|
||||
public:
|
||||
ButteraugliComparator(const std::vector<ImageF>& rgb0);
|
||||
|
||||
// Computes the butteraugli map between the original image given in the
|
||||
// constructor and the distorted image give here.
|
||||
void Diffmap(const std::vector<ImageF>& rgb1, ImageF& result) const;
|
||||
|
||||
// Same as above, but OpsinDynamicsImage() was already applied.
|
||||
void DiffmapOpsinDynamicsImage(const std::vector<ImageF>& xyb1,
|
||||
ImageF& result) const;
|
||||
|
||||
// Same as above, but the frequency decomposition was already applied.
|
||||
void DiffmapPsychoImage(const PsychoImage& ps1, ImageF &result) const;
|
||||
|
||||
void Mask(std::vector<ImageF>* BUTTERAUGLI_RESTRICT mask,
|
||||
std::vector<ImageF>* BUTTERAUGLI_RESTRICT mask_dc) const;
|
||||
|
||||
private:
|
||||
void MaltaDiffMapLF(const ImageF& y0,
|
||||
const ImageF& y1,
|
||||
double w_0gt1,
|
||||
double w_0lt1,
|
||||
double normalization,
|
||||
ImageF* BUTTERAUGLI_RESTRICT block_diff_ac) const;
|
||||
|
||||
void MaltaDiffMap(const ImageF& y0,
|
||||
const ImageF& y1,
|
||||
double w_0gt1,
|
||||
double w_0lt1,
|
||||
double normalization,
|
||||
ImageF* BUTTERAUGLI_RESTRICT block_diff_ac) const;
|
||||
|
||||
ImageF CombineChannels(const std::vector<ImageF>& scale_xyb,
|
||||
const std::vector<ImageF>& scale_xyb_dc,
|
||||
const std::vector<ImageF>& block_diff_dc,
|
||||
const std::vector<ImageF>& block_diff_ac) const;
|
||||
|
||||
const size_t xsize_;
|
||||
const size_t ysize_;
|
||||
const size_t num_pixels_;
|
||||
PsychoImage pi0_;
|
||||
};
|
||||
|
||||
void ButteraugliDiffmap(const std::vector<ImageF> &rgb0,
|
||||
const std::vector<ImageF> &rgb1,
|
||||
ImageF &diffmap);
|
||||
|
||||
double ButteraugliScoreFromDiffmap(const ImageF& distmap);
|
||||
|
||||
// Generate rgb-representation of the distance between two images.
|
||||
void CreateHeatMapImage(const std::vector<float> &distmap,
|
||||
double good_threshold, double bad_threshold,
|
||||
size_t xsize, size_t ysize,
|
||||
std::vector<uint8_t> *heatmap);
|
||||
|
||||
// Compute values of local frequency and dc masking based on the activity
|
||||
// in the two images.
|
||||
void Mask(const std::vector<ImageF>& xyb0,
|
||||
const std::vector<ImageF>& xyb1,
|
||||
std::vector<ImageF>* BUTTERAUGLI_RESTRICT mask,
|
||||
std::vector<ImageF>* BUTTERAUGLI_RESTRICT mask_dc);
|
||||
|
||||
template <class V>
|
||||
BUTTERAUGLI_INLINE void RgbToXyb(const V &r, const V &g, const V &b,
|
||||
V *BUTTERAUGLI_RESTRICT valx,
|
||||
V *BUTTERAUGLI_RESTRICT valy,
|
||||
V *BUTTERAUGLI_RESTRICT valb) {
|
||||
*valx = r - g;
|
||||
*valy = r + g;
|
||||
*valb = b;
|
||||
}
|
||||
|
||||
template <class V>
|
||||
BUTTERAUGLI_INLINE void OpsinAbsorbance(const V &in0, const V &in1,
|
||||
const V &in2,
|
||||
V *BUTTERAUGLI_RESTRICT out0,
|
||||
V *BUTTERAUGLI_RESTRICT out1,
|
||||
V *BUTTERAUGLI_RESTRICT out2) {
|
||||
// https://en.wikipedia.org/wiki/Photopsin absorbance modeling.
|
||||
static const double mixi0 = 0.254462330846;
|
||||
static const double mixi1 = 0.488238255095;
|
||||
static const double mixi2 = 0.0635278003854;
|
||||
static const double mixi3 = 1.01681026909;
|
||||
static const double mixi4 = 0.195214015766;
|
||||
static const double mixi5 = 0.568019861857;
|
||||
static const double mixi6 = 0.0860755536007;
|
||||
static const double mixi7 = 1.1510118369;
|
||||
static const double mixi8 = 0.07374607900105684;
|
||||
static const double mixi9 = 0.06142425304154509;
|
||||
static const double mixi10 = 0.24416850520714256;
|
||||
static const double mixi11 = 1.20481945273;
|
||||
|
||||
const V mix0(mixi0);
|
||||
const V mix1(mixi1);
|
||||
const V mix2(mixi2);
|
||||
const V mix3(mixi3);
|
||||
const V mix4(mixi4);
|
||||
const V mix5(mixi5);
|
||||
const V mix6(mixi6);
|
||||
const V mix7(mixi7);
|
||||
const V mix8(mixi8);
|
||||
const V mix9(mixi9);
|
||||
const V mix10(mixi10);
|
||||
const V mix11(mixi11);
|
||||
|
||||
*out0 = mix0 * in0 + mix1 * in1 + mix2 * in2 + mix3;
|
||||
*out1 = mix4 * in0 + mix5 * in1 + mix6 * in2 + mix7;
|
||||
*out2 = mix8 * in0 + mix9 * in1 + mix10 * in2 + mix11;
|
||||
}
|
||||
|
||||
std::vector<ImageF> OpsinDynamicsImage(const std::vector<ImageF>& rgb);
|
||||
|
||||
ImageF Blur(const ImageF& in, float sigma, float border_ratio);
|
||||
|
||||
double SimpleGamma(double v);
|
||||
|
||||
double GammaMinArg();
|
||||
double GammaMaxArg();
|
||||
|
||||
// Polynomial evaluation via Clenshaw's scheme (similar to Horner's).
|
||||
// Template enables compile-time unrolling of the recursion, but must reside
|
||||
// outside of a class due to the specialization.
|
||||
template <int INDEX>
|
||||
static inline void ClenshawRecursion(const double x, const double *coefficients,
|
||||
double *b1, double *b2) {
|
||||
const double x_b1 = x * (*b1);
|
||||
const double t = (x_b1 + x_b1) - (*b2) + coefficients[INDEX];
|
||||
*b2 = *b1;
|
||||
*b1 = t;
|
||||
|
||||
ClenshawRecursion<INDEX - 1>(x, coefficients, b1, b2);
|
||||
}
|
||||
|
||||
// Base case
|
||||
template <>
|
||||
inline void ClenshawRecursion<0>(const double x, const double *coefficients,
|
||||
double *b1, double *b2) {
|
||||
const double x_b1 = x * (*b1);
|
||||
// The final iteration differs - no 2 * x_b1 here.
|
||||
*b1 = x_b1 - (*b2) + coefficients[0];
|
||||
}
|
||||
|
||||
// Rational polynomial := dividing two polynomial evaluations. These are easier
|
||||
// to find than minimax polynomials.
|
||||
struct RationalPolynomial {
|
||||
template <int N>
|
||||
static double EvaluatePolynomial(const double x,
|
||||
const double (&coefficients)[N]) {
|
||||
double b1 = 0.0;
|
||||
double b2 = 0.0;
|
||||
ClenshawRecursion<N - 1>(x, coefficients, &b1, &b2);
|
||||
return b1;
|
||||
}
|
||||
|
||||
// Evaluates the polynomial at x (in [min_value, max_value]).
|
||||
inline double operator()(const double x) const {
|
||||
// First normalize to [0, 1].
|
||||
const double x01 = (x - min_value) / (max_value - min_value);
|
||||
// And then to [-1, 1] domain of Chebyshev polynomials.
|
||||
const double xc = 2.0 * x01 - 1.0;
|
||||
|
||||
const double yp = EvaluatePolynomial(xc, p);
|
||||
const double yq = EvaluatePolynomial(xc, q);
|
||||
if (yq == 0.0) return 0.0;
|
||||
return static_cast<float>(yp / yq);
|
||||
}
|
||||
|
||||
// Domain of the polynomials; they are undefined elsewhere.
|
||||
double min_value;
|
||||
double max_value;
|
||||
|
||||
// Coefficients of T_n (Chebyshev polynomials of the first kind).
|
||||
// Degree 5/5 is a compromise between accuracy (0.1%) and numerical stability.
|
||||
double p[5 + 1];
|
||||
double q[5 + 1];
|
||||
};
|
||||
|
||||
static inline double GammaPolynomial(double value) {
|
||||
static const RationalPolynomial r = {
|
||||
0.971783, 590.188894,
|
||||
{
|
||||
98.7821300963361, 164.273222212631, 92.948112871376,
|
||||
33.8165311212688, 6.91626704983562, 0.556380877028234
|
||||
},
|
||||
{
|
||||
1, 1.64339473427892, 0.89392405219969, 0.298947051776379,
|
||||
0.0507146002577288, 0.00226495093949756
|
||||
}};
|
||||
return r(value);
|
||||
}
|
||||
|
||||
} // namespace butteraugli
|
||||
|
||||
#endif // BUTTERAUGLI_BUTTERAUGLI_H_
|
|
@ -1,457 +0,0 @@
|
|||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
#include "butteraugli/butteraugli.h"
|
||||
|
||||
extern "C" {
|
||||
#include "png.h"
|
||||
#include "jpeglib.h"
|
||||
}
|
||||
|
||||
namespace butteraugli {
|
||||
namespace {
|
||||
|
||||
// "rgb": cleared and filled with same-sized image planes (one per channel);
|
||||
// either RGB, or RGBA if the PNG contains an alpha channel.
|
||||
bool ReadPNG(FILE* f, std::vector<Image8>* rgb) {
|
||||
png_structp png_ptr =
|
||||
png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
if (!png_ptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr) {
|
||||
png_destroy_read_struct(&png_ptr, NULL, NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)) != 0) {
|
||||
// Ok we are here because of the setjmp.
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
rewind(f);
|
||||
png_init_io(png_ptr, f);
|
||||
|
||||
// The png_transforms flags are as follows:
|
||||
// packing == convert 1,2,4 bit images,
|
||||
// strip == 16 -> 8 bits / channel,
|
||||
// shift == use sBIT dynamics, and
|
||||
// expand == palettes -> rgb, grayscale -> 8 bit images, tRNS -> alpha.
|
||||
const unsigned int png_transforms =
|
||||
PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_16;
|
||||
|
||||
png_read_png(png_ptr, info_ptr, png_transforms, NULL);
|
||||
|
||||
png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr);
|
||||
|
||||
const int xsize = png_get_image_width(png_ptr, info_ptr);
|
||||
const int ysize = png_get_image_height(png_ptr, info_ptr);
|
||||
const int components = png_get_channels(png_ptr, info_ptr);
|
||||
|
||||
*rgb = CreatePlanes<uint8_t>(xsize, ysize, 3);
|
||||
|
||||
switch (components) {
|
||||
case 1: {
|
||||
// GRAYSCALE
|
||||
for (int y = 0; y < ysize; ++y) {
|
||||
const uint8_t* const BUTTERAUGLI_RESTRICT row = row_pointers[y];
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row0 = (*rgb)[0].Row(y);
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row1 = (*rgb)[1].Row(y);
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row2 = (*rgb)[2].Row(y);
|
||||
|
||||
for (int x = 0; x < xsize; ++x) {
|
||||
const uint8_t gray = row[x];
|
||||
row0[x] = row1[x] = row2[x] = gray;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// GRAYSCALE_ALPHA
|
||||
rgb->push_back(Image8(xsize, ysize));
|
||||
for (int y = 0; y < ysize; ++y) {
|
||||
const uint8_t* const BUTTERAUGLI_RESTRICT row = row_pointers[y];
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row0 = (*rgb)[0].Row(y);
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row1 = (*rgb)[1].Row(y);
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row2 = (*rgb)[2].Row(y);
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row3 = (*rgb)[3].Row(y);
|
||||
|
||||
for (int x = 0; x < xsize; ++x) {
|
||||
const uint8_t gray = row[2 * x + 0];
|
||||
const uint8_t alpha = row[2 * x + 1];
|
||||
row0[x] = gray;
|
||||
row1[x] = gray;
|
||||
row2[x] = gray;
|
||||
row3[x] = alpha;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// RGB
|
||||
for (int y = 0; y < ysize; ++y) {
|
||||
const uint8_t* const BUTTERAUGLI_RESTRICT row = row_pointers[y];
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row0 = (*rgb)[0].Row(y);
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row1 = (*rgb)[1].Row(y);
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row2 = (*rgb)[2].Row(y);
|
||||
|
||||
for (int x = 0; x < xsize; ++x) {
|
||||
row0[x] = row[3 * x + 0];
|
||||
row1[x] = row[3 * x + 1];
|
||||
row2[x] = row[3 * x + 2];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// RGBA
|
||||
rgb->push_back(Image8(xsize, ysize));
|
||||
for (int y = 0; y < ysize; ++y) {
|
||||
const uint8_t* const BUTTERAUGLI_RESTRICT row = row_pointers[y];
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row0 = (*rgb)[0].Row(y);
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row1 = (*rgb)[1].Row(y);
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row2 = (*rgb)[2].Row(y);
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row3 = (*rgb)[3].Row(y);
|
||||
|
||||
for (int x = 0; x < xsize; ++x) {
|
||||
row0[x] = row[4 * x + 0];
|
||||
row1[x] = row[4 * x + 1];
|
||||
row2[x] = row[4 * x + 2];
|
||||
row3[x] = row[4 * x + 3];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
return false;
|
||||
}
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
const double* NewSrgbToLinearTable() {
|
||||
double* table = new double[256];
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
const double srgb = i / 255.0;
|
||||
table[i] =
|
||||
255.0 * (srgb <= 0.04045 ? srgb / 12.92
|
||||
: std::pow((srgb + 0.055) / 1.055, 2.4));
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
void jpeg_catch_error(j_common_ptr cinfo) {
|
||||
(*cinfo->err->output_message) (cinfo);
|
||||
jmp_buf* jpeg_jmpbuf = (jmp_buf*) cinfo->client_data;
|
||||
jpeg_destroy(cinfo);
|
||||
longjmp(*jpeg_jmpbuf, 1);
|
||||
}
|
||||
|
||||
// "rgb": cleared and filled with same-sized image planes (one per channel);
|
||||
// either RGB, or RGBA if the PNG contains an alpha channel.
|
||||
bool ReadJPEG(FILE* f, std::vector<Image8>* rgb) {
|
||||
rewind(f);
|
||||
|
||||
struct jpeg_decompress_struct cinfo;
|
||||
struct jpeg_error_mgr jerr;
|
||||
cinfo.err = jpeg_std_error(&jerr);
|
||||
jmp_buf jpeg_jmpbuf;
|
||||
cinfo.client_data = &jpeg_jmpbuf;
|
||||
jerr.error_exit = jpeg_catch_error;
|
||||
if (setjmp(jpeg_jmpbuf)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
jpeg_create_decompress(&cinfo);
|
||||
|
||||
jpeg_stdio_src(&cinfo, f);
|
||||
jpeg_read_header(&cinfo, TRUE);
|
||||
jpeg_start_decompress(&cinfo);
|
||||
|
||||
int row_stride = cinfo.output_width * cinfo.output_components;
|
||||
JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)
|
||||
((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
|
||||
|
||||
const size_t xsize = cinfo.output_width;
|
||||
const size_t ysize = cinfo.output_height;
|
||||
|
||||
*rgb = CreatePlanes<uint8_t>(xsize, ysize, 3);
|
||||
|
||||
switch (cinfo.out_color_space) {
|
||||
case JCS_GRAYSCALE:
|
||||
while (cinfo.output_scanline < cinfo.output_height) {
|
||||
jpeg_read_scanlines(&cinfo, buffer, 1);
|
||||
|
||||
const uint8_t* const BUTTERAUGLI_RESTRICT row = buffer[0];
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row0 =
|
||||
(*rgb)[0].Row(cinfo.output_scanline - 1);
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row1 =
|
||||
(*rgb)[1].Row(cinfo.output_scanline - 1);
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row2 =
|
||||
(*rgb)[2].Row(cinfo.output_scanline - 1);
|
||||
|
||||
for (int x = 0; x < xsize; x++) {
|
||||
const uint8_t gray = row[x];
|
||||
row0[x] = row1[x] = row2[x] = gray;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case JCS_RGB:
|
||||
while (cinfo.output_scanline < cinfo.output_height) {
|
||||
jpeg_read_scanlines(&cinfo, buffer, 1);
|
||||
|
||||
const uint8_t* const BUTTERAUGLI_RESTRICT row = buffer[0];
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row0 =
|
||||
(*rgb)[0].Row(cinfo.output_scanline - 1);
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row1 =
|
||||
(*rgb)[1].Row(cinfo.output_scanline - 1);
|
||||
uint8_t* const BUTTERAUGLI_RESTRICT row2 =
|
||||
(*rgb)[2].Row(cinfo.output_scanline - 1);
|
||||
|
||||
for (int x = 0; x < xsize; x++) {
|
||||
row0[x] = row[3 * x + 0];
|
||||
row1[x] = row[3 * x + 1];
|
||||
row2[x] = row[3 * x + 2];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
return false;
|
||||
}
|
||||
|
||||
jpeg_finish_decompress(&cinfo);
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Translate R, G, B channels from sRGB to linear space. If an alpha channel
|
||||
// is present, overlay the image over a black or white background. Overlaying
|
||||
// is done in the sRGB space; while technically incorrect, this is aligned with
|
||||
// many other software (web browsers, WebP near lossless).
|
||||
void FromSrgbToLinear(const std::vector<Image8>& rgb,
|
||||
std::vector<ImageF>& linear, int background) {
|
||||
const size_t xsize = rgb[0].xsize();
|
||||
const size_t ysize = rgb[0].ysize();
|
||||
static const double* const kSrgbToLinearTable = NewSrgbToLinearTable();
|
||||
|
||||
if (rgb.size() == 3) { // RGB
|
||||
for (int c = 0; c < 3; c++) {
|
||||
linear.push_back(ImageF(xsize, ysize));
|
||||
for (int y = 0; y < ysize; ++y) {
|
||||
const uint8_t* const BUTTERAUGLI_RESTRICT row_rgb = rgb[c].Row(y);
|
||||
float* const BUTTERAUGLI_RESTRICT row_linear = linear[c].Row(y);
|
||||
for (size_t x = 0; x < xsize; x++) {
|
||||
const int value = row_rgb[x];
|
||||
row_linear[x] = kSrgbToLinearTable[value];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // RGBA
|
||||
for (int c = 0; c < 3; c++) {
|
||||
linear.push_back(ImageF(xsize, ysize));
|
||||
for (int y = 0; y < ysize; ++y) {
|
||||
const uint8_t* const BUTTERAUGLI_RESTRICT row_rgb = rgb[c].Row(y);
|
||||
float* const BUTTERAUGLI_RESTRICT row_linear = linear[c].Row(y);
|
||||
const uint8_t* const BUTTERAUGLI_RESTRICT row_alpha = rgb[3].Row(y);
|
||||
for (size_t x = 0; x < xsize; x++) {
|
||||
int value;
|
||||
if (row_alpha[x] == 255) {
|
||||
value = row_rgb[x];
|
||||
} else if (row_alpha[x] == 0) {
|
||||
value = background;
|
||||
} else {
|
||||
const int fg_weight = row_alpha[x];
|
||||
const int bg_weight = 255 - fg_weight;
|
||||
value =
|
||||
(row_rgb[x] * fg_weight + background * bg_weight + 127) / 255;
|
||||
}
|
||||
row_linear[x] = kSrgbToLinearTable[value];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Image8> ReadImageOrDie(const char* filename) {
|
||||
std::vector<Image8> rgb;
|
||||
FILE* f = fopen(filename, "rb");
|
||||
if (!f) {
|
||||
fprintf(stderr, "Cannot open %s\n", filename);
|
||||
exit(1);
|
||||
}
|
||||
unsigned char magic[2];
|
||||
if (fread(magic, 1, 2, f) != 2) {
|
||||
fprintf(stderr, "Cannot read from %s\n", filename);
|
||||
exit(1);
|
||||
}
|
||||
if (magic[0] == 0xFF && magic[1] == 0xD8) {
|
||||
if (!ReadJPEG(f, &rgb)) {
|
||||
fprintf(stderr, "File %s is a malformed JPEG.\n", filename);
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
if (!ReadPNG(f, &rgb)) {
|
||||
fprintf(stderr, "File %s is neither a valid JPEG nor a valid PNG.\n",
|
||||
filename);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
return rgb;
|
||||
}
|
||||
|
||||
static void ScoreToRgb(double score, double good_threshold,
|
||||
double bad_threshold, uint8_t rgb[3]) {
|
||||
double heatmap[12][3] = {
|
||||
{ 0, 0, 0 },
|
||||
{ 0, 0, 1 },
|
||||
{ 0, 1, 1 },
|
||||
{ 0, 1, 0 }, // Good level
|
||||
{ 1, 1, 0 },
|
||||
{ 1, 0, 0 }, // Bad level
|
||||
{ 1, 0, 1 },
|
||||
{ 0.5, 0.5, 1.0 },
|
||||
{ 1.0, 0.5, 0.5 }, // Pastel colors for the very bad quality range.
|
||||
{ 1.0, 1.0, 0.5 },
|
||||
{ 1, 1, 1, },
|
||||
{ 1, 1, 1, },
|
||||
};
|
||||
if (score < good_threshold) {
|
||||
score = (score / good_threshold) * 0.3;
|
||||
} else if (score < bad_threshold) {
|
||||
score = 0.3 + (score - good_threshold) /
|
||||
(bad_threshold - good_threshold) * 0.15;
|
||||
} else {
|
||||
score = 0.45 + (score - bad_threshold) /
|
||||
(bad_threshold * 12) * 0.5;
|
||||
}
|
||||
static const int kTableSize = sizeof(heatmap) / sizeof(heatmap[0]);
|
||||
score = std::min<double>(std::max<double>(
|
||||
score * (kTableSize - 1), 0.0), kTableSize - 2);
|
||||
int ix = static_cast<int>(score);
|
||||
double mix = score - ix;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
double v = mix * heatmap[ix + 1][i] + (1 - mix) * heatmap[ix][i];
|
||||
rgb[i] = static_cast<uint8_t>(255 * pow(v, 0.5) + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
void CreateHeatMapImage(const ImageF& distmap, double good_threshold,
|
||||
double bad_threshold, size_t xsize, size_t ysize,
|
||||
std::vector<uint8_t>* heatmap) {
|
||||
heatmap->resize(3 * xsize * ysize);
|
||||
for (size_t y = 0; y < ysize; ++y) {
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
int px = xsize * y + x;
|
||||
double d = distmap.Row(y)[x];
|
||||
uint8_t* rgb = &(*heatmap)[3 * px];
|
||||
ScoreToRgb(d, good_threshold, bad_threshold, rgb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// main() function, within butteraugli namespace for convenience.
|
||||
int Run(int argc, char* argv[]) {
|
||||
if (argc != 3 && argc != 4) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s {image1.(png|jpg|jpeg)} {image2.(png|jpg|jpeg)} "
|
||||
"[heatmap.ppm]\n",
|
||||
argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<Image8> rgb1 = ReadImageOrDie(argv[1]);
|
||||
std::vector<Image8> rgb2 = ReadImageOrDie(argv[2]);
|
||||
|
||||
if (rgb1.size() != rgb2.size()) {
|
||||
fprintf(stderr, "Different number of channels: %lu vs %lu\n", rgb1.size(),
|
||||
rgb2.size());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
for (size_t c = 0; c < rgb1.size(); ++c) {
|
||||
if (rgb1[c].xsize() != rgb2[c].xsize() ||
|
||||
rgb1[c].ysize() != rgb2[c].ysize()) {
|
||||
fprintf(
|
||||
stderr, "The images are not equal in size: (%lu,%lu) vs (%lu,%lu)\n",
|
||||
rgb1[c].xsize(), rgb2[c].xsize(), rgb1[c].ysize(), rgb2[c].ysize());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Figure out if it is a good idea to fetch the gamma from the image
|
||||
// instead of applying sRGB conversion.
|
||||
std::vector<ImageF> linear1, linear2;
|
||||
// Overlay the image over a black background.
|
||||
FromSrgbToLinear(rgb1, linear1, 0);
|
||||
FromSrgbToLinear(rgb2, linear2, 0);
|
||||
ImageF diff_map, diff_map_on_white;
|
||||
double diff_value;
|
||||
if (!butteraugli::ButteraugliInterface(linear1, linear2, diff_map,
|
||||
diff_value)) {
|
||||
fprintf(stderr, "Butteraugli comparison failed\n");
|
||||
return 1;
|
||||
}
|
||||
ImageF* diff_map_ptr = &diff_map;
|
||||
if (rgb1.size() == 4 || rgb2.size() == 4) {
|
||||
// If the alpha channel is present, overlay the image over a white
|
||||
// background as well.
|
||||
FromSrgbToLinear(rgb1, linear1, 255);
|
||||
FromSrgbToLinear(rgb2, linear2, 255);
|
||||
double diff_value_on_white;
|
||||
if (!butteraugli::ButteraugliInterface(linear1, linear2, diff_map_on_white,
|
||||
diff_value_on_white)) {
|
||||
fprintf(stderr, "Butteraugli comparison failed\n");
|
||||
return 1;
|
||||
}
|
||||
if (diff_value_on_white > diff_value) {
|
||||
diff_value = diff_value_on_white;
|
||||
diff_map_ptr = &diff_map_on_white;
|
||||
}
|
||||
}
|
||||
printf("%lf\n", diff_value);
|
||||
|
||||
if (argc == 4) {
|
||||
const double good_quality = ::butteraugli::ButteraugliFuzzyInverse(1.5);
|
||||
const double bad_quality = ::butteraugli::ButteraugliFuzzyInverse(0.5);
|
||||
std::vector<uint8_t> rgb;
|
||||
CreateHeatMapImage(*diff_map_ptr, good_quality, bad_quality,
|
||||
rgb1[0].xsize(), rgb2[0].ysize(), &rgb);
|
||||
FILE* const fmap = fopen(argv[3], "wb");
|
||||
if (fmap == NULL) {
|
||||
fprintf(stderr, "Cannot open %s\n", argv[3]);
|
||||
perror("fopen");
|
||||
return 1;
|
||||
}
|
||||
bool ok = true;
|
||||
if (fprintf(fmap, "P6\n%lu %lu\n255\n",
|
||||
rgb1[0].xsize(), rgb1[0].ysize()) < 0){
|
||||
perror("fprintf");
|
||||
ok = false;
|
||||
}
|
||||
if (ok && fwrite(rgb.data(), 1, rgb.size(), fmap) != rgb.size()) {
|
||||
perror("fwrite");
|
||||
ok = false;
|
||||
}
|
||||
if (fclose(fmap) != 0) {
|
||||
perror("fclose");
|
||||
ok = false;
|
||||
}
|
||||
if (!ok) return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace butteraugli
|
||||
|
||||
int main(int argc, char** argv) { return butteraugli::Run(argc, argv); }
|
|
@ -1,33 +0,0 @@
|
|||
# Description:
|
||||
# libpng is the official PNG reference library.
|
||||
|
||||
licenses(["notice"]) # BSD/MIT-like license
|
||||
|
||||
cc_library(
|
||||
name = "png",
|
||||
srcs = [
|
||||
"png.c",
|
||||
"pngerror.c",
|
||||
"pngget.c",
|
||||
"pngmem.c",
|
||||
"pngpread.c",
|
||||
"pngread.c",
|
||||
"pngrio.c",
|
||||
"pngrtran.c",
|
||||
"pngrutil.c",
|
||||
"pngset.c",
|
||||
"pngtrans.c",
|
||||
"pngwio.c",
|
||||
"pngwrite.c",
|
||||
"pngwtran.c",
|
||||
"pngwutil.c",
|
||||
],
|
||||
hdrs = [
|
||||
"png.h",
|
||||
"pngconf.h",
|
||||
],
|
||||
includes = ["."],
|
||||
linkopts = ["-lm"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["@zlib_archive//:zlib"],
|
||||
)
|
|
@ -1,36 +0,0 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"]) # BSD/MIT-like license (for zlib)
|
||||
|
||||
cc_library(
|
||||
name = "zlib",
|
||||
srcs = [
|
||||
"adler32.c",
|
||||
"compress.c",
|
||||
"crc32.c",
|
||||
"crc32.h",
|
||||
"deflate.c",
|
||||
"deflate.h",
|
||||
"gzclose.c",
|
||||
"gzguts.h",
|
||||
"gzlib.c",
|
||||
"gzread.c",
|
||||
"gzwrite.c",
|
||||
"infback.c",
|
||||
"inffast.c",
|
||||
"inffast.h",
|
||||
"inffixed.h",
|
||||
"inflate.c",
|
||||
"inflate.h",
|
||||
"inftrees.c",
|
||||
"inftrees.h",
|
||||
"trees.c",
|
||||
"trees.h",
|
||||
"uncompr.c",
|
||||
"zconf.h",
|
||||
"zutil.c",
|
||||
"zutil.h",
|
||||
],
|
||||
hdrs = ["zlib.h"],
|
||||
includes = ["."],
|
||||
)
|
Loading…
Reference in New Issue
Block a user