Add avifinfo_demo.c

Update README.md.

Change-Id: Iea78b740b332bae4e751cd8169bd68b8136ce965
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 154183b..29bd74c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -34,6 +34,13 @@
     NAME avifinfo_test
     COMMAND ${CMAKE_CURRENT_BINARY_DIR}/avifinfo_test
     WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests)
+
+  add_executable(avifinfo_demo tests/avifinfo_demo.c)
+  target_link_libraries(avifinfo_demo PRIVATE avifinfo)
+  add_test(
+    NAME avifinfo_demo
+    COMMAND ${CMAKE_CURRENT_BINARY_DIR}/avifinfo_demo avifinfo_test_1x1.avif
+    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests)
 endif()
 
 # C++ tools
diff --git a/README.md b/README.md
index fe27e5d..83d9a6f 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 height, bit depth, number of channels and other metadata from an AVIF payload.
 
 See `avifinfo.h` for details on the API and `avifinfo.c` for the implementation.
-See `avifinfo_test.cc` for usage examples.
+See `tests/avifinfo_demo.cc` for API usage examples.
 
 ## Contents
 
@@ -29,15 +29,9 @@
 not an option. For decoding an image or extracting more features, please rely on
 [libavif](https://github.com/AOMediaCodec/libavif).
 
-```
-AvifInfoFeatures features;
-if (AvifInfoGet(bytes, number_of_available_bytes, &features) == kAvifInfoOk) {
-  // Use 'features.width' etc.
-}
-```
-
-Note: `AvifInfoGet()` is designed to return the same `avifImage` field values as
-[`avifDecoderRead()`](https://github.com/AOMediaCodec/libavif/blob/9d8f9f9eb24fcea36113c946fa72f9f92aa7b317/include/avif/avif.h#L894).
+Note: `AvifInfoGetFeatures()` is designed to return the same `avifImage` field
+values as
+[`avifDecoderParse()`](https://github.com/AOMediaCodec/libavif/blob/e34204f5370509c72b3b2f065e5ebb2767cbbd48/include/avif/avif.h#L1049).
 However **libavifinfo** is more permissive and may return features of images
 considered invalid by **libavif**.
 
@@ -45,7 +39,7 @@
 
 `avifinfo.c` is written in C. To build from this directory:
 
-```
+```sh
 mkdir build && \
 cd build && \
 cmake .. && \
@@ -56,7 +50,7 @@
 
 Tests are written in C++. GoogleTest is required.
 
-```
+```sh
 mkdir build && \
 cd build && \
 cmake .. -DAVIFINFO_BUILD_TESTS=ON && \
diff --git a/tests/avifinfo_demo.c b/tests/avifinfo_demo.c
new file mode 100644
index 0000000..b016c94
--- /dev/null
+++ b/tests/avifinfo_demo.c
@@ -0,0 +1,147 @@
+// Copyright (c) 2023, Alliance for Open Media. All rights reserved
+//
+// This source code is subject to the terms of the BSD 2 Clause License and
+// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+// was not distributed with this source code in the LICENSE file, you can
+// obtain it at www.aomedia.org/license/software. If the Alliance for Open
+// Media Patent License 1.0 was not distributed with this source code in the
+// PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avifinfo.h"
+
+//------------------------------------------------------------------------------
+// Simple API
+
+// Returns true if the file is an AVIF file.
+static int Identify(const char* file_path) {
+  FILE* file = fopen(file_path, "rb");
+  if (!file) return 0;
+  uint8_t data[32];  // File type is likely known in the first 32 bytes.
+  const size_t data_size = fread(data, 1, sizeof(data), file);
+  fclose(file);
+  if (AvifInfoIdentify(data, data_size) == kAvifInfoOk) {
+    // The file signature identifies it as AVIF.
+    return 1;
+  }
+  return 0;
+}
+
+// Returns true if the file is an AVIF file and if a few features were
+// successfully parsed.
+static int IdentifyAndGetFeatures(const char* file_path) {
+  FILE* file = fopen(file_path, "rb");
+  if (!file) return 0;
+  uint8_t data[512];  // Features are probably available within 512 bytes.
+  const size_t data_size = fread(data, 1, sizeof(data), file);
+  fclose(file);
+  AvifInfoFeatures features;
+  if (AvifInfoGetFeatures(data, data_size, &features) == kAvifInfoOk) {
+    // Use features.width, features.height etc.
+    return 1;
+  }
+  return 0;
+}
+
+//------------------------------------------------------------------------------
+// Stream API
+
+typedef struct Stream {
+  FILE* file;
+  uint8_t data[AVIFINFO_MAX_NUM_READ_BYTES];
+} Stream;
+
+static const uint8_t* StreamRead(void* stream, size_t num_bytes) {
+  Stream* s = (Stream*)stream;
+  if (!s->file) return NULL;
+  const size_t data_size = fread(s->data, 1, num_bytes, s->file);
+  if (num_bytes > data_size) return NULL;
+  return s->data;
+}
+
+static void StreamSkip(void* stream, size_t num_bytes) {
+  Stream* s = (Stream*)stream;
+  if (!s->file) return;
+  if (fseek(s->file, num_bytes, SEEK_CUR)) {
+    fclose(s->file);
+    s->file = NULL;
+  }
+}
+
+// Returns true if the file is an AVIF file.
+static int IdentifyStream(const char* file_path) {
+  Stream stream = {0};
+  stream.file = fopen(file_path, "rb");
+  if (AvifInfoIdentifyStream(&stream, StreamRead, StreamSkip) == kAvifInfoOk) {
+    // The file signature identifies it as AVIF.
+    fclose(stream.file);
+    return 1;
+  }
+  fclose(stream.file);
+  return 0;
+}
+
+// Returns true if the file is an AVIF file and if a few features were
+// successfully parsed.
+static int IdentifyAndGetFeaturesStream(const char* file_path) {
+  Stream stream = {0};
+  stream.file = fopen(file_path, "rb");
+  if (AvifInfoIdentifyStream(&stream, StreamRead, StreamSkip) == kAvifInfoOk) {
+    // The file signature identifies it as AVIF.
+
+    AvifInfoFeatures features;
+    if (AvifInfoGetFeaturesStream(&stream, StreamRead, StreamSkip, &features) ==
+        kAvifInfoOk) {
+      // Use features.width, features.height etc.
+      fclose(stream.file);
+      return 1;
+    }
+  }
+  fclose(stream.file);
+  return 0;
+}
+
+// Alternative to IdentifyAndGetFeaturesStream() where the stream object cannot
+// be shared between AvifInfoIdentifyStream() and AvifInfoGetFeaturesStream().
+static int IdentifyAndGetFeaturesStreams(const char* file_path) {
+  {
+    Stream stream = {0};
+    stream.file = fopen(file_path, "rb");
+    if (AvifInfoIdentifyStream(&stream, StreamRead, StreamSkip) ==
+        kAvifInfoOk) {
+      // The file signature identifies it as AVIF.
+      fclose(stream.file);
+      return 1;
+    }
+    fclose(stream.file);
+  }
+  {
+    Stream stream = {0};
+    stream.file = fopen(file_path, "rb");
+    AvifInfoFeatures features;
+    // This is allowed because AvifInfoIdentifyStream() was successful on the
+    // same input file bytes.
+    if (AvifInfoGetFeaturesStream(&stream, StreamRead, StreamSkip, &features) ==
+        kAvifInfoOk) {
+      // Use features.width, features.height etc.
+      fclose(stream.file);
+      return 1;
+    }
+    fclose(stream.file);
+  }
+  return 0;
+}
+
+//------------------------------------------------------------------------------
+
+int main(int argc, char** argv) {
+  if (argc >= 2 && Identify(argv[1]) && IdentifyAndGetFeatures(argv[1]) &&
+      IdentifyStream(argv[1]) && IdentifyAndGetFeaturesStream(argv[1]) &&
+      IdentifyAndGetFeaturesStreams(argv[1])) {
+    return 0;
+  }
+  return 1;
+}