Adding a basic test for lossless. (#984)
This will be used when YCgCo-R is added next.
diff --git a/apps/avifenc.c b/apps/avifenc.c
index 7529e89..1a1a0ff 100644
--- a/apps/avifenc.c
+++ b/apps/avifenc.c
@@ -278,27 +278,14 @@
return AVIF_APP_FILE_FORMAT_UNKNOWN;
}
- avifAppFileFormat nextInputFormat = avifGuessFileFormat(input->files[input->fileIndex].filename);
- if (nextInputFormat == AVIF_APP_FILE_FORMAT_Y4M) {
- if (!y4mRead(input->files[input->fileIndex].filename, image, sourceTiming, &input->frameIter)) {
- return AVIF_APP_FILE_FORMAT_UNKNOWN;
- }
- if (outDepth) {
- *outDepth = image->depth;
- }
- } else if (nextInputFormat == AVIF_APP_FILE_FORMAT_JPEG) {
- if (!avifJPEGRead(input->files[input->fileIndex].filename, image, input->requestedFormat, input->requestedDepth)) {
- return AVIF_APP_FILE_FORMAT_UNKNOWN;
- }
- if (outDepth) {
- *outDepth = 8;
- }
- } else if (nextInputFormat == AVIF_APP_FILE_FORMAT_PNG) {
- if (!avifPNGRead(input->files[input->fileIndex].filename, image, input->requestedFormat, input->requestedDepth, outDepth)) {
- return AVIF_APP_FILE_FORMAT_UNKNOWN;
- }
- } else {
- fprintf(stderr, "Unrecognized file format: %s\n", input->files[input->fileIndex].filename);
+ const avifAppFileFormat nextInputFormat = avifReadImage(input->files[input->fileIndex].filename,
+ input->requestedFormat,
+ input->requestedDepth,
+ image,
+ outDepth,
+ sourceTiming,
+ &input->frameIter);
+ if (nextInputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) {
return AVIF_APP_FILE_FORMAT_UNKNOWN;
}
diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c
index 81281a4..4721b8d 100644
--- a/apps/shared/avifutil.c
+++ b/apps/shared/avifutil.c
@@ -7,6 +7,10 @@
#include <stdio.h>
#include <string.h>
+#include "avifjpeg.h"
+#include "avifpng.h"
+#include "y4m.h"
+
static int32_t calcGCD(int32_t a, int32_t b)
{
if (a < 0) {
@@ -220,6 +224,40 @@
return AVIF_APP_FILE_FORMAT_UNKNOWN;
}
+avifAppFileFormat avifReadImage(const char * filename,
+ avifPixelFormat requestedFormat,
+ int requestedDepth,
+ avifImage * image,
+ uint32_t * outDepth,
+ avifAppSourceTiming * sourceTiming,
+ struct y4mFrameIterator ** frameIter)
+{
+ const avifAppFileFormat format = avifGuessFileFormat(filename);
+ if (format == AVIF_APP_FILE_FORMAT_Y4M) {
+ if (!y4mRead(filename, image, sourceTiming, frameIter)) {
+ return AVIF_APP_FILE_FORMAT_UNKNOWN;
+ }
+ if (outDepth) {
+ *outDepth = image->depth;
+ }
+ } else if (format == AVIF_APP_FILE_FORMAT_JPEG) {
+ if (!avifJPEGRead(filename, image, requestedFormat, requestedDepth)) {
+ return AVIF_APP_FILE_FORMAT_UNKNOWN;
+ }
+ if (outDepth) {
+ *outDepth = 8;
+ }
+ } else if (format == AVIF_APP_FILE_FORMAT_PNG) {
+ if (!avifPNGRead(filename, image, requestedFormat, requestedDepth, outDepth)) {
+ return AVIF_APP_FILE_FORMAT_UNKNOWN;
+ }
+ } else {
+ fprintf(stderr, "Unrecognized file format: %s\n", filename);
+ return AVIF_APP_FILE_FORMAT_UNKNOWN;
+ }
+ return format;
+}
+
void avifDumpDiagnostics(const avifDiagnostics * diag)
{
if (!*diag->error) {
diff --git a/apps/shared/avifutil.h b/apps/shared/avifutil.h
index 22dfb03..29a6087 100644
--- a/apps/shared/avifutil.h
+++ b/apps/shared/avifutil.h
@@ -56,6 +56,18 @@
uint64_t timescale; // timescale of the media (Hz)
} avifAppSourceTiming;
+struct y4mFrameIterator;
+// Reads an image from a file with the requested format and depth.
+// In case of a y4m file, sourceTiming and frameIter can be set.
+// Returns AVIF_APP_FILE_FORMAT_UNKNOWN in case of error.
+avifAppFileFormat avifReadImage(const char * filename,
+ avifPixelFormat requestedFormat,
+ int requestedDepth,
+ avifImage * image,
+ uint32_t * outDepth,
+ avifAppSourceTiming * sourceTiming,
+ struct y4mFrameIterator ** frameIter);
+
// Used by image decoders when the user doesn't explicitly choose a format with --yuv
// This must match the cited fallback for "--yuv auto" in avifenc.c's syntax() function.
#define AVIF_APP_DEFAULT_PIXEL_FORMAT AVIF_PIXEL_FORMAT_YUV444
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index a46342a..eab84e6 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -42,9 +42,14 @@
################################################################################
# GoogleTest
-if(AVIF_ENABLE_GTEST)
+if(AVIF_ENABLE_GTEST OR AVIF_BUILD_APPS)
enable_language(CXX)
set(CMAKE_CXX_STANDARD 11)
+ add_library(aviftest_helpers OBJECT gtest/aviftest_helpers.cc)
+ target_link_libraries(aviftest_helpers avif ${AVIF_PLATFORM_LIBRARIES})
+endif()
+
+if(AVIF_ENABLE_GTEST)
if(AVIF_LOCAL_GTEST)
set(GTEST_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/ext/googletest/googletest/include)
set(GTEST_LIBRARIES ${CMAKE_SOURCE_DIR}/ext/googletest/build/lib/libgtest${CMAKE_STATIC_LIBRARY_SUFFIX})
@@ -63,9 +68,6 @@
find_package(GTest REQUIRED)
endif()
- add_library(aviftest_helpers OBJECT gtest/aviftest_helpers.cc)
- target_link_libraries(aviftest_helpers avif ${AVIF_PLATFORM_LIBRARIES})
-
add_executable(avifgridapitest gtest/avifgridapitest.cc)
target_link_libraries(avifgridapitest aviftest_helpers ${GTEST_BOTH_LIBRARIES})
target_include_directories(avifgridapitest PRIVATE ${GTEST_INCLUDE_DIRS})
@@ -96,3 +98,17 @@
else()
message(STATUS "Most tests are disabled because AVIF_ENABLE_GTEST is OFF.")
endif()
+
+################################################################################
+# Bash tests
+
+if(AVIF_BUILD_APPS)
+ # When building apps, test the avifenc/avifdec.
+ # 'are_images_equal' is used to make sure inputs/outputs are unchanged.
+ add_executable(are_images_equal gtest/are_images_equal.cc)
+ target_link_libraries(are_images_equal aviftest_helpers avif_apps)
+ add_test(
+ NAME test_cmd
+ COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test_cmd.sh ${CMAKE_BINARY_DIR}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+endif()
diff --git a/tests/gtest/are_images_equal.cc b/tests/gtest/are_images_equal.cc
new file mode 100644
index 0000000..7e01a79
--- /dev/null
+++ b/tests/gtest/are_images_equal.cc
@@ -0,0 +1,54 @@
+// Copyright 2022 Google LLC. All rights reserved.
+// SPDX-License-Identifier: BSD-2-Clause
+// Compares two files and returns whether they are the same once decoded.
+
+#include <iostream>
+
+#include "aviftest_helpers.h"
+#include "avifutil.h"
+
+using libavif::testutil::AvifImagePtr;
+
+int main(int argc, char** argv) {
+ if (argc != 4) {
+ std::cerr << "Wrong argument: " << argv[0]
+ << " file1 file2 ignore_alpha_flag" << std::endl;
+ return 2;
+ }
+ AvifImagePtr decoded[2] = {
+ AvifImagePtr(avifImageCreateEmpty(), avifImageDestroy),
+ AvifImagePtr(avifImageCreateEmpty(), avifImageDestroy)};
+ if (!decoded[0] || !decoded[1]) {
+ std::cerr << "Cannot create AVIF images." << std::endl;
+ return 2;
+ }
+ uint32_t depth[2];
+ // Request the bit depth closest to the bit depth of the input file.
+ constexpr int kRequestedDepth = 0;
+ constexpr avifPixelFormat requestedFormat = AVIF_PIXEL_FORMAT_NONE;
+ for (int i : {0, 1}) {
+ // Make sure no color conversion happens.
+ decoded[i]->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
+ if (avifReadImage(argv[i + 1], requestedFormat, kRequestedDepth,
+ decoded[i].get(), &depth[i], nullptr,
+ nullptr) == AVIF_APP_FILE_FORMAT_UNKNOWN) {
+ std::cerr << "Image " << argv[i + 1] << " cannot be read." << std::endl;
+ return 2;
+ }
+ }
+
+ if (depth[0] != depth[1]) {
+ std::cerr << "Images " << argv[1] << " and " << argv[2]
+ << " have different depths." << std::endl;
+ return 1;
+ }
+ if (!libavif::testutil::AreImagesEqual(*decoded[0], *decoded[1],
+ std::stoi(argv[3]))) {
+ std::cerr << "Images " << argv[1] << " and " << argv[2] << " are different."
+ << std::endl;
+ return 1;
+ }
+ std::cout << "Images " << argv[1] << " and " << argv[2] << " are identical."
+ << std::endl;
+ return 0;
+}
diff --git a/tests/gtest/aviftest_helpers.cc b/tests/gtest/aviftest_helpers.cc
index f35775e..6ca14dc 100644
--- a/tests/gtest/aviftest_helpers.cc
+++ b/tests/gtest/aviftest_helpers.cc
@@ -154,7 +154,8 @@
//------------------------------------------------------------------------------
// Returns true if image1 and image2 are identical.
-bool AreImagesEqual(const avifImage& image1, const avifImage& image2) {
+bool AreImagesEqual(const avifImage& image1, const avifImage& image2,
+ bool ignore_alpha) {
if (image1.width != image2.width || image1.height != image2.height ||
image1.depth != image2.depth || image1.yuvFormat != image2.yuvFormat ||
image1.yuvRange != image2.yuvRange) {
@@ -166,6 +167,7 @@
avifGetPixelFormatInfo(image1.yuvFormat, &info);
for (int c = 0; c < 4; c++) {
+ if (ignore_alpha && c == AVIF_CHAN_A) continue;
uint8_t* row1 =
(c == AVIF_CHAN_A) ? image1.alphaPlane : image1.yuvPlanes[c];
uint8_t* row2 =
diff --git a/tests/gtest/aviftest_helpers.h b/tests/gtest/aviftest_helpers.h
index adea4f9..b7c3138 100644
--- a/tests/gtest/aviftest_helpers.h
+++ b/tests/gtest/aviftest_helpers.h
@@ -54,7 +54,10 @@
uint32_t value);
// Returns true if both images have the same features and pixel values.
-bool AreImagesEqual(const avifImage& image1, const avifImage& image2);
+// If ignore_alpha is true, the alpha channel is not taken into account in the
+// comparison.
+bool AreImagesEqual(const avifImage& image1, const avifImage& image2,
+ bool ignore_alpha = false);
//------------------------------------------------------------------------------
diff --git a/tests/test_cmd.sh b/tests/test_cmd.sh
new file mode 100755
index 0000000..6bfccbb
--- /dev/null
+++ b/tests/test_cmd.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+# Copyright 2022 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.
+# ------------------------------------------------------------------------------
+#
+# tests for command lines
+
+set -ex
+
+if [[ "$#" -ge 1 ]]; then
+ # eval so that the passed in directory can contain variables.
+ BINARY_DIR="$(eval echo "$1")"
+else
+ # assume "tests" is the current directory
+ BINARY_DIR=..
+fi
+if [[ "$#" -ge 2 ]]; then
+ TESTDATA_DIR="$(eval echo "$2")"
+else
+ TESTDATA_DIR=./data
+fi
+
+AVIFENC="${BINARY_DIR}/avifenc"
+AVIFDEC="${BINARY_DIR}/avifdec"
+ARE_IMAGES_EQUAL="${BINARY_DIR}/tests/are_images_equal"
+TMP_ENCODED_FILE=/tmp/encoded.avif
+DECODED_FILE=/tmp/decoded.png
+PNG_FILE=/tmp/kodim03.png
+
+# Prepare some extra data.
+set +x
+echo "Generating a color PNG"
+"${AVIFENC}" -s 10 "${TESTDATA_DIR}"/kodim03_yuv420_8bpc.y4m -o "${TMP_ENCODED_FILE}" &> /dev/null
+"${AVIFDEC}" "${TMP_ENCODED_FILE}" "${PNG_FILE}" &> /dev/null
+set -x
+
+# Basic calls.
+"${AVIFENC}" --version
+"${AVIFDEC}" --version
+
+# Lossless test.
+echo "Testing basic lossless"
+"${AVIFENC}" -s 10 -l "${PNG_FILE}" -o "${TMP_ENCODED_FILE}"
+"${AVIFDEC}" "${TMP_ENCODED_FILE}" "${DECODED_FILE}"
+"${ARE_IMAGES_EQUAL}" "${PNG_FILE}" "${DECODED_FILE}" 0
+
+# Test code that should fail.
+set +e
+"${ARE_IMAGES_EQUAL}" "${TESTDATA_DIR}"/kodim23_yuv420_8bpc.y4m "${DECODED_FILE}" 0
+if [[ $? -ne 1 ]]; then
+ echo "Image should be different"
+ exit 1
+fi
+
+echo "TEST OK"
+exit 0