diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..5520e59df9 --- /dev/null +++ b/.clang-format @@ -0,0 +1,32 @@ +--- +Language: Cpp +Standard: c++17 +BasedOnStyle: LLVM + +IndentWidth: 4 +TabWidth: 4 +UseTab: ForContinuationAndIndentation # ForIndentation + +AccessModifierOffset: -4 +BreakBeforeBraces: Mozilla +ColumnLimit: 120 + +#AlignConsecutiveAssignments: true +AlignEscapedNewlines: DontAlign +AlignTrailingComments: true +AllowShortCaseLabelsOnASingleLine: true + +AllowShortFunctionsOnASingleLine: Inline +#AllowShortLambdasOnASingleLine: Inline + +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeBraces: Mozilla +BreakBeforeTernaryOperators: true +ConstructorInitializerAllOnOneLineOrOnePerLine: false +FixNamespaceComments: true +IncludeBlocks: Regroup +KeepEmptyLinesAtTheStartOfBlocks: false +PointerAlignment: Left +ReflowComments: true +SortIncludes: true diff --git a/.github/workflows/build-winrt.yml b/.github/workflows/build-winrt.yml index 31183f902b..7bdce312aa 100644 --- a/.github/workflows/build-winrt.yml +++ b/.github/workflows/build-winrt.yml @@ -34,7 +34,7 @@ jobs: working-directory: ${{runner.workspace}}/build run: cmake ${{github.workspace}}/wrappers/winrt -A ${{matrix.target}} -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_BUILD_TYPE=Release -DBUILD_WINRT_LIB=ON -DBUILD_EXAMPLES=OFF -DBUILD_BLACKBOX_TESTS=OFF -DEXTENSION_SDK_OUTPUT=dist/UAP/v0.8.0.0/ExtensionSDKs/ZXingWinRT/1.0.0.0 - - name: Buid + - name: Build shell: cmd working-directory: ${{runner.workspace}}/build run: cmake --build . -j8 --config Release @@ -76,7 +76,7 @@ jobs: - name: Publish NuGet package shell: cmd - run: nuget push huycn.zxingcpp.winrt.nupkg ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json + run: nuget push huycn.zxingcpp.winrt.nupkg -ApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json - uses: actions/upload-artifact@v2 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f3f5a0046..79becdffc6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,7 +92,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [3.7, 3.8, 3.9] + python-version: ['3.8', '3.9', '3.10'] os: [ubuntu-latest, macos-latest, windows-latest] steps: diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml index 71dafce622..ca5e4f5e35 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/python-build.yml @@ -26,24 +26,23 @@ jobs: steps: - uses: actions/checkout@v2 - with: - submodules: recursive + - name: Set up Python uses: actions/setup-python@v2 - with: - python-version: 3.8 + + - name: Install cibuildwheel + run: python -m pip install cibuildwheel==2.4.0 - name: Build wheels - uses: joerick/cibuildwheel@v1.10.0 - with: - package-dir: ./wrappers/python - output-dir: ./wrappers/python/wheelhouse + run: python -m cibuildwheel --output-dir wheelhouse wrappers/python env: - CIBW_BUILD: cp37-* cp38-* cp39-* + CIBW_BUILD: cp38-* cp39-* cp310-* + # "Installing Python cp310" fails on macOS on 2022-04-26 -> disable + CIBW_SKIP: "*musllinux* *310-macos*" - uses: actions/upload-artifact@v2 with: - path: ./wrappers/python/wheelhouse/*.whl + path: ./wheelhouse/*.whl build-sdist: name: Build source distribution @@ -55,8 +54,6 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 - with: - python-version: 3.8 - name: Build sdist working-directory: wrappers/python diff --git a/.gitignore b/.gitignore index 1661636184..a4223377f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ CMakeLists.txt.user +*.o +*.so +*.lib +*.d +*.a diff --git a/CMakeLists.txt b/CMakeLists.txt index 13617929da..7d01ee88b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,14 @@ -cmake_minimum_required (VERSION 3.10) +cmake_minimum_required (VERSION 3.14) -project (ZXing VERSION "1.2.0" LANGUAGES CXX) +project (ZXing VERSION "1.3.0" LANGUAGES CXX) option (BUILD_WRITERS "Build with writer support (encoders)" ON) option (BUILD_READERS "Build with reader support (decoders)" ON) -option (BUILD_EXAMPLES "Build the example barcode reader/writer applicatons" ON) +option (BUILD_EXAMPLES "Build the example barcode reader/writer applications" ON) option (BUILD_BLACKBOX_TESTS "Build the black box reader/writer tests" ON) option (BUILD_UNIT_TESTS "Build the unit tests (don't enable for production builds)" OFF) option (BUILD_PYTHON_MODULE "Build the python module" OFF) +set(BUILD_DEPENDENCIES "AUTO" CACHE STRING "Fetch from github or use locally installed (AUTO/GITHUB/LOCAL)") if (WIN32) option (BUILD_SHARED_LIBS "Build and link as shared library" OFF) @@ -18,7 +19,7 @@ endif() if (MSVC) option (LINK_CPP_STATICALLY "MSVC only, link standard library statically (/MT and /MTd)" OFF) - add_definitions (-DUNICODE -D_UNICODE) + add_definitions (-DUNICODE -D_UNICODE -D_CRT_SECURE_NO_WARNINGS) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Oi /GS-") set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GS-") @@ -44,19 +45,28 @@ endif() set (CMAKE_CXX_STANDARD 17) set (CMAKE_CXX_EXTENSIONS OFF) -if (BUILD_UNIT_TESTS) - add_definitions (-DZXING_BUILD_FOR_TEST) - if (NOT BUILD_WRITERS OR NOT BUILD_READERS) - message("Note: To build with unit tests, the library will be build with READERS and WRITERS.") - set (BUILD_WRITERS ON) - set (BUILD_READERS ON) - endif() +if (NOT (BUILD_READERS OR BUILD_WRITERS)) + message(FATAL_ERROR "At least one of BUILD_READERS/BUILD_WRITERS must be enabled.") +endif() + +if (BUILD_UNIT_TESTS AND (NOT BUILD_WRITERS OR NOT BUILD_READERS)) + message("Note: To build with unit tests, the library will be build with READERS and WRITERS.") + set (BUILD_WRITERS ON) + set (BUILD_READERS ON) +endif() + +set(BUILD_DEPENDENCIES_LIST AUTO GITHUB LOCAL) +set_property(CACHE BUILD_DEPENDENCIES PROPERTY STRINGS ${BUILD_DEPENDENCIES_LIST}) +if(NOT BUILD_DEPENDENCIES IN_LIST BUILD_DEPENDENCIES_LIST) + message(FATAL_ERROR "BUILD_DEPENDENCIES must be one of ${BUILD_DEPENDENCIES_LIST}") endif() add_subdirectory (core) enable_testing() +include(zxing.cmake) + if (BUILD_EXAMPLES) add_subdirectory (example) endif() diff --git a/README.md b/README.md index 9b6132fb70..58a274cf5f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ It was originally ported from the Java [ZXing Library](https://github.com/zxing/ ## Features -* In pure C++17, no third-party dependencies +* In pure C++17, no third-party dependencies (for the library) * Stateless, thread-safe readers/scanners and writers/generators * Wrapper/Bindings for: * WinRT @@ -27,7 +27,7 @@ It was originally ported from the Java [ZXing Library](https://github.com/zxing/ | DataBar | ITF | MaxiCode (beta) | | DataBar Expanded | -Note: DataBar used to be called RSS. +Note: DataBar used to be called RSS. DataBar is not supported for writing. ## Getting Started @@ -57,20 +57,21 @@ PM> Install-Package huycn.zxingcpp.winrt ## Build Instructions ### Standard setup on Windows/macOS/Linux -1. Make sure [CMake](https://cmake.org) version 3.10 or newer is installed. -2. Make sure a C++17 compliant compiler is installed (minimum VS 2019 16.8 / gcc 7 / clang 5) +1. Make sure [CMake](https://cmake.org) version 3.14 or newer is installed. +2. Make sure a C++17 compliant compiler is installed (minimum VS 2019 16.8 / gcc 7 / clang 5). 3. See the cmake `BUILD_...` options to enable the testing code, python wrapper, etc. ### Windows Universal Platform -1. Download and install [CMake](https://cmake.org) 3.4 or more recent if it's not already installed. -2. Edit the file [`wrappers/winrt/BuildWinCom.bat`](wrappers/winrt/BuildWinCom.bat) to adjust the path to your CMake installation. -3. Double-click on the batch script to run it. -4. If the build succeeds, it will put the results in the folder UAP which is ready-to-use SDK extension. +1. Make sure [CMake](https://cmake.org) version 3.4 or newer is installed. +2. Make sure a C++17 compliant compiler is installed (minimum VS 2019 16.8). +3. Edit the file [`wrappers/winrt/BuildWinCom.bat`](wrappers/winrt/BuildWinCom.bat) to adjust the path to your CMake installation. +4. Double-click on the batch script to run it. +5. If the build succeeds, it will put the results in the folder UAP which is ready-to-use SDK extension. ### Android 1. Install AndroidStudio including NDK and CMake (see 'SDK Tools'). 2. Open the project in folder [wrappers/android](wrappers/android). -3. The project contains 2 modules: `zxingcpp` is the wrapper library, `app` is the demo app using `zxingcpp` +3. The project contains 2 modules: `zxingcpp` is the wrapper library, `app` is the demo app using `zxingcpp`. ### WebAssembly 1. [Install Emscripten](https://kripken.github.io/emscripten-site/docs/getting_started/) if not done already. diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 7a2db607cf..0d4bff9f0c 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -15,7 +15,11 @@ if (WINRT) ) endif() -set (ZXING_CORE_LOCAL_DEFINES) +set (ZXING_CORE_LOCAL_DEFINES + $<$:-DZXING_BUILD_READERS> + $<$:-DZXING_BUILD_WRITERS> + $<$:-DZXING_BUILD_FOR_TEST> +) if (MSVC) set (ZXING_CORE_LOCAL_DEFINES ${ZXING_CORE_LOCAL_DEFINES} -D_SCL_SECURE_NO_WARNINGS @@ -25,7 +29,7 @@ if (MSVC) ) else() set (ZXING_CORE_LOCAL_DEFINES ${ZXING_CORE_LOCAL_DEFINES} - -Wall -Wextra -Wno-missing-braces -Werror=undef) + -Wall -Wextra -Wno-missing-braces -Werror=undef -Werror=return-type) endif() @@ -86,16 +90,13 @@ if (BUILD_READERS) src/DecodeStatus.cpp src/DecoderResult.h src/DetectorResult.h - src/GenericLuminanceSource.h - src/GenericLuminanceSource.cpp src/GlobalHistogramBinarizer.h src/GlobalHistogramBinarizer.cpp src/GridSampler.h src/GridSampler.cpp src/HybridBinarizer.h src/HybridBinarizer.cpp - src/LuminanceSource.h - src/LuminanceSource.cpp + src/ImageView.h src/MultiFormatReader.h src/MultiFormatReader.cpp src/PerspectiveTransform.h @@ -107,8 +108,6 @@ if (BUILD_READERS) src/ReedSolomonDecoder.cpp src/Result.h src/Result.cpp - src/ResultMetadata.h - src/ResultMetadata.cpp src/ResultPoint.h src/ResultPoint.cpp src/TextDecoder.h @@ -162,6 +161,8 @@ endif() set (DATAMATRIX_FILES src/datamatrix/DMBitLayout.h src/datamatrix/DMBitLayout.cpp + src/datamatrix/DMVersion.h + src/datamatrix/DMVersion.cpp ) if (BUILD_READERS) set (DATAMATRIX_FILES ${DATAMATRIX_FILES} @@ -171,8 +172,6 @@ if (BUILD_READERS) src/datamatrix/DMDecoder.cpp src/datamatrix/DMDetector.h src/datamatrix/DMDetector.cpp - src/datamatrix/DMVersion.h - src/datamatrix/DMVersion.cpp src/datamatrix/DMReader.h src/datamatrix/DMReader.cpp ) @@ -340,7 +339,6 @@ if (BUILD_READERS) src/qrcode/QRDataMask.h src/qrcode/QRDecoder.h src/qrcode/QRDecoder.cpp - src/qrcode/QRDecoderMetadata.h src/qrcode/QRDetector.h src/qrcode/QRDetector.cpp src/qrcode/QRECB.h @@ -453,7 +451,7 @@ add_library(ZXing::ZXing ALIAS ZXing) add_library(ZXing::Core ALIAS ZXing) set_target_properties(ZXing PROPERTIES EXPORT_NAME ZXing) -# force position indepent code to be able to link it as static lib into a DLL (e.g. the python module) +# force position independent code to be able to link it as static lib into a DLL (e.g. the python module) set_target_properties(ZXing PROPERTIES POSITION_INDEPENDENT_CODE ON) if (PROJECT_VERSION) set_target_properties(ZXing PROPERTIES VERSION ${PROJECT_VERSION}) @@ -476,5 +474,6 @@ if(MSVC) COMPILE_PDB_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ZXing.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} - CONFIGURATIONS Debug RelWithDebInfo) + CONFIGURATIONS Debug RelWithDebInfo + OPTIONAL) endif() diff --git a/core/src/BarcodeFormat.cpp b/core/src/BarcodeFormat.cpp index fd9ab7cad5..fddb6eface 100644 --- a/core/src/BarcodeFormat.cpp +++ b/core/src/BarcodeFormat.cpp @@ -17,7 +17,6 @@ #include "BarcodeFormat.h" -#include "BitHacks.h" #include "ZXContainerAlgorithms.h" #include diff --git a/core/src/BarcodeFormat.h b/core/src/BarcodeFormat.h index cc540c44dc..2858ed57e6 100644 --- a/core/src/BarcodeFormat.h +++ b/core/src/BarcodeFormat.h @@ -19,7 +19,6 @@ #include "Flags.h" #include -#include namespace ZXing { @@ -71,7 +70,6 @@ enum class BarcodeFormat UPC_A [[deprecated]] = UPCA, UPC_E [[deprecated]] = UPCE, - FORMAT_COUNT [[deprecated]] = None, ///> DEPRECATED: will be removed _max = UPCE, ///> implementation detail, don't use }; diff --git a/core/src/BinaryBitmap.cpp b/core/src/BinaryBitmap.cpp index 93ad54ccde..e68cccd2bc 100644 --- a/core/src/BinaryBitmap.cpp +++ b/core/src/BinaryBitmap.cpp @@ -16,7 +16,24 @@ #include "BinaryBitmap.h" +#include + namespace ZXing { +struct BinaryBitmap::Cache +{ + std::once_flag once; + std::shared_ptr matrix; +}; + +BinaryBitmap::BinaryBitmap(const ImageView& buffer) : _cache(new Cache), _buffer(buffer) {} + +BinaryBitmap::~BinaryBitmap() = default; + +const BitMatrix* BinaryBitmap::getBitMatrix() const +{ + std::call_once(_cache->once, [&](){_cache->matrix = getBlackMatrix();}); + return _cache->matrix.get(); +} } // ZXing diff --git a/core/src/BinaryBitmap.h b/core/src/BinaryBitmap.h index 357a4d1a12..896294db73 100644 --- a/core/src/BinaryBitmap.h +++ b/core/src/BinaryBitmap.h @@ -2,6 +2,7 @@ /* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors +* Copyright 2021 Axel Waggershauser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +17,14 @@ * limitations under the License. */ -#include -#include +#include "ReadBarcode.h" + #include +#include #include namespace ZXing { -class BitArray; class BitMatrix; using PatternRow = std::vector; @@ -31,81 +32,35 @@ using PatternRow = std::vector; /** * This class is the core bitmap class used by ZXing to represent 1 bit data. Reader objects * accept a BinaryBitmap and attempt to decode it. -* -* @author dswitkin@google.com (Daniel Switkin) */ class BinaryBitmap { -public: - virtual ~BinaryBitmap() = default; - - /** - * Image is a pure monochrome image of a barcode. - */ - [[deprecated]] - virtual bool isPureBarcode() const { return false; } + struct Cache; + std::unique_ptr _cache; - /** - * @return The width of the bitmap. - */ - virtual int width() const = 0; +protected: + const ImageView _buffer; /** - * @return The height of the bitmap. - */ - virtual int height() const = 0; - - /** - * Converts one row of luminance data to a vector of ints denoting the widths of the bars and spaces. - */ - virtual bool getPatternRow(int y, PatternRow& res) const = 0; - - /** - * Converts a 2D array of luminance data to 1 bit. This method is intended for decoding 2D - * barcodes and may or may not apply sharpening. Therefore, a row from this matrix may not be - * identical to one fetched using getBlackRow(), so don't mix and match between them. + * Converts a 2D array of luminance data to 1 bit (true means black). * - * @return The 2D array of bits for the image (true means black). - * @return null if image can't be binarized to make a matrix + * @return The 2D array of bits for the image, nullptr on error. */ virtual std::shared_ptr getBlackMatrix() const = 0; - /** - * @return Whether this bitmap can be cropped. - */ - virtual bool canCrop() const { return false; } +public: + BinaryBitmap(const ImageView& buffer); + virtual ~BinaryBitmap(); - /** - * Returns a new object with cropped image data. Implementations may keep a reference to the - * original data rather than a copy. Only callable if isCropSupported() is true. - * - * @param left The left coordinate, which must be in [0,getWidth()) - * @param top The top coordinate, which must be in [0,getHeight()) - * @param width The width of the rectangle to crop. - * @param height The height of the rectangle to crop. - * @return A cropped version of this object. - */ - virtual std::shared_ptr cropped(int /*left*/, int /*top*/, int /*width*/, int /*height*/) const - { - throw std::runtime_error("This binarizer does not support cropping."); - } + int width() const { return _buffer.width(); } + int height() const { return _buffer.height(); } /** - * @return Whether this bitmap supports counter-clockwise rotation. + * Converts one row of luminance data to a vector of ints denoting the widths of the bars and spaces. */ - virtual bool canRotate() const { return false; } + virtual bool getPatternRow(int row, int rotation, PatternRow& res) const = 0; - /** - * Returns a new object with rotated image data by 90 degrees clockwise. - * Only callable if {@link #isRotateSupported()} is true. - * - * @param degreeCW degree in clockwise direction, possible values are 90, 180 and 270 - * @return A rotated version of this object. - */ - virtual std::shared_ptr rotated(int /*degreeCW*/) const - { - throw std::runtime_error("This binarizer does not support rotation."); - } + const BitMatrix* getBitMatrix() const; }; } // ZXing diff --git a/core/src/BitArray.h b/core/src/BitArray.h index 24a399c453..2fdb4a567e 100644 --- a/core/src/BitArray.h +++ b/core/src/BitArray.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -237,15 +238,14 @@ class BitArray * * @param i bit to set */ - void set(int i) { + void set(int i, bool val) { #ifdef ZX_FAST_BIT_STORAGE -#if 0 - _bits[i] = 1; + _bits.at(i) = val; #else - _bits.at(i) = 1; -#endif -#else - _bits.at(i >> 5) |= 1 << (i & 0x1F); + if (val) + _bits.at(i >> 5) |= 1 << (i & 0x1F); + else + _bits.at(i >> 5) &= ~(1 << (i & 0x1F)); #endif } @@ -274,9 +274,9 @@ class BitArray #endif // Little helper method to make common isRange use case more readable. - // Pass positive zone size to look for quite zone after i and negative for zone in front of i. + // Pass positive zone size to look for quiet zone after i and negative for zone in front of i. // Set allowClippedZone to false if clipping the zone at the image border is not acceptable. - bool hasQuiteZone(Iterator i, int signedZoneSize, bool allowClippedZone = true) const { + bool hasQuietZone(Iterator i, int signedZoneSize, bool allowClippedZone = true) const { int index = static_cast(i - begin()); if (signedZoneSize > 0) { if (!allowClippedZone && index + signedZoneSize >= size()) @@ -289,8 +289,8 @@ class BitArray } } - bool hasQuiteZone(ReverseIterator i, int signedZoneSize, bool allowClippedZone = true) const { - return hasQuiteZone(i.base(), -signedZoneSize, allowClippedZone); + bool hasQuietZone(ReverseIterator i, int signedZoneSize, bool allowClippedZone = true) const { + return hasQuietZone(i.base(), -signedZoneSize, allowClippedZone); } /** @@ -345,6 +345,8 @@ class BitArray */ ByteArray toBytes(int bitOffset = 0, int numBytes = -1) const; + Range range() const { return {begin(), end()}; } + friend bool operator==(const BitArray& a, const BitArray& b) { return @@ -372,6 +374,14 @@ int ToInt(const ARRAY& a) return pattern; } +inline int ReadBits(BitArray::Range& bits, int n) +{ + int res = 0; + for (; n > 0 && bits.size(); --n, bits.begin++) + AppendBit(res, *bits.begin); + return res; +} + template >> T ToInt(const BitArray& bits, int pos = 0, int count = 8 * sizeof(T)) { @@ -388,12 +398,15 @@ T ToInt(const BitArray& bits, int pos = 0, int count = 8 * sizeof(T)) } template >> -std::vector ToInts(const BitArray& bits, int wordSize, int totalWords) +std::vector ToInts(const BitArray& bits, int wordSize, int totalWords, int offset = 0) { assert(totalWords >= bits.size() / wordSize); - std::vector res(totalWords, 0); - for (int i = 0; i < bits.size(); i += wordSize) - res[i/wordSize] = ToInt(bits, i, wordSize); + assert(wordSize <= 8 * (int)sizeof(T)); + + std::vector res(totalWords, 0); + for (int i = offset; i < bits.size(); i += wordSize) + res[(i - offset) / wordSize] = ToInt(bits, i, wordSize); + return res; } diff --git a/core/src/BitMatrix.cpp b/core/src/BitMatrix.cpp index 5868b5fa23..2f484f7d1c 100644 --- a/core/src/BitMatrix.cpp +++ b/core/src/BitMatrix.cpp @@ -18,7 +18,6 @@ #include "BitMatrix.h" #include "BitArray.h" -#include "ByteMatrix.h" #include "Pattern.h" #ifndef ZX_FAST_BIT_STORAGE @@ -46,15 +45,6 @@ BitMatrix::getRow(int y, BitArray& row) const #endif } -ByteMatrix BitMatrix::toByteMatrix(int black, int white) const -{ - ByteMatrix res(width(), height()); - for (int y = 0; y < height(); ++y) - for (int x = 0; x < width(); ++x) - res.set(x, y, get(x, y) ? black : white); - return res; -} - void BitMatrix::setRegion(int left, int top, int width, int height) { diff --git a/core/src/BitMatrix.h b/core/src/BitMatrix.h index 780ed82af8..1569e885a5 100644 --- a/core/src/BitMatrix.h +++ b/core/src/BitMatrix.h @@ -100,9 +100,6 @@ class BitMatrix return *this; } - [[deprecated]] - ByteMatrix toByteMatrix(int black = 0, int white = 255) const; - #ifdef ZX_FAST_BIT_STORAGE // experimental iterator based access template @@ -284,11 +281,11 @@ class BitMatrix }; /** - * @brief Inflate scales a BitMatrix up and adds a quite Zone plus padding + * @brief Inflate scales a BitMatrix up and adds a quiet Zone plus padding * @param matrix input to be expanded * @param width new width in bits (pixel) * @param height new height in bits (pixel) - * @param quietZone size of quite zone to add in modules + * @param quietZone size of quiet zone to add in modules * @return expanded BitMatrix, maybe move(input) if size did not change */ BitMatrix Inflate(BitMatrix&& input, int width, int height, int quietZone); diff --git a/core/src/BitMatrixCursor.h b/core/src/BitMatrixCursor.h index 7d993e4a07..a7a98e1b61 100644 --- a/core/src/BitMatrixCursor.h +++ b/core/src/BitMatrixCursor.h @@ -119,24 +119,27 @@ class BitMatrixCursor * @brief stepToEdge advances cursor to one step behind the next (or n-th) edge. * @param nth number of edges to pass * @param range max number of steps to take + * @param backup whether or not to backup one step so we land in front of the edge * @return number of steps taken or 0 if moved outside of range/image */ - int stepToEdge(int nth = 1, int range = 0) + int stepToEdge(int nth = 1, int range = 0, bool backup = false) { // TODO: provide an alternative and faster out-of-bounds check than isIn() inside testAt() - int sum = 0; + int steps = 0; auto lv = testAt(p); - while (nth && (!range || sum < range) && lv.isValid()) { - step(); - ++sum; - auto v = testAt(p); + while (nth && (!range || steps < range) && lv.isValid()) { + ++steps; + auto v = testAt(p + steps * d); if (lv != v) { lv = v; --nth; } } - return sum * (nth == 0); + if (backup) + --steps; + p += steps * d; + return steps * (nth == 0); } bool stepAlongEdge(Direction dir, bool skipCorner = false) diff --git a/core/src/BitMatrixIO.cpp b/core/src/BitMatrixIO.cpp index c796833932..be1732326c 100644 --- a/core/src/BitMatrixIO.cpp +++ b/core/src/BitMatrixIO.cpp @@ -64,9 +64,9 @@ BitMatrix ParseBitMatrix(const std::string& str, char one, bool expectSpace) return mat; } -void SaveAsPBM(const BitMatrix& matrix, const std::string filename, int quiteZone) +void SaveAsPBM(const BitMatrix& matrix, const std::string filename, int quietZone) { - auto out = Inflate(matrix.copy(), 0, 0, quiteZone); + auto out = Inflate(matrix.copy(), 0, 0, quietZone); std::ofstream file(filename); file << "P1\n" << out.width() << ' ' << out.height() << '\n'; file << ToString(out, '1', '0', true); diff --git a/core/src/BitMatrixIO.h b/core/src/BitMatrixIO.h index fde09e0252..6946b33905 100644 --- a/core/src/BitMatrixIO.h +++ b/core/src/BitMatrixIO.h @@ -25,6 +25,6 @@ namespace ZXing { std::string ToString(const BitMatrix& matrix, char one = 'X', char zero = ' ', bool addSpace = true, bool printAsCString = false); BitMatrix ParseBitMatrix(const std::string& str, char one = 'X', bool expectSpace = true); -void SaveAsPBM(const BitMatrix& matrix, const std::string filename, int quiteZone = 0); +void SaveAsPBM(const BitMatrix& matrix, const std::string filename, int quietZone = 0); } // ZXing diff --git a/core/src/ByteMatrix.h b/core/src/ByteMatrix.h index 8f0cf9cfdc..06ffd1ddc3 100644 --- a/core/src/ByteMatrix.h +++ b/core/src/ByteMatrix.h @@ -23,7 +23,7 @@ namespace ZXing { // TODO: If kept at all, this should be replaced by `using ByteMatrix = Matrix;` to be consistent with ByteArray -// This non-template class is kept for now to stay source-compatible with oder versions of the library. +// This non-template class is kept for now to stay source-compatible with older versions of the library. class ByteMatrix : public Matrix { diff --git a/core/src/CharacterSetECI.cpp b/core/src/CharacterSetECI.cpp index be2b065372..c9142f3891 100644 --- a/core/src/CharacterSetECI.cpp +++ b/core/src/CharacterSetECI.cpp @@ -21,7 +21,6 @@ #include #include #include -#include namespace ZXing::CharacterSetECI { @@ -179,6 +178,7 @@ CharacterSet InitEncoding(const std::string& name, CharacterSet encodingDefault) return encodingDefault; } +#ifdef ZXING_BUILD_READERS CharacterSet OnChangeAppendReset(const int eci, std::wstring& encoded, std::string& data, CharacterSet encoding) { // Character set ECIs only @@ -194,5 +194,6 @@ CharacterSet OnChangeAppendReset(const int eci, std::wstring& encoded, std::stri return encoding; } +#endif } // namespace ZXing::CharacterSetECI diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index d7fe9c2c1d..66ece3d655 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -101,6 +101,8 @@ std::optional FinetuneConcentricPatternCenter(const BitMatrix& image, Po res = CenterOfDoubleCross(image, PointI(center), range, finderPatternSize / 2 + 1); if (!res || !image.get(*res)) res = center; + if (!res || !image.get(*res)) + return {}; return res; } diff --git a/core/src/ConcentricFinder.h b/core/src/ConcentricFinder.h index adf986b139..1cfbb6c825 100644 --- a/core/src/ConcentricFinder.h +++ b/core/src/ConcentricFinder.h @@ -86,15 +86,24 @@ std::optional LocateConcentricPattern(const BitMatrix& image, { auto cur = BitMatrixCursorF(image, center, {}); int minSpread = image.width(), maxSpread = 0; - for (auto d : {PointF{0, 1}, {1, 0}, {1, 1}, {1, -1}}) { - int spread = - CheckDirection(cur, d, finderPattern, range, length(d) < 1.1 && !RELAXED_THRESHOLD); + for (auto d : {PointF{0, 1}, {1, 0}}) { + int spread = CheckDirection(cur, d, finderPattern, range, !RELAXED_THRESHOLD); if (!spread) return {}; minSpread = std::min(spread, minSpread); maxSpread = std::max(spread, maxSpread); } +#if 1 + for (auto d : {PointF{1, 1}, {1, -1}}) { + int spread = CheckDirection(cur, d, finderPattern, range, false); + if (!spread) + return {}; + minSpread = std::min(spread, minSpread); + maxSpread = std::max(spread, maxSpread); + } +#endif + if (maxSpread > 5 * minSpread) return {}; diff --git a/core/src/DecodeHints.h b/core/src/DecodeHints.h index 017c166769..d4ef5e6e32 100644 --- a/core/src/DecodeHints.h +++ b/core/src/DecodeHints.h @@ -2,6 +2,7 @@ /* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors +* Copyright 2020 Axel Waggershauser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +19,9 @@ #include "BarcodeFormat.h" -#include #include +#include +#include namespace ZXing { @@ -49,24 +51,29 @@ class DecodeHints { bool _tryHarder : 1; bool _tryRotate : 1; + bool _tryDownscale : 1; bool _isPure : 1; bool _tryCode39ExtendedMode : 1; - bool _assumeCode39CheckDigit : 1; - bool _assumeGS1 : 1; + bool _validateCode39CheckSum : 1; + bool _validateITFCheckSum : 1; bool _returnCodabarStartEnd : 1; Binarizer _binarizer : 2; EanAddOnSymbol _eanAddOnSymbol : 2; - BarcodeFormats _formats = BarcodeFormat::None; std::string _characterSet; std::vector _allowedLengths; + BarcodeFormats _formats = BarcodeFormat::None; + uint16_t _downscaleThreshold = 500; + uint8_t _downscaleFactor = 3; + uint8_t _minLineCount = 2; + uint8_t _maxNumberOfSymbols = 0xff; public: // bitfields don't get default initialized to 0. DecodeHints() - : _tryHarder(1), _tryRotate(1), _isPure(0), _tryCode39ExtendedMode(0), _assumeCode39CheckDigit(0), - _assumeGS1(0), _returnCodabarStartEnd(0), _binarizer(Binarizer::LocalAverage), - _eanAddOnSymbol(EanAddOnSymbol::Ignore) + : _tryHarder(1), _tryRotate(1), _tryDownscale(1), _isPure(0), _tryCode39ExtendedMode(0), + _validateCode39CheckSum(0), _validateITFCheckSum(0), _returnCodabarStartEnd(0), + _binarizer(Binarizer::LocalAverage), _eanAddOnSymbol(EanAddOnSymbol::Ignore) {} #define ZX_PROPERTY(TYPE, GETTER, SETTER) \ @@ -82,12 +89,29 @@ class DecodeHints /// Also try detecting code in 90, 180 and 270 degree rotated images. ZX_PROPERTY(bool, tryRotate, setTryRotate) + /// Also try detecting code in downscaled images (depending on image size). + ZX_PROPERTY(bool, tryDownscale, setTryDownscale) + /// Binarizer to use internally when using the ReadBarcode function ZX_PROPERTY(Binarizer, binarizer, setBinarizer) - /// Set to true if the input contains nothing but a perfectly aligned barcode (generated image) + /// Set to true if the input contains nothing but a single perfectly aligned barcode (generated image) ZX_PROPERTY(bool, isPure, setIsPure) + /// Image size ( min(width, height) ) threshold at which to start downscaled scanning + // WARNING: this API is experimental and may change/disappear + ZX_PROPERTY(uint16_t, downscaleThreshold, setDownscaleThreshold) + + /// Scale factor used during downscaling, meaningful values are 2, 3 and 4 + // WARNING: this API is experimental and may change/disappear + ZX_PROPERTY(uint8_t, downscaleFactor, setDownscaleFactor) + + /// The number of scan lines in a 1D barcode that have to be equal to accept the result, default is 2 + ZX_PROPERTY(uint8_t, minLineCount, setMinLineCount) + + /// The maximum number of symbols (barcodes) to detect / look for in the image with ReadBarcodes + ZX_PROPERTY(uint8_t, maxNumberOfSymbols, setMaxNumberOfSymbols) + /// Specifies what character encoding to use when decoding, where applicable. ZX_PROPERTY(std::string, characterSet, setCharacterSet) @@ -97,14 +121,11 @@ class DecodeHints /// If true, the Code-39 reader will try to read extended mode. ZX_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode) - /// Assume Code-39 codes employ a check digit. - ZX_PROPERTY(bool, assumeCode39CheckDigit, setAssumeCode39CheckDigit) + /// Assume Code-39 codes employ a check digit and validate it. + ZX_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum) - /** - * Assume the barcode is being processed as a GS1 barcode, and modify behavior as needed. - * For example this affects FNC1 handling for Code 128 (aka GS1-128). - */ - ZX_PROPERTY(bool, assumeGS1, setAssumeGS1) + /// Assume ITF codes employ a GS1 check digit and validate it. + ZX_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum) /** * If true, return the start and end digits in a Codabar barcode instead of stripping them. They @@ -117,31 +138,18 @@ class DecodeHints ZX_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, setEanAddOnSymbol) #undef ZX_PROPERTY +#undef ZX_PROPERTY_DEPRECATED + + /// NOTE: used to affect FNC1 handling for Code 128 (aka GS1-128) but behavior now based on position of FNC1. + [[deprecated]] bool assumeGS1() const noexcept { return true; } + [[deprecated]] DecodeHints& setAssumeGS1(bool v [[maybe_unused]]) { return *this; } + + /// NOTE: use validateCode39CheckSum + [[deprecated]] bool assumeCode39CheckDigit() const noexcept { return validateCode39CheckSum(); } + [[deprecated]] DecodeHints& setAssumeCode39CheckDigit(bool v) { return setValidateCode39CheckSum(v); } bool hasFormat(BarcodeFormats f) const noexcept { return _formats.testFlags(f); } bool hasNoFormat() const noexcept { return _formats.empty(); } - - [[deprecated]] DecodeHints& setPossibleFormats(const std::vector& formats) - { - _formats.clear(); - for (auto f : formats) - _formats |= f; - return *this; - } - - [[deprecated]] bool requireEanAddOnSymbol() const { return _eanAddOnSymbol == EanAddOnSymbol::Require; } - [[deprecated]] DecodeHints& setRequireEanAddOnSymbol(bool v) - { - return setEanAddOnSymbol(v ? EanAddOnSymbol::Require : EanAddOnSymbol::Ignore); - } - [[deprecated]] std::vector allowedEanExtensions() const - { - return _eanAddOnSymbol == EanAddOnSymbol::Require ? std::vector{2, 5} : std::vector{}; - } - [[deprecated]] DecodeHints& setAllowedEanExtensions(const std::vector& v) - { - return setEanAddOnSymbol(v.empty() ? EanAddOnSymbol::Ignore : EanAddOnSymbol::Require); - } }; } // ZXing diff --git a/core/src/DecoderResult.h b/core/src/DecoderResult.h index 4dec4acd2a..30abc8c772 100644 --- a/core/src/DecoderResult.h +++ b/core/src/DecoderResult.h @@ -45,7 +45,9 @@ class DecoderResult std::wstring _ecLevel; int _errorsCorrected = -1; int _erasures = -1; + std::string _symbologyIdentifier; StructuredAppendInfo _structuredAppend; + bool _isMirrored = false; bool _readerInit = false; std::shared_ptr _extra; @@ -89,7 +91,9 @@ class DecoderResult ZX_PROPERTY(std::wstring, ecLevel, setEcLevel) ZX_PROPERTY(int, errorsCorrected, setErrorsCorrected) ZX_PROPERTY(int, erasures, setErasures) + ZX_PROPERTY(std::string, symbologyIdentifier, setSymbologyIdentifier) ZX_PROPERTY(StructuredAppendInfo, structuredAppend, setStructuredAppend) + ZX_PROPERTY(bool, isMirrored, setIsMirrored) ZX_PROPERTY(bool, readerInit, setReaderInit) ZX_PROPERTY(std::shared_ptr, extra, setExtra) diff --git a/core/src/Flags.h b/core/src/Flags.h index d3cc0cc15f..616082d85b 100644 --- a/core/src/Flags.h +++ b/core/src/Flags.h @@ -17,6 +17,7 @@ #include "BitHacks.h" +#include #include #include @@ -52,7 +53,7 @@ class Flags public: using iterator_category = std::input_iterator_tag; using value_type = Enum; - using difference_type = ptrdiff_t; + using difference_type = std::ptrdiff_t; using pointer = Enum*; using reference = Enum&; diff --git a/core/src/GTIN.cpp b/core/src/GTIN.cpp index 4ee1a1ec68..3dc969a66c 100644 --- a/core/src/GTIN.cpp +++ b/core/src/GTIN.cpp @@ -18,6 +18,7 @@ #include "GTIN.h" #include "Result.h" +#include "TextUtfEncoding.h" #include #include @@ -39,132 +40,185 @@ bool operator<(const CountryId& lhs, const CountryId& rhs) return lhs.last < rhs.last; } +// https://www.gs1.org/standards/id-keys/company-prefix (as of 7 Feb 2022) +// and https://en.wikipedia.org/wiki/List_of_GS1_country_codes static const CountryId COUNTRIES[] = { // clang-format off - {0, 19, "US/CA"}, + {1, 19, "US/CA"}, {30, 39, "US"}, - {60, 139, "US/CA"}, - {300, 379, "FR"}, - {380, 380, "BG"}, - {383, 383, "SI"}, - {385, 385, "HR"}, - {387, 387, "BA"}, - {400, 440, "DE"}, - {450, 459, "JP"}, - {460, 469, "RU"}, - {471, 471, "TW"}, - {474, 474, "EE"}, - {475, 475, "LV"}, - {476, 476, "AZ"}, - {477, 477, "LT"}, - {478, 478, "UZ"}, - {479, 479, "LK"}, - {480, 480, "PH"}, - {481, 481, "BY"}, - {482, 482, "UA"}, - {484, 484, "MD"}, - {485, 485, "AM"}, - {486, 486, "GE"}, - {487, 487, "KZ"}, - {489, 489, "HK"}, - {490, 499, "JP"}, - {500, 509, "GB"}, - {520, 520, "GR"}, - {528, 528, "LB"}, - {529, 529, "CY"}, - {531, 531, "MK"}, - {535, 535, "MT"}, - {539, 539, "IE"}, - {540, 549, "BE/LU"}, - {560, 560, "PT"}, - {569, 569, "IS"}, - {570, 579, "DK"}, - {590, 590, "PL"}, - {594, 594, "RO"}, - {599, 599, "HU"}, - {600, 601, "ZA"}, - {603, 603, "GH"}, - {608, 608, "BH"}, - {609, 609, "MU"}, - {611, 611, "MA"}, - {613, 613, "DZ"}, - {616, 616, "KE"}, - {618, 618, "CI"}, - {619, 619, "TN"}, - {621, 621, "SY"}, - {622, 622, "EG"}, - {624, 624, "LY"}, - {625, 625, "JO"}, - {626, 626, "IR"}, - {627, 627, "KW"}, - {628, 628, "SA"}, - {629, 629, "AE"}, - {640, 649, "FI"}, - {690, 695, "CN"}, - {700, 709, "NO"}, - {729, 729, "IL"}, - {730, 739, "SE"}, - {740, 740, "GT"}, - {741, 741, "SV"}, - {742, 742, "HN"}, - {743, 743, "NI"}, - {744, 744, "CR"}, - {745, 745, "PA"}, - {746, 746, "DO"}, - {750, 750, "MX"}, - {754, 755, "CA"}, - {759, 759, "VE"}, - {760, 769, "CH"}, - {770, 770, "CO"}, - {773, 773, "UY"}, - {775, 775, "PE"}, - {777, 777, "BO"}, - {779, 779, "AR"}, - {780, 780, "CL"}, - {784, 784, "PY"}, - {785, 785, "PE"}, - {786, 786, "EC"}, - {789, 790, "BR"}, - {800, 839, "IT"}, - {840, 849, "ES"}, - {850, 850, "CU"}, - {858, 858, "SK"}, - {859, 859, "CZ"}, - {860, 860, "YU"}, - {865, 865, "MN"}, - {867, 867, "KP"}, - {868, 869, "TR"}, - {870, 879, "NL"}, - {880, 880, "KR"}, - {885, 885, "TH"}, - {888, 888, "SG"}, - {890, 890, "IN"}, - {893, 893, "VN"}, - {896, 896, "PK"}, - {899, 899, "ID"}, - {900, 919, "AT"}, - {930, 939, "AU"}, - {940, 949, "AZ"}, - {955, 955, "MY"}, - {958, 958, "MO"}, + {60, 99, "US/CA"}, // Note 99 coupon identification + {100, 139, "US"}, + {300, 379, "FR"}, // France (and Monaco according to Wikipedia) + {380, 380, "BG"}, // Bulgaria + {383, 383, "SI"}, // Slovenia + {385, 385, "HR"}, // Croatia + {387, 387, "BA"}, // Bosnia and Herzegovina + {389, 389, "ME"}, // Montenegro + //{390, 390, "XK"}, // (Kosovo according to Wikipedia, unsourced) + {400, 440, "DE"}, // Germany + {450, 459, "JP"}, // Japan + {460, 469, "RU"}, // Russia + {470, 470, "KG"}, // Kyrgyzstan + {471, 471, "TW"}, // Taiwan + {474, 474, "EE"}, // Estonia + {475, 475, "LV"}, // Latvia + {476, 476, "AZ"}, // Azerbaijan + {477, 477, "LT"}, // Lithuania + {478, 478, "UZ"}, // Uzbekistan + {479, 479, "LK"}, // Sri Lanka + {480, 480, "PH"}, // Philippines + {481, 481, "BY"}, // Belarus + {482, 482, "UA"}, // Ukraine + {483, 483, "TM"}, // Turkmenistan + {484, 484, "MD"}, // Moldova + {485, 485, "AM"}, // Armenia + {486, 486, "GE"}, // Georgia + {487, 487, "KZ"}, // Kazakhstan + {488, 488, "TJ"}, // Tajikistan + {489, 489, "HK"}, // Hong Kong + {490, 499, "JP"}, // Japan + {500, 509, "GB"}, // UK + {520, 521, "GR"}, // Greece + {528, 528, "LB"}, // Lebanon + {529, 529, "CY"}, // Cyprus + {530, 530, "AL"}, // Albania + {531, 531, "MK"}, // North Macedonia + {535, 535, "MT"}, // Malta + {539, 539, "IE"}, // Ireland + {540, 549, "BE/LU"}, // Belgium & Luxembourg + {560, 560, "PT"}, // Portugal + {569, 569, "IS"}, // Iceland + {570, 579, "DK"}, // Denmark (and Faroe Islands and Greenland according to Wikipedia) + {590, 590, "PL"}, // Poland + {594, 594, "RO"}, // Romania + {599, 599, "HU"}, // Hungary + {600, 601, "ZA"}, // South Africa + {603, 603, "GH"}, // Ghana + {604, 604, "SN"}, // Senegal + {608, 608, "BH"}, // Bahrain + {609, 609, "MU"}, // Mauritius + {611, 611, "MA"}, // Morocco + {613, 613, "DZ"}, // Algeria + {615, 615, "NG"}, // Nigeria + {616, 616, "KE"}, // Kenya + {617, 617, "CM"}, // Cameroon + {618, 618, "CI"}, // Côte d'Ivoire + {619, 619, "TN"}, // Tunisia + {620, 620, "TZ"}, // Tanzania + {621, 621, "SY"}, // Syria + {622, 622, "EG"}, // Egypt + {623, 623, "BN"}, // Brunei + {624, 624, "LY"}, // Libya + {625, 625, "JO"}, // Jordan + {626, 626, "IR"}, // Iran + {627, 627, "KW"}, // Kuwait + {628, 628, "SA"}, // Saudi Arabia + {629, 629, "AE"}, // United Arab Emirates + {630, 630, "QA"}, // Qatar + {631, 631, "NA"}, // Namibia + {640, 649, "FI"}, // Finland + {690, 699, "CN"}, // China + {700, 709, "NO"}, // Norway + {729, 729, "IL"}, // Israel + {730, 739, "SE"}, // Sweden + {740, 740, "GT"}, // Guatemala + {741, 741, "SV"}, // El Salvador + {742, 742, "HN"}, // Honduras + {743, 743, "NI"}, // Nicaragua + {744, 744, "CR"}, // Costa Rica + {745, 745, "PA"}, // Panama + {746, 746, "DO"}, // Dominican Republic + {750, 750, "MX"}, // Mexico + {754, 755, "CA"}, // Canada + {759, 759, "VE"}, // Venezuela + {760, 769, "CH"}, // Switzerland (and Liechtenstein according to Wikipedia) + {770, 771, "CO"}, // Colombia + {773, 773, "UY"}, // Uruguay + {775, 775, "PE"}, // Peru + {777, 777, "BO"}, // Bolivia + {778, 779, "AR"}, // Argentina + {780, 780, "CL"}, // Chile + {784, 784, "PY"}, // Paraguay + {786, 786, "EC"}, // Ecuador + {789, 790, "BR"}, // Brazil + {800, 839, "IT"}, // Italy (and San Marino and Vatican City according to Wikipedia) + {840, 849, "ES"}, // Spain (and Andorra according to Wikipedia) + {850, 850, "CU"}, // Cuba + {858, 858, "SK"}, // Slovakia + {859, 859, "CZ"}, // Czechia + {860, 860, "RS"}, // Serbia + {865, 865, "MN"}, // Mongolia + {867, 867, "KP"}, // North Korea + {868, 869, "TR"}, // Turkey + {870, 879, "NL"}, // Netherlands + {880, 880, "KR"}, // South Korea + {883, 883, "MM"}, // Myanmar + {884, 884, "KH"}, // Cambodia + {885, 885, "TH"}, // Thailand + {888, 888, "SG"}, // Singapore + {890, 890, "IN"}, // India + {893, 893, "VN"}, // Vietnam + {896, 896, "PK"}, // Pakistan + {899, 899, "ID"}, // Indonesia + {900, 919, "AT"}, // Austria + {930, 939, "AU"}, // Australia + {940, 949, "NZ"}, // New Zealand + {955, 955, "MY"}, // Malaysia + {958, 958, "MO"}, // Macao + //{960, 961, "GB"}, // Global Office - assigned to GS1 UK for GTIN-8 allocations (also 9620-9624999) // clang-format on }; -std::string LookupCountryIdentifier(const std::string& GTIN) +std::string LookupCountryIdentifier(const std::string& GTIN, const BarcodeFormat format) { - // TODO: support GTIN-14 numbers? - int prefix = std::stoi(GTIN.substr(0, 3)); - auto it = std::lower_bound(std::begin(COUNTRIES), std::end(COUNTRIES), CountryId{0, prefix, nullptr}); - return it != std::end(COUNTRIES) ? it->id : std::string(); + // Ignore add-on if any + const auto space = GTIN.find(' '); + const std::string::size_type size = space != std::string::npos ? space : GTIN.size(); + + if (size != 14 && size != 13 && size != 12 && size != 8) + return std::string(); + + // GTIN-14 leading packaging level indicator + const int first = size == 14 ? 1 : 0; + // UPC-A/E implicit leading 0 + const int implicitZero = size == 12 || (size == 8 && format != BarcodeFormat::EAN8) ? 1 : 0; + + if (size != 8 || format != BarcodeFormat::EAN8) { // Assuming following doesn't apply to EAN-8 + // 0000000 Restricted Circulation Numbers; 0000001-0000099 unused to avoid collision with GTIN-8 + int prefix = std::stoi(GTIN.substr(first, 7 - implicitZero)); + if (prefix >= 0 && prefix <= 99) + return std::string(); + + // 00001-00009 US + prefix = std::stoi(GTIN.substr(first, 5 - implicitZero)); + if (prefix >= 1 && prefix <= 9) + return "US"; + + // 0001-0009 US + prefix = std::stoi(GTIN.substr(first, 4 - implicitZero)); + if (prefix >= 1 && prefix <= 9) + return "US"; + } + + const int prefix = std::stoi(GTIN.substr(first, 3 - implicitZero)); + + // Special case EAN-8 for prefix < 100 (GS1 General Specifications Figure 1.4.3-1) + if (size == 8 && format == BarcodeFormat::EAN8 && prefix <= 99) // Restricted Circulation Numbers + return std::string(); + + const auto it = std::lower_bound(std::begin(COUNTRIES), std::end(COUNTRIES), CountryId{0, prefix, nullptr}); + + return it != std::end(COUNTRIES) && prefix >= it->first && prefix <= it->last ? it->id : std::string(); } std::string EanAddOn(const Result& result) { - if (!(BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::UPCE).testFlag(result.format())) + if (!(BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::UPCE | BarcodeFormat::EAN8) + .testFlag(result.format())) return {}; auto txt = result.text(); auto pos = txt.find(L' '); - return std::string(pos == std::wstring::npos ? txt.end() : txt.begin() + pos + 1, txt.end()); + return pos != std::wstring::npos ? TextUtfEncoding::ToUtf8(txt.substr(pos + 1)) : std::string(); } std::string IssueNr(const std::string& ean2AddOn) diff --git a/core/src/GTIN.h b/core/src/GTIN.h index 56add63d26..310da18c84 100644 --- a/core/src/GTIN.h +++ b/core/src/GTIN.h @@ -17,6 +17,7 @@ * limitations under the License. */ +#include "BarcodeFormat.h" #include "ZXContainerAlgorithms.h" #include @@ -47,10 +48,14 @@ bool IsCheckDigitValid(const std::basic_string& s) /** * Evaluate the prefix of the GTIN to estimate the country of origin. See - * - * http://en.wikipedia.org/wiki/List_of_GS1_country_codes. + * + * https://www.gs1.org/standards/id-keys/company-prefix and + * + * https://en.wikipedia.org/wiki/List_of_GS1_country_codes. + * + * `format` required for EAN-8 (UPC-E assumed if not given) */ -std::string LookupCountryIdentifier(const std::string& GTIN); +std::string LookupCountryIdentifier(const std::string& GTIN, const BarcodeFormat format = BarcodeFormat::None); std::string EanAddOn(const Result& result); diff --git a/core/src/GenericGF.h b/core/src/GenericGF.h index 489826c3b5..5c11f22aac 100644 --- a/core/src/GenericGF.h +++ b/core/src/GenericGF.h @@ -19,8 +19,6 @@ #include "GenericGFPoly.h" #include "ZXConfig.h" -#include -#include #include #include @@ -68,7 +66,7 @@ class GenericGF static const GenericGF& MaxiCodeField64(); // note: replaced addOrSubstract calls with '^' / '^='. everyone trying to understand this code needs to look into - // Galois Fields with caracteristic 2 and will then understand that XOR is addition/substraction. And those + // Galois Fields with characteristic 2 and will then understand that XOR is addition/subtraction. And those // operators are way more readable than a noisy member function name /** diff --git a/core/src/GenericLuminanceSource.cpp b/core/src/GenericLuminanceSource.cpp deleted file mode 100644 index 501fd2631e..0000000000 --- a/core/src/GenericLuminanceSource.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -* -* 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. -*/ - -#if defined(_MSC_VER) && (_MSC_VER >= 1915) -#pragma warning(disable : 4996) -#endif - -#include "GenericLuminanceSource.h" - -#include "ByteArray.h" -#include "ZXContainerAlgorithms.h" - -#include -#include -#include -#include -#include - -namespace ZXing { - -static uint8_t RGBToGray(unsigned r, unsigned g, unsigned b) -{ - // This optimization is not necessary as the computation below is cheap enough. - //if (r == g && g == b) { - // // Image is already greyscale, so pick any channel. - // return static_cast(r); - //} - - // .299R + 0.587G + 0.114B (YUV/YIQ for PAL and NTSC), - // (306*R) >> 10 is approximately equal to R*0.299, and so on. - // 0x200 >> 10 is 0.5, it implements rounding. - return static_cast((306 * r + 601 * g + 117 * b + 0x200) >> 10); -} - -static std::shared_ptr MakeCopy(const void* src, int rowBytes, int left, int top, int width, int height) -{ - auto result = std::make_shared(); - result->resize(width * height); - const uint8_t* srcRow = static_cast(src) + top * rowBytes + left; - uint8_t* destRow = result->data(); - for (int y = 0; y < height; ++y, srcRow += rowBytes, destRow += width) { - std::copy_n(srcRow, width, destRow); - } - return result; -} - -static std::shared_ptr MakeCopy(const ByteArray& pixels, int rowBytes, int left, int top, int width, int height) -{ - if (top == 0 && left == 0 && width * height == Size(pixels)) { - return std::make_shared(pixels); - } - return MakeCopy(pixels.data(), rowBytes, left, top, width, height); -} - -GenericLuminanceSource::GenericLuminanceSource(int left, int top, int width, int height, const void* bytes, int rowBytes, int pixelBytes, int redIndex, int greenIndex, int blueIndex, void*) : - _left(0), // since we copy the pixels - _top(0), - _width(width), - _height(height), - _rowBytes(width) -{ - if (left < 0 || top < 0 || width < 0 || height < 0) { - throw std::out_of_range("Requested offset is outside the image"); - } - - if (pixelBytes == 1) - _pixels = MakeCopy(bytes, rowBytes, left, top, width, height); - else { - auto pixels = std::make_shared(width * height); - const uint8_t *rgbSource = static_cast(bytes) + top * rowBytes; - uint8_t *destRow = pixels->data(); - for (int y = 0; y < height; ++y, rgbSource += rowBytes, destRow += width) { - const uint8_t *src = rgbSource + left * pixelBytes; - for (int x = 0; x < width; ++x, src += pixelBytes) { - destRow[x] = RGBToGray(src[redIndex], src[greenIndex], src[blueIndex]); - } - } - _pixels = std::move(pixels); - } -} - -GenericLuminanceSource::GenericLuminanceSource(int left, int top, int width, int height, std::shared_ptr pixels, int rowBytes) : - _pixels(std::move(pixels)), - _left(left), - _top(top), - _width(width), - _height(height), - _rowBytes(rowBytes) -{ - if (left < 0 || top < 0 || width < 0 || height < 0) { - throw std::out_of_range("Requested offset is outside the image"); - } -} - -// Defined here instead of inlining in header to avoid link error since WINDOWS_EXPORT_ALL_SYMBOLS does not cover vtable. -GenericLuminanceSource::GenericLuminanceSource(int width, int height, const void* bytes, int rowBytes, int pixelBytes, int redIndex, int greenIndex, int blueIndex) : - GenericLuminanceSource(0, 0, width, height, bytes, rowBytes, pixelBytes, redIndex, greenIndex, blueIndex, nullptr) -{ -} - -// Defined here instead of inlining in header to avoid link error since WINDOWS_EXPORT_ALL_SYMBOLS does not cover vtable. -GenericLuminanceSource::GenericLuminanceSource(int left, int top, int width, int height, const void* bytes, int rowBytes, int pixelBytes, int redIndex, int greenIndex, int blueIndex) : - GenericLuminanceSource(left, top, width, height, bytes, rowBytes, pixelBytes, redIndex, greenIndex, blueIndex, nullptr) -{ -} - -// Defined here instead of inlining in header to avoid link error since WINDOWS_EXPORT_ALL_SYMBOLS does not cover vtable. -GenericLuminanceSource::GenericLuminanceSource(int width, int height, const void* bytes, int rowBytes) : - GenericLuminanceSource(0, 0, width, height, bytes, rowBytes, 1, 0, 0, 0, nullptr) -{ -} - -// Defined here instead of inlining in header to avoid link error since WINDOWS_EXPORT_ALL_SYMBOLS does not cover vtable. -GenericLuminanceSource::GenericLuminanceSource(int left, int top, int width, int height, const void* bytes, int rowBytes) : - GenericLuminanceSource(left, top, width, height, bytes, rowBytes, 1, 0, 0, 0, nullptr) -{ -} - -int -GenericLuminanceSource::width() const -{ - return _width; -} - -int -GenericLuminanceSource::height() const -{ - return _height; -} - - - -const uint8_t * -GenericLuminanceSource::getRow(int y, ByteArray& buffer, bool forceCopy) const -{ - if (y < 0 || y >= _height) { - throw std::out_of_range("Requested row is outside the image"); - } - - const uint8_t* row = _pixels->data() + (y + _top)*_rowBytes + _left; - if (!forceCopy) { - return row; - } - - buffer.resize(_width); - std::copy_n(row, _width, buffer.begin()); - return buffer.data(); -} - -const uint8_t * -GenericLuminanceSource::getMatrix(ByteArray& buffer, int& outRowBytes, bool forceCopy) const -{ - const uint8_t* row = _pixels->data() + _top*_rowBytes + _left; - if (!forceCopy) { - outRowBytes = _rowBytes; - return row; - } - - outRowBytes = _width; - buffer.resize(_width * _height); - uint8_t* dest = buffer.data(); - for (int y = 0; y < _height; ++y, row += _rowBytes, dest += _width) { - std::copy_n(row, _width, dest); - } - return buffer.data(); -} - -bool -GenericLuminanceSource::canCrop() const -{ - return true; -} - -std::shared_ptr -GenericLuminanceSource::cropped(int left, int top, int width, int height) const -{ - if (left < 0 || top < 0 || width < 0 || height < 0 || left + width > _width || top + height > _height) { - throw std::out_of_range("Crop rectangle does not fit within image data."); - } - return std::make_shared(_left + left, _top + top, width, height, _pixels, _rowBytes); -} - -bool -GenericLuminanceSource::canRotate() const -{ - return true; -} - -std::shared_ptr -GenericLuminanceSource::rotated(int degreeCW) const -{ - degreeCW = (degreeCW + 360) % 360; - if (degreeCW == 90) - { - auto pixels = std::make_shared(_width * _height); - const uint8_t* srcRow = _pixels->data() + _top * _rowBytes + _left; - uint8_t* dest = pixels->data(); - for (int y = 0; y < _height; ++y, srcRow += _rowBytes) { - for (int x = 0; x < _width; ++x) { - dest[x * _height + (_height - y - 1)] = srcRow[x]; - } - } - return std::make_shared(0, 0, _height, _width, pixels, _height); - } - else if (degreeCW == 180) { - // same as a vertical flip followed a horizontal flip - auto pixels = MakeCopy(*_pixels, _rowBytes, _left, _top, _width, _height); - std::reverse(pixels->begin(), pixels->end()); - return std::make_shared(0, 0, _width, _height, pixels, _width); - } - else if (degreeCW == 270) { - auto pixels = std::make_shared(_width * _height); - const uint8_t* srcRow = _pixels->data() + _top * _rowBytes + _left; - uint8_t* dest = pixels->data(); - for (int y = 0; y < _height; ++y, srcRow += _rowBytes) { - for (int x = 0; x < _width; ++x) { - dest[(_width - x - 1) * _height + y] = srcRow[x]; - } - } - return std::make_shared(0, 0, _height, _width, pixels, _height); - } - else if (degreeCW == 0) { - return std::make_shared(0, 0, _width, _height, _pixels, _width); - } - throw std::invalid_argument("Unsupported rotation"); -} - -} // ZXing diff --git a/core/src/GenericLuminanceSource.h b/core/src/GenericLuminanceSource.h deleted file mode 100644 index cfb5ad2a19..0000000000 --- a/core/src/GenericLuminanceSource.h +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once -/* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -* -* 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. -*/ - -#include "LuminanceSource.h" - -#include -#include - -namespace ZXing { - -/** -* This decode images from an address in memory as RGB or ARGB data. -*/ -class GenericLuminanceSource : public LuminanceSource -{ -public: - virtual ~GenericLuminanceSource() = default; - - // Don't use in client code. Used internally for now to prevent lots of deprecation warning noise until the GenericLuminanceSource is completely removed. - GenericLuminanceSource(int left, int top, int width, int height, const void* bytes, int rowBytes, int pixelBytes, int redIndex, int greenIndex, int blueIndex, void* deprecation_tag); - /** - * Init with a RGB source. - */ - [[deprecated]] // please use interface from ReadBarcode.h - GenericLuminanceSource(int width, int height, const void* bytes, int rowBytes, int pixelBytes, int redIndex, int greenIndex, int blueIndex); - - /** - * Init with a RGB source, left, top, width, height specify the subregion area in original image; 'bytes' points to the beginning of image buffer (i.e. pixel (0,0)). - */ - [[deprecated]] // please use interface from ReadBarcode.h - GenericLuminanceSource(int left, int top, int width, int height, const void* bytes, int rowBytes, int pixelBytes, int redIndex, int greenIndex, int blueIndex); - - /** - * Init with a grayscale source. - */ - [[deprecated]] // please use interface from ReadBarcode.h - GenericLuminanceSource(int width, int height, const void* bytes, int rowBytes); - - /** - * Init with a grayscale source, left, top, width, height specify the subregion area in original image; 'bytes' points to the beginning of image buffer (i.e. pixel (0,0)). - */ - [[deprecated]] // please use interface from ReadBarcode.h - GenericLuminanceSource(int left, int top, int width, int height, const void* bytes, int rowBytes); - - /** - * Init with a grayscale source, left, top, width, height specify the subregion area in original image; 'bytes' points the beginning of image buffer (i.e. pixel (0,0)). - */ - [[deprecated]] // please use interface from ReadBarcode.h - GenericLuminanceSource(int left, int top, int width, int height, std::shared_ptr pixels, int rowBytes); - - virtual int width() const override; - virtual int height() const override; - virtual const uint8_t* getRow(int y, ByteArray& buffer, bool forceCopy = false) const override; - virtual const uint8_t* getMatrix(ByteArray& buffer, int& outRowBytes, bool forceCopy = false) const override; - virtual bool canCrop() const override; - virtual std::shared_ptr cropped(int left, int top, int width, int height) const override; - virtual bool canRotate() const override; - virtual std::shared_ptr rotated(int degreeCW) const override; - -private: - std::shared_ptr _pixels; - int _left; - int _top; - int _width; - int _height; - int _rowBytes; -}; - -} // ZXing diff --git a/core/src/GlobalHistogramBinarizer.cpp b/core/src/GlobalHistogramBinarizer.cpp index 1e3fd111b7..59ad786b43 100644 --- a/core/src/GlobalHistogramBinarizer.cpp +++ b/core/src/GlobalHistogramBinarizer.cpp @@ -17,16 +17,13 @@ #include "GlobalHistogramBinarizer.h" -#include "BitArray.h" #include "BitMatrix.h" #include "ByteArray.h" -#include "LuminanceSource.h" #include #include #include #include -#include #include namespace ZXing { @@ -35,34 +32,10 @@ static const int LUMINANCE_BITS = 5; static const int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; static const int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; - -struct GlobalHistogramBinarizer::DataCache -{ - std::once_flag once; - std::shared_ptr matrix; -}; - -GlobalHistogramBinarizer::GlobalHistogramBinarizer(std::shared_ptr source) : - _source(std::move(source)), - _cache(new DataCache) -{ -} +GlobalHistogramBinarizer::GlobalHistogramBinarizer(const ImageView& buffer) : BinaryBitmap(buffer) {} GlobalHistogramBinarizer::~GlobalHistogramBinarizer() = default; -int -GlobalHistogramBinarizer::width() const -{ - return _source->width(); -} - -int -GlobalHistogramBinarizer::height() const -{ - return _source->height(); -} - - // Return -1 on error static int EstimateBlackPoint(const std::array& buckets) { @@ -111,20 +84,21 @@ static int EstimateBlackPoint(const std::array& buckets) return bestValley << LUMINANCE_SHIFT; } -bool GlobalHistogramBinarizer::getPatternRow(int y, PatternRow& res) const +bool GlobalHistogramBinarizer::getPatternRow(int row, int rotation, PatternRow& res) const { - int width = _source->width(); - if (width < 3) + auto buffer = _buffer.rotated(rotation); + + if (buffer.width() < 3) return false; // special casing the code below for a width < 3 makes no sense res.clear(); - ByteArray buffer; - const uint8_t* luminances = _source->getRow(y, buffer); + const uint8_t* luminances = buffer.data(0, row); + const int pixStride = buffer.pixStride(); std::array buckets = {}; - for (int x = 0; x < width; x++) { - buckets[luminances[x] >> LUMINANCE_SHIFT]++; - } + for (int x = 0; x < buffer.width(); x++) + buckets[luminances[x * pixStride] >> LUMINANCE_SHIFT]++; + int blackPoint = EstimateBlackPoint(buckets); if (blackPoint <= 0) return false; @@ -136,20 +110,20 @@ bool GlobalHistogramBinarizer::getPatternRow(int y, PatternRow& res) const auto process = [&](bool val, const uint8_t* p) { if (val != lastVal) { - res.push_back(static_cast(p - lastPos)); + res.push_back(static_cast((p - lastPos) / pixStride)); lastVal = val; lastPos = p; } }; - for (auto* p = luminances + 1; p < luminances + width - 1; ++p) - process((-*(p - 1) + (int(*p) * 4) - *(p + 1)) / 2 < blackPoint, p); + for (auto *p = luminances + pixStride, *e = luminances + (buffer.width() - 1) * pixStride; p < e; p += pixStride) + process((-*(p - pixStride) + (int(*p) * 4) - *(p + pixStride)) / 2 < blackPoint, p); - auto* backPos = luminances + width - 1; + auto* backPos = buffer.data(buffer.width() - 1, row); bool backVal = *backPos < blackPoint; process(backVal, backPos); - res.push_back(static_cast(backPos - lastPos + 1)); + res.push_back(static_cast((backPos - lastPos) / pixStride + 1)); if (backVal) res.push_back(0); // last value is number of white pixels, here 0 @@ -159,85 +133,36 @@ bool GlobalHistogramBinarizer::getPatternRow(int y, PatternRow& res) const return true; } -static void InitBlackMatrix(const LuminanceSource& source, std::shared_ptr& outMatrix) +// Does not sharpen the data, as this call is intended to only be used by 2D Readers. +std::shared_ptr +GlobalHistogramBinarizer::getBlackMatrix() const { - int width = source.width(); - int height = source.height(); - auto matrix = std::make_shared(width, height); - // Quickly calculates the histogram by sampling four rows from the image. This proved to be // more robust on the blackbox tests than sampling a diagonal as we used to do. std::array localBuckets = {}; { - ByteArray buffer; for (int y = 1; y < 5; y++) { - int row = height * y / 5; - const uint8_t* luminances = source.getRow(row, buffer); - int right = (width * 4) / 5; - for (int x = width / 5; x < right; x++) { + int row = height() * y / 5; + const uint8_t* luminances = _buffer.data(0, row); + int right = (width() * 4) / 5; + for (int x = width() / 5; x < right; x++) localBuckets[luminances[x] >> LUMINANCE_SHIFT]++; - } } } int blackPoint = EstimateBlackPoint(localBuckets); if (blackPoint <= 0) - return; + return {}; // We delay reading the entire image luminance until the black point estimation succeeds. // Although we end up reading four rows twice, it is consistent with our motto of // "fail quickly" which is necessary for continuous scanning. - ByteArray buffer; - int stride; - const uint8_t* luminances = source.getMatrix(buffer, stride); - for (int y = 0; y < height; y++) { - int offset = y * stride; - for (int x = 0; x < width; x++) { - if (luminances[offset + x] < blackPoint) { - matrix->set(x, y); - } - } - } - outMatrix = matrix; -} + auto matrix = std::make_shared(width(), height()); + for(int y = 0; y < height(); ++y) + for(int x = 0; x < width(); ++x) + matrix->set(x, y, *_buffer.data(x, y) < blackPoint); -// Does not sharpen the data, as this call is intended to only be used by 2D Readers. -std::shared_ptr -GlobalHistogramBinarizer::getBlackMatrix() const -{ - std::call_once(_cache->once, &InitBlackMatrix, std::cref(*_source), std::ref(_cache->matrix)); - return _cache->matrix; -} - -bool -GlobalHistogramBinarizer::canCrop() const -{ - return _source->canCrop(); -} - -std::shared_ptr -GlobalHistogramBinarizer::cropped(int left, int top, int width, int height) const -{ - return newInstance(_source->cropped(left, top, width, height)); + return matrix; } -bool -GlobalHistogramBinarizer::canRotate() const -{ - return _source->canRotate(); -} - -std::shared_ptr -GlobalHistogramBinarizer::rotated(int degreeCW) const -{ - return newInstance(_source->rotated(degreeCW)); -} - -std::shared_ptr -GlobalHistogramBinarizer::newInstance(const std::shared_ptr& source) const -{ - return std::make_shared(source); -} - - } // ZXing diff --git a/core/src/GlobalHistogramBinarizer.h b/core/src/GlobalHistogramBinarizer.h index 391969d29d..fbeac5d9df 100644 --- a/core/src/GlobalHistogramBinarizer.h +++ b/core/src/GlobalHistogramBinarizer.h @@ -18,12 +18,8 @@ #include "BinaryBitmap.h" -#include - namespace ZXing { -class LuminanceSource; - /** * This Binarizer implementation uses the old ZXing global histogram approach. It is suitable * for low-end mobile devices which don't have enough CPU or memory to use a local thresholding @@ -37,27 +33,12 @@ class LuminanceSource; */ class GlobalHistogramBinarizer : public BinaryBitmap { -protected: - std::shared_ptr _source; - public: - explicit GlobalHistogramBinarizer(std::shared_ptr source); + explicit GlobalHistogramBinarizer(const ImageView& buffer); ~GlobalHistogramBinarizer() override; - int width() const override; - int height() const override; - bool getPatternRow(int y, PatternRow &res) const override; + bool getPatternRow(int row, int rotation, PatternRow &res) const override; std::shared_ptr getBlackMatrix() const override; - bool canCrop() const override; - std::shared_ptr cropped(int left, int top, int width, int height) const override; - bool canRotate() const override; - std::shared_ptr rotated(int degreeCW) const override; - - virtual std::shared_ptr newInstance(const std::shared_ptr& source) const; - -private: - struct DataCache; - std::unique_ptr _cache; }; } // ZXing diff --git a/core/src/GridSampler.cpp b/core/src/GridSampler.cpp index d93e00c906..260d763c27 100644 --- a/core/src/GridSampler.cpp +++ b/core/src/GridSampler.cpp @@ -35,12 +35,20 @@ DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const P LogMatrix log; LogMatrixWriter lmw(log, image, 5, "grid.pnm"); #endif - auto isInside = [&](PointI p) { return image.isIn(mod2Pix(centered(p))); }; - - if (width <= 0 || height <= 0 || !mod2Pix.isValid() || !isInside({0, 0}) || !isInside({width - 1, 0}) || - !isInside({width - 1, height - 1}) || !isInside({0, height - 1})) + if (width <= 0 || height <= 0 || !mod2Pix.isValid()) return {}; + // To deal with remaining examples (see #251 and #267) of "numercial instabilities" that have not been + // prevented with the Quadrilateral.h:IsConvex() check, we check for all boundary points of the grid to + // be inside. + auto isInside = [&](PointI p) { return image.isIn(mod2Pix(centered(p))); }; + for (int y = 0; y < height; ++y) + if (!isInside({0, y}) || !isInside({width - 1, y})) + return {}; + for (int x = 1; x < width - 1; ++x) + if (!isInside({x, 0}) || !isInside({x, height - 1})) + return {}; + BitMatrix res(width, height); for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) { diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index fa0162f99b..2a9bb7aea5 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -20,17 +20,13 @@ #include "BitMatrix.h" #include "BitMatrixIO.h" #include "ByteArray.h" -#include "LuminanceSource.h" #include "Matrix.h" #include "ZXContainerAlgorithms.h" -#include #include -#include #include #include #include -#include namespace ZXing { @@ -40,17 +36,7 @@ static const int BLOCK_SIZE = 8; static const int MINIMUM_DIMENSION = BLOCK_SIZE * 5; static const int MIN_DYNAMIC_RANGE = 24; -struct HybridBinarizer::DataCache -{ - std::once_flag once; - std::shared_ptr matrix; -}; - -HybridBinarizer::HybridBinarizer(const std::shared_ptr& source) : - GlobalHistogramBinarizer(source), - _cache(new DataCache) -{ -} +HybridBinarizer::HybridBinarizer(const ImageView& iv) : GlobalHistogramBinarizer(iv) {} HybridBinarizer::~HybridBinarizer() = default; @@ -59,7 +45,7 @@ HybridBinarizer::~HybridBinarizer() = default; * See the following thread for a discussion of this algorithm: * http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 */ -static Matrix CalculateBlackPoints(const uint8_t* luminances, int subWidth, int subHeight, int width, int height, int stride) +static Matrix CalculateBlackPoints(const uint8_t* luminances, int subWidth, int subHeight, int width, int height, int rowStride) { Matrix blackPoints(subWidth, subHeight); @@ -70,7 +56,7 @@ static Matrix CalculateBlackPoints(const uint8_t* luminances, int subWidth, int sum = 0; uint8_t min = 0xFF; uint8_t max = 0; - for (int yy = 0, offset = yoffset * stride + xoffset; yy < BLOCK_SIZE; yy++, offset += stride) { + for (int yy = 0, offset = yoffset * rowStride + xoffset; yy < BLOCK_SIZE; yy++, offset += rowStride) { for (int xx = 0; xx < BLOCK_SIZE; xx++) { auto pixel = luminances[offset + xx]; sum += pixel; @@ -80,7 +66,7 @@ static Matrix CalculateBlackPoints(const uint8_t* luminances, int subWidth, // short-circuit min/max tests once dynamic range is met if (max - min > MIN_DYNAMIC_RANGE) { // finish the rest of the rows quickly - for (yy++, offset += stride; yy < BLOCK_SIZE; yy++, offset += stride) { + for (yy++, offset += rowStride; yy < BLOCK_SIZE; yy++, offset += rowStride) { for (int xx = 0; xx < BLOCK_SIZE; xx++) { sum += luminances[offset + xx]; } @@ -124,17 +110,17 @@ static Matrix CalculateBlackPoints(const uint8_t* luminances, int subWidth, /** * Applies a single threshold to a block of pixels. */ -static void ThresholdBlock(const uint8_t* luminances, int xoffset, int yoffset, int threshold, int stride, BitMatrix& matrix) +static void ThresholdBlock(const uint8_t* luminances, int xoffset, int yoffset, int threshold, int rowStride, BitMatrix& matrix) { #ifdef ZX_FAST_BIT_STORAGE for (int y = yoffset; y < yoffset + BLOCK_SIZE; ++y) { - auto* src = luminances + y * stride + xoffset; + auto* src = luminances + y * rowStride + xoffset; auto* const dstBegin = matrix.row(y).begin() + xoffset; for (auto* dst = dstBegin; dst < dstBegin + BLOCK_SIZE; ++dst, ++src) *dst = *src <= threshold; } #else - for (int y = 0, offset = yoffset * stride + xoffset; y < BLOCK_SIZE; y++, offset += stride) { + for (int y = 0, offset = yoffset * rowStride + xoffset; y < BLOCK_SIZE; y++, offset += rowStride) { for (int x = 0; x < BLOCK_SIZE; x++) { // Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0. if (luminances[offset + x] <= threshold) { @@ -150,9 +136,11 @@ static void ThresholdBlock(const uint8_t* luminances, int xoffset, int yoffset, * of the blocks around it. Also handles the corner cases (fractional blocks are computed based * on the last pixels in the row/column which are also used in the previous block). */ -static void CalculateThresholdForBlock(const uint8_t* luminances, int subWidth, int subHeight, int width, int height, - int stride, const Matrix& blackPoints, BitMatrix& matrix) +static std::shared_ptr CalculateMatrix(const uint8_t* luminances, int subWidth, int subHeight, int width, + int height, int rowStride, const Matrix& blackPoints) { + auto matrix = std::make_shared(width, height); + for (int y = 0; y < subHeight; y++) { int yoffset = std::min(y * BLOCK_SIZE, height - BLOCK_SIZE); for (int x = 0; x < subWidth; x++) { @@ -166,52 +154,27 @@ static void CalculateThresholdForBlock(const uint8_t* luminances, int subWidth, } } int average = sum / 25; - ThresholdBlock(luminances, xoffset, yoffset, average, stride, matrix); + ThresholdBlock(luminances, xoffset, yoffset, average, rowStride, *matrix); } } -} - -/** -* Calculates the final BitMatrix once for all requests. This could be called once from the -* constructor instead, but there are some advantages to doing it lazily, such as making -* profiling easier, and not doing heavy lifting when callers don't expect it. -*/ -static void InitBlackMatrix(const LuminanceSource& source, std::shared_ptr& outMatrix) -{ - int width = source.width(); - int height = source.height(); - ByteArray buffer; - int stride; - const uint8_t* luminances = source.getMatrix(buffer, stride); - int subWidth = (width + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(width/BS) - int subHeight = (height + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(height/BS) - auto blackPoints = CalculateBlackPoints(luminances, subWidth, subHeight, width, height, stride); - - auto matrix = std::make_shared(width, height); - CalculateThresholdForBlock(luminances, subWidth, subHeight, width, height, stride, blackPoints, *matrix); - outMatrix = std::move(matrix); + return matrix; } -std::shared_ptr -HybridBinarizer::getBlackMatrix() const +std::shared_ptr HybridBinarizer::getBlackMatrix() const { - int width = _source->width(); - int height = _source->height(); - if (width >= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION) { - std::call_once(_cache->once, &InitBlackMatrix, std::cref(*_source), std::ref(_cache->matrix)); - return _cache->matrix; - } - else { + if (width() >= MINIMUM_DIMENSION && height() >= MINIMUM_DIMENSION) { + const uint8_t* luminances = _buffer.data(0, 0); + int subWidth = (width() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(width/BS) + int subHeight = (height() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(height/BS) + auto blackPoints = + CalculateBlackPoints(luminances, subWidth, subHeight, width(), height(), _buffer.rowStride()); + + return CalculateMatrix(luminances, subWidth, subHeight, width(), height(), _buffer.rowStride(), blackPoints); + } else { // If the image is too small, fall back to the global histogram approach. return GlobalHistogramBinarizer::getBlackMatrix(); } } -std::shared_ptr -HybridBinarizer::newInstance(const std::shared_ptr& source) const -{ - return std::make_shared(source); -} - } // ZXing diff --git a/core/src/HybridBinarizer.h b/core/src/HybridBinarizer.h index dcee037428..53fd8851d8 100644 --- a/core/src/HybridBinarizer.h +++ b/core/src/HybridBinarizer.h @@ -18,8 +18,6 @@ #include "GlobalHistogramBinarizer.h" -#include - namespace ZXing { /** @@ -42,15 +40,10 @@ namespace ZXing { class HybridBinarizer : public GlobalHistogramBinarizer { public: - explicit HybridBinarizer(const std::shared_ptr& source); + explicit HybridBinarizer(const ImageView& iv); ~HybridBinarizer() override; std::shared_ptr getBlackMatrix() const override; - std::shared_ptr newInstance(const std::shared_ptr& source) const override; - -private: - struct DataCache; - std::unique_ptr _cache; }; } // ZXing diff --git a/core/src/ImageView.h b/core/src/ImageView.h new file mode 100644 index 0000000000..ec72d18ae2 --- /dev/null +++ b/core/src/ImageView.h @@ -0,0 +1,109 @@ +#pragma once +/* +* Copyright 2019 Axel Waggershauser +* +* 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. +*/ + +#include +#include + +namespace ZXing { + +enum class ImageFormat : uint32_t +{ + None = 0, + Lum = 0x01000000, + RGB = 0x03000102, + BGR = 0x03020100, + RGBX = 0x04000102, + XRGB = 0x04010203, + BGRX = 0x04020100, + XBGR = 0x04030201, +}; + +constexpr inline int PixStride(ImageFormat format) { return (static_cast(format) >> 3*8) & 0xFF; } +constexpr inline int RedIndex(ImageFormat format) { return (static_cast(format) >> 2*8) & 0xFF; } +constexpr inline int GreenIndex(ImageFormat format) { return (static_cast(format) >> 1*8) & 0xFF; } +constexpr inline int BlueIndex(ImageFormat format) { return (static_cast(format) >> 0*8) & 0xFF; } + +constexpr inline uint8_t RGBToLum(unsigned r, unsigned g, unsigned b) +{ + // .299R + 0.587G + 0.114B (YUV/YIQ for PAL and NTSC), + // (306*R) >> 10 is approximately equal to R*0.299, and so on. + // 0x200 >> 10 is 0.5, it implements rounding. + return static_cast((306 * r + 601 * g + 117 * b + 0x200) >> 10); +} + +/** + * Simple class that stores a non-owning const pointer to image data plus layout and format information. + */ +class ImageView +{ +protected: + const uint8_t* _data = nullptr; + ImageFormat _format; + int _width = 0, _height = 0, _pixStride = 0, _rowStride = 0; + +public: + /** + * ImageView constructor + * + * @param data pointer to image buffer + * @param width image width in pixels + * @param height image height in pixels + * @param format image/pixel format + * @param rowStride optional row stride in bytes, default is width * pixStride + * @param pixStride optional pixel stride in bytes, default is calculated from format + */ + ImageView(const uint8_t* data, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) + : _data(data), _format(format), _width(width), _height(height), + _pixStride(pixStride ? pixStride : PixStride(format)), _rowStride(rowStride ? rowStride : width * _pixStride) + {} + + int width() const { return _width; } + int height() const { return _height; } + int pixStride() const { return _pixStride; } + int rowStride() const { return _rowStride; } + ImageFormat format() const { return _format; } + + const uint8_t* data(int x, int y) const { return _data + y * _rowStride + x * _pixStride; } + + ImageView cropped(int left, int top, int width, int height) const + { + left = std::max(0, left); + top = std::max(0, top); + width = width <= 0 ? (_width - left) : std::min(_width - left, width); + height = height <= 0 ? (_height - top) : std::min(_height - top, height); + return {data(left, top), width, height, _format, _rowStride, _pixStride}; + } + + ImageView rotated(int degree) const + { + switch ((degree + 360) % 360) { + case 90: return {data(0, _height - 1), _height, _width, _format, _pixStride, -_rowStride}; + case 180: return {data(_width - 1, _height - 1), _width, _height, _format, -_rowStride, -_pixStride}; + case 270: return {data(_width - 1, 0), _height, _width, _format, -_pixStride, _rowStride}; + } + return *this; + } + + ImageView subsampled(int scale) const + { + return {_data, _width / scale, _height / scale, _format, _rowStride * scale, _pixStride * scale}; + } + +}; + +} // ZXing + diff --git a/core/src/LuminanceSource.cpp b/core/src/LuminanceSource.cpp deleted file mode 100644 index d80d51efcd..0000000000 --- a/core/src/LuminanceSource.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -* -* 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. -*/ - -#include "LuminanceSource.h" - -#include "ByteArray.h" - -#include -#include -#include - -namespace ZXing { - -namespace { - -/** -* A wrapper implementation of {@link LuminanceSource} which inverts the luminances it returns -- black becomes -* white and vice versa, and each value becomes (255-value). -* -* @author Sean Owen -*/ -class InvertedLuminanceSource : public LuminanceSource -{ - std::shared_ptr _src; - -public: - explicit InvertedLuminanceSource(std::shared_ptr src) : _src(std::move(src)) {} - - const uint8_t* getRow(int y, ByteArray& outBytes, bool) const override - { - _src->getRow(y, outBytes, true); - std::transform(outBytes.begin(), outBytes.end(), outBytes.begin(), [](uint8_t b) { return 255 - b; }); - return outBytes.data(); - } - - const uint8_t* getMatrix(ByteArray& outBytes, int& outRowBytes, bool) const override - { - _src->getMatrix(outBytes, outRowBytes, true); - std::transform(outBytes.begin(), outBytes.end(), outBytes.begin(), [](uint8_t b) { return 255 - b; }); - return outBytes.data(); - } - - int width() const override - { - return _src->width(); - } - - /** - * @return The height of the bitmap. - */ - int height() const override - { - return _src->height(); - } - - bool canCrop() const override - { - return _src->canCrop(); - } - - std::shared_ptr cropped(int left, int top, int width, int height) const override - { - return CreateInverted(_src->cropped(left, top, width, height)); - } - - bool canRotate() const override - { - return _src->canRotate(); - } - - std::shared_ptr rotated(int degreeCW) const override - { - return CreateInverted(_src->rotated(degreeCW)); - } - -protected: - std::shared_ptr getInverted() const override - { - return _src; - } - -}; // InvertedLuminanceSource - -} // anonymous - - -bool -LuminanceSource::canCrop() const -{ - return false; -} - -std::shared_ptr -LuminanceSource::cropped(int, int, int, int) const -{ - throw std::runtime_error("This luminance source does not support cropping."); -} - -bool -LuminanceSource::canRotate() const -{ - return false; -} - -/** -* Returns a new object with rotated image data by 90 degrees counterclockwise. -* Only callable if {@link #isRotateSupported()} is true. -* -* @return A rotated version of this object. -*/ -std::shared_ptr -LuminanceSource::rotated(int) const -{ - throw std::runtime_error("This luminance source does not support rotation by 90 degrees."); -} - -std::shared_ptr -LuminanceSource::getInverted() const -{ - return nullptr; -} - -std::shared_ptr -LuminanceSource::CreateInverted(const std::shared_ptr& src) -{ - auto result = src->getInverted(); - if (result == nullptr) - result = std::make_shared(src); - return result; -} - -} // ZXing diff --git a/core/src/LuminanceSource.h b/core/src/LuminanceSource.h deleted file mode 100644 index 8f7b2bf769..0000000000 --- a/core/src/LuminanceSource.h +++ /dev/null @@ -1,114 +0,0 @@ -#pragma once -/* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -* -* 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. -*/ - -#include - -namespace ZXing { - -class ByteArray; - -/** -* The purpose of this class hierarchy is to abstract different bitmap implementations across -* platforms into a standard interface for requesting greyscale luminance values. The interface -* only provides immutable methods; therefore crop and rotation create copies. This is to ensure -* that one Reader does not modify the original luminance source and leave it in an unknown state -* for other Readers in the chain. -* -* @author dswitkin@google.com (Daniel Switkin) -*/ -class LuminanceSource -{ -public: - virtual ~LuminanceSource() = default; - - /** - * @return The width of the bitmap. - */ - virtual int width() const = 0; - - /** - * @return The height of the bitmap. - */ - virtual int height() const = 0; - - /** - * Fetches one row of luminance data from the underlying platform's bitmap. Values range from - * 0 (black) to 255 (white). Because Java does not have an unsigned byte type, callers will have - * to bitwise and with 0xff for each value. It is preferable for implementations of this method - * to only fetch this row rather than the whole image, since no 2D Readers may be installed and - * getMatrix() may never be called. - * - * @param y The row to fetch, which must be in [0,getHeight()) - * @param row An optional preallocated array. If null or too small, it will be ignored. - * Always use the returned object, and ignore the .length of the array. - * @return An array containing the luminance data. - */ - virtual const uint8_t* getRow(int y, ByteArray& buffer, bool forceCopy = false) const = 0; - - /** - * Fetches luminance data for the underlying bitmap. Values should be fetched using: - * {@code int luminance = array[y * width + x] & 0xff} - * - * @return A row-major 2D array of luminance values. Do not use result.length as it may be - * larger than width * height bytes on some platforms. Do not modify the contents - * of the result. - */ - virtual const uint8_t* getMatrix(ByteArray& buffer, int& outRowBytes, bool forceCopy = false) const = 0; - - /** - * @return Whether this subclass supports cropping. - */ - virtual bool canCrop() const; - - /** - * Returns a new object with cropped image data. Implementations may keep a reference to the - * original data rather than a copy. Only callable if isCropSupported() is true. - * - * @param left The left coordinate, which must be in [0,getWidth()) - * @param top The top coordinate, which must be in [0,getHeight()) - * @param width The width of the rectangle to crop. - * @param height The height of the rectangle to crop. - * @return A cropped version of this object. - */ - virtual std::shared_ptr cropped(int left, int top, int width, int height) const; - - /** - * @return Whether this subclass supports counter-clockwise rotation. - */ - virtual bool canRotate() const; - - /** - * Returns a new object with rotated image data by 90 degrees counterclockwise. - * Only callable if {@link #isRotateSupported()} is true. - * - * @return A rotated version of this object. - */ - virtual std::shared_ptr rotated(int degreeCW) const; - - /** - * @return a wrapper of this {@code LuminanceSource} which inverts the luminances it returns -- black becomes - * white and vice versa, and each value becomes (255-value). - */ - static std::shared_ptr CreateInverted(const std::shared_ptr& src); - -protected: - virtual std::shared_ptr getInverted() const; - -}; - -} // ZXing diff --git a/core/src/MultiFormatReader.cpp b/core/src/MultiFormatReader.cpp index 7418803c30..7f01e33ee7 100644 --- a/core/src/MultiFormatReader.cpp +++ b/core/src/MultiFormatReader.cpp @@ -19,7 +19,6 @@ #include "BarcodeFormat.h" #include "DecodeHints.h" -#include "Result.h" #include "aztec/AZReader.h" #include "datamatrix/DMReader.h" #include "maxicode/MCReader.h" @@ -75,4 +74,26 @@ MultiFormatReader::read(const BinaryBitmap& image) const return Result(DecodeStatus::NotFound); } +Results MultiFormatReader::readMultiple(const BinaryBitmap& image, int maxSymbols) const +{ + std::vector res; + + for (const auto& reader : _readers) { + auto r = reader->decode(image, maxSymbols); + maxSymbols -= r.size(); + res.insert(res.end(), std::move_iterator(r.begin()), std::move_iterator(r.end())); + if (maxSymbols <= 0) + break; + } + + // sort results based on their position on the image + std::sort(res.begin(), res.end(), [](const Result& l, const Result& r) { + auto lp = l.position().topLeft(); + auto rp = r.position().topLeft(); + return lp.y < rp.y || (lp.y == rp.y && lp.x <= rp.x); + }); + + return res; +} + } // ZXing diff --git a/core/src/MultiFormatReader.h b/core/src/MultiFormatReader.h index 0da0a140d1..defafc4b1f 100644 --- a/core/src/MultiFormatReader.h +++ b/core/src/MultiFormatReader.h @@ -16,6 +16,8 @@ * limitations under the License. */ +#include "Result.h" + #include #include @@ -42,6 +44,9 @@ class MultiFormatReader Result read(const BinaryBitmap& image) const; + // WARNING: this API is experimental and may change/disappear + Results readMultiple(const BinaryBitmap& image, int maxSymbols = 0xFF) const; + private: std::vector> _readers; }; diff --git a/core/src/MultiFormatWriter.h b/core/src/MultiFormatWriter.h index 7816b5023c..1ec252aa0f 100644 --- a/core/src/MultiFormatWriter.h +++ b/core/src/MultiFormatWriter.h @@ -52,7 +52,7 @@ class MultiFormatWriter } /** - * Used for all formats, sets the minimum number of quite zone pixels. + * Used for all formats, sets the minimum number of quiet zone pixels. */ MultiFormatWriter& setMargin(int margin) { _margin = margin; diff --git a/core/src/Pattern.h b/core/src/Pattern.h index c4a3f7c512..c317be3e57 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -78,13 +78,13 @@ class PatternView bool isValid() const { return isValid(size()); } template - bool hasQuiteZoneBefore(float scale) const + bool hasQuietZoneBefore(float scale) const { return (acceptIfAtFirstBar && isAtFirstBar()) || _data[-1] >= sum() * scale; } template - bool hasQuiteZoneAfter(float scale) const + bool hasQuietZoneAfter(float scale) const { return (acceptIfAtLastBar && isAtLastBar()) || _data[_size] >= sum() * scale; } @@ -124,7 +124,7 @@ class PatternView void extend() { - _size = static_cast(_end - _data); + _size = std::max(0, static_cast(_end - _data)); } }; @@ -168,7 +168,7 @@ using FixedSparcePattern = FixedPattern; template float IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, - float minQuiteZone = 0, float moduleSizeRef = 0.f) + float minQuietZone = 0, float moduleSizeRef = 0) { int width = view.sum(N); if (SUM > N && width < SUM) @@ -176,7 +176,7 @@ float IsPattern(const PatternView& view, const FixedPattern& patt const float moduleSize = (float)width / SUM; - if (minQuiteZone && spaceInPixel < minQuiteZone * moduleSize - 1) + if (minQuietZone && spaceInPixel < minQuietZone * moduleSize - 1) return 0; if (!moduleSizeRef) @@ -195,7 +195,7 @@ float IsPattern(const PatternView& view, const FixedPattern& patt template float IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, - float minQuiteZone = 0, float moduleSizeRef = 0.f) + float minQuietZone = 0, float moduleSizeRef = 0) { // pattern contains the indices with the bars/spaces that need to be equally wide int width = 0; @@ -204,7 +204,7 @@ float IsPattern(const PatternView& view, const FixedPattern& patte const float moduleSize = (float)width / SUM; - if (minQuiteZone && spaceInPixel < minQuiteZone * moduleSize - 1) + if (minQuietZone && spaceInPixel < minQuietZone * moduleSize - 1) return 0; if (!moduleSizeRef) @@ -222,11 +222,11 @@ float IsPattern(const PatternView& view, const FixedPattern& patte } template -bool IsRightGuard(const PatternView& view, const FixedPattern& pattern, float minQuiteZone, +bool IsRightGuard(const PatternView& view, const FixedPattern& pattern, float minQuietZone, float moduleSizeRef = 0.f) { int spaceInPixel = view.isAtLastBar() ? std::numeric_limits::max() : *view.end(); - return IsPattern(view, pattern, spaceInPixel, minQuiteZone, moduleSizeRef) != 0; + return IsPattern(view, pattern, spaceInPixel, minQuietZone, moduleSizeRef) != 0; } template @@ -247,11 +247,11 @@ PatternView FindLeftGuard(const PatternView& view, int minSize, Pred isGuard) template PatternView FindLeftGuard(const PatternView& view, int minSize, const FixedPattern& pattern, - float minQuiteZone) + float minQuietZone) { return FindLeftGuard(view, std::max(minSize, LEN), - [&pattern, minQuiteZone](const PatternView& window, int spaceInPixel) { - return IsPattern(window, pattern, spaceInPixel, minQuiteZone); + [&pattern, minQuietZone](const PatternView& window, int spaceInPixel) { + return IsPattern(window, pattern, spaceInPixel, minQuietZone); }); } diff --git a/core/src/PerspectiveTransform.h b/core/src/PerspectiveTransform.h index 75894ed3ce..b02ce66fe1 100644 --- a/core/src/PerspectiveTransform.h +++ b/core/src/PerspectiveTransform.h @@ -20,8 +20,6 @@ #include "Point.h" #include "Quadrilateral.h" -#include - namespace ZXing { /** diff --git a/core/src/Point.h b/core/src/Point.h index 9f734291d1..66b8e41f4e 100644 --- a/core/src/Point.h +++ b/core/src/Point.h @@ -149,7 +149,7 @@ inline PointF centered(PointF p) template PointF normalized(PointT d) { - return PointF(d) / length(d); + return PointF(d) / length(PointF(d)); } template @@ -161,7 +161,6 @@ PointT bresenhamDirection(PointT d) template PointT mainDirection(PointT d) { - assert(std::abs(d.x) != std::abs(d.y)); return std::abs(d.x) > std::abs(d.y) ? PointT(d.x, 0) : PointT(0, d.y); } diff --git a/core/src/Quadrilateral.h b/core/src/Quadrilateral.h index d11117a4ea..23d7e76a8f 100644 --- a/core/src/Quadrilateral.h +++ b/core/src/Quadrilateral.h @@ -102,6 +102,36 @@ bool IsConvex(const Quadrilateral& poly) return M / m < 4.0; } +template +Quadrilateral Scale(const Quadrilateral& q, int factor) +{ + return {factor * q[0], factor * q[1], factor * q[2], factor * q[3]}; +} + +template +PointT Center(const Quadrilateral& q) +{ + return Reduce(q) / Size(q); +} + +template +bool IsInside(const PointT& p, const Quadrilateral& q) +{ + // Test if p is on the same side (right or left) of all polygon segments + int pos = 0, neg = 0; + for (int i = 0; i < Size(q); ++i) + (cross(p - q[i], q[(i + 1) % Size(q)] - q[i]) < 0 ? neg : pos)++; + return pos == 0 || neg == 0; +} + +template +bool HaveIntersectingBoundingBoxes(const Quadrilateral& a, const Quadrilateral& b) +{ + // TODO: this is only a quick and dirty approximation that works for the trivial standard cases + bool x = b.topRight().x < a.topLeft().x || b.topLeft().x > a.topRight().x; + bool y = b.bottomLeft().y < a.topLeft().y || b.topLeft().y > a.bottomLeft().y; + return !(x || y); +} } // ZXing diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index c96115a63c..add15b2db7 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -16,67 +16,161 @@ #include "ReadBarcode.h" -#include "BinaryBitmap.h" -#include "BitArray.h" -#include "BitMatrix.h" #include "DecodeHints.h" -#include "GenericLuminanceSource.h" #include "GlobalHistogramBinarizer.h" #include "HybridBinarizer.h" #include "MultiFormatReader.h" #include "ThresholdBinarizer.h" #include +#include namespace ZXing { -static Result ReadBarcode(GenericLuminanceSource&& source, const DecodeHints& hints) +class LumImage : public ImageView { - MultiFormatReader reader(hints); - auto srcPtr = std::shared_ptr(&source, [](void*) {}); + std::unique_ptr _memory; + LumImage(std::unique_ptr&& data, int w, int h) + : ImageView(data.get(), w, h, ImageFormat::Lum), _memory(std::move(data)) + {} + +public: + LumImage() : ImageView(nullptr, 0, 0, ImageFormat::Lum) {} + LumImage(int w, int h) : LumImage(std::make_unique(w * h), w, h) {} + + uint8_t* data() { return _memory.get(); } +}; + +template +static LumImage ExtractLum(const ImageView& iv, P projection) +{ + LumImage res(iv.width(), iv.height()); + + auto* dst = res.data(); + for(int y = 0; y < iv.height(); ++y) + for(int x = 0, w = iv.width(); x < w; ++x) + *dst++ = projection(iv.data(x, y)); + + return res; +} + +class LumImagePyramid +{ + int N = 3; + std::vector buffers; + + void addLayer() + { + auto siv = layers.back(); + buffers.emplace_back(siv.width() / N, siv.height() / N); + layers.push_back(buffers.back()); + auto& div = buffers.back(); + auto* d = div.data(); + + for (int dy = 0; dy < div.height(); ++dy) + for (int dx = 0; dx < div.width(); ++dx) { + int sum = (N * N) / 2; + for (int ty = 0; ty < N; ++ty) + for (int tx = 0; tx < N; ++tx) + sum += *siv.data(dx * N + tx, dy * N + ty); + *d++ = sum / (N * N); + } + } + +public: + std::vector layers; - if (hints.binarizer() == Binarizer::LocalAverage) - return reader.read(HybridBinarizer(srcPtr)); - else - return reader.read(GlobalHistogramBinarizer(srcPtr)); + LumImagePyramid(const ImageView& iv, int threshold, int factor) : N(factor) + { + if (factor < 2) + throw std::invalid_argument("Invalid DecodeHints::downscaleFactor"); + + layers.push_back(iv); + while (threshold > 0 && std::min(layers.back().width(), layers.back().height()) > threshold) + addLayer(); +#if 0 + // Reversing the layers means we'd start with the smallest. that can make sense if we are only looking for a + // single symbol. If we start with the higher resolution, we get better (high res) position information. + // TODO: see if masking out higher res layers based on found symbols in lower res helps overall performance. + std::reverse(layers.begin(), layers.end()); +#endif + } +}; + +ImageView SetupLumImageView(ImageView iv, LumImage& lum, const DecodeHints& hints) +{ + if (iv.format() == ImageFormat::None) + throw std::invalid_argument("Invalid image format"); + + if (hints.binarizer() == Binarizer::GlobalHistogram || hints.binarizer() == Binarizer::LocalAverage) { + if (iv.format() != ImageFormat::Lum) { + lum = ExtractLum(iv, [r = RedIndex(iv.format()), g = GreenIndex(iv.format()), b = BlueIndex(iv.format())]( + const uint8_t* src) { return RGBToLum(src[r], src[g], src[b]); }); + } else if (iv.pixStride() != 1) { + // GlobalHistogram and LocalAverage need dense line memory layout + lum = ExtractLum(iv, [](const uint8_t* src) { return *src; }); + } + if (lum.data()) + return lum; + } + return iv; } -Result ReadBarcode(const ImageView& iv, const DecodeHints& hints) +std::unique_ptr CreateBitmap(ZXing::Binarizer binarizer, const ImageView& iv) { - switch (hints.binarizer()) { - case Binarizer::BoolCast: return MultiFormatReader(hints).read(ThresholdBinarizer(iv, 0)); - case Binarizer::FixedThreshold: return MultiFormatReader(hints).read(ThresholdBinarizer(iv, 127)); - default: - return ReadBarcode( - { - 0, - 0, - iv._width, - iv._height, - iv._data, - iv._rowStride, - iv._pixStride, - RedIndex(iv._format), - GreenIndex(iv._format), - BlueIndex(iv._format), - nullptr - }, - hints); + switch (binarizer) { + case Binarizer::BoolCast: return std::make_unique(iv, 0); + case Binarizer::FixedThreshold: return std::make_unique(iv, 127); + case Binarizer::GlobalHistogram: return std::make_unique(iv); + case Binarizer::LocalAverage: return std::make_unique(iv); } + return {}; // silence gcc warning } -Result ReadBarcode(int width, int height, const uint8_t* data, int rowStride, BarcodeFormats formats, bool tryRotate, - bool tryHarder) +Result ReadBarcode(const ImageView& _iv, const DecodeHints& hints) { - return ReadBarcode({0, 0, width, height, data, rowStride, 1, 0, 0, 0, nullptr}, - DecodeHints().setTryHarder(tryHarder).setTryRotate(tryRotate).setFormats(formats)); + if (hints.maxNumberOfSymbols() == 1) { + // HACK: use the maxNumberOfSymbols value as a switch to ReadBarcodes to enable the downscaling + // see python and android wrapper + auto ress = ReadBarcodes(_iv, DecodeHints(hints).setMaxNumberOfSymbols(1)); + return ress.empty() ? Result(DecodeStatus::NotFound) : ress.front(); + } else { + LumImage lum; + ImageView iv = SetupLumImageView(_iv, lum, hints); + + return MultiFormatReader(hints).read(*CreateBitmap(hints.binarizer(), iv)); + } } -Result ReadBarcode(int width, int height, const uint8_t* data, int rowStride, int pixelStride, int rIndex, int gIndex, - int bIndex, BarcodeFormats formats, bool tryRotate, bool tryHarder) +Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints) { - return ReadBarcode({0, 0, width, height, data, rowStride, pixelStride, rIndex, gIndex, bIndex, nullptr}, - DecodeHints().setTryHarder(tryHarder).setTryRotate(tryRotate).setFormats(formats)); + LumImage lum; + ImageView iv = SetupLumImageView(_iv, lum, hints); + MultiFormatReader reader(hints); + + if (hints.isPure()) + return {reader.read(*CreateBitmap(hints.binarizer(), iv))}; + + LumImagePyramid pyramid(iv, hints.downscaleThreshold() * hints.tryDownscale(), hints.downscaleFactor()); + + Results results; + int maxSymbols = hints.maxNumberOfSymbols(); + for (auto&& iv : pyramid.layers) { + auto bitmap = CreateBitmap(hints.binarizer(), iv); + auto rs = reader.readMultiple(*bitmap, maxSymbols); + for (auto& r : rs) { + if (iv.width() != _iv.width()) + r.setPosition(Scale(r.position(), _iv.width() / iv.width())); + if (!Contains(results, r)) { + results.push_back(std::move(r)); + --maxSymbols; + } + } + if (maxSymbols <= 0) + break; + } + + return results; } } // ZXing diff --git a/core/src/ReadBarcode.h b/core/src/ReadBarcode.h index 4204ec3764..3bbc48f7b4 100644 --- a/core/src/ReadBarcode.h +++ b/core/src/ReadBarcode.h @@ -16,80 +16,11 @@ */ #include "DecodeHints.h" +#include "ImageView.h" #include "Result.h" -#include - namespace ZXing { -enum class ImageFormat : uint32_t -{ - None = 0, - Lum = 0x01000000, - RGB = 0x03000102, - BGR = 0x03020100, - RGBX = 0x04000102, - XRGB = 0x04010203, - BGRX = 0x04020100, - XBGR = 0x04030201, -}; - -constexpr inline int PixStride(ImageFormat format) { return (static_cast(format) >> 3*8) & 0xFF; } -constexpr inline int RedIndex(ImageFormat format) { return (static_cast(format) >> 2*8) & 0xFF; } -constexpr inline int GreenIndex(ImageFormat format) { return (static_cast(format) >> 1*8) & 0xFF; } -constexpr inline int BlueIndex(ImageFormat format) { return (static_cast(format) >> 0*8) & 0xFF; } - -/** - * Simple class that stores a non-owning const pointer to image data plus layout and format information. - */ -class ImageView -{ -protected: - const uint8_t* _data = nullptr; - ImageFormat _format; - int _width = 0, _height = 0, _pixStride = 0, _rowStride = 0; - - friend Result ReadBarcode(const ImageView&, const DecodeHints&); - friend class ThresholdBinarizer; - -public: - /** - * ImageView contructor - * - * @param data pointer to image buffer - * @param width image width in pixels - * @param height image height in pixels - * @param format image/pixel format - * @param rowStride optional row stride in bytes, default is width * pixStride - * @param pixStride optional pixel stride in bytes, default is calculated from format - */ - ImageView(const uint8_t* data, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) - : _data(data), _format(format), _width(width), _height(height), - _pixStride(pixStride ? pixStride : PixStride(format)), _rowStride(rowStride ? rowStride : width * _pixStride) - {} - - const uint8_t* data(int x, int y) const { return _data + y * _rowStride + x * _pixStride; } - - ImageView cropped(int left, int top, int width, int height) const - { - left = std::max(0, left); - top = std::max(0, top); - width = width <= 0 ? (_width - left) : std::min(_width - left, width); - height = height <= 0 ? (_height - top) : std::min(_height - top, height); - return {data(left, top), width, height, _format, _rowStride, _pixStride}; - } - - ImageView rotated(int degree) const - { - switch ((degree + 360) % 360) { - case 90: return {data(0, _height - 1), _height, _width, _format, _pixStride, -_rowStride}; - case 180: return {data(_width - 1, _height - 1), _width, _height, _format, -_rowStride, -_pixStride}; - case 270: return {data(_width - 1, 0), _height, _width, _format, -_pixStride, _rowStride}; - } - return *this; - } -}; - /** * Read barcode from an ImageView * @@ -99,14 +30,8 @@ class ImageView */ Result ReadBarcode(const ImageView& buffer, const DecodeHints& hints = {}); - -[[deprecated]] -Result ReadBarcode(int width, int height, const uint8_t* data, int rowStride, - BarcodeFormats formats = {}, bool tryRotate = true, bool tryHarder = true); - -[[deprecated]] -Result ReadBarcode(int width, int height, const uint8_t* data, int rowStride, int pixelStride, int rIndex, int gIndex, int bIndex, - BarcodeFormats formats = {}, bool tryRotate = true, bool tryHarder = true); +// WARNING: this API is experimental and may change/disappear +Results ReadBarcodes(const ImageView& buffer, const DecodeHints& hints = {}); } // ZXing diff --git a/core/src/Reader.h b/core/src/Reader.h index 1ae56ca51a..642ac9c35f 100644 --- a/core/src/Reader.h +++ b/core/src/Reader.h @@ -16,10 +16,11 @@ * limitations under the License. */ +#include "Result.h" + namespace ZXing { class BinaryBitmap; -class Result; /** * Implementations of this interface can decode an image of a barcode in some format into @@ -55,6 +56,12 @@ class Reader * @throws FormatException if a potential barcode is found but format is invalid */ virtual Result decode(const BinaryBitmap& image) const = 0; + + // WARNING: this API is experimental and may change/disappear + virtual Results decode(const BinaryBitmap& image, [[maybe_unused]] int maxSymbols) const { + auto res = decode(image); + return res.isValid() ? Results{std::move(res)} : Results{}; + } }; } // ZXing diff --git a/core/src/ReedSolomonDecoder.h b/core/src/ReedSolomonDecoder.h index c71b3e6654..d6f001ffa0 100644 --- a/core/src/ReedSolomonDecoder.h +++ b/core/src/ReedSolomonDecoder.h @@ -50,7 +50,7 @@ class GenericGF; * * @param message data and error-correction/parity codewords * @param numECCodeWords number of error-correction code words - * @return true iff message errors could sucessfully be fixed (or there have not been any) + * @return true iff message errors could successfully be fixed (or there have not been any) */ bool ReedSolomonDecode(const GenericGF& field, std::vector& message, int numECCodeWords); diff --git a/core/src/Result.cpp b/core/src/Result.cpp index 4b37224b58..19ef576fac 100644 --- a/core/src/Result.cpp +++ b/core/src/Result.cpp @@ -25,31 +25,28 @@ namespace ZXing { -Result::Result(std::wstring&& text, Position&& position, BarcodeFormat format, ByteArray&& rawBytes, - const bool readerInit) +Result::Result(std::wstring&& text, Position&& position, BarcodeFormat format, std::string&& symbologyIdentifier, + ByteArray&& rawBytes, StructuredAppendInfo&& sai, const bool readerInit, int lineCount) : _format(format), _text(std::move(text)), _position(std::move(position)), _rawBytes(std::move(rawBytes)), - _readerInit(readerInit) + _symbologyIdentifier(std::move(symbologyIdentifier)), _sai(std::move(sai)), _readerInit(readerInit), + _lineCount(lineCount) { _numBits = Size(_rawBytes) * 8; } -Result::Result(const std::string& text, int y, int xStart, int xStop, BarcodeFormat format, ByteArray&& rawBytes, - const bool readerInit) - : Result(TextDecoder::FromLatin1(text), Line(y, xStart, xStop), format, std::move(rawBytes), readerInit) +Result::Result(const std::string& text, int y, int xStart, int xStop, BarcodeFormat format, + std::string&& symbologyIdentifier, ByteArray&& rawBytes, const bool readerInit) + : Result(TextDecoder::FromLatin1(text), Line(y, xStart, xStop), format, std::move(symbologyIdentifier), + std::move(rawBytes), {}, readerInit) {} Result::Result(DecoderResult&& decodeResult, Position&& position, BarcodeFormat format) : _status(decodeResult.errorCode()), _format(format), _text(std::move(decodeResult).text()), _position(std::move(position)), _rawBytes(std::move(decodeResult).rawBytes()), _numBits(decodeResult.numBits()), - _ecLevel(decodeResult.ecLevel()), _sai(decodeResult.structuredAppend()), _readerInit(decodeResult.readerInit()) + _ecLevel(decodeResult.ecLevel()), _symbologyIdentifier(decodeResult.symbologyIdentifier()), + _sai(decodeResult.structuredAppend()), _isMirrored(decodeResult.isMirrored()), + _readerInit(decodeResult.readerInit()) { - // TODO: keep that for one release so people get the deprecation warning with a still intact functionality - if (isPartOfSequence()) { - _metadata.put(ResultMetadata::STRUCTURED_APPEND_SEQUENCE, sequenceIndex()); - _metadata.put(ResultMetadata::STRUCTURED_APPEND_CODE_COUNT, sequenceSize()); - if (_format == BarcodeFormat::QRCode) - _metadata.put(ResultMetadata::STRUCTURED_APPEND_PARITY, std::stoi(sequenceId())); - } // TODO: add type opaque and code specific 'extra data'? (see DecoderResult::extra()) } @@ -60,4 +57,21 @@ int Result::orientation() const return std::lround(_position.orientation() * 180 / std_numbers_pi_v); } +bool Result::operator==(const Result& o) const +{ + if (format() != o.format() || text() != o.text()) + return false; + + if (BarcodeFormats(BarcodeFormat::TwoDCodes).testFlag(format())) + return IsInside(Center(o.position()), position()); + + // if one line is less than half the length of the other away from the + // latter, we consider it to belong to the same symbol + auto dTop = maxAbsComponent(o.position().topLeft() - position().topLeft()); + auto dBot = maxAbsComponent(o.position().bottomLeft() - position().topLeft()); + auto length = maxAbsComponent(position().topLeft() - position().bottomRight()); + + return std::min(dTop, dBot) < length / 2; +} + } // ZXing diff --git a/core/src/Result.h b/core/src/Result.h index 0d3363a85d..c44c95455c 100644 --- a/core/src/Result.h +++ b/core/src/Result.h @@ -21,12 +21,9 @@ #include "ByteArray.h" #include "DecodeStatus.h" #include "Quadrilateral.h" -#include "ResultMetadata.h" -#include "ResultPoint.h" #include "StructuredAppend.h" #include -#include #include namespace ZXing { @@ -45,12 +42,13 @@ class Result public: explicit Result(DecodeStatus status) : _status(status) {} - Result(std::wstring&& text, Position&& position, BarcodeFormat format, ByteArray&& rawBytes = {}, - const bool readerInit = false); + Result(std::wstring&& text, Position&& position, BarcodeFormat format, std::string&& symbologyIdentifier = "", + ByteArray&& rawBytes = {}, StructuredAppendInfo&& sai = {}, const bool readerInit = false, + int lineCount = 0); // 1D convenience constructor - Result(const std::string& text, int y, int xStart, int xStop, BarcodeFormat format, ByteArray&& rawBytes = {}, - const bool readerInit = false); + Result(const std::string& text, int y, int xStart, int xStop, BarcodeFormat format, + std::string&& symbologyIdentifier = "", ByteArray&& rawBytes = {}, const bool readerInit = false); Result(DecoderResult&& decodeResult, Position&& position, BarcodeFormat format); @@ -66,20 +64,10 @@ class Result return _format; } - [[deprecated]] - void setFormat(BarcodeFormat format) { - _format = format; - } - const std::wstring& text() const { return _text; } - [[deprecated]] - void setText(std::wstring&& text) { - _text = std::move(text); - } - const Position& position() const { return _position; } @@ -89,10 +77,17 @@ class Result int orientation() const; //< orientation of barcode in degree, see also Position::orientation() + /** + * @brief isMirrored is the symbol mirrored (currently only supported by QRCode and DataMatrix) + */ + bool isMirrored() const { + return _isMirrored; + } + const ByteArray& rawBytes() const { return _rawBytes; } - + int numBits() const { return _numBits; } @@ -101,19 +96,11 @@ class Result return _ecLevel; } - [[deprecated]] - std::vector resultPoints() const { - return {position().begin(), position().end()}; - } - - [[deprecated]] - const ResultMetadata& metadata() const { - return _metadata; - } - - [[deprecated]] - ResultMetadata& metadata() { - return _metadata; + /** + * @brief symbologyIdentifier Symbology identifier "]cm" where "c" is symbology code character, "m" the modifier. + */ + const std::string& symbologyIdentifier() const { + return _symbologyIdentifier; } /** @@ -149,6 +136,18 @@ class Result return _readerInit; } + /** + * @brief How many lines have been detected with this code (applies only to 1D symbologies) + */ + int lineCount() const { + return _lineCount; + } + void incrementLineCount() { + ++_lineCount; + } + + bool operator==(const Result& o) const; + private: DecodeStatus _status = DecodeStatus::NoError; BarcodeFormat _format = BarcodeFormat::None; @@ -157,9 +156,13 @@ class Result ByteArray _rawBytes; int _numBits = 0; std::wstring _ecLevel; - ResultMetadata _metadata; + std::string _symbologyIdentifier; StructuredAppendInfo _sai; + bool _isMirrored = false; bool _readerInit = false; + int _lineCount = 0; }; +using Results = std::vector; + } // ZXing diff --git a/core/src/ResultMetadata.cpp b/core/src/ResultMetadata.cpp deleted file mode 100644 index ec228e266a..0000000000 --- a/core/src/ResultMetadata.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -* -* 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. -*/ - -#include "ResultMetadata.h" - -#include "ByteArray.h" - -#include - -namespace ZXing { - -struct ResultMetadata::Value -{ - virtual ~Value() = default; - virtual int toInteger(int fallback) const { - return fallback; - } - virtual std::wstring toString() const { - return std::wstring(); - } - virtual std::list toByteArrayList() const { - return std::list(); - } - virtual std::shared_ptr toCustomData() const { - return nullptr; - } -}; - -struct ResultMetadata::IntegerValue : public Value -{ - int value; - explicit IntegerValue(int v) : value(v) {} - int toInteger(int) const override { - return value; - } - std::wstring toString() const override { - return std::to_wstring(value); - } -}; - -struct ResultMetadata::StringValue : public Value -{ - std::wstring value; - explicit StringValue(std::wstring v) : value(std::move(v)) {} - std::wstring toString() const override { - return value; - } -}; - -struct ResultMetadata::ByteArrayListValue : public Value -{ - std::list value; - explicit ByteArrayListValue(std::list v) : value(std::move(v)) {} - std::list toByteArrayList() const override { - return value; - } -}; - -struct ResultMetadata::CustomDataValue : public Value -{ - std::shared_ptr value; - explicit CustomDataValue(std::shared_ptr v) : value(std::move(v)) {} - std::shared_ptr toCustomData() const override { - return value; - } -}; - -int -ResultMetadata::getInt(Key key, int fallbackValue) const -{ - auto it = _contents.find(key); - return it != _contents.end() ? it->second->toInteger(fallbackValue) : fallbackValue; -} - -std::wstring -ResultMetadata::getString(Key key) const -{ - auto it = _contents.find(key); - return it != _contents.end() ? it->second->toString() : std::wstring(); -} - -std::list -ResultMetadata::getByteArrayList(Key key) const -{ - auto it = _contents.find(key); - return it != _contents.end() ? it->second->toByteArrayList() : std::list(); -} - -std::shared_ptr -ResultMetadata::getCustomData(Key key) const -{ - auto it = _contents.find(key); - return it != _contents.end() ? it->second->toCustomData() : nullptr; -} - -void -ResultMetadata::put(Key key, int value) -{ - _contents[key] = std::make_shared(value); -} - -void -ResultMetadata::put(Key key, const std::wstring& value) -{ - _contents[key] = std::make_shared(value); -} - -void -ResultMetadata::put(Key key, const std::list& value) -{ - _contents[key] = std::make_shared(value); -} - -void -ResultMetadata::put(Key key, const std::shared_ptr& value) -{ - _contents[key] = std::make_shared(value); -} - -void -ResultMetadata::putAll(const ResultMetadata& other) -{ - _contents.insert(other._contents.begin(), other._contents.end()); -} - -} // ZXing diff --git a/core/src/ResultMetadata.h b/core/src/ResultMetadata.h deleted file mode 100644 index 314a8a1ace..0000000000 --- a/core/src/ResultMetadata.h +++ /dev/null @@ -1,142 +0,0 @@ -#pragma once -/* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -* -* 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. -*/ - -#include -#include -#include -#include - -namespace ZXing { - -class ByteArray; -class CustomData; - -/** -* @author Sean Owen -*/ -class ResultMetadata -{ -public: - - /** - * Represents some type of metadata about the result of the decoding that the decoder - * wishes to communicate back to the caller. - */ - enum Key { - - /** - * Unspecified, application-specific metadata. Maps to an unspecified {@link CustomData}. - */ - OTHER [[deprecated]], - - /** - * Denotes the likely approximate orientation of the barcode in the image. This value - * is given as degrees rotated clockwise from the normal, upright orientation. - * For example a 1D barcode which was found by reading top-to-bottom would be - * said to have orientation "90". This key maps to an {@link Integer} whose - * value is in the range [0,360). - */ - ORIENTATION [[deprecated]], - - /** - *

2D barcode formats typically encode text, but allow for a sort of 'byte mode' - * which is sometimes used to encode binary data. While {@link Result} makes available - * the complete raw bytes in the barcode for these formats, it does not offer the bytes - * from the byte segments alone.

- * - *

This maps to a {@link java.util.List} of byte arrays corresponding to the - * raw bytes in the byte segments in the barcode, in order.

- */ - BYTE_SEGMENTS [[deprecated]], - - /** - * Error correction level used, if applicable. The value type depends on the - * format, but is typically a String. - */ - ERROR_CORRECTION_LEVEL [[deprecated]], - - /** - * For some periodicals, indicates the issue number as an {@link Integer}. - */ - ISSUE_NUMBER [[deprecated]], - - /** - * For some products, indicates the suggested retail price in the barcode as a - * formatted {@link String}. - */ - SUGGESTED_PRICE [[deprecated]], - - /** - * For some products, the possible country of manufacture as a {@link String} denoting the - * ISO country code. Some map to multiple possible countries, like "US/CA". - */ - POSSIBLE_COUNTRY [[deprecated]], - - /** - * For some products, the extension text - */ - UPC_EAN_EXTENSION [[deprecated]], - - /** - * PDF417-specific metadata - */ - PDF417_EXTRA_METADATA [[deprecated]], - - /** - * If the code format supports structured append and the current scanned code is part of one then the - * sequence number is given with it. - */ - STRUCTURED_APPEND_SEQUENCE [[deprecated]], - - /** - * If the code format supports structured append and the current scanned code is part of one then the - * total code count is given with it. - */ - STRUCTURED_APPEND_CODE_COUNT [[deprecated]], - - /** - * If the code format supports structured append and the current scanned code is part of one then the - * parity is given with it. - */ - STRUCTURED_APPEND_PARITY [[deprecated]], - - }; - - int getInt(Key key, int fallbackValue = 0) const; - std::wstring getString(Key key) const; - std::list getByteArrayList(Key key) const; - std::shared_ptr getCustomData(Key key) const; - - void put(Key key, int value); - void put(Key key, const std::wstring& value); - void put(Key key, const std::list& value); - void put(Key key, const std::shared_ptr& value); - - void putAll(const ResultMetadata& other); - -private: - struct Value; - struct IntegerValue; - struct StringValue; - struct ByteArrayListValue; - struct CustomDataValue; - - std::map> _contents; -}; - -} // ZXing diff --git a/core/src/TextUtfEncoding.cpp b/core/src/TextUtfEncoding.cpp index 57674383f6..b2e3e1e4e7 100644 --- a/core/src/TextUtfEncoding.cpp +++ b/core/src/TextUtfEncoding.cpp @@ -19,7 +19,6 @@ #include #include #include -#include namespace ZXing::TextUtfEncoding { diff --git a/core/src/ThresholdBinarizer.h b/core/src/ThresholdBinarizer.h index 51411082a1..160ff5a960 100644 --- a/core/src/ThresholdBinarizer.h +++ b/core/src/ThresholdBinarizer.h @@ -17,36 +17,31 @@ #include "BinaryBitmap.h" #include "BitMatrix.h" -#include "ReadBarcode.h" #include -#include namespace ZXing { -class LuminanceSource; - class ThresholdBinarizer : public BinaryBitmap { - const ImageView _buffer; const uint8_t _threshold = 0; - mutable std::shared_ptr _cache; public: - ThresholdBinarizer(const ImageView& buffer, uint8_t threshold = 1) : _buffer(buffer), _threshold(threshold) {} - - int width() const override { return _buffer._width; } - int height() const override { return _buffer._height; } + ThresholdBinarizer(const ImageView& buffer, uint8_t threshold = 1) : BinaryBitmap(buffer), _threshold(threshold) {} - bool getPatternRow(int y, PatternRow& res) const override + bool getPatternRow(int row, int rotation, PatternRow& res) const override { - const int stride = _buffer._pixStride; - const uint8_t* begin = _buffer.data(0, y) + GreenIndex(_buffer._format); - const uint8_t* end = begin + _buffer._width * stride; + auto buffer = _buffer.rotated(rotation); + + const int stride = buffer.pixStride(); + const uint8_t* begin = buffer.data(0, row) + GreenIndex(buffer.format()); + const uint8_t* end = begin + buffer.width() * stride; auto* lastPos = begin; bool lastVal = false; + res.clear(); + for (const uint8_t* p = begin; p < end; p += stride) { bool val = *p <= _threshold; if (val != lastVal) { @@ -66,40 +61,37 @@ class ThresholdBinarizer : public BinaryBitmap std::shared_ptr getBlackMatrix() const override { - if (!_cache) { - BitMatrix res(width(), height()); + BitMatrix res(width(), height()); #ifdef ZX_FAST_BIT_STORAGE - if (_buffer._pixStride == 1 && _buffer._rowStride == _buffer._width) { - // Specialize for a packed buffer with pixStride 1 to support auto vectorization (16x speedup on AVX2) - auto dst = res.row(0).begin(); - for (auto src = _buffer.data(0, 0), end = _buffer.data(0, height()); src != end; ++src, ++dst) - *dst = *src <= _threshold; - } else { - auto processLine = [this, &res](int y, const auto* src, const int stride) { - for (auto& dst : res.row(y)) { - dst = *src <= _threshold; - src += stride; - } - }; - for (int y = 0; y < res.height(); ++y) { - auto src = _buffer.data(0, y) + GreenIndex(_buffer._format); - // Specialize the inner loop for strides 1 and 4 to support auto vectorization - switch (_buffer._pixStride) { - case 1: processLine(y, src, 1); break; - case 4: processLine(y, src, 4); break; - default: processLine(y, src, _buffer._pixStride); break; - } + if (_buffer.pixStride() == 1 && _buffer.rowStride() == _buffer.width()) { + // Specialize for a packed buffer with pixStride 1 to support auto vectorization (16x speedup on AVX2) + auto dst = res.row(0).begin(); + for (auto src = _buffer.data(0, 0), end = _buffer.data(0, height()); src != end; ++src, ++dst) + *dst = *src <= _threshold; + } else { + auto processLine = [this, &res](int y, const auto* src, const int stride) { + for (auto& dst : res.row(y)) { + dst = *src <= _threshold; + src += stride; + } + }; + for (int y = 0; y < res.height(); ++y) { + auto src = _buffer.data(0, y) + GreenIndex(_buffer.format()); + // Specialize the inner loop for strides 1 and 4 to support auto vectorization + switch (_buffer.pixStride()) { + case 1: processLine(y, src, 1); break; + case 4: processLine(y, src, 4); break; + default: processLine(y, src, _buffer.pixStride()); break; } } + } #else - const int channel = GreenIndex(_buffer._format); - for (int y = 0; y < res.height(); ++y) - for (int x = 0; x < res.width(); ++x) - res.set(x, y, _buffer.data(x, y)[channel] <= _threshold); + const int channel = GreenIndex(_buffer.format()); + for (int y = 0; y < res.height(); ++y) + for (int x = 0; x < res.width(); ++x) + res.set(x, y, _buffer.data(x, y)[channel] <= _threshold); #endif - _cache = std::make_shared(std::move(res)); - } - return _cache; + return std::make_shared(std::move(res)); } }; diff --git a/core/src/ZXBigInteger.cpp b/core/src/ZXBigInteger.cpp index 841b438d31..1bb9ee7b70 100644 --- a/core/src/ZXBigInteger.cpp +++ b/core/src/ZXBigInteger.cpp @@ -365,7 +365,7 @@ static int CompareMag(const Magnitude& a, const Magnitude& b) // Compare blocks one by one from left to right. auto p = std::mismatch(a.rbegin(), a.rend(), b.rbegin()); if (p.first != a.rend()) { - return *p.first < *p.second ? -1 : 1; // note: cannot use substraction here + return *p.first < *p.second ? -1 : 1; // note: cannot use subtraction here } return 0; } diff --git a/core/src/aztec/AZDecoder.cpp b/core/src/aztec/AZDecoder.cpp index 6fda14dfcc..2e196ad3bc 100644 --- a/core/src/aztec/AZDecoder.cpp +++ b/core/src/aztec/AZDecoder.cpp @@ -21,8 +21,8 @@ #include "BitArray.h" #include "BitMatrix.h" #include "CharacterSetECI.h" -#include "DecoderResult.h" #include "DecodeStatus.h" +#include "DecoderResult.h" #include "GenericGF.h" #include "ReedSolomonDecoder.h" #include "TextDecoder.h" @@ -30,6 +30,7 @@ #include "ZXTestSupport.h" #include +#include #include #include #include @@ -38,7 +39,8 @@ namespace ZXing::Aztec { -enum class Table { +enum class Table +{ UPPER, LOWER, MIXED, @@ -82,28 +84,27 @@ static int TotalBitsInLayer(int layers, bool compact) * * @return the array of bits */ -static std::vector ExtractBits(const DetectorResult& ddata) +static BitArray ExtractBits(const DetectorResult& ddata) { bool compact = ddata.isCompact(); int layers = ddata.nbLayers(); int baseMatrixSize = (compact ? 11 : 14) + layers * 4; // not including alignment lines - std::vector alignmentMap(baseMatrixSize, 0); + std::vector map(baseMatrixSize, 0); if (compact) { - std::iota(alignmentMap.begin(), alignmentMap.end(), 0); - } - else { + std::iota(map.begin(), map.end(), 0); + } else { int matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15); int origCenter = baseMatrixSize / 2; int center = matrixSize / 2; for (int i = 0; i < origCenter; i++) { int newOffset = i + i / 15; - alignmentMap[origCenter - i - 1] = center - newOffset - 1; - alignmentMap[origCenter + i] = center + newOffset + 1; + map[origCenter - i - 1] = center - newOffset - 1; + map[origCenter + i] = center + newOffset + 1; } } auto& matrix = ddata.bits(); - std::vector rawbits(TotalBitsInLayer(layers, compact)); + BitArray rawbits(TotalBitsInLayer(layers, compact)); for (int i = 0, rowOffset = 0; i < layers; i++) { int rowSize = (layers - i) * 4 + (compact ? 9 : 12); // The top-left most point of this layer is (not including alignment lines) @@ -112,20 +113,16 @@ static std::vector ExtractBits(const DetectorResult& ddata) int high = baseMatrixSize - 1 - low; // We pull bits from the two 2 x rowSize columns and two rowSize x 2 rows for (int j = 0; j < rowSize; j++) { - int columnOffset = j * 2; + int colOffset = j * 2; for (int k = 0; k < 2; k++) { // left column - rawbits[rowOffset + columnOffset + k] = - matrix.get(alignmentMap[low + k], alignmentMap[low + j]); + rawbits.set(rowOffset + 0 * rowSize + colOffset + k, matrix.get(map[low + k], map[low + j])); // bottom row - rawbits[rowOffset + 2 * rowSize + columnOffset + k] = - matrix.get(alignmentMap[low + j], alignmentMap[high - k]); + rawbits.set(rowOffset + 2 * rowSize + colOffset + k, matrix.get(map[low + j], map[high - k])); // right column - rawbits[rowOffset + 4 * rowSize + columnOffset + k] = - matrix.get(alignmentMap[high - k], alignmentMap[high - j]); + rawbits.set(rowOffset + 4 * rowSize + colOffset + k, matrix.get(map[high - k], map[high - j])); // top row - rawbits[rowOffset + 6 * rowSize + columnOffset + k] = - matrix.get(alignmentMap[high - j], alignmentMap[low + k]); + rawbits.set(rowOffset + 6 * rowSize + colOffset + k, matrix.get(map[high - j], map[low + k])); } } rowOffset += rowSize * 8; @@ -133,25 +130,13 @@ static std::vector ExtractBits(const DetectorResult& ddata) return rawbits; } -/** -* Reads a code of given length and at given index in an array of bits -*/ -static int ReadCode(const std::vector& rawbits, int startIndex, int length) -{ - int res = 0; - for (int i = startIndex; i < startIndex + length; i++) - AppendBit(res, rawbits[i]); - - return res; -} - /** *

Performs RS error correction on an array of bits.

* * @return the corrected array * @throws FormatException if the input contains too many errors */ -static bool CorrectBits(const DetectorResult& ddata, const std::vector& rawbits, std::vector& correctedBits) +static BitArray CorrectBits(const DetectorResult& ddata, const BitArray& rawbits) { const GenericGF* gf = nullptr; int codewordSize; @@ -159,67 +144,47 @@ static bool CorrectBits(const DetectorResult& ddata, const std::vector& ra if (ddata.nbLayers() <= 2) { codewordSize = 6; gf = &GenericGF::AztecData6(); - } - else if (ddata.nbLayers() <= 8) { + } else if (ddata.nbLayers() <= 8) { codewordSize = 8; gf = &GenericGF::AztecData8(); - } - else if (ddata.nbLayers() <= 22) { + } else if (ddata.nbLayers() <= 22) { codewordSize = 10; gf = &GenericGF::AztecData10(); - } - else { + } else { codewordSize = 12; gf = &GenericGF::AztecData12(); } - int numDataCodewords = ddata.nbDatablocks(); int numCodewords = Size(rawbits) / codewordSize; - if (numCodewords < numDataCodewords) { - return false; - } - int offset = rawbits.size() % codewordSize; + int numDataCodewords = ddata.nbDatablocks(); int numECCodewords = numCodewords - numDataCodewords; - - std::vector dataWords(numCodewords); - for (int i = 0; i < numCodewords; i++, offset += codewordSize) { - dataWords[i] = ReadCode(rawbits, offset, codewordSize); - } + if (numCodewords < numDataCodewords) + return {}; + + auto dataWords = ToInts(rawbits, codewordSize, numCodewords, Size(rawbits) % codewordSize); if (!ReedSolomonDecode(*gf, dataWords, numECCodewords)) - return false; + return {}; + + // drop the ECCodewords from the dataWords array + dataWords.resize(numDataCodewords); // Now perform the unstuffing operation. - // First, count how many bits are going to be thrown out as stuffing - int mask = (1 << codewordSize) - 1; - int stuffedBits = 0; - for (int i = 0; i < numDataCodewords; i++) { - int dataWord = dataWords[i]; - if (dataWord == 0 || dataWord == mask) { - return false; - } - else if (dataWord == 1 || dataWord == mask - 1) { - stuffedBits++; - } + BitArray correctedBits; + // correctedBits.reserve(numDataCodewords * codewordSize - stuffedBits); + for (int dataWord : dataWords) { + if (dataWord == 0 || dataWord == (1 << codewordSize) - 1) + return {}; + else if (dataWord == 1) // next codewordSize-1 bits are all zeros or all ones + correctedBits.appendBits(0, codewordSize - 1); + else if (dataWord == (1 << codewordSize) - 2) + correctedBits.appendBits(0xffffffff, codewordSize - 1); + else + correctedBits.appendBits(dataWord, codewordSize); } - // Now, actually unpack the bits and remove the stuffing - correctedBits.resize(numDataCodewords * codewordSize - stuffedBits); - int index = 0; - for (int i = 0; i < numDataCodewords; i++) { - int dataWord = dataWords[i]; - if (dataWord == 1 || dataWord == mask - 1) { - // next codewordSize-1 bits are all zeros or all ones - std::fill_n(correctedBits.begin() + index, codewordSize - 1, dataWord > 1); - index += codewordSize - 1; - } - else { - for (int bit = codewordSize - 1; bit >= 0; --bit) { - correctedBits[index++] = (dataWord & (1 << bit)) != 0; - } - } - } - return true; + + return correctedBits; } /** @@ -228,19 +193,13 @@ static bool CorrectBits(const DetectorResult& ddata, const std::vector& ra static Table GetTable(char t) { switch (t) { - case 'L': - return Table::LOWER; - case 'P': - return Table::PUNCT; - case 'M': - return Table::MIXED; - case 'D': - return Table::DIGIT; - case 'B': - return Table::BINARY; + case 'L': return Table::LOWER; + case 'P': return Table::PUNCT; + case 'M': return Table::MIXED; + case 'D': return Table::DIGIT; + case 'B': return Table::BINARY; case 'U': - default: - return Table::UPPER; + default: return Table::UPPER; } } @@ -253,126 +212,116 @@ static Table GetTable(char t) static const char* GetCharacter(Table table, int code) { switch (table) { - case Table::UPPER: - return UPPER_TABLE[code]; - case Table::LOWER: - return LOWER_TABLE[code]; - case Table::MIXED: - return MIXED_TABLE[code]; - case Table::PUNCT: - return PUNCT_TABLE[code]; - case Table::DIGIT: - return DIGIT_TABLE[code]; - default: - return nullptr; - // Should not reach here. - //throw new IllegalStateException("Bad table"); + case Table::UPPER: return UPPER_TABLE[code]; + case Table::LOWER: return LOWER_TABLE[code]; + case Table::MIXED: return MIXED_TABLE[code]; + case Table::PUNCT: return PUNCT_TABLE[code]; + case Table::DIGIT: return DIGIT_TABLE[code]; + case Table::BINARY: return nullptr; // should not happen } + // silence gcc warning/error (this code can not be reached) + return nullptr; } /** * See ISO/IEC 24778:2008 Section 10.1 */ -static int ParseECIValue(const std::vector& correctedBits, const int flg, const int endIndex, int& index) +static int ParseECIValue(BitArray::Range& bits, const int flg) { int eci = 0; - for (int i = 0; i < flg && endIndex - index >= 4; i++) { - eci *= 10; - eci += ReadCode(correctedBits, index, 4) - 2; - index += 4; - } + for (int i = 0; i < flg && bits.size() >= 4; i++) + eci = 10 * eci + ReadBits(bits, 4) - 2; return eci; } /** * See ISO/IEC 24778:2008 Section 8 */ -static void ParseStructuredAppend(std::wstring& resultEncoded, StructuredAppendInfo& sai) +static StructuredAppendInfo ParseStructuredAppend(std::wstring& text) { std::wstring id; - int i = 0; + std::string::size_type i = 0; - if (resultEncoded[0] == ' ') { // Space-delimited id - std::string::size_type sp = resultEncoded.find(' ', 1); - if (sp == std::string::npos) { - return; - } - id = resultEncoded.substr(1, sp - 1); // Strip space delimiters + if (text[0] == ' ') { // Space-delimited id + std::string::size_type sp = text.find(' ', 1); + if (sp == std::string::npos) + return {}; + + id = text.substr(1, sp - 1); // Strip space delimiters i = sp + 1; } - if (i + 1 >= (int)resultEncoded.size() || resultEncoded[i] < 'A' || resultEncoded[i] > 'Z' - || resultEncoded[i + 1] < 'A' || resultEncoded[i + 1] > 'Z') { - return; - } - sai.index = resultEncoded[i] - 'A'; - sai.count = resultEncoded[i + 1] - 'A' + 1; + if (i + 1 >= text.size() || !std::isupper(text[i]) || !std::isupper(text[i + 1])) + return {}; - if (sai.count == 1 || sai.count <= sai.index) { // If info doesn't make sense + StructuredAppendInfo sai; + sai.index = text[i] - 'A'; + sai.count = text[i + 1] - 'A' + 1; + + if (sai.count == 1 || sai.count <= sai.index) // If info doesn't make sense sai.count = 0; // Choose to mark count as unknown - } - if (!id.empty()) { + if (!id.empty()) TextUtfEncoding::ToUtf8(id, sai.id); - } - resultEncoded.erase(0, i + 2); // Remove + text.erase(0, i + 2); // Remove + + return sai; } +struct AztecData +{ + std::wstring text; + std::string symbologyIdentifier; + StructuredAppendInfo sai; +}; + /** * Gets the string encoded in the aztec code bits * * @return the decoded string */ ZXING_EXPORT_TEST_ONLY -std::wstring GetEncodedData(const std::vector& correctedBits, const std::string& characterSet, - StructuredAppendInfo& sai) +AztecData GetEncodedData(const BitArray& bits, const std::string& characterSet) { - int endIndex = Size(correctedBits); + AztecData res; Table latchTable = Table::UPPER; // table most recently latched to Table shiftTable = Table::UPPER; // table to use for the next read - std::string result; - result.reserve(20); - std::wstring resultEncoded; - int index = 0; + std::string text; + text.reserve(20); + int symbologyIdModifier = 0; CharacterSet encoding = CharacterSetECI::InitEncoding(characterSet); // Check for Structured Append - need 4 5-bit words, beginning with ML UL, ending with index and count - bool haveStructuredAppend = endIndex > 20 && ReadCode(correctedBits, 0, 5) == 29 // ML (UPPER table) - && ReadCode(correctedBits, 5, 5) == 29; // UL (MIXED table) + bool haveStructuredAppend = Size(bits) > 20 && ToInt(bits, 0, 5) == 29 // ML (UPPER table) + && ToInt(bits, 5, 5) == 29; // UL (MIXED table) + bool haveFNC1 = false; + auto remBits = bits.range(); - while (index < endIndex) { + while (remBits) { if (shiftTable == Table::BINARY) { - if (endIndex - index < 5) { + if (remBits.size() < 5) break; - } - int length = ReadCode(correctedBits, index, 5); - index += 5; + int length = ReadBits(remBits, 5); if (length == 0) { - if (endIndex - index < 11) { + if (remBits.size() < 11) break; - } - length = ReadCode(correctedBits, index, 11) + 31; - index += 11; + length = ReadBits(remBits, 11) + 31; } for (int charCount = 0; charCount < length; charCount++) { - if (endIndex - index < 8) { - index = endIndex; // Force outer loop to exit + if (remBits.size() < 8) { + remBits.begin = remBits.end; // Force outer loop to exit break; } - int code = ReadCode(correctedBits, index, 8); - result.push_back((char)code); - index += 8; + int code = ReadBits(remBits, 8); + text.push_back((char)code); } // Go back to whatever mode we had been in shiftTable = latchTable; - } - else { + } else { int size = shiftTable == Table::DIGIT ? 4 : 5; - if (endIndex - index < size) { + if (remBits.size() < size) break; - } - int code = ReadCode(correctedBits, index, size); - index += size; + int code = ReadBits(remBits, size); const char* str = GetCharacter(shiftTable, code); if (std::strncmp(str, "CTRL_", 5) == 0) { // Table changes @@ -381,84 +330,78 @@ std::wstring GetEncodedData(const std::vector& correctedBits, const std::s // Our test case dlusbs.png for issue #642 exercises that. latchTable = shiftTable; // Latch the current mode, so as to return to Upper after U/S B/S shiftTable = GetTable(str[5]); - if (str[6] == 'L') { + if (str[6] == 'L') latchTable = shiftTable; - } - } - else if (std::strcmp(str, "FLGN") == 0) { - if (endIndex - index < 3) { + } else if (std::strcmp(str, "FLGN") == 0) { + if (remBits.size() < 3) break; - } - int flg = ReadCode(correctedBits, index, 3); - index += 3; - if (flg == 0) { - // TODO: Handle FNC1 - } - else if (flg <= 6) { + int flg = ReadBits(remBits, 3); + if (flg == 0) { // FNC1 + haveFNC1 = true; // Will process first/second FNC1 at end after any Structured Append + text.push_back((char)29); // May be removed at end if first/second FNC1 + } else if (flg <= 6) { // FLG(1) to FLG(6) ECI - encoding = CharacterSetECI::OnChangeAppendReset(ParseECIValue(correctedBits, flg, endIndex, index), - resultEncoded, result, encoding); - } - else { + encoding = + CharacterSetECI::OnChangeAppendReset(ParseECIValue(remBits, flg), res.text, text, encoding); + } else { // FLG(7) is invalid } shiftTable = latchTable; - } - else { - result.append(str); + } else { + text.append(str); // Go back to whatever mode we had been in shiftTable = latchTable; } } } - TextDecoder::Append(resultEncoded, reinterpret_cast(result.data()), result.size(), encoding); - if (haveStructuredAppend && !resultEncoded.empty()) { - ParseStructuredAppend(resultEncoded, sai); + TextDecoder::Append(res.text, reinterpret_cast(text.data()), text.size(), encoding); + + if (!res.text.empty()) { + if (haveStructuredAppend) + res.sai = ParseStructuredAppend(res.text); + if (haveFNC1) { + // As converting character set ECIs ourselves and ignoring/skipping non-character ECIs, not using + // modifiers that indicate ECI protocol (ISO/IEC 24778:2008 Annex F Table F.1) + if (res.text.front() == 29) { + symbologyIdModifier = 1; // GS1 + res.text.erase(0, 1); // Remove FNC1 + } else if (res.text.size() > 2 && std::isupper(res.text[0]) && res.text[1] == 29) { + // FNC1 following single uppercase letter (the AIM Application Indicator) + symbologyIdModifier = 2; // AIM + res.text.erase(1, 1); // Remove FNC1 + // The AIM Application Indicator character "A"-"Z" is left in the stream (ISO/IEC 24778:2008 16.2) + } else if (res.text.size() > 3 && std::isdigit(res.text[0]) && std::isdigit(res.text[1]) && + res.text[2] == 29) { + // FNC1 following 2 digits (the AIM Application Indicator) + symbologyIdModifier = 2; // AIM + res.text.erase(2, 1); // Remove FNC1 + // The AIM Application Indicator characters "00"-"99" are left in the stream (ISO/IEC 24778:2008 16.2) + } + } } - return resultEncoded; -} + res.symbologyIdentifier = "]z" + std::to_string(symbologyIdModifier + (res.sai.index > -1 ? 6 : 0)); -/** -* Reads a code of length 8 in an array of bits, padding with zeros -*/ -static uint8_t ReadByte(const std::vector& rawbits, int startIndex) -{ - int n = Size(rawbits) - startIndex; - if (n >= 8) { - return static_cast(ReadCode(rawbits, startIndex, 8)); - } - return static_cast(ReadCode(rawbits, startIndex, n) << (8 - n)); + return res; } -/** -* Packs a bit array into bytes, most significant bit first -*/ -static ByteArray ConvertBoolArrayToByteArray(const std::vector& boolArr) +DecoderResult Decode(const DetectorResult& detectorResult, const std::string& characterSet) { - ByteArray byteArr((Size(boolArr) + 7) / 8); - for (int i = 0; i < Size(byteArr); ++i) { - byteArr[i] = ReadByte(boolArr, 8 * i); - } - return byteArr; -} + BitArray bits = CorrectBits(detectorResult, ExtractBits(detectorResult)); -DecoderResult Decoder::Decode(const DetectorResult& detectorResult, const std::string& characterSet) -{ - std::vector rawbits = ExtractBits(detectorResult); - std::vector correctedBits; - StructuredAppendInfo sai; - if (CorrectBits(detectorResult, rawbits, correctedBits)) { - return DecoderResult(ConvertBoolArrayToByteArray(correctedBits), - GetEncodedData(correctedBits, characterSet, sai)) - .setNumBits(Size(correctedBits)) - .setStructuredAppend(sai) - .setReaderInit(detectorResult.readerInit()); - } - else { + if (!bits.size()) return DecodeStatus::FormatError; - } + + auto data = GetEncodedData(bits, characterSet); + if (data.text.empty()) + return DecodeStatus::FormatError; + + return DecoderResult(bits.toBytes(), std::move(data.text)) + .setNumBits(Size(bits)) + .setSymbologyIdentifier(std::move(data.symbologyIdentifier)) + .setStructuredAppend(data.sai) + .setReaderInit(detectorResult.readerInit()); } } // namespace ZXing::Aztec diff --git a/core/src/aztec/AZDecoder.h b/core/src/aztec/AZDecoder.h index ab148aa480..d9eb6dd1fd 100644 --- a/core/src/aztec/AZDecoder.h +++ b/core/src/aztec/AZDecoder.h @@ -27,16 +27,9 @@ namespace Aztec { class DetectorResult; /** -*

The main class which implements Aztec Code decoding -- as opposed to locating and extracting -* the Aztec Code from an image.

-* -* @author David Olivier -*/ -class Decoder -{ -public: - static DecoderResult Decode(const DetectorResult& detectorResult, const std::string& characterSet); -}; + * @brief Decode Aztec Code after locating and extracting from an image. + */ +DecoderResult Decode(const DetectorResult& detectorResult, const std::string& characterSet = ""); } // Aztec } // ZXing diff --git a/core/src/aztec/AZDetector.cpp b/core/src/aztec/AZDetector.cpp index 74d09754d2..6ff3089a8e 100644 --- a/core/src/aztec/AZDetector.cpp +++ b/core/src/aztec/AZDetector.cpp @@ -79,7 +79,7 @@ static int GetRotation(const std::array& sides, int length) static bool IsValidPoint(int x, int y, int imgWidth, int imgHeight) { - return x >= 0 && x < imgWidth && y > 0 && y < imgHeight; + return x >= 0 && x < imgWidth && y >= 0 && y < imgHeight; } static bool IsValidPoint(const ResultPoint& point, int imgWidth, int imgHeight) @@ -483,7 +483,9 @@ static int GetDimension(bool compact, int nbLayers) * topLeft, topRight, bottomRight, and bottomLeft are the centers of the squares on the * diagonal just outside the bull's eye. */ -static ZXing::DetectorResult SampleGrid(const BitMatrix& image, const ResultPoint& topLeft, const ResultPoint& topRight, const ResultPoint& bottomRight, const ResultPoint& bottomLeft, bool compact, int nbLayers, int nbCenterLayers) +static ZXing::DetectorResult SampleGrid(const BitMatrix& image, const ResultPoint& topLeft, + const ResultPoint& topRight, const ResultPoint& bottomRight, + const ResultPoint& bottomLeft, bool compact, int nbLayers, int nbCenterLayers) { int dimension = GetDimension(compact, nbLayers); @@ -495,7 +497,7 @@ static ZXing::DetectorResult SampleGrid(const BitMatrix& image, const ResultPoin {topLeft, topRight, bottomRight, bottomLeft}}); } -DetectorResult Detector::Detect(const BitMatrix& image, bool isMirror, bool isPure) +DetectorResult Detect(const BitMatrix& image, bool isMirror, bool isPure) { // 1. Get the center of the aztec matrix auto pCenter = isPure ? GetMatrixCenterPure(image) : GetMatrixCenter(image); diff --git a/core/src/aztec/AZDetector.h b/core/src/aztec/AZDetector.h index 338d62e3cc..b8307310e1 100644 --- a/core/src/aztec/AZDetector.h +++ b/core/src/aztec/AZDetector.h @@ -25,24 +25,13 @@ namespace Aztec { class DetectorResult; /** -* Encapsulates logic that can detect an Aztec Code in an image, even if the Aztec Code -* is rotated or skewed, or partially obscured. -* -* @author David Olivier -* @author Frank Yellin -*/ -class Detector -{ -public: - /** - * Detects an Aztec Code in an image. - * - * @param isMirror if true, image is a mirror-image of original - * @return {@link AztecDetectorResult} encapsulating results of detecting an Aztec Code - * @throws NotFoundException if no Aztec Code can be found - */ - static DetectorResult Detect(const BitMatrix& image, bool isMirror, bool isPure); -}; + * Detects an Aztec Code in an image. + * + * @param isMirror if true, image is a mirror-image of original + * @return {@link AztecDetectorResult} encapsulating results of detecting an Aztec Code + * @throws NotFoundException if no Aztec Code can be found + */ +DetectorResult Detect(const BitMatrix& image, bool isMirror, bool isPure); } // Aztec } // ZXing diff --git a/core/src/aztec/AZHighLevelEncoder.cpp b/core/src/aztec/AZHighLevelEncoder.cpp index 2a473f180b..f27bac3b5f 100644 --- a/core/src/aztec/AZHighLevelEncoder.cpp +++ b/core/src/aztec/AZHighLevelEncoder.cpp @@ -367,28 +367,18 @@ HighLevelEncoder::Encode(const std::string& text) int pairCode; int nextChar = index + 1 < Size(text) ? text[index + 1] : 0; switch (text[index]) { - case '\r': - pairCode = nextChar == '\n' ? 2 : 0; - break; - case '.': - pairCode = nextChar == ' ' ? 3 : 0; - break; - case ',': - pairCode = nextChar == ' ' ? 4 : 0; - break; - case ':': - pairCode = nextChar == ' ' ? 5 : 0; - break; - default: - pairCode = 0; + case '\r': pairCode = nextChar == '\n' ? 2 : 0; break; + case '.': pairCode = nextChar == ' ' ? 3 : 0; break; + case ',': pairCode = nextChar == ' ' ? 4 : 0; break; + case ':': pairCode = nextChar == ' ' ? 5 : 0; break; + default: pairCode = 0; } if (pairCode > 0) { // We have one of the four special PUNCT pairs. Treat them specially. // Get a new set of states for the two new characters. states = UpdateStateListForPair(states, index, pairCode); index++; - } - else { + } else { // Get a new set of states for the new character. states = UpdateStateListForChar(states, text, index); } diff --git a/core/src/aztec/AZReader.cpp b/core/src/aztec/AZReader.cpp index 42b4d18cbf..4cd8157778 100644 --- a/core/src/aztec/AZReader.cpp +++ b/core/src/aztec/AZReader.cpp @@ -27,7 +27,6 @@ #include #include -#include namespace ZXing::Aztec { @@ -39,22 +38,22 @@ Reader::Reader(const DecodeHints& hints) Result Reader::decode(const BinaryBitmap& image) const { - auto binImg = image.getBlackMatrix(); + auto binImg = image.getBitMatrix(); if (binImg == nullptr) { return Result(DecodeStatus::NotFound); } - DetectorResult detectResult = Detector::Detect(*binImg, false, _isPure); + DetectorResult detectResult = Detect(*binImg, false, _isPure); DecoderResult decodeResult = DecodeStatus::NotFound; if (detectResult.isValid()) { - decodeResult = Decoder::Decode(detectResult, _characterSet); + decodeResult = Decode(detectResult, _characterSet); } //TODO: don't start detection all over again, just to swap 2 corner points if (!decodeResult.isValid()) { - detectResult = Detector::Detect(*binImg, true, _isPure); + detectResult = Detect(*binImg, true, _isPure); if (detectResult.isValid()) { - decodeResult = Decoder::Decode(detectResult, _characterSet); + decodeResult = Decode(detectResult, _characterSet); } } diff --git a/core/src/datamatrix/DMDecoder.cpp b/core/src/datamatrix/DMDecoder.cpp index d12ba7a6e2..feb469cb38 100644 --- a/core/src/datamatrix/DMDecoder.cpp +++ b/core/src/datamatrix/DMDecoder.cpp @@ -100,9 +100,11 @@ static const char TEXT_SHIFT3_SET_CHARS[] = { struct State { CharacterSet encoding; + int symbologyIdModifier = 1; // ECC 200 (ISO 16022:2006 Annex N Table N.1) struct StructuredAppendInfo sai; bool readerInit = false; bool firstCodeword = true; + int firstFNC1Position = 1; }; struct Shift128 @@ -170,11 +172,28 @@ static Mode DecodeAsciiSegment(BitSource& bits, std::string& result, std::string case 231: // Latch to Base 256 encodation return Mode::BASE256_ENCODE; case 232: // FNC1 - // TODO: handle first/second position - result.push_back((char)29); // translate as ASCII 29 + if (bits.byteOffset() == state.firstFNC1Position || bits.byteOffset() == state.firstFNC1Position + 1) { + // As converting character set ECIs ourselves and ignoring/skipping non-character ECIs, not using + // modifiers that indicate ECI protocol (ISO 16022:2006 Annex N Table N.1, ISO 21471:2020 Annex G Table G.1) + + // Only recognizing an FNC1 as first/second by codeword position (aka symbol character position), not + // by decoded character position, i.e. not recognizing a C40/Text encoded FNC1 (which requires a latch + // and a shift) + if (bits.byteOffset() == state.firstFNC1Position) + state.symbologyIdModifier = 2; // GS1 + else { + state.symbologyIdModifier = 3; // AIM + // Note no AIM Application Indicator format defined, ISO 16022:2006 11.2 + } + } else + result.push_back((char)29); // translate as ASCII 29 break; case 233: // Structured Append - ParseStructuredAppend(bits, state.sai); + if (state.firstCodeword) { // Must be first ISO 16022:2006 5.6.1 + ParseStructuredAppend(bits, state.sai); + state.firstFNC1Position = 5; + } else + return Mode::FORMAT_ERROR; break; case 234: // Reader Programming if (state.firstCodeword) // Must be first ISO 16022:2006 5.2.4.9 @@ -301,7 +320,9 @@ static bool DecodeAnsiX12Segment(BitSource& bits, std::string& result) for (int cValue : *triple) { // X12 segment terminator , separator *, sub-element separator >, space static const char segChars[4] = {'\r', '*', '>', ' '}; - if (cValue < 4) + if (cValue < 0) + return false; + else if (cValue < 4) result.push_back(segChars[cValue]); else if (cValue < 14) // 0 - 9 result.push_back((char)(cValue + 44)); @@ -382,14 +403,13 @@ static bool DecodeBase256Segment(BitSource& bits, std::string& result) bytes[i] = (uint8_t)Unrandomize255State(bits.readBits(8), codewordPosition++); } - // bytes is in ISO-8859-1 result.append(reinterpret_cast(bytes.data()), bytes.size()); return true; } ZXING_EXPORT_TEST_ONLY -DecoderResult Decode(ByteArray&& bytes, const std::string& characterSet) +DecoderResult Decode(ByteArray&& bytes, const std::string& characterSet, const bool isDMRE) { BitSource bits(bytes); std::string result; @@ -404,6 +424,7 @@ DecoderResult Decode(ByteArray&& bytes, const std::string& characterSet) if (mode == Mode::ASCII_ENCODE) { mode = DecodeAsciiSegment(bits, result, resultTrailer, resultEncoded, state); state.firstCodeword = false; + state.firstFNC1Position = -1; // Only recognize in first segment } else { bool decodeOK; switch (mode) { @@ -429,7 +450,10 @@ DecoderResult Decode(ByteArray&& bytes, const std::string& characterSet) TextDecoder::Append(resultEncoded, reinterpret_cast(result.data()), result.size(), state.encoding); + std::string symbologyIdentifier("]d" + std::to_string(state.symbologyIdModifier + (isDMRE ? 6 : 0))); + return DecoderResult(std::move(bytes), std::move(resultEncoded)) + .setSymbologyIdentifier(std::move(symbologyIdentifier)) .setStructuredAppend(state.sai) .setReaderInit(state.readerInit); } @@ -450,6 +474,7 @@ CorrectErrors(ByteArray& codewordBytes, int numDataCodewords) // First read into an array of ints std::vector codewordsInts(codewordBytes.begin(), codewordBytes.end()); int numECCodewords = Size(codewordBytes) - numDataCodewords; + if (!ReedSolomonDecode(GenericGF::DataMatrixField256(), codewordsInts, numECCodewords)) return false; @@ -496,7 +521,7 @@ static DecoderResult DoDecode(const BitMatrix& bits, const std::string& characte } // Decode the contents of that stream of bytes - return DecodedBitStreamParser::Decode(std::move(resultBytes), characterSet); + return DecodedBitStreamParser::Decode(std::move(resultBytes), characterSet, version->isDMRE()); } static BitMatrix FlippedL(const BitMatrix& bits) @@ -515,11 +540,12 @@ DecoderResult Decode(const BitMatrix& bits, const std::string& characterSet) return res; //TODO: - // * report mirrored state (see also QRReader) // * unify bit mirroring helper code with QRReader? // * rectangular symbols with the a size of 8 x Y are not supported a.t.m. - if (auto mirroredRes = DoDecode(FlippedL(bits), characterSet); mirroredRes.isValid()) + if (auto mirroredRes = DoDecode(FlippedL(bits), characterSet); mirroredRes.isValid()) { + mirroredRes.setIsMirrored(true); return mirroredRes; + } return res; } diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index 92eefc3c07..68cc5fff5f 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -398,7 +398,7 @@ static DetectorResult DetectOld(const BitMatrix& image) dimensionCorrected++; } - dimensionTop = dimensionRight = dimension; + dimensionTop = dimensionRight = dimensionCorrected; } return SampleGrid(image, *topLeft, *bottomLeft, *bottomRight, correctedTopRight, dimensionTop, dimensionRight); @@ -409,7 +409,7 @@ static DetectorResult DetectOld(const BitMatrix& image) * It is performing something like a (back) trace search along edges through the bit matrix, first looking for * the 'L'-pattern, then tracing the black/white borders at the top/right. Advantages over the old code are: * * works with lower resolution scans (around 2 pixel per module), due to sub-pixel precision grid placement -* * works with real-world codes that have just one module wide quite-zone (which is perfectly in spec) +* * works with real-world codes that have just one module wide quiet-zone (which is perfectly in spec) */ class DMRegressionLine : public RegressionLine @@ -580,7 +580,10 @@ class EdgeTracer : public BitMatrixCursorF // make sure we are making progress even when back-projecting: // consider a 90deg corner, rotated 45deg. we step away perpendicular from the line and get // back projected where we left off the line. - if (distance(np, line.project(line.points().back())) < 1) + // The 'while' instead of 'if' was introduced to fix the issue with #245. It turns out that + // np can actually be behind the projection of the last line point and we need 2 steps in d + // to prevent a dead lock. see #245.png + while (distance(np, line.project(line.points().back())) < 1) np = np + d; p = centered(np); } diff --git a/core/src/datamatrix/DMHighLevelEncoder.cpp b/core/src/datamatrix/DMHighLevelEncoder.cpp index dc44a53a4c..0b43f6a7eb 100644 --- a/core/src/datamatrix/DMHighLevelEncoder.cpp +++ b/core/src/datamatrix/DMHighLevelEncoder.cpp @@ -607,26 +607,16 @@ namespace X12Encoder { static int EncodeChar(int c, std::string& sb) { switch (c) { - case '\r': - sb.push_back('\0'); - break; - case '*': - sb.push_back('\1'); - break; - case '>': - sb.push_back('\2'); - break; - case ' ': - sb.push_back('\3'); - break; + case '\r': sb.push_back('\0'); break; + case '*': sb.push_back('\1'); break; + case '>': sb.push_back('\2'); break; + case ' ': sb.push_back('\3'); break; default: if (c >= '0' && c <= '9') { sb.push_back((char)(c - 48 + 4)); - } - else if (c >= 'A' && c <= 'Z') { + } else if (c >= 'A' && c <= 'Z') { sb.push_back((char)(c - 65 + 14)); - } - else { + } else { throw std::invalid_argument("Illegal character: " + ToHexString(c)); } break; @@ -915,12 +905,12 @@ ByteArray Encode(const std::wstring& msg, SymbolShape shape, int minWidth, int m int encodingMode = ASCII_ENCODATION; //Default mode while (context.hasMoreCharacters()) { switch (encodingMode) { - case ASCII_ENCODATION: ASCIIEncoder::EncodeASCII(context); break; - case C40_ENCODATION: C40Encoder::EncodeC40(context); break; - case TEXT_ENCODATION: DMTextEncoder::EncodeText(context); break; - case X12_ENCODATION: X12Encoder::EncodeX12(context); break; - case EDIFACT_ENCODATION: EdifactEncoder::EncodeEdifact(context); break; - case BASE256_ENCODATION: Base256Encoder::EncodeBase256(context); break; + case ASCII_ENCODATION: ASCIIEncoder::EncodeASCII(context); break; + case C40_ENCODATION: C40Encoder::EncodeC40(context); break; + case TEXT_ENCODATION: DMTextEncoder::EncodeText(context); break; + case X12_ENCODATION: X12Encoder::EncodeX12(context); break; + case EDIFACT_ENCODATION: EdifactEncoder::EncodeEdifact(context); break; + case BASE256_ENCODATION: Base256Encoder::EncodeBase256(context); break; } if (context.newEncoding() >= 0) { encodingMode = context.newEncoding(); diff --git a/core/src/datamatrix/DMReader.cpp b/core/src/datamatrix/DMReader.cpp index 4939f5f337..d134f778bb 100644 --- a/core/src/datamatrix/DMReader.cpp +++ b/core/src/datamatrix/DMReader.cpp @@ -18,7 +18,6 @@ #include "DMReader.h" #include "BinaryBitmap.h" -#include "BitMatrix.h" #include "DMDecoder.h" #include "DMDetector.h" #include "DecodeHints.h" @@ -27,7 +26,6 @@ #include "Result.h" #include -#include namespace ZXing::DataMatrix { @@ -48,7 +46,7 @@ Reader::Reader(const DecodeHints& hints) Result Reader::decode(const BinaryBitmap& image) const { - auto binImg = image.getBlackMatrix(); + auto binImg = image.getBitMatrix(); if (binImg == nullptr) { return Result(DecodeStatus::NotFound); } diff --git a/core/src/datamatrix/DMSymbolInfo.cpp b/core/src/datamatrix/DMSymbolInfo.cpp index 97ac6c0b90..344a0d4832 100644 --- a/core/src/datamatrix/DMSymbolInfo.cpp +++ b/core/src/datamatrix/DMSymbolInfo.cpp @@ -129,36 +129,24 @@ int SymbolInfo::horizontalDataRegions() const { switch (_dataRegions) { - case 1: - return 1; - case 2: - return 2; - case 4: - return 2; - case 16: - return 4; - case 36: - return 6; - default: - throw std::out_of_range("Cannot handle this number of data regions"); + case 1: return 1; + case 2: return 2; + case 4: return 2; + case 16: return 4; + case 36: return 6; + default: throw std::out_of_range("Cannot handle this number of data regions"); } } int SymbolInfo::verticalDataRegions() const { switch (_dataRegions) { - case 1: - return 1; - case 2: - return 1; - case 4: - return 2; - case 16: - return 4; - case 36: - return 6; - default: - throw std::out_of_range("Cannot handle this number of data regions"); + case 1: return 1; + case 2: return 1; + case 4: return 2; + case 16: return 4; + case 36: return 6; + default: throw std::out_of_range("Cannot handle this number of data regions"); } } diff --git a/core/src/datamatrix/DMWriter.cpp b/core/src/datamatrix/DMWriter.cpp index 1f792820ea..fe79e603cb 100644 --- a/core/src/datamatrix/DMWriter.cpp +++ b/core/src/datamatrix/DMWriter.cpp @@ -117,8 +117,8 @@ Writer::encode(const std::wstring& contents, int width, int height) const //4. step: low-level encoding BitMatrix result = EncodeLowLevel(symbolData, *symbolInfo); - //5. step: scale-up to requested size, minimum required quite zone is 1 - return Inflate(std::move(result), width, height, _quiteZone); + //5. step: scale-up to requested size, minimum required quiet zone is 1 + return Inflate(std::move(result), width, height, _quietZone); } } // namespace ZXing::DataMatrix diff --git a/core/src/datamatrix/DMWriter.h b/core/src/datamatrix/DMWriter.h index ef9c5513f4..e90e8bfda7 100644 --- a/core/src/datamatrix/DMWriter.h +++ b/core/src/datamatrix/DMWriter.h @@ -32,7 +32,7 @@ class Writer Writer(); Writer& setMargin(int margin) { - _quiteZone = margin; + _quietZone = margin; return *this; } @@ -57,7 +57,7 @@ class Writer private: SymbolShape _shapeHint; - int _quiteZone = 1, _minWidth = -1, _minHeight = -1, _maxWidth = -1, _maxHeight = -1; + int _quietZone = 1, _minWidth = -1, _minHeight = -1, _maxWidth = -1, _maxHeight = -1; }; } // DataMatrix diff --git a/core/src/maxicode/MCDecoder.cpp b/core/src/maxicode/MCDecoder.cpp index d27c446b42..15b9a7bfa5 100644 --- a/core/src/maxicode/MCDecoder.cpp +++ b/core/src/maxicode/MCDecoder.cpp @@ -294,30 +294,38 @@ namespace DecodedBitStreamParser result.reserve(144); StructuredAppendInfo sai; switch (mode) { - case 2: - case 3: { - auto postcode = mode == 2 ? ToString(GetPostCode2(bytes), GetPostCode2Length(bytes)) : GetPostCode3(bytes); - auto country = ToString(GetCountry(bytes), 3); - auto service = ToString(GetServiceClass(bytes), 3); - result.append(GetMessage(bytes, 10, 84, characterSet, sai)); - if (result.size() >= 9 && result.compare(0, 7, L"[)>\u001E01\u001D") == 0) { // "[)>" + RS + "01" + GS - result.insert(9, TextDecoder::FromLatin1(postcode + GS + country + GS + service + GS)); - } - else { - result.insert(0, TextDecoder::FromLatin1(postcode + GS + country + GS + service + GS)); - } - break; + case 2: + case 3: { + auto postcode = mode == 2 ? ToString(GetPostCode2(bytes), GetPostCode2Length(bytes)) : GetPostCode3(bytes); + auto country = ToString(GetCountry(bytes), 3); + auto service = ToString(GetServiceClass(bytes), 3); + result.append(GetMessage(bytes, 10, 84, characterSet, sai)); + if (result.size() >= 9 && result.compare(0, 7, L"[)>\u001E01\u001D") == 0) { // "[)>" + RS + "01" + GS + result.insert(9, TextDecoder::FromLatin1(postcode + GS + country + GS + service + GS)); + } else { + result.insert(0, TextDecoder::FromLatin1(postcode + GS + country + GS + service + GS)); } - case 4: - case 6: - result.append(GetMessage(bytes, 1, 93, characterSet, sai)); - break; - case 5: - result.append(GetMessage(bytes, 1, 77, characterSet, sai)); - break; + break; + } + case 4: + case 6: result.append(GetMessage(bytes, 1, 93, characterSet, sai)); break; + case 5: result.append(GetMessage(bytes, 1, 77, characterSet, sai)); break; + } + + // As converting character set ECIs ourselves and ignoring/skipping non-character ECIs, not using modifiers + // that indicate ECI protocol (ISO/IEC 16023:2000 Annexe E Table E1) + std::string symbologyIdentifier; + if (mode == 4 || mode == 5) { + symbologyIdentifier = "]U0"; + } + else if (mode == 2 || mode == 3) { + symbologyIdentifier = "]U1"; } + // No identifier defined for mode 6 + return DecoderResult(std::move(bytes), std::move(result)) .setEcLevel(std::to_wstring(mode)) + .setSymbologyIdentifier(std::move(symbologyIdentifier)) .setStructuredAppend(sai) .setReaderInit(mode == 6); } @@ -337,27 +345,24 @@ Decoder::Decode(const BitMatrix& bits, const std::string& characterSet) int mode = codewords[0] & 0x0F; ByteArray datawords; switch (mode) { - case 2: // Structured Carrier Message (numeric postcode) - case 3: // Structured Carrier Message (alphanumeric postcode) - case 4: // Standard Symbol - case 6: // Reader Programming - if (CorrectErrors(codewords, 20, 84, 40, EVEN) && CorrectErrors(codewords, 20, 84, 40, ODD)) { - datawords.resize(94, 0); - } - else { - return DecodeStatus::ChecksumError; - } - break; - case 5: // Full ECC - if (CorrectErrors(codewords, 20, 68, 56, EVEN) && CorrectErrors(codewords, 20, 68, 56, ODD)) { - datawords.resize(78, 0); - } - else { - return DecodeStatus::ChecksumError; - } - break; - default: - return DecodeStatus::FormatError; + case 2: // Structured Carrier Message (numeric postcode) + case 3: // Structured Carrier Message (alphanumeric postcode) + case 4: // Standard Symbol + case 6: // Reader Programming + if (CorrectErrors(codewords, 20, 84, 40, EVEN) && CorrectErrors(codewords, 20, 84, 40, ODD)) { + datawords.resize(94, 0); + } else { + return DecodeStatus::ChecksumError; + } + break; + case 5: // Full ECC + if (CorrectErrors(codewords, 20, 68, 56, EVEN) && CorrectErrors(codewords, 20, 68, 56, ODD)) { + datawords.resize(78, 0); + } else { + return DecodeStatus::ChecksumError; + } + break; + default: return DecodeStatus::FormatError; } std::copy_n(codewords.begin(), 10, datawords.begin()); diff --git a/core/src/maxicode/MCReader.cpp b/core/src/maxicode/MCReader.cpp index 6d464010f2..36c5a53607 100644 --- a/core/src/maxicode/MCReader.cpp +++ b/core/src/maxicode/MCReader.cpp @@ -59,7 +59,7 @@ Reader::Reader(const DecodeHints& hints) : _isPure(hints.isPure()), _characterSe Result Reader::decode(const BinaryBitmap& image) const { - auto binImg = image.getBlackMatrix(); + auto binImg = image.getBitMatrix(); if (binImg == nullptr) { return Result(DecodeStatus::NotFound); } diff --git a/core/src/oned/ODCodabarReader.cpp b/core/src/oned/ODCodabarReader.cpp index f4ea0cd92f..354f068511 100644 --- a/core/src/oned/ODCodabarReader.cpp +++ b/core/src/oned/ODCodabarReader.cpp @@ -18,15 +18,12 @@ #include "ODCodabarReader.h" -#include "BitArray.h" #include "DecodeHints.h" #include "Result.h" #include "ZXContainerAlgorithms.h" -#include -#include #include -#include +#include namespace ZXing::OneD { @@ -51,8 +48,8 @@ CodabarReader::CodabarReader(const DecodeHints& hints) // each character has 4 bars and 3 spaces constexpr int CHAR_LEN = 7; -// quite zone is half the width of a character symbol -constexpr float QUITE_ZONE_SCALE = 0.5f; +// quiet zone is half the width of a character symbol +constexpr float QUIET_ZONE_SCALE = 0.5f; // official start and stop symbols are "ABCD" // some codabar generator allow the codabar string to be closed by every @@ -60,12 +57,12 @@ constexpr float QUITE_ZONE_SCALE = 0.5f; bool IsLeftGuard(const PatternView& view, int spaceInPixel) { - return spaceInPixel > view.sum() * QUITE_ZONE_SCALE && + return spaceInPixel > view.sum() * QUIET_ZONE_SCALE && Contains({0x1A, 0x29, 0x0B, 0x0E}, RowReader::NarrowWideBitPattern(view)); } Result -CodabarReader::decodePattern(int rowNumber, const PatternView& row, std::unique_ptr&) const +CodabarReader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const { // minimal number of characters that must be present (including start, stop and checksum characters) // absolute minimum would be 2 (meaning 0 'content'). everything below 4 produces too many false @@ -73,7 +70,7 @@ CodabarReader::decodePattern(int rowNumber, const PatternView& row, std::unique_ const int minCharCount = 4; auto isStartOrStopSymbol = [](char c) { return 'A' <= c && c <= 'D'; }; - auto next = FindLeftGuard(row, minCharCount * CHAR_LEN, IsLeftGuard); + next = FindLeftGuard(next, minCharCount * CHAR_LEN, IsLeftGuard); if (!next.isValid()) return Result(DecodeStatus::NotFound); @@ -99,15 +96,19 @@ CodabarReader::decodePattern(int rowNumber, const PatternView& row, std::unique_ // next now points to the last decoded symbol // check txt length and whitespace after the last char. See also FindStartPattern. - if (Size(txt) < minCharCount || !next.hasQuiteZoneAfter(QUITE_ZONE_SCALE)) + if (Size(txt) < minCharCount || !next.hasQuietZoneAfter(QUIET_ZONE_SCALE)) return Result(DecodeStatus::NotFound); // remove stop/start characters if (!_returnStartEnd) txt = txt.substr(1, txt.size() - 2); + // symbology identifier ISO/IEC 15424:2008 4.4.9 + // if checksum processing were implemented and checksum present and stripped then modifier would be 4 + std::string symbologyIdentifier("]F0"); + int xStop = next.pixelsTillEnd(); - return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::Codabar); + return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::Codabar, std::move(symbologyIdentifier)); } } // namespace ZXing::OneD diff --git a/core/src/oned/ODCodabarReader.h b/core/src/oned/ODCodabarReader.h index b004c5064c..69945c3d6d 100644 --- a/core/src/oned/ODCodabarReader.h +++ b/core/src/oned/ODCodabarReader.h @@ -34,7 +34,7 @@ class CodabarReader : public RowReader { public: explicit CodabarReader(const DecodeHints& hints); - Result decodePattern(int rowNumber, const PatternView& row, std::unique_ptr& state) const override; + Result decodePattern(int rowNumber, PatternView& next, std::unique_ptr& state) const override; private: bool _returnStartEnd; diff --git a/core/src/oned/ODCode128Reader.cpp b/core/src/oned/ODCode128Reader.cpp index a0276f92e4..f730999853 100644 --- a/core/src/oned/ODCode128Reader.cpp +++ b/core/src/oned/ODCode128Reader.cpp @@ -17,14 +17,11 @@ #include "ODCode128Reader.h" -#include "BitArray.h" -#include "DecodeHints.h" #include "ODCode128Patterns.h" #include "Result.h" #include "ZXContainerAlgorithms.h" #include -#include #include #include #include @@ -55,7 +52,7 @@ static const int CODE_STOP = 106; class Raw2TxtDecoder { int codeSet = 0; - bool _convertFNC1 = false; + std::string _symbologyIdentifier = "]C0"; // ISO/IEC 15417:2007 Annex C Table C.1 bool _readerInit = false; std::string txt; size_t lastTxtSize = 0; @@ -64,23 +61,31 @@ class Raw2TxtDecoder bool fnc4Next = false; bool shift = false; - void fnc1() + void fnc1(const bool isCodeSetC) { - if (_convertFNC1) { - if (txt.empty()) { - // GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code - // is FNC1 then this is GS1-128. We add the symbology identifier. - txt.append("]C1"); - } - else { - // GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS) - txt.push_back((char)29); - } + if (txt.empty()) { + // ISO/IEC 15417:2007 Annex B.1 and GS1 General Specifications 21.0.1 Section 5.4.3.7 + // If the first char after the start code is FNC1 then this is GS1-128. + _symbologyIdentifier = "]C1"; + // GS1 General Specifications Section 5.4.6.4 + // "Transmitted data ... is prefixed by the symbology identifier ]C1, if used." + // Choosing not to use symbology identifier, i.e. to not prefix to data. + } + else if ((isCodeSetC && txt.size() == 2 && txt[0] >= '0' && txt[0] <= '9' && txt[1] >= '0' && txt[1] <= '9') + || (!isCodeSetC && txt.size() == 1 && ((txt[0] >= 'A' && txt[0] <= 'Z') + || (txt[0] >= 'a' && txt[0] <= 'z')))) { + // ISO/IEC 15417:2007 Annex B.2 + // FNC1 in second position following Code Set C "00-99" or Code Set A/B "A-Za-z" - AIM + _symbologyIdentifier = "]C2"; + } + else { + // ISO/IEC 15417:2007 Annex B.3. Otherwise FNC1 is returned as ASCII 29 (GS) + txt.push_back((char)29); } }; public: - Raw2TxtDecoder(int startCode, bool convertFNC1) : codeSet(204 - startCode), _convertFNC1(convertFNC1) + Raw2TxtDecoder(int startCode) : codeSet(204 - startCode) { txt.reserve(20); } @@ -95,7 +100,7 @@ class Raw2TxtDecoder txt.push_back('0'); txt.append(std::to_string(code)); } else if (code == CODE_FNC_1) { - fnc1(); + fnc1(true /*isCodeSetC*/); } else { codeSet = code; // CODE_A / CODE_B } @@ -103,9 +108,9 @@ class Raw2TxtDecoder bool unshift = shift; switch (code) { - case CODE_FNC_1: fnc1(); break; + case CODE_FNC_1: fnc1(false /*isCodeSetC*/); break; case CODE_FNC_2: - // do nothing? + // Message Append - do nothing? break; case CODE_FNC_3: _readerInit = true; // Can occur anywhere in the symbol (ISO/IEC 15417:2007 4.3.4.2 (c)) @@ -159,6 +164,8 @@ class Raw2TxtDecoder return txt.substr(0, lastTxtSize); } + std::string symbologyIdentifier() const { return _symbologyIdentifier; } + bool readerInit() const { return _readerInit; } }; @@ -177,15 +184,10 @@ static int DetectStartCode(const C& c) return bestVariance < MAX_AVG_VARIANCE ? bestCode : 0; } -Code128Reader::Code128Reader(const DecodeHints& hints) : - _convertFNC1(hints.assumeGS1()) -{ -} - // all 3 start patterns share the same 2-1-1 prefix constexpr auto START_PATTERN_PREFIX = FixedPattern<3, 4>{2, 1, 1}; constexpr int CHAR_LEN = 6; -constexpr float QUITE_ZONE = 8; // quite zone spec is 10 modules +constexpr float QUIET_ZONE = 5; // quiet zone spec is 10 modules, real world examples ignore that, see #138 //#define USE_FAST_1_TO_4_BIT_PATTERN_DECODING #ifdef USE_FAST_1_TO_4_BIT_PATTERN_DECODING @@ -216,7 +218,7 @@ constexpr int CHARACTER_ENCODINGS[] = { }; #endif -Result Code128Reader::decodePattern(int rowNumber, const PatternView& row, std::unique_ptr&) const +Result Code128Reader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const { int minCharCount = 4; // start + payload + checksum + stop auto decodePattern = [](const PatternView& view, bool start = false) { @@ -231,7 +233,7 @@ Result Code128Reader::decodePattern(int rowNumber, const PatternView& row, std:: #endif }; - auto next = FindLeftGuard(row, minCharCount * CHAR_LEN, START_PATTERN_PREFIX, QUITE_ZONE); + next = FindLeftGuard(next, minCharCount * CHAR_LEN, START_PATTERN_PREFIX, QUIET_ZONE); if (!next.isValid()) return Result(DecodeStatus::NotFound); @@ -245,7 +247,7 @@ Result Code128Reader::decodePattern(int rowNumber, const PatternView& row, std:: rawCodes.reserve(20); rawCodes.push_back(static_cast(startCode)); - Raw2TxtDecoder raw2txt(startCode, _convertFNC1); + Raw2TxtDecoder raw2txt(startCode); while (true) { if (!next.skipSymbol()) @@ -268,10 +270,10 @@ Result Code128Reader::decodePattern(int rowNumber, const PatternView& row, std:: if (Size(rawCodes) < minCharCount - 1) // stop code is missing in rawCodes return Result(DecodeStatus::NotFound); - // check termination bar (is present and not wider than about 2 modules) and quite zone (next is now 13 modules + // check termination bar (is present and not wider than about 2 modules) and quiet zone (next is now 13 modules // wide, require at least 8) next = next.subView(0, CHAR_LEN + 1); - if (!next.isValid() || next[CHAR_LEN] > next.sum(CHAR_LEN) / 4 || !next.hasQuiteZoneAfter(QUITE_ZONE/13)) + if (!next.isValid() || next[CHAR_LEN] > next.sum(CHAR_LEN) / 4 || !next.hasQuietZoneAfter(QUIET_ZONE/13)) return Result(DecodeStatus::NotFound); int checksum = rawCodes.front(); @@ -282,8 +284,8 @@ Result Code128Reader::decodePattern(int rowNumber, const PatternView& row, std:: return Result(DecodeStatus::ChecksumError); int xStop = next.pixelsTillEnd(); - return Result(raw2txt.text(), rowNumber, xStart, xStop, BarcodeFormat::Code128, std::move(rawCodes), - raw2txt.readerInit()); + return Result(raw2txt.text(), rowNumber, xStart, xStop, BarcodeFormat::Code128, raw2txt.symbologyIdentifier(), + std::move(rawCodes), raw2txt.readerInit()); } } // namespace ZXing::OneD diff --git a/core/src/oned/ODCode128Reader.h b/core/src/oned/ODCode128Reader.h index 624784641b..ebdc6bf19f 100644 --- a/core/src/oned/ODCode128Reader.h +++ b/core/src/oned/ODCode128Reader.h @@ -32,11 +32,7 @@ namespace OneD { class Code128Reader : public RowReader { public: - explicit Code128Reader(const DecodeHints& hints); - Result decodePattern(int rowNumber, const PatternView& row, std::unique_ptr&) const override; - -private: - bool _convertFNC1; + Result decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const override; }; } // OneD diff --git a/core/src/oned/ODCode128Writer.cpp b/core/src/oned/ODCode128Writer.cpp index a26b014ac9..24a097e3bf 100644 --- a/core/src/oned/ODCode128Writer.cpp +++ b/core/src/oned/ODCode128Writer.cpp @@ -155,16 +155,15 @@ Code128Writer::encode(const std::wstring& contents, int width, int height) const for (int i = 0; i < length; ++i) { int c = contents[i]; switch (c) { - case ESCAPE_FNC_1: - case ESCAPE_FNC_2: - case ESCAPE_FNC_3: - case ESCAPE_FNC_4: - break; - default: - if (c > 127) { - // support for FNC4 isn't implemented, no full Latin-1 character set available at the moment - throw std::invalid_argument(std::string("Bad character in input: ") + static_cast(c)); - } + case ESCAPE_FNC_1: + case ESCAPE_FNC_2: + case ESCAPE_FNC_3: + case ESCAPE_FNC_4: break; + default: + if (c > 127) { + // support for FNC4 isn't implemented, no full Latin-1 character set available at the moment + throw std::invalid_argument(std::string("Bad character in input: ") + static_cast(c)); + } } } @@ -184,23 +183,10 @@ Code128Writer::encode(const std::wstring& contents, int width, int height) const // Encode the current character // First handle escapes switch (contents[position]) { - case ESCAPE_FNC_1: - patternIndex = CODE_FNC_1; - break; - case ESCAPE_FNC_2: - patternIndex = CODE_FNC_2; - break; - case ESCAPE_FNC_3: - patternIndex = CODE_FNC_3; - break; - case ESCAPE_FNC_4: - if (codeSet == CODE_CODE_A) { - patternIndex = CODE_FNC_4_A; - } - else { - patternIndex = CODE_FNC_4_B; - } - break; + case ESCAPE_FNC_1: patternIndex = CODE_FNC_1; break; + case ESCAPE_FNC_2: patternIndex = CODE_FNC_2; break; + case ESCAPE_FNC_3: patternIndex = CODE_FNC_3; break; + case ESCAPE_FNC_4: patternIndex = (codeSet == CODE_CODE_A) ? CODE_FNC_4_A : CODE_FNC_4_B; break; default: // Then handle normal characters otherwise if (codeSet == CODE_CODE_A) { @@ -209,12 +195,11 @@ Code128Writer::encode(const std::wstring& contents, int width, int height) const // everything below a space character comes behind the underscore in the code patterns table patternIndex += '`'; } - } - else if (codeSet == CODE_CODE_B) { + } else if (codeSet == CODE_CODE_B) { patternIndex = contents[position] - ' '; - } - else { // CODE_CODE_C - patternIndex = (contents[position] - '0') * 10 + (position+1 < length ? contents[position+1] - '0' : 0); + } else { // CODE_CODE_C + patternIndex = + (contents[position] - '0') * 10 + (position + 1 < length ? contents[position + 1] - '0' : 0); position++; // Also incremented below } } @@ -268,11 +253,8 @@ Code128Writer::encode(const std::wstring& contents, int width, int height) const // Compute result std::vector result(codeWidth, false); - int pos = 0; - for (const auto& pattern : patterns) { - pos += WriterHelper::AppendPattern(result, pos, pattern, true); - } - + const auto op = [&result](auto pos, const auto& pattern){ return pos + WriterHelper::AppendPattern(result, pos, pattern, true);}; + auto pos = std::accumulate(std::begin(patterns), std::end(patterns), int{}, op); // Append termination bar result[pos++] = true; result[pos++] = true; diff --git a/core/src/oned/ODCode39Reader.cpp b/core/src/oned/ODCode39Reader.cpp index 25ab91f043..58c1a70871 100644 --- a/core/src/oned/ODCode39Reader.cpp +++ b/core/src/oned/ODCode39Reader.cpp @@ -17,13 +17,11 @@ #include "ODCode39Reader.h" -#include "BitArray.h" #include "DecodeHints.h" #include "Result.h" #include "ZXContainerAlgorithms.h" #include -#include namespace ZXing::OneD { @@ -90,22 +88,22 @@ DecodeExtendedCode39AndCode93(std::string& encoded, const char ctrl[4]) Code39Reader::Code39Reader(const DecodeHints& hints) : _extendedMode(hints.tryCode39ExtendedMode()), - _usingCheckDigit(hints.assumeCode39CheckDigit()) + _validateCheckSum(hints.validateCode39CheckSum()) { } -Result Code39Reader::decodePattern(int rowNumber, const PatternView& row, std::unique_ptr&) const +Result Code39Reader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const { // minimal number of characters that must be present (including start, stop and checksum characters) - int minCharCount = _usingCheckDigit ? 4 : 3; + int minCharCount = _validateCheckSum ? 4 : 3; auto isStartOrStopSymbol = [](char c) { return c == '*'; }; // provide the indices with the narrow bars/spaces which have to be equally wide constexpr auto START_PATTERN = FixedSparcePattern{0, 2, 3, 5, 7, 8}; - // quite zone is half the width of a character symbol - constexpr float QUITE_ZONE_SCALE = 0.5f; + // quiet zone is half the width of a character symbol + constexpr float QUIET_ZONE_SCALE = 0.5f; - auto next = FindLeftGuard(row, minCharCount * CHAR_LEN, START_PATTERN, QUITE_ZONE_SCALE * 12); + next = FindLeftGuard(next, minCharCount * CHAR_LEN, START_PATTERN, QUIET_ZONE_SCALE * 12); if (!next.isValid()) return Result(DecodeStatus::NotFound); @@ -131,10 +129,10 @@ Result Code39Reader::decodePattern(int rowNumber, const PatternView& row, std::u txt.pop_back(); // remove asterisk // check txt length and whitespace after the last char. See also FindStartPattern. - if (Size(txt) < minCharCount - 2 || !next.hasQuiteZoneAfter(QUITE_ZONE_SCALE)) + if (Size(txt) < minCharCount - 2 || !next.hasQuietZoneAfter(QUIET_ZONE_SCALE)) return Result(DecodeStatus::NotFound); - if (_usingCheckDigit) { + if (_validateCheckSum) { auto checkDigit = txt.back(); txt.pop_back(); int checksum = TransformReduce(txt, 0, [](char c) { return IndexOf(ALPHABET, c); }); @@ -145,8 +143,14 @@ Result Code39Reader::decodePattern(int rowNumber, const PatternView& row, std::u if (_extendedMode && !DecodeExtendedCode39AndCode93(txt, "$%/+")) return Result(DecodeStatus::FormatError); + // Symbology identifier modifiers ISO/IEC 16388:2007 Annex C Table C.1 + static const int symbologyModifiers[4] = { 0, 3 /*checksum*/, 4 /*extended*/, 7 /*checksum,extended*/ }; + int symbologyIdModifier = symbologyModifiers[(int)_extendedMode * 2 + (int)_validateCheckSum]; + + std::string symbologyIdentifier("]A" + std::to_string(symbologyIdModifier)); + int xStop = next.pixelsTillEnd(); - return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::Code39); + return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::Code39, std::move(symbologyIdentifier)); } } // namespace ZXing::OneD diff --git a/core/src/oned/ODCode39Reader.h b/core/src/oned/ODCode39Reader.h index c6a53448d3..5c5daef9f8 100644 --- a/core/src/oned/ODCode39Reader.h +++ b/core/src/oned/ODCode39Reader.h @@ -45,11 +45,11 @@ class Code39Reader : public RowReader */ explicit Code39Reader(const DecodeHints& hints); - Result decodePattern(int rowNumber, const PatternView& row, std::unique_ptr&) const override; + Result decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const override; private: bool _extendedMode; - bool _usingCheckDigit; + bool _validateCheckSum; }; } // OneD diff --git a/core/src/oned/ODCode93Reader.cpp b/core/src/oned/ODCode93Reader.cpp index b769f767ec..d9f3dd6859 100644 --- a/core/src/oned/ODCode93Reader.cpp +++ b/core/src/oned/ODCode93Reader.cpp @@ -18,7 +18,6 @@ #include "ODCode93Reader.h" -#include "BitArray.h" #include "Result.h" #include "ZXContainerAlgorithms.h" @@ -78,8 +77,8 @@ bool DecodeExtendedCode39AndCode93(std::string& encoded, const char ctrl[4]); constexpr int CHAR_LEN = 6; constexpr int CHAR_SUM = 9; -// quite zone is half the width of a character symbol -constexpr float QUITE_ZONE_SCALE = 0.5f; +// quiet zone is half the width of a character symbol +constexpr float QUIET_ZONE_SCALE = 0.5f; static bool IsStartGuard(const PatternView& window, int spaceInPixel) { @@ -87,17 +86,17 @@ static bool IsStartGuard(const PatternView& window, int spaceInPixel) // Use only the first 4 elements which results in more than a 2x speedup. This is counter-intuitive since we save at // most 1/3rd of the loop iterations in FindPattern. The reason might be a successful vectorization with the limited // pattern size that is missed otherwise. We check for the remaining 2 slots for plausibility of the 4:1 ratio. - return IsPattern(window, FixedPattern<4, 4>{1, 1, 1, 1}, spaceInPixel, QUITE_ZONE_SCALE * 12) && + return IsPattern(window, FixedPattern<4, 4>{1, 1, 1, 1}, spaceInPixel, QUIET_ZONE_SCALE * 12) && window[4] > 3 * window[5] - 2 && RowReader::OneToFourBitPattern(window) == ASTERISK_ENCODING; } -Result Code93Reader::decodePattern(int rowNumber, const PatternView& row, std::unique_ptr&) const +Result Code93Reader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const { // minimal number of characters that must be present (including start, stop, checksum and 1 payload characters) int minCharCount = 5; - auto next = FindLeftGuard(row, minCharCount * CHAR_LEN, IsStartGuard); + next = FindLeftGuard(next, minCharCount * CHAR_LEN, IsStartGuard); if (!next.isValid()) return Result(DecodeStatus::NotFound); @@ -121,9 +120,9 @@ Result Code93Reader::decodePattern(int rowNumber, const PatternView& row, std::u if (Size(txt) < minCharCount - 2) return Result(DecodeStatus::NotFound); - // check termination bar (is present and not wider than about 2 modules) and quite zone + // check termination bar (is present and not wider than about 2 modules) and quiet zone next = next.subView(0, CHAR_LEN + 1); - if (!next.isValid() || next[CHAR_LEN] > next.sum(CHAR_LEN) / 4 || !next.hasQuiteZoneAfter(QUITE_ZONE_SCALE)) + if (!next.isValid() || next[CHAR_LEN] > next.sum(CHAR_LEN) / 4 || !next.hasQuietZoneAfter(QUIET_ZONE_SCALE)) return Result(DecodeStatus::NotFound); if (!CheckChecksums(txt)) @@ -135,8 +134,11 @@ Result Code93Reader::decodePattern(int rowNumber, const PatternView& row, std::u if (!DecodeExtendedCode39AndCode93(txt, "abcd")) return Result(DecodeStatus::FormatError); + // Symbology identifier ISO/IEC 15424:2008 4.4.10 no modifiers + std::string symbologyIdentifier("]G0"); + int xStop = next.pixelsTillEnd(); - return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::Code93); + return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::Code93, std::move(symbologyIdentifier)); } } // namespace ZXing::OneD diff --git a/core/src/oned/ODCode93Reader.h b/core/src/oned/ODCode93Reader.h index 9455019a90..95a385e0ad 100644 --- a/core/src/oned/ODCode93Reader.h +++ b/core/src/oned/ODCode93Reader.h @@ -30,7 +30,7 @@ namespace OneD { class Code93Reader : public RowReader { public: - Result decodePattern(int rowNumber, const PatternView& row, std::unique_ptr&) const override; + Result decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const override; }; } // OneD diff --git a/core/src/oned/ODDataBarCommon.cpp b/core/src/oned/ODDataBarCommon.cpp index 0ba9a32c91..aba8505311 100644 --- a/core/src/oned/ODDataBarCommon.cpp +++ b/core/src/oned/ODDataBarCommon.cpp @@ -118,8 +118,8 @@ bool ReadDataCharacterRaw(const PatternView& view, int numModules, bool reversed #if 0 // the 'signal improving' strategy of trying to fix off-by-one errors in the sum or parity leads to a massively - // increased likelyhood of false positives / misreads especially with expanded codes that are composed of many - // pairs. the combinatorial explosion of posible pair combinations (see FindValidSequence) results in many possible + // increased likelihood of false positives / misreads especially with expanded codes that are composed of many + // pairs. the combinatorial explosion of possible pair combinations (see FindValidSequence) results in many possible // sequences with valid checksums. It can slightly lower the minimum required resolution to detect something at all // but the introduced error rate is clearly not worth it. @@ -163,4 +163,10 @@ Position EstimatePosition(const Pair& first, const Pair& last) return Position{{first.xStart, first.y}, {first.xStop, first.y}, {last.xStop, last.y}, {last.xStart, last.y}}; } +int EstimateLineCount(const Pair& first, const Pair& last) +{ + // see incrementLineCount() in ODReader.cpp for the -1 here + return std::min(first.count, last.count) * ((first.y == last.y) ? 1 : 2) - 1; +} + } // namespace ZXing::OneD::DataBar diff --git a/core/src/oned/ODDataBarCommon.h b/core/src/oned/ODDataBarCommon.h index ce8e98683e..08669b208e 100644 --- a/core/src/oned/ODDataBarCommon.h +++ b/core/src/oned/ODDataBarCommon.h @@ -138,5 +138,6 @@ bool ReadDataCharacterRaw(const PatternView& view, int numModules, bool reversed int GetValue(const Array4I& widths, int maxWidth, bool noNarrow); Position EstimatePosition(const Pair& first, const Pair& last); +int EstimateLineCount(const Pair& first, const Pair& last); } // namespace ZXing::OneD::DataBar diff --git a/core/src/oned/ODDataBarExpandedReader.cpp b/core/src/oned/ODDataBarExpandedReader.cpp index 3bf1683c09..769b279648 100644 --- a/core/src/oned/ODDataBarExpandedReader.cpp +++ b/core/src/oned/ODDataBarExpandedReader.cpp @@ -131,7 +131,7 @@ constexpr int FINDER_D = 4; constexpr int FINDER_E = 5; constexpr int FINDER_F = 6; -// A negative number means the finder pattern is layed out right2left. Note: each finder may only occur once per code. +// A negative number means the finder pattern is laid out right2left. Note: each finder may only occur once per code. static const std::array, 10> FINDER_PATTERN_SEQUENCES = {{ {FINDER_A, -FINDER_A}, {FINDER_A, -FINDER_B, FINDER_B}, @@ -201,16 +201,15 @@ static Pair ReadPair(const PatternView& view, Direction dir) } template -static Pairs ReadRowOfPairs(const PatternView& view, int rowNumber) +static Pairs ReadRowOfPairs(PatternView& next, int rowNumber) { Pairs pairs; Pair pair; - PatternView next; if constexpr (STACKED) { // a possible first pair is either left2right starting on a space or right2left starting on a bar. // it might be a half-pair - next = view.subView(0, HALF_PAIR_SIZE); + next = next.subView(0, HALF_PAIR_SIZE); while (next.shift(1)) { if (IsL2RPair(next) && (pair = ReadPair(next, Direction::Right)) && (pair.finder != FINDER_A || IsGuard(next[-1], next[11]))) @@ -221,7 +220,7 @@ static Pairs ReadRowOfPairs(const PatternView& view, int rowNumber) } else { // the only possible first pair is a full, left2right FINDER_A pair starting on a space // with a guard bar on the left - next = view.subView(-1, FULL_PAIR_SIZE); + next = next.subView(-1, FULL_PAIR_SIZE); while (next.shift(2)) { if (IsL2RPair(next) && IsGuard(next[-1], next[11]) && (pair = ReadPair(next, Direction::Right)).finder == FINDER_A) @@ -231,8 +230,10 @@ static Pairs ReadRowOfPairs(const PatternView& view, int rowNumber) next = next.subView(0, HALF_PAIR_SIZE); } - if (!pair) + if (!pair) { + next = {}; // if we didn't find a single pair, consume the rest of the row return {}; + } auto flippedDir = [](Pair p) { return p.finder < 0 ? Direction::Right : Direction::Left; }; auto isValidPair = [](Pair p, PatternView v) { return p.right || IsGuard(v[p.finder < 0 ? 9 : 11], v[13]); }; @@ -280,7 +281,8 @@ static bool FindValidSequence(const PairMap& all, ITER begin, ITER end, Pairs& s constexpr int N = 2; // TODO c++20 ranges::views::take() auto& pairs = ppairs->second; - for (auto p = pairs.begin(), pend = std::min(pairs.end(), pairs.begin() + N); p != pend; ++p) { + int n = 0; + for (auto p = pairs.begin(), pend = pairs.end(); p != pend && n < N; ++p, ++n) { // skip p if it is a half-pair but not the last one in the sequence if (!p->right && std::next(begin) != end) continue; @@ -314,6 +316,14 @@ static Pairs FindValidSequence(PairMap& all) return stack; } +static void RemovePairs(PairMap& all, const Pairs& pairs) +{ + for(const auto& p : pairs) + if (auto i = Find(all[p.finder], p); i != all[p.finder].end()) + if (--i->count == 0) + all[p.finder].erase(i); +} + static BitArray BuildBitArray(const Pairs& pairs) { BitArray res; @@ -333,7 +343,7 @@ struct DBERState : public RowReader::DecodingState PairMap allPairs; }; -Result DataBarExpandedReader::decodePattern(int rowNumber, const PatternView& view, +Result DataBarExpandedReader::decodePattern(int rowNumber, PatternView& view, std::unique_ptr& state) const { #if 0 // non-stacked version @@ -346,7 +356,7 @@ Result DataBarExpandedReader::decodePattern(int rowNumber, const PatternView& vi state.reset(new DBERState); auto& allPairs = static_cast(state.get())->allPairs; - // Stacked codes can be layed out in a number of ways. The following rules apply: + // Stacked codes can be laid out in a number of ways. The following rules apply: // * the first row starts with FINDER_A in left-to-right (l2r) layout // * pairs in l2r layout start with a space, r2l ones with a bar // * l2r and r2l finders always alternate @@ -368,11 +378,19 @@ Result DataBarExpandedReader::decodePattern(int rowNumber, const PatternView& vi #endif auto txt = DecodeExpandedBits(BuildBitArray(pairs)); - if(txt.empty()) + if (txt.empty()) return Result(DecodeStatus::NotFound); + RemovePairs(allPairs, pairs); + + // Symbology identifier ISO/IEC 24724:2011 Section 9 and GS1 General Specifications 5.1.3 Figure 5.1.3-2 + std::string symbologyIdentifier("]e0"); + + // TODO: EstimatePosition misses part of the symbol in the stacked case where the last row contains less pairs than + // the first return {TextDecoder::FromLatin1(txt), EstimatePosition(pairs.front(), pairs.back()), - BarcodeFormat::DataBarExpanded}; + BarcodeFormat::DataBarExpanded, std::move(symbologyIdentifier), {}, {}, false, + EstimateLineCount(pairs.front(), pairs.back())}; } } // namespace ZXing::OneD diff --git a/core/src/oned/ODDataBarExpandedReader.h b/core/src/oned/ODDataBarExpandedReader.h index bbc93411df..a2e9a306d6 100644 --- a/core/src/oned/ODDataBarExpandedReader.h +++ b/core/src/oned/ODDataBarExpandedReader.h @@ -33,7 +33,7 @@ class DataBarExpandedReader : public RowReader explicit DataBarExpandedReader(const DecodeHints& hints); ~DataBarExpandedReader() override; - Result decodePattern(int rowNumber, const PatternView& row, std::unique_ptr& state) const override; + Result decodePattern(int rowNumber, PatternView& next, std::unique_ptr& state) const override; }; } // OneD diff --git a/core/src/oned/ODDataBarReader.cpp b/core/src/oned/ODDataBarReader.cpp index 3be9fe5b38..f418fd64e8 100644 --- a/core/src/oned/ODDataBarReader.cpp +++ b/core/src/oned/ODDataBarReader.cpp @@ -127,8 +127,8 @@ static Pair ReadPair(const PatternView& view, bool rightPair) if (auto outside = ReadDataCharacter(rightPair ? RightChar(view) : LeftChar(view), true, rightPair)) if (auto inside = ReadDataCharacter(rightPair ? LeftChar(view) : RightChar(view), false, rightPair)) { // include left and right guards - int xStart = view.pixelsInFront() - (rightPair ? 0 : view[-1] + std::min(view[-2], view[-1])); - int xStop = view.pixelsTillEnd() + (rightPair ? view[FULL_PAIR_SIZE] + view[FULL_PAIR_SIZE + 1] : 0); + int xStart = view.pixelsInFront() - view[-1]; + int xStop = view.pixelsTillEnd() + 2 * view[FULL_PAIR_SIZE]; return {outside, inside, pattern, xStart, xStop}; } @@ -163,11 +163,11 @@ struct State : public RowReader::DecodingState std::unordered_set rightPairs; }; -Result DataBarReader::decodePattern(int rowNumber, const PatternView& view, +Result DataBarReader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr& state) const { #if 0 // non-stacked version - auto next = view.subView(-1, FULL_PAIR_SIZE + 2); + next = next.subView(-1, FULL_PAIR_SIZE); // yes: the first view we test is at index 1 (black bar at 0 would be the guard pattern) while (next.shift(2)) { if (IsLeftPair(next)) { @@ -184,7 +184,7 @@ Result DataBarReader::decodePattern(int rowNumber, const PatternView& view, state.reset(new State); auto* prevState = static_cast(state.get()); - auto next = view.subView(0, FULL_PAIR_SIZE + 2); // +2 reflects the guard pattern on the right + next = next.subView(0, FULL_PAIR_SIZE); // yes: the first view we test is at index 1 (black bar at 0 would be the guard pattern) while (next.shift(1)) { if (IsLeftPair(next)) { @@ -199,17 +199,31 @@ Result DataBarReader::decodePattern(int rowNumber, const PatternView& view, if (auto rightPair = ReadPair(next, true)) { rightPair.y = rowNumber; prevState->rightPairs.insert(rightPair); + next.shift(FULL_PAIR_SIZE + 2); } } } for (const auto& leftPair : prevState->leftPairs) for (const auto& rightPair : prevState->rightPairs) - if (ChecksumIsValid(leftPair, rightPair)) - return {TextDecoder::FromLatin1(ConstructText(leftPair, rightPair)), - EstimatePosition(leftPair, rightPair), BarcodeFormat::DataBar}; + if (ChecksumIsValid(leftPair, rightPair)) { + // Symbology identifier ISO/IEC 24724:2011 Section 9 and GS1 General Specifications 5.1.3 Figure 5.1.3-2 + std::string symbologyIdentifier("]e0"); + + Result res{TextDecoder::FromLatin1(ConstructText(leftPair, rightPair)), + EstimatePosition(leftPair, rightPair), BarcodeFormat::DataBar, + std::move(symbologyIdentifier), {}, {}, false, + EstimateLineCount(leftPair, rightPair)}; + + prevState->leftPairs.erase(leftPair); + prevState->rightPairs.erase(rightPair); + return res; + } #endif + // guaratee progress (see loop in ODReader.cpp) + next = {}; + return Result(DecodeStatus::NotFound); } diff --git a/core/src/oned/ODDataBarReader.h b/core/src/oned/ODDataBarReader.h index 1402b6991d..2da0109691 100644 --- a/core/src/oned/ODDataBarReader.h +++ b/core/src/oned/ODDataBarReader.h @@ -33,7 +33,7 @@ class DataBarReader : public RowReader explicit DataBarReader(const DecodeHints& hints); ~DataBarReader() override; - Result decodePattern(int rowNumber, const PatternView& row, std::unique_ptr& state) const override; + Result decodePattern(int rowNumber, PatternView& next, std::unique_ptr& state) const override; }; } // OneD diff --git a/core/src/oned/ODITFReader.cpp b/core/src/oned/ODITFReader.cpp index f6fb1359af..e314b99368 100644 --- a/core/src/oned/ODITFReader.cpp +++ b/core/src/oned/ODITFReader.cpp @@ -17,8 +17,8 @@ #include "ODITFReader.h" -#include "BitArray.h" #include "DecodeHints.h" +#include "GTIN.h" #include "Result.h" #include "ZXContainerAlgorithms.h" @@ -27,26 +27,26 @@ namespace ZXing::OneD { /** Valid ITF lengths. Anything longer than the largest value is also allowed. */ -static const std::array DEFAULT_ALLOWED_LENGTHS = { 6, 8, 10, 12, 14 }; +constexpr auto DEFAULT_ALLOWED_LENGTHS = { 6, 8, 10, 12, 14 }; ITFReader::ITFReader(const DecodeHints& hints) : - _allowedLengths(hints.allowedLengths()) + _allowedLengths(hints.allowedLengths()), + _validateCheckSum(hints.validateITFCheckSum()) { - if (_allowedLengths.empty()) { + if (_allowedLengths.empty()) _allowedLengths.assign(DEFAULT_ALLOWED_LENGTHS.begin(), DEFAULT_ALLOWED_LENGTHS.end()); - } } constexpr auto START_PATTERN_ = FixedPattern<4, 4>{1, 1, 1, 1}; constexpr auto STOP_PATTERN_1 = FixedPattern<3, 4>{2, 1, 1}; constexpr auto STOP_PATTERN_2 = FixedPattern<3, 5>{3, 1, 1}; -Result ITFReader::decodePattern(int rowNumber, const PatternView& row, std::unique_ptr&) const +Result ITFReader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const { const int minCharCount = 6; - const int minQuiteZone = 10; + const int minQuietZone = 10; - auto next = FindLeftGuard(row, 4 + minCharCount/2 + 3, START_PATTERN_, minQuiteZone); + next = FindLeftGuard(next, 4 + minCharCount/2 + 3, START_PATTERN_, minQuietZone); if (!next.isValid()) return Result(DecodeStatus::NotFound); @@ -57,7 +57,7 @@ Result ITFReader::decodePattern(int rowNumber, const PatternView& row, std::uniq int xStart = next.pixelsInFront(); next = next.subView(4, 10); - while (next.index() < row.size() - (10 + 3)) { + while (next.isValid()) { const auto threshold = NarrowWideThreshold(next); if (!threshold.isValid()) break; @@ -81,14 +81,24 @@ Result ITFReader::decodePattern(int rowNumber, const PatternView& row, std::uniq next = next.subView(0, 3); - if (Size(txt) < minCharCount) + if (Size(txt) < minCharCount || !next.isValid()) return Result(DecodeStatus::NotFound); - if (!IsRightGuard(next, STOP_PATTERN_1, minQuiteZone) && !IsRightGuard(next, STOP_PATTERN_2, minQuiteZone)) + if (!IsRightGuard(next, STOP_PATTERN_1, minQuietZone) && !IsRightGuard(next, STOP_PATTERN_2, minQuietZone)) return Result(DecodeStatus::NotFound); + if (_validateCheckSum && !GTIN::IsCheckDigitValid(txt)) + return Result(DecodeStatus::ChecksumError); + + // Symbology identifier ISO/IEC 16390:2007 Annex C Table C.1 + // See also GS1 General Specifications 5.1.3 Figure 5.1.3-2 + std::string symbologyIdentifier("]I0"); // No check character validation + + if (_validateCheckSum || (txt.size() == 14 && GTIN::IsCheckDigitValid(txt))) // If no hint test if valid ITF-14 + symbologyIdentifier = "]I1"; // Modulo 10 symbol check character validated and transmitted + int xStop = next.pixelsTillEnd(); - return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::ITF); + return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::ITF, std::move(symbologyIdentifier)); } } // namespace ZXing::OneD diff --git a/core/src/oned/ODITFReader.h b/core/src/oned/ODITFReader.h index 45085b9eb6..322ee17d20 100644 --- a/core/src/oned/ODITFReader.h +++ b/core/src/oned/ODITFReader.h @@ -34,8 +34,7 @@ namespace OneD { * lengths are scanned, especially shorter ones, to avoid false positives. This in turn is due to a lack of * required checksum function.

* -*

The checksum is optional and is not applied by this Reader. The consumer of the decoded -* value will have to apply a checksum if required.

+*

The checksum is optional and is only applied by this Reader if the assumeITFCheckDigit hint is given.

* *

http://en.wikipedia.org/wiki/Interleaved_2_of_5 * is a great reference for Interleaved 2 of 5 information.

@@ -46,10 +45,11 @@ class ITFReader : public RowReader { public: explicit ITFReader(const DecodeHints& hints); - Result decodePattern(int rowNumber, const PatternView& row, std::unique_ptr&) const override; + Result decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const override; private: std::vector _allowedLengths; + bool _validateCheckSum; }; } // OneD diff --git a/core/src/oned/ODMultiUPCEANReader.cpp b/core/src/oned/ODMultiUPCEANReader.cpp index eda63dd552..e567a07a84 100644 --- a/core/src/oned/ODMultiUPCEANReader.cpp +++ b/core/src/oned/ODMultiUPCEANReader.cpp @@ -24,9 +24,6 @@ #include "GTIN.h" #include "ODUPCEANCommon.h" #include "Result.h" -#include "TextDecoder.h" - -#include namespace ZXing::OneD { @@ -51,16 +48,17 @@ static const int FIRST_DIGIT_ENCODINGS[] = { 0x00, 0x0B, 0x0D, 0x0E, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A }; -// The GS1 specification has the following to say about quite zones +// The GS1 specification has the following to say about quiet zones // Type: EAN-13 | EAN-8 | UPC-A | UPC-E | EAN Add-on | UPC Add-on // QZ L: 11 | 7 | 9 | 9 | 7-12 | 9-12 // QZ R: 7 | 7 | 9 | 7 | 5 | 5 constexpr float QUIET_ZONE_LEFT = 6; constexpr float QUIET_ZONE_RIGHT = 6; +constexpr float QUIET_ZONE_ADDON = 3; // There is a single sample (ean13-1/12.png) that fails to decode with these (new) settings because -// it has a right-side quite zone of only about 4.5 modules, which is clearly out of spec. +// it has a right-side quiet zone of only about 4.5 modules, which is clearly out of spec. static bool DecodeDigit(const PatternView& view, std::string& txt, int* lgPattern = nullptr) { @@ -254,6 +252,8 @@ static bool AddOn(PartialResult& res, PatternView begin, int digitCount) auto moduleSize = IsPattern(ext, EXT_START_PATTERN); CHECK(moduleSize); + CHECK(ext.isAtLastBar() || *ext.end() > QUIET_ZONE_ADDON * moduleSize - 1); + res.end = ext; ext = ext.subView(EXT_START_PATTERN.size(), CHAR_LEN); int lgPattern = 0; @@ -268,8 +268,6 @@ static bool AddOn(PartialResult& res, PatternView begin, int digitCount) } } - //TODO: check right quite zone - if (digitCount == 2) { CHECK(std::stoi(res.txt) % 4 == lgPattern); } else { @@ -280,15 +278,16 @@ static bool AddOn(PartialResult& res, PatternView begin, int digitCount) return true; } -Result MultiUPCEANReader::decodePattern(int rowNumber, const PatternView& row, std::unique_ptr&) const +Result MultiUPCEANReader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const { const int minSize = 3 + 6*4 + 6; // UPC-E - auto begin = FindLeftGuard(row, minSize, END_PATTERN, QUIET_ZONE_LEFT); - if (!begin.isValid()) + next = FindLeftGuard(next, minSize, END_PATTERN, QUIET_ZONE_LEFT); + if (!next.isValid()) return Result(DecodeStatus::NotFound); PartialResult res; + auto begin = next; if (!(((_hints.hasFormat(BarcodeFormat::EAN13 | BarcodeFormat::UPCA)) && EAN13(res, begin)) || (_hints.hasFormat(BarcodeFormat::EAN8) && EAN8(res, begin)) || @@ -306,19 +305,32 @@ Result MultiUPCEANReader::decodePattern(int rowNumber, const PatternView& row, s res.format = BarcodeFormat::UPCA; } + // Symbology identifier modifiers ISO/IEC 15420:2009 Annex B Table B.1 + // ISO/IEC 15420:2009 (& GS1 General Specifications 5.1.3) states that the content for "]E0" should be 13 digits, + // i.e. converted to EAN-13 if UPC-A/E, but not doing this here to maintain backward compatibility + std::string symbologyIdentifier(res.format == BarcodeFormat::EAN8 ? "]E4" : "]E0"); + auto ext = res.end; PartialResult addOnRes; if (_hints.eanAddOnSymbol() != EanAddOnSymbol::Ignore && ext.skipSymbol() && ext.skipSingle(static_cast(begin.sum() * 3.5)) && (AddOn(addOnRes, ext, 5) || AddOn(addOnRes, ext, 2))) { + // ISO/IEC 15420:2009 states that the content for "]E3" should be 15 or 18 digits, i.e. converted to EAN-13 + // and extended with no separator, and that the content for "]E4" should be 8 digits, i.e. no add-on //TODO: extend position in include extension res.txt += " " + addOnRes.txt; + + if (res.format != BarcodeFormat::EAN8) // Keeping EAN-8 with add-on as "]E4" + symbologyIdentifier = "]E3"; // Combined packet, EAN-13, UPC-A, UPC-E, with add-on } + next = res.end; + if (_hints.eanAddOnSymbol() == EanAddOnSymbol::Require && !addOnRes.isValid()) return Result(DecodeStatus::NotFound); - return {res.txt, rowNumber, begin.pixelsInFront(), res.end.pixelsTillEnd(), res.format}; + return { + res.txt, rowNumber, begin.pixelsInFront(), res.end.pixelsTillEnd(), res.format, std::move(symbologyIdentifier)}; } } // namespace ZXing::OneD diff --git a/core/src/oned/ODMultiUPCEANReader.h b/core/src/oned/ODMultiUPCEANReader.h index 9fa6281f62..9d3154331e 100644 --- a/core/src/oned/ODMultiUPCEANReader.h +++ b/core/src/oned/ODMultiUPCEANReader.h @@ -36,7 +36,7 @@ class MultiUPCEANReader : public RowReader explicit MultiUPCEANReader(const DecodeHints& hints); ~MultiUPCEANReader() override; - Result decodePattern(int rowNumber, const PatternView& row, std::unique_ptr&) const override; + Result decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const override; private: bool _canReturnUPCA = false; diff --git a/core/src/oned/ODReader.cpp b/core/src/oned/ODReader.cpp index fd96babce4..eb4d4fbcdb 100644 --- a/core/src/oned/ODReader.cpp +++ b/core/src/oned/ODReader.cpp @@ -19,7 +19,6 @@ #include "ODReader.h" #include "BinaryBitmap.h" -#include "BitArray.h" #include "DecodeHints.h" #include "ODCodabarReader.h" #include "ODCode128Reader.h" @@ -39,7 +38,8 @@ namespace ZXing::OneD { Reader::Reader(const DecodeHints& hints) : _tryHarder(hints.tryHarder()), _tryRotate(hints.tryRotate()), - _isPure(hints.isPure()) + _isPure(hints.isPure()), + _minLineCount(hints.minLineCount()) { _readers.reserve(8); @@ -53,7 +53,7 @@ Reader::Reader(const DecodeHints& hints) : if (formats.testFlag(BarcodeFormat::Code93)) _readers.emplace_back(new Code93Reader()); if (formats.testFlag(BarcodeFormat::Code128)) - _readers.emplace_back(new Code128Reader(hints)); + _readers.emplace_back(new Code128Reader()); if (formats.testFlag(BarcodeFormat::ITF)) _readers.emplace_back(new ITFReader(hints)); if (formats.testFlag(BarcodeFormat::Codabar)) @@ -74,28 +74,33 @@ Reader::~Reader() = default; * rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily * decided that moving up and down by about 1/16 of the image is pretty good; we try more of the * image if "trying harder". -* -* @param image The image to decode -* @param hints Any hints that were requested -* @return The contents of the decoded barcode -* @throws NotFoundException Any spontaneous errors which occur */ -static Result -DoDecode(const std::vector>& readers, const BinaryBitmap& image, bool tryHarder, bool isPure) +static Results DoDecode(const std::vector>& readers, const BinaryBitmap& image, + bool tryHarder, bool rotate, bool isPure, int maxSymbols, int minLineCount) { + Results res; + std::vector> decodingState(readers.size()); int width = image.width(); int height = image.height(); + if (rotate) + std::swap(width, height); + int middle = height / 2; - int rowStep = std::max(1, height / (tryHarder ? 256 : 32)); + // TODO: find a better heuristic/parameterization if maxSymbols != 1 + int rowStep = std::max(1, height / ((tryHarder && !isPure) ? (maxSymbols == 1 ? 256 : 512) : 32)); int maxLines = tryHarder ? height : // Look at the whole image, not just the center 15; // 15 rows spaced 1/32 apart is roughly the middle half of the image + if (isPure) + minLineCount = 1; + std::vector checkRows; + PatternRow bars; - bars.reserve(128); // e.g. EAN-13 has 96 bars + bars.reserve(128); // e.g. EAN-13 has 59 bars/spaces for (int i = 0; i < maxLines; i++) { @@ -108,16 +113,24 @@ DoDecode(const std::vector>& readers, const BinaryBit break; } - if (!image.getPatternRow(rowNumber, bars)) + // See if we have additional check rows (see below) to process + if (checkRows.size()) { + --i; + rowNumber = checkRows.back(); + checkRows.pop_back(); + if (rowNumber < 0 || rowNumber >= height) + continue; + } + + if (!image.getPatternRow(rowNumber, rotate ? 270 : 0, bars)) continue; // While we have the image data in a PatternRow, it's fairly cheap to reverse it in place to // handle decoding upside down barcodes. - // Note: the DataBarExpanded decoder depends on seeing each line from both directions. This - // 'surprising' and inconsistent. It also requires the decoderState to be shared between - // normal and reversed scans, which makes no sense in general because it would mix partial - // detection data from two codes of the same type next to each other. TODO.. - // See also https://github.com/nu-book/zxing-cpp/issues/87 + // TODO: the DataBarExpanded (stacked) decoder depends on seeing each line from both directions. This + // 'surprising' and inconsistent. It also requires the decoderState to be shared between normal and reversed + // scans, which makes no sense in general because it would mix partial detection data from two codes of the same + // type next to each other. See also https://github.com/nu-book/zxing-cpp/issues/87 for (bool upsideDown : {false, true}) { // trying again? if (upsideDown) { @@ -126,48 +139,117 @@ DoDecode(const std::vector>& readers, const BinaryBit } // Look for a barcode for (size_t r = 0; r < readers.size(); ++r) { - Result result = readers[r]->decodePattern(rowNumber, bars, decodingState[r]); - if (result.isValid()) { - if (upsideDown) { - // update position (flip horizontally). - auto points = result.position(); - for (auto& p : points) { - p = {width - p.x - 1, p.y}; + // If this is a pure symbol, then checking a single non-empty line is sufficient for all but the stacked + // DataBar codes. They are the only ones using the decodingState, which we can use as a flag here. + if (isPure && i && !decodingState[r]) + continue; + + PatternView next(bars); + do { + Result result = readers[r]->decodePattern(rowNumber, next, decodingState[r]); + if (result.isValid()) { + result.incrementLineCount(); + if (upsideDown) { + // update position (flip horizontally). + auto points = result.position(); + for (auto& p : points) { + p = {width - p.x - 1, p.y}; + } + result.setPosition(std::move(points)); + } + if (rotate) { + auto points = result.position(); + for (auto& p : points) { + p = {height - p.y - 1, p.x}; + } + result.setPosition(std::move(points)); + } + + // check if we know this code already + for (auto& other : res) { + if (other == result) { + // merge the position information + auto dTop = maxAbsComponent(other.position().topLeft() - result.position().topLeft()); + auto dBot = maxAbsComponent(other.position().bottomLeft() - result.position().topLeft()); + auto points = other.position(); + if (dTop < dBot || (dTop == dBot && rotate ^ (sumAbsComponent(points[0]) > + sumAbsComponent(result.position()[0])))) { + points[0] = result.position()[0]; + points[1] = result.position()[1]; + } else { + points[2] = result.position()[2]; + points[3] = result.position()[3]; + } + other.setPosition(points); + other.incrementLineCount(); + // clear the result, so we don't insert it again below + result = Result(DecodeStatus::NotFound); + break; + } + } + + if (result.isValid()) + res.push_back(std::move(result)); + + if (maxSymbols && Reduce(res, 0, [&](int s, const Result& r) { + return s + (r.lineCount() >= minLineCount); + }) == maxSymbols) { + goto out; + } + + // if we found a valid code but have a minLineCount > 1, add additional check rows above and + // below the current one + if (checkRows.empty() && minLineCount > 1 && rowStep > 1) { + checkRows = {rowNumber - 1, rowNumber + 1}; + if (rowStep > 2) + checkRows.insert(checkRows.end(), {rowNumber - 2, rowNumber + 2}); } - result.setPosition(std::move(points)); } - return result; - } + // make sure we make progress and we start the next try on a bar + next.shift(2 - (next.index() % 2)); + next.extend(); + } while (tryHarder && next.size()); } } - - // If this is a pure symbol, then checking a single non-empty line is sufficient - if (isPure) - break; } - return Result(DecodeStatus::NotFound); + +out: + // remove all symbols with insufficient line count + auto it = std::remove_if(res.begin(), res.end(), [&](auto&& r) { return r.lineCount() < minLineCount; }); + res.erase(it, res.end()); + + // if symbols overlap, remove the one with a lower line count + for (auto a = res.begin(); a != res.end(); ++a) + for (auto b = std::next(a); b != res.end(); ++b) + if (HaveIntersectingBoundingBoxes(a->position(), b->position())) + *(a->lineCount() < b->lineCount() ? a : b) = Result(DecodeStatus::NotFound); + + //TODO: C++20 res.erase_if() + it = std::remove_if(res.begin(), res.end(), [](auto&& r) { return r.status() == DecodeStatus::NotFound; }); + res.erase(it, res.end()); + + return res; } Result Reader::decode(const BinaryBitmap& image) const { - Result result = DoDecode(_readers, image, _tryHarder, _isPure); - - if (!result.isValid() && _tryRotate && image.canRotate()) { - auto rotatedImage = image.rotated(270); - result = DoDecode(_readers, *rotatedImage, _tryHarder, _isPure); - if (result.isValid()) { - // Update position - auto points = result.position(); - int height = rotatedImage->height(); - for (auto& p : points) { - p = {height - p.y - 1, p.x}; - } - result.setPosition(std::move(points)); - } - } + auto result = DoDecode(_readers, image, _tryHarder, false, _isPure, 1, _minLineCount); - return result; + if (result.empty() && _tryRotate) + result = DoDecode(_readers, image, _tryHarder, true, _isPure, 1, _minLineCount); + + return result.empty() ? Result(DecodeStatus::NotFound) : result.front(); +} + +Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const +{ + auto resH = DoDecode(_readers, image, _tryHarder, false, _isPure, maxSymbols, _minLineCount); + if ((!maxSymbols || Size(resH) < maxSymbols) && _tryRotate) { + auto resV = DoDecode(_readers, image, _tryHarder, true, _isPure, maxSymbols - Size(resH), _minLineCount); + resH.insert(resH.end(), resV.begin(), resV.end()); + } + return resH; } } // namespace ZXing::OneD diff --git a/core/src/oned/ODReader.h b/core/src/oned/ODReader.h index 3603acb93f..e07d8d4e57 100644 --- a/core/src/oned/ODReader.h +++ b/core/src/oned/ODReader.h @@ -40,12 +40,14 @@ class Reader : public ZXing::Reader ~Reader() override; Result decode(const BinaryBitmap& image) const override; + Results decode(const BinaryBitmap& image, int maxSymbols) const override; private: std::vector> _readers; bool _tryHarder; bool _tryRotate; bool _isPure; + int _minLineCount; }; } // OneD diff --git a/core/src/oned/ODRowReader.cpp b/core/src/oned/ODRowReader.cpp index c3de5c5532..de0febc20e 100644 --- a/core/src/oned/ODRowReader.cpp +++ b/core/src/oned/ODRowReader.cpp @@ -39,7 +39,8 @@ Result RowReader::decodeSingleRow(int rowNumber, const BitArray& row) const if (*(i-1)) res.push_back(0); - return decodePattern(rowNumber, res, state); + PatternView view(res); + return decodePattern(rowNumber, view, state); } } // namespace ZXing::OneD diff --git a/core/src/oned/ODRowReader.h b/core/src/oned/ODRowReader.h index 57ad645fd4..ec6bc2882e 100644 --- a/core/src/oned/ODRowReader.h +++ b/core/src/oned/ODRowReader.h @@ -25,8 +25,8 @@ #include #include #include +#include #include -#include /* Code39 : 1:2/3, 5+4+1 (0x3|2x1 wide) -> 12-15 mods, v1-? | ToNarrowWide(OMG 1) == * @@ -70,7 +70,7 @@ class RowReader virtual ~RowReader() {} - virtual Result decodePattern(int rowNumber, const PatternView& row, std::unique_ptr& state) const = 0; + virtual Result decodePattern(int rowNumber, PatternView& next, std::unique_ptr& state) const = 0; /** * Determines how closely a set of observed counts of runs of black/white values matches a given @@ -118,7 +118,7 @@ class RowReader * digit. * * @param counters the counts of runs of observed black/white/black/... values - * @param patterns the list of patterns to compare the contens of counters to + * @param patterns the list of patterns to compare the contents of counters to * @param requireUnambiguousMatch the 'best match' must be better than all other matches * @return The decoded digit index, -1 if no pattern matched */ diff --git a/core/src/oned/rss/ODRSSExpandedBinaryDecoder.cpp b/core/src/oned/rss/ODRSSExpandedBinaryDecoder.cpp index dcb3612ff2..f2785d6be0 100644 --- a/core/src/oned/rss/ODRSSExpandedBinaryDecoder.cpp +++ b/core/src/oned/rss/ODRSSExpandedBinaryDecoder.cpp @@ -119,7 +119,7 @@ DecodeAI01AndOtherAIs(const BitArray& bits) buffer.append(std::to_string(firstGtinDigit)); AI01EncodeCompressedGtinWithoutAI(buffer, bits, HEADER_SIZE + 4, initialGtinPosition); - if (StatusIsOK(DecodeAppIdAllCodes(bits, HEADER_SIZE + 44, buffer))) { + if (StatusIsOK(DecodeAppIdAllCodes(bits, HEADER_SIZE + 44, -1, buffer))) { return buffer; } return {}; @@ -130,7 +130,7 @@ DecodeAnyAI(const BitArray& bits) { static const int HEADER_SIZE = 2 + 1 + 2; std::string buffer; - if (StatusIsOK(DecodeAppIdAllCodes(bits, HEADER_SIZE, buffer))) { + if (StatusIsOK(DecodeAppIdAllCodes(bits, HEADER_SIZE, -1, buffer))) { return buffer; } return std::string(); @@ -196,7 +196,10 @@ DecodeAI01392x(const BitArray& bits) buffer.append(std::to_string(lastAIdigit)); buffer.push_back(')'); - if (StatusIsOK(DecodeAppIdGeneralPurposeField(bits, HEADER_SIZE + AI01_GTIN_SIZE + LAST_DIGIT_SIZE, buffer))) { + int pos = HEADER_SIZE + AI01_GTIN_SIZE + LAST_DIGIT_SIZE; + int remainingValue = -1; + if (StatusIsOK(DecodeAppIdGeneralPurposeField(bits, pos, remainingValue, buffer)) + && StatusIsOK(DecodeAppIdAllCodes(bits, pos, remainingValue, buffer))) { return buffer; } return std::string(); @@ -231,7 +234,10 @@ DecodeAI01393x(const BitArray& bits) } buffer.append(std::to_string(firstThreeDigits)); - if (StatusIsOK(DecodeAppIdGeneralPurposeField(bits, HEADER_SIZE + AI01_GTIN_SIZE + LAST_DIGIT_SIZE + FIRST_THREE_DIGITS_SIZE, buffer))) { + int pos = HEADER_SIZE + AI01_GTIN_SIZE + LAST_DIGIT_SIZE + FIRST_THREE_DIGITS_SIZE; + int remainingValue = -1; + if (StatusIsOK(DecodeAppIdGeneralPurposeField(bits, pos, remainingValue, buffer)) + && StatusIsOK(DecodeAppIdAllCodes(bits, pos, remainingValue, buffer))) { return buffer; } return std::string(); @@ -332,4 +338,4 @@ DecodeExpandedBits(const BitArray& bits) //throw new IllegalStateException("unknown decoder: " + information); } -} // namespace ZXing::OneD::RSS +} // namespace ZXing::OneD::DataBar diff --git a/core/src/oned/rss/ODRSSFieldParser.cpp b/core/src/oned/rss/ODRSSFieldParser.cpp index 9f0d2d4486..ef8552b847 100644 --- a/core/src/oned/rss/ODRSSFieldParser.cpp +++ b/core/src/oned/rss/ODRSSFieldParser.cpp @@ -22,6 +22,8 @@ #include #include +#include +#include namespace ZXing::OneD::DataBar { @@ -33,6 +35,7 @@ struct AiInfo int fieldSize; // if negative, the length is variable and abs(length) give the max size }; +// GS1 General Specifications Release 22.0 (Jan 22, 2022) static const AiInfo aiInfos[] = { // TWO_DIGIT_DATA_LENGTH { "00", 18 }, @@ -44,35 +47,39 @@ static const AiInfo aiInfos[] = { { "12", 6 }, { "13", 6 }, { "15", 6 }, + { "16", 6 }, { "17", 6 }, { "20", 2 }, { "21", -20 }, - { "22", -29 }, + { "22", -20 }, { "30", -8 }, { "37", -8 }, //internal company codes { "90", -30 }, - { "91", -30 }, - { "92", -30 }, - { "93", -30 }, - { "94", -30 }, - { "95", -30 }, - { "96", -30 }, - { "97", -30 }, - { "98", -30 }, - { "99", -30 }, + { "91", -90 }, + { "92", -90 }, + { "93", -90 }, + { "94", -90 }, + { "95", -90 }, + { "96", -90 }, + { "97", -90 }, + { "98", -90 }, + { "99", -90 }, //THREE_DIGIT_DATA_LENGTH + { "235", -28 }, { "240", -30 }, { "241", -30 }, { "242", -6 }, + { "243", -20 }, { "250", -30 }, { "251", -30 }, - { "253", -17 }, + { "253", -30 }, { "254", -20 }, + { "255", -25 }, { "400", -30 }, { "401", -30 }, @@ -83,13 +90,24 @@ static const AiInfo aiInfos[] = { { "412", 13 }, { "413", 13 }, { "414", 13 }, + { "415", 13 }, + { "416", 13 }, + { "417", 13 }, { "420", -20 }, - { "421", -15 }, + { "421", -12 }, { "422", 3 }, { "423", -15 }, { "424", 3 }, - { "425", 3 }, + { "425", -15 }, { "426", 3 }, + { "427", -3 }, + + { "710", -20 }, + { "711", -20 }, + { "712", -20 }, + { "713", -20 }, + { "714", -20 }, + { "715", -20 }, //THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH { "310", 6 }, @@ -116,6 +134,7 @@ static const AiInfo aiInfos[] = { { "334", 6 }, { "335", 6 }, { "336", 6 }, + { "337", 6 }, { "340", 6 }, { "341", 6 }, { "342", 6 }, @@ -148,12 +167,55 @@ static const AiInfo aiInfos[] = { { "391", -18 }, { "392", -15 }, { "393", -18 }, + { "394", 4 }, + { "395", 6 }, { "703", -30 }, + { "723", -30 }, //FOUR_DIGIT_DATA_LENGTH + { "4300", -35 }, + { "4301", -35 }, + { "4302", -70 }, + { "4303", -70 }, + { "4304", -70 }, + { "4305", -70 }, + { "4306", -70 }, + { "4307", 2 }, + { "4308", -30 }, + { "4310", -35 }, + { "4311", -35 }, + { "4312", -70 }, + { "4313", -70 }, + { "4314", -70 }, + { "4315", -70 }, + { "4316", -70 }, + { "4317", 2 }, + { "4318", -20 }, + { "4319", -30 }, + { "4320", -35 }, + { "4321", 1 }, + { "4322", 1 }, + { "4323", 1 }, + { "4324", 10 }, + { "4325", 10 }, + { "4326", 6 }, + { "7001", 13 }, { "7002", -30 }, { "7003", 10 }, + { "7004", -4 }, + { "7005", -12 }, + { "7006", 6 }, + { "7007", -12 }, + { "7008", -3 }, + { "7009", -10 }, + { "7010", -2 }, + { "7020", -20 }, + { "7021", -20 }, + { "7022", -20 }, + { "7023", -30 }, + { "7040", 4 }, + { "7240", -20 }, { "8001", 14 }, { "8002", -20 }, @@ -161,21 +223,29 @@ static const AiInfo aiInfos[] = { { "8004", -30 }, { "8005", 6 }, { "8006", 18 }, - { "8007", -30 }, + { "8007", -34 }, { "8008", -12 }, + { "8009", -50 }, + { "8010", -30 }, + { "8011", -12 }, + { "8012", -20 }, + { "8013", -25 }, + { "8017", 18 }, { "8018", 18 }, + { "8019", -10 }, { "8020", -25 }, - { "8100", 6 }, - { "8101", 10 }, - { "8102", 2 }, + { "8026", 18 }, { "8110", -70 }, + { "8111", 4 }, + { "8112", -70 }, { "8200", -70 }, }; static size_t AiSize(const char* aiPrefix) { - if ((aiPrefix[0] == '3' && Contains("1234569", aiPrefix[1])) || std::string(aiPrefix) == "703") + if ((aiPrefix[0] == '3' && Contains("1234569", aiPrefix[1])) || std::string(aiPrefix) == "703" + || std::string(aiPrefix) == "723") return 4; else return strlen(aiPrefix); @@ -215,4 +285,4 @@ ParseFieldsInGeneralPurpose(const std::string &rawInfo, std::string& result) return status; } -} // namespace ZXing::OneD::RSS +} // namespace ZXing::OneD::DataBar diff --git a/core/src/oned/rss/ODRSSGenericAppIdDecoder.cpp b/core/src/oned/rss/ODRSSGenericAppIdDecoder.cpp index a692a5a484..6f4881aa68 100644 --- a/core/src/oned/rss/ODRSSGenericAppIdDecoder.cpp +++ b/core/src/oned/rss/ODRSSGenericAppIdDecoder.cpp @@ -444,13 +444,16 @@ DoDecodeGeneralPurposeField(ParsingState& state, const BitArray& bits, std::stri } DecodeStatus -DecodeAppIdGeneralPurposeField(const BitArray& bits, int pos, std::string& result) +DecodeAppIdGeneralPurposeField(const BitArray& bits, int& pos, int& remainingValue, std::string& result) { try { ParsingState state; state.position = pos; - result += DoDecodeGeneralPurposeField(state, bits, std::string()).newString; + DecodedInformation info = DoDecodeGeneralPurposeField(state, bits, std::string()); + result += info.newString; + pos = state.position; + remainingValue = info.remainingValue; return DecodeStatus::NoError; } catch (const std::exception &) @@ -460,12 +463,15 @@ DecodeAppIdGeneralPurposeField(const BitArray& bits, int pos, std::string& resul } DecodeStatus -DecodeAppIdAllCodes(const BitArray& bits, int pos, std::string& result) +DecodeAppIdAllCodes(const BitArray& bits, int pos, int remainingValue, std::string& result) { try { ParsingState state; std::string remaining; + if (remainingValue != -1) { + remaining = std::to_string(remainingValue); + } while (true) { state.position = pos; DecodedInformation info = DoDecodeGeneralPurposeField(state, bits, remaining); @@ -499,4 +505,4 @@ DecodeAppIdAllCodes(const BitArray& bits, int pos, std::string& result) return DecodeStatus::FormatError; } -} // namespace ZXing::OneD::RSS +} // namespace ZXing::OneD::DataBar diff --git a/core/src/oned/rss/ODRSSGenericAppIdDecoder.h b/core/src/oned/rss/ODRSSGenericAppIdDecoder.h index f222f69e27..e93ccd2d2b 100644 --- a/core/src/oned/rss/ODRSSGenericAppIdDecoder.h +++ b/core/src/oned/rss/ODRSSGenericAppIdDecoder.h @@ -25,8 +25,8 @@ enum class DecodeStatus; namespace OneD::DataBar { -DecodeStatus DecodeAppIdGeneralPurposeField(const BitArray& bits, int pos, std::string& result); -DecodeStatus DecodeAppIdAllCodes(const BitArray& bits, int initialPosition, std::string& result); +DecodeStatus DecodeAppIdGeneralPurposeField(const BitArray& bits, int& pos, int& remainingValue, std::string& result); +DecodeStatus DecodeAppIdAllCodes(const BitArray& bits, int pos, const int remainingValue, std::string& result); } // namespace OneD::DataBar } // namespace ZXing diff --git a/core/src/pdf417/PDFBoundingBox.cpp b/core/src/pdf417/PDFBoundingBox.cpp index 39a89c97d8..fb07c74df2 100644 --- a/core/src/pdf417/PDFBoundingBox.cpp +++ b/core/src/pdf417/PDFBoundingBox.cpp @@ -54,7 +54,7 @@ BoundingBox::calculateMinMaxValues() } else if (_topRight == nullptr) { _topRight = ResultPoint(static_cast(_imgWidth - 1), _topLeft.value().y()); - _bottomRight = ResultPoint(static_cast(_imgHeight - 1), _bottomLeft.value().y()); + _bottomRight = ResultPoint(static_cast(_imgWidth - 1), _bottomLeft.value().y()); } _minX = static_cast(std::min(_topLeft.value().x(), _bottomLeft.value().x())); diff --git a/core/src/pdf417/PDFCodewordDecoder.cpp b/core/src/pdf417/PDFCodewordDecoder.cpp index 9570422b39..ec84e962ac 100644 --- a/core/src/pdf417/PDFCodewordDecoder.cpp +++ b/core/src/pdf417/PDFCodewordDecoder.cpp @@ -19,9 +19,9 @@ #include "BitArray.h" #include "ZXContainerAlgorithms.h" +#include #include #include -#include #include namespace ZXing { diff --git a/core/src/pdf417/PDFDecodedBitStreamParser.cpp b/core/src/pdf417/PDFDecodedBitStreamParser.cpp index d22a3852dd..ab61c4c6c8 100644 --- a/core/src/pdf417/PDFDecodedBitStreamParser.cpp +++ b/core/src/pdf417/PDFDecodedBitStreamParser.cpp @@ -91,9 +91,7 @@ static bool TerminatesCompaction(int code) case BYTE_COMPACTION_MODE_LATCH_6: case BEGIN_MACRO_PDF417_CONTROL_BLOCK: case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: - case MACRO_PDF417_TERMINATOR: - return true; - break; + case MACRO_PDF417_TERMINATOR: return true; } return false; } @@ -297,6 +295,7 @@ static int TextCompaction(DecodeStatus& status, const std::vector& codeword int index = 0; bool end = false; + while ((codeIndex < codewords[0]) && !end) { int code = codewords[codeIndex++]; if (code < TEXT_COMPACTION_MODE_LATCH) { @@ -669,8 +668,9 @@ DecodeStatus DecodeMacroBlock(const std::vector& codewords, int codeIndex, // the fileId using text compaction, so in those cases the fileId will appear mangled. std::ostringstream fileId; fileId.fill('0'); - for (int i = 0; codeIndex < codewords[0] && codewords[codeIndex] != MACRO_PDF417_TERMINATOR - && codewords[codeIndex] != BEGIN_MACRO_PDF417_OPTIONAL_FIELD; i++, codeIndex++) { + for (; codeIndex < codewords[0] && codewords[codeIndex] != MACRO_PDF417_TERMINATOR && + codewords[codeIndex] != BEGIN_MACRO_PDF417_OPTIONAL_FIELD; + codeIndex++) { fileId << std::setw(3) << codewords[codeIndex]; } resultMetadata.setFileId(fileId.str()); @@ -682,70 +682,64 @@ DecodeStatus DecodeMacroBlock(const std::vector& codewords, int codeIndex, while (codeIndex < codewords[0]) { switch (codewords[codeIndex]) { - case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: { - codeIndex++; - if (codeIndex >= codewords[0]) { - break; - } - switch (codewords[codeIndex]) { - case MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME: { - std::string fileName; - codeIndex = DecodeMacroOptionalTextField(status, codewords, codeIndex + 1, fileName); - resultMetadata.setFileName(fileName); - break; - } - case MACRO_PDF417_OPTIONAL_FIELD_SENDER: { - std::string sender; - codeIndex = DecodeMacroOptionalTextField(status, codewords, codeIndex + 1, sender); - resultMetadata.setSender(sender); - break; - } - case MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE: { - std::string addressee; - codeIndex = DecodeMacroOptionalTextField(status, codewords, codeIndex + 1, addressee); - resultMetadata.setAddressee(addressee); - break; - } - case MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT: { - uint64_t segmentCount; - codeIndex = DecodeMacroOptionalNumericField(status, codewords, codeIndex + 1, segmentCount); - resultMetadata.setSegmentCount(segmentCount); - break; - } - case MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP: { - uint64_t timestamp; - codeIndex = DecodeMacroOptionalNumericField(status, codewords, codeIndex + 1, timestamp); - resultMetadata.setTimestamp(timestamp); - break; - } - case MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM: { - uint64_t checksum; - codeIndex = DecodeMacroOptionalNumericField(status, codewords, codeIndex + 1, checksum); - resultMetadata.setChecksum(checksum); - break; - } - case MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE: { - uint64_t fileSize; - codeIndex = DecodeMacroOptionalNumericField(status, codewords, codeIndex + 1, fileSize); - resultMetadata.setFileSize(fileSize); - break; - } - default: { - status = DecodeStatus::FormatError; - break; - } - } + case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: { + codeIndex++; + if (codeIndex >= codewords[0]) { break; } - case MACRO_PDF417_TERMINATOR: { - codeIndex++; - resultMetadata.setLastSegment(true); + switch (codewords[codeIndex]) { + case MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME: { + std::string fileName; + codeIndex = DecodeMacroOptionalTextField(status, codewords, codeIndex + 1, fileName); + resultMetadata.setFileName(fileName); break; } - default: { - status = DecodeStatus::FormatError; + case MACRO_PDF417_OPTIONAL_FIELD_SENDER: { + std::string sender; + codeIndex = DecodeMacroOptionalTextField(status, codewords, codeIndex + 1, sender); + resultMetadata.setSender(sender); + break; + } + case MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE: { + std::string addressee; + codeIndex = DecodeMacroOptionalTextField(status, codewords, codeIndex + 1, addressee); + resultMetadata.setAddressee(addressee); break; } + case MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT: { + uint64_t segmentCount; + codeIndex = DecodeMacroOptionalNumericField(status, codewords, codeIndex + 1, segmentCount); + resultMetadata.setSegmentCount(static_cast(segmentCount)); + break; + } + case MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP: { + uint64_t timestamp; + codeIndex = DecodeMacroOptionalNumericField(status, codewords, codeIndex + 1, timestamp); + resultMetadata.setTimestamp(timestamp); + break; + } + case MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM: { + uint64_t checksum; + codeIndex = DecodeMacroOptionalNumericField(status, codewords, codeIndex + 1, checksum); + resultMetadata.setChecksum(static_cast(checksum)); + break; + } + case MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE: { + uint64_t fileSize; + codeIndex = DecodeMacroOptionalNumericField(status, codewords, codeIndex + 1, fileSize); + resultMetadata.setFileSize(fileSize); + break; + } + default: status = DecodeStatus::FormatError; break; + } + break; + } + case MACRO_PDF417_TERMINATOR: { + codeIndex++; + resultMetadata.setLastSegment(true); + break; + } + default: status = DecodeStatus::FormatError; break; } if (StatusIsError(status)) { return status; @@ -860,6 +854,9 @@ DecodedBitStreamParser::Decode(const std::vector& codewords, int ecLevel, c return DecoderResult(ByteArray(), std::move(resultEncoded)) .setEcLevel(std::to_wstring(ecLevel)) + // As converting character set ECIs ourselves and ignoring/skipping non-character ECIs, not using modifier + // that indicates ECI protocol (ISO/IEC 15438:2015 Annex L Table L.1) + .setSymbologyIdentifier("]L2") .setStructuredAppend(sai) .setReaderInit(readerInit) .setExtra(resultMetadata); diff --git a/core/src/pdf417/PDFDetector.cpp b/core/src/pdf417/PDFDetector.cpp index 11f8fa2b2b..5ddf915927 100644 --- a/core/src/pdf417/PDFDetector.cpp +++ b/core/src/pdf417/PDFDetector.cpp @@ -162,10 +162,11 @@ FindRowsWithPattern(const BitMatrix& matrix, int height, int width, int startRow { bool found = false; int startPos, endPos; + int minStartRow = startRow; std::vector counters(pattern.size(), 0); for (; startRow < height; startRow += ROW_STEP) { if (FindGuardPattern(matrix, startColumn, startRow, width, false, pattern, counters, startPos, endPos)) { - while (startRow > 0) { + while (startRow > minStartRow + 1) { if (!FindGuardPattern(matrix, startColumn, --startRow, width, false, pattern, counters, startPos, endPos)) { startRow++; break; @@ -348,8 +349,10 @@ bool HasStartPattern(const BitMatrix& m) DecodeStatus Detector::Detect(const BinaryBitmap& image, bool multiple, Result& result) { - auto binImg = image.getBlackMatrix(); - if (binImg == nullptr) { + // construct a 'dummy' shared pointer, just be able to pass it up the call chain in DecodeStatus + // TODO: reimplement PDF Detector + auto binImg = std::shared_ptr(image.getBitMatrix(), [](const BitMatrix*){}); + if (!binImg) { return DecodeStatus::NotFound; } diff --git a/core/src/pdf417/PDFEncoder.cpp b/core/src/pdf417/PDFEncoder.cpp index 662ff4f70f..c38a35ce2f 100644 --- a/core/src/pdf417/PDFEncoder.cpp +++ b/core/src/pdf417/PDFEncoder.cpp @@ -19,7 +19,6 @@ #include "PDFEncoder.h" #include "PDFHighLevelEncoder.h" #include -#include #include #include @@ -219,8 +218,7 @@ static const std::array, 3> CODEWORD_TABLE = { }; static const float PREFERRED_RATIO = 3.0f; -static const float DEFAULT_MODULE_WIDTH = 0.357f; //1px in mm -static const float HEIGHT = 2.0f; //mm +static const float MODULE_RATIO = 0.25f; // keep in sync with Writer::encode() /** * PDF417 error correction code following the algorithm described in ISO/IEC 15438:2001(E) in @@ -477,7 +475,7 @@ static void DetermineDimensions(int minCols, int maxCols, int minRows, int maxRo continue; } - float newRatio = ((17 * cols + 69) * DEFAULT_MODULE_WIDTH) / (rows * HEIGHT); + float newRatio = ((17 * cols + 69) / rows) * MODULE_RATIO; // ignore if previous ratio is closer to preferred ratio if (haveDimension && std::abs(newRatio - PREFERRED_RATIO) > std::abs(ratio - PREFERRED_RATIO)) { diff --git a/core/src/pdf417/PDFEncoder.h b/core/src/pdf417/PDFEncoder.h index c49cbdb9b5..459a8634c0 100644 --- a/core/src/pdf417/PDFEncoder.h +++ b/core/src/pdf417/PDFEncoder.h @@ -71,7 +71,7 @@ class BarcodeRow }; /** -* Holds all of the information for a barcode in a format where it can be easily accessable +* Holds all of the information for a barcode in a format where it can be easily accessible * * @author Jacob Haynes */ diff --git a/core/src/pdf417/PDFHighLevelEncoder.cpp b/core/src/pdf417/PDFHighLevelEncoder.cpp index 6bd2f4a6ab..c37b41c11b 100644 --- a/core/src/pdf417/PDFHighLevelEncoder.cpp +++ b/core/src/pdf417/PDFHighLevelEncoder.cpp @@ -231,80 +231,70 @@ static int EncodeText(const std::wstring& msg, int startpos, int count, int subm while (true) { int ch = msg[startpos + idx]; switch (submode) { - case SUBMODE_ALPHA: - if (IsAlphaUpper(ch)) { - tmp.push_back(ch == ' ' ? 26 : (ch - 65)); //space - } - else if (IsAlphaLower(ch)) { - submode = SUBMODE_LOWER; - tmp.push_back(27); //ll - continue; - } - else if (IsMixed(ch)) { - submode = SUBMODE_MIXED; - tmp.push_back(28); //ml - continue; - } - else { - tmp.push_back(29); //ps - tmp.push_back(PUNCTUATION[ch]); - } - break; - case SUBMODE_LOWER: - if (IsAlphaLower(ch)) { - tmp.push_back(ch == ' ' ? 26 : (ch - 97)); //space - } - else if (IsAlphaUpper(ch)) { - tmp.push_back(27); //as - tmp.push_back(ch - 65); - //space cannot happen here, it is also in "Lower" - } - else if (IsMixed(ch)) { - submode = SUBMODE_MIXED; - tmp.push_back(28); //ml - continue; - } - else { - tmp.push_back(29); //ps - tmp.push_back(PUNCTUATION[ch]); - } - break; - case SUBMODE_MIXED: - if (IsMixed(ch)) { - tmp.push_back(MIXED[ch]); - } - else if (IsAlphaUpper(ch)) { - submode = SUBMODE_ALPHA; - tmp.push_back(28); //al - continue; - } - else if (IsAlphaLower(ch)) { - submode = SUBMODE_LOWER; - tmp.push_back(27); //ll - continue; - } - else { - if (startpos + idx + 1 < count) { - int next = msg[startpos + idx + 1]; - if (IsPunctuation(next)) { - submode = SUBMODE_PUNCTUATION; - tmp.push_back(25); //pl - continue; - } + case SUBMODE_ALPHA: + if (IsAlphaUpper(ch)) { + tmp.push_back(ch == ' ' ? 26 : (ch - 65)); // space + } else if (IsAlphaLower(ch)) { + submode = SUBMODE_LOWER; + tmp.push_back(27); // ll + continue; + } else if (IsMixed(ch)) { + submode = SUBMODE_MIXED; + tmp.push_back(28); // ml + continue; + } else { + tmp.push_back(29); // ps + tmp.push_back(PUNCTUATION[ch]); + } + break; + case SUBMODE_LOWER: + if (IsAlphaLower(ch)) { + tmp.push_back(ch == ' ' ? 26 : (ch - 97)); // space + } else if (IsAlphaUpper(ch)) { + tmp.push_back(27); // as + tmp.push_back(ch - 65); + // space cannot happen here, it is also in "Lower" + } else if (IsMixed(ch)) { + submode = SUBMODE_MIXED; + tmp.push_back(28); // ml + continue; + } else { + tmp.push_back(29); // ps + tmp.push_back(PUNCTUATION[ch]); + } + break; + case SUBMODE_MIXED: + if (IsMixed(ch)) { + tmp.push_back(MIXED[ch]); + } else if (IsAlphaUpper(ch)) { + submode = SUBMODE_ALPHA; + tmp.push_back(28); // al + continue; + } else if (IsAlphaLower(ch)) { + submode = SUBMODE_LOWER; + tmp.push_back(27); // ll + continue; + } else { + if (startpos + idx + 1 < count) { + int next = msg[startpos + idx + 1]; + if (IsPunctuation(next)) { + submode = SUBMODE_PUNCTUATION; + tmp.push_back(25); // pl + continue; } - tmp.push_back(29); //ps - tmp.push_back(PUNCTUATION[ch]); - } - break; - default: //SUBMODE_PUNCTUATION - if (IsPunctuation(ch)) { - tmp.push_back(PUNCTUATION[ch]); - } - else { - submode = SUBMODE_ALPHA; - tmp.push_back(29); //al - continue; } + tmp.push_back(29); // ps + tmp.push_back(PUNCTUATION[ch]); + } + break; + default: // SUBMODE_PUNCTUATION + if (IsPunctuation(ch)) { + tmp.push_back(PUNCTUATION[ch]); + } else { + submode = SUBMODE_ALPHA; + tmp.push_back(29); // al + continue; + } } idx++; if (idx >= count) { diff --git a/core/src/pdf417/PDFModulusGF.h b/core/src/pdf417/PDFModulusGF.h index 01630c8d21..12095c9491 100644 --- a/core/src/pdf417/PDFModulusGF.h +++ b/core/src/pdf417/PDFModulusGF.h @@ -20,6 +20,7 @@ #include "ZXConfig.h" #include +#include namespace ZXing { namespace Pdf417 { diff --git a/core/src/pdf417/PDFModulusPoly.cpp b/core/src/pdf417/PDFModulusPoly.cpp index 1cca7bb377..bfd414cb36 100644 --- a/core/src/pdf417/PDFModulusPoly.cpp +++ b/core/src/pdf417/PDFModulusPoly.cpp @@ -60,11 +60,8 @@ ModulusPoly::evaluateAt(int a) const size_t size = _coefficients.size(); if (a == 1) { // Just the sum of the coefficients - int result = 0; - for (int coefficient : _coefficients) { - result = _field->add(result, coefficient); - } - return result; + const auto op = [this](auto result, const auto coefficient){ return _field->add(result, coefficient);}; + return std::accumulate(std::begin(_coefficients), std::end(_coefficients), int{}, op); } int result = _coefficients[0]; for (size_t i = 1; i < size; i++) { diff --git a/core/src/pdf417/PDFReader.cpp b/core/src/pdf417/PDFReader.cpp index 992c1a03a5..dadc456117 100644 --- a/core/src/pdf417/PDFReader.cpp +++ b/core/src/pdf417/PDFReader.cpp @@ -20,7 +20,6 @@ #include "PDFDetector.h" #include "PDFScanningDecoder.h" #include "PDFCodewordDecoder.h" -#include "PDFDecoderResultExtra.h" #include "DecodeHints.h" #include "DecoderResult.h" #include "Result.h" @@ -30,9 +29,6 @@ #include "BitArray.h" #include "DecodeStatus.h" #include "Pattern.h" -#include "PDFDecodedBitStreamParser.h" -#include "BitMatrixIO.h" -#include #include #include @@ -40,6 +36,13 @@ #include #include +#ifdef PRINT_DEBUG +#include "PDFDecoderResultExtra.h" +#include "PDFDecodedBitStreamParser.h" +#include "BitMatrixIO.h" +#include +#endif + namespace ZXing { namespace Pdf417 { @@ -214,7 +217,7 @@ SymbolInfo DetectSymbol(BitMatrixCursor topCur, int width, int height) res.lastRow = botSI.firstRow; res.rowHeight = float(height) / (std::abs(res.lastRow - res.firstRow) + 1); if (topSI.nCols != botSI.nCols) - // if there is something fishy with the number of cols (alising), guess them from the width + // if there is something fishy with the number of cols (aliasing), guess them from the width res.nCols = (width + res.colWidth / 2) / res.colWidth - 4; return res; @@ -268,7 +271,7 @@ DecoderResult DecodeCodewords(std::vector& codewords, int ecLevel, const st static Result DecodePure(const BinaryBitmap& image_, const std::string& characterSet) { - auto pimage = image_.getBlackMatrix(); + auto pimage = image_.getBitMatrix(); if (!pimage) return Result(DecodeStatus::NotFound); auto& image = *pimage; @@ -335,6 +338,13 @@ Reader::decode(const BinaryBitmap& image) const return Result(status); } +Results Reader::decode(const BinaryBitmap& image, [[maybe_unused]] int maxSymbols) const +{ + std::list results; + DoDecode(image, true, results, _characterSet); + return Results(results.begin(), results.end()); +} + std::list Reader::decodeMultiple(const BinaryBitmap& image) const { diff --git a/core/src/pdf417/PDFReader.h b/core/src/pdf417/PDFReader.h index 0f8777b309..138d31ef1e 100644 --- a/core/src/pdf417/PDFReader.h +++ b/core/src/pdf417/PDFReader.h @@ -41,7 +41,9 @@ class Reader : public ZXing::Reader explicit Reader(const DecodeHints& hints); Result decode(const BinaryBitmap& image) const override; - std::list decodeMultiple(const BinaryBitmap& image) const; + Results decode(const BinaryBitmap& image, int maxSymbols) const override; + + [[deprecated]] std::list decodeMultiple(const BinaryBitmap& image) const; }; } // Pdf417 diff --git a/core/src/pdf417/PDFScanningDecoder.cpp b/core/src/pdf417/PDFScanningDecoder.cpp index a56fe7d298..c95f80a1dc 100644 --- a/core/src/pdf417/PDFScanningDecoder.cpp +++ b/core/src/pdf417/PDFScanningDecoder.cpp @@ -32,7 +32,6 @@ #include "ZXTestSupport.h" #include -#include #include #include @@ -688,7 +687,7 @@ static DecoderResult CreateDecoderResult(DetectionResult& detectionResult, const // TODO don't pass in minCodewordWidth and maxCodewordWidth, pass in barcode columns for start and stop pattern // columns. That way width can be deducted from the pattern column. -// This approach also allows to detect more details about the barcode, e.g. if a bar type (white or black) is wider +// This approach also allows detecting more details about the barcode, e.g. if a bar type (white or black) is wider // than it should be. This can happen if the scanner used a bad blackpoint. DecoderResult ScanningDecoder::Decode(const BitMatrix& image, const Nullable& imageTopLeft, const Nullable& imageBottomLeft, diff --git a/core/src/pdf417/PDFWriter.cpp b/core/src/pdf417/PDFWriter.cpp index 89c88f837d..60d8206bea 100644 --- a/core/src/pdf417/PDFWriter.cpp +++ b/core/src/pdf417/PDFWriter.cpp @@ -85,7 +85,7 @@ Writer::encode(const std::wstring& contents, int width, int height) const int ecLevel = _ecLevel >= 0 ? _ecLevel : DEFAULT_ERROR_CORRECTION_LEVEL; BarcodeMatrix resultMatrix = _encoder->generateBarcodeLogic(contents, ecLevel); - int aspectRatio = 4; + int aspectRatio = 4; // keep in sync with MODULE_RATIO in PDFEncoder.cpp std::vector> originalScale; resultMatrix.getScaledMatrix(1, aspectRatio, originalScale); bool rotated = false; diff --git a/core/src/qrcode/QRBitMatrixParser.cpp b/core/src/qrcode/QRBitMatrixParser.cpp index 23c92f62df..ab599a7b67 100644 --- a/core/src/qrcode/QRBitMatrixParser.cpp +++ b/core/src/qrcode/QRBitMatrixParser.cpp @@ -24,6 +24,8 @@ #include "QRFormatInformation.h" #include "QRVersion.h" +#include + namespace ZXing::QRCode { static bool getBit(const BitMatrix& bitMatrix, int x, int y, bool mirrored) diff --git a/core/src/qrcode/QRCodecMode.cpp b/core/src/qrcode/QRCodecMode.cpp index 05e32038a9..a64e6bd9c6 100644 --- a/core/src/qrcode/QRCodecMode.cpp +++ b/core/src/qrcode/QRCodecMode.cpp @@ -44,12 +44,12 @@ int CharacterCountBits(CodecMode mode, const Version& version) i = 2; switch (mode) { - case CodecMode::NUMERIC: return std::array{10, 12, 14}[i]; + case CodecMode::NUMERIC: return std::array{10, 12, 14}[i]; case CodecMode::ALPHANUMERIC: return std::array{9, 11, 13}[i]; - case CodecMode::BYTE: return std::array{8, 16, 16}[i]; - case CodecMode::KANJI: [[fallthrough]]; - case CodecMode::HANZI: return std::array{8, 10, 12}[i]; - default: return 0; + case CodecMode::BYTE: return std::array{8, 16, 16}[i]; + case CodecMode::KANJI: [[fallthrough]]; + case CodecMode::HANZI: return std::array{8, 10, 12}[i]; + default: return 0; } } diff --git a/core/src/qrcode/QRDataBlock.cpp b/core/src/qrcode/QRDataBlock.cpp index 59b88b6b9e..7c04f5e760 100644 --- a/core/src/qrcode/QRDataBlock.cpp +++ b/core/src/qrcode/QRDataBlock.cpp @@ -17,7 +17,6 @@ #include "QRDataBlock.h" -#include "DecodeStatus.h" #include "QRErrorCorrectionLevel.h" #include "QRVersion.h" #include "ZXContainerAlgorithms.h" diff --git a/core/src/qrcode/QRDecoder.cpp b/core/src/qrcode/QRDecoder.cpp index a31b6f023c..cc0899d7c7 100644 --- a/core/src/qrcode/QRDecoder.cpp +++ b/core/src/qrcode/QRDecoder.cpp @@ -27,10 +27,9 @@ #include "QRBitMatrixParser.h" #include "QRCodecMode.h" #include "QRDataBlock.h" -#include "QRDataMask.h" -#include "QRDecoderMetadata.h" #include "QRFormatInformation.h" #include "ReedSolomonDecoder.h" +#include "StructuredAppend.h" #include "TextDecoder.h" #include "ZXContainerAlgorithms.h" #include "ZXTestSupport.h" @@ -309,6 +308,8 @@ DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCorrectionLevel { BitSource bits(bytes); std::wstring result; + int symbologyIdModifier = 1; // ISO/IEC 18004:2015 Annex F Table F.1 + int appIndValue = -1; // ISO/IEC 18004:2015 7.4.8.3 AIM Application Indicator (FNC1 in second position) StructuredAppendInfo structuredAppend; static const int GB2312_SUBSET = 1; @@ -330,9 +331,16 @@ DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCorrectionLevel case CodecMode::TERMINATOR: break; case CodecMode::FNC1_FIRST_POSITION: + fc1InEffect = true; // In Alphanumeric mode undouble doubled percents and treat single percent as + // As converting character set ECIs ourselves and ignoring/skipping non-character ECIs, not using + // modifiers that indicate ECI protocol (ISO/IEC 18004:2015 Annex F Table F.1) + symbologyIdModifier = 3; + break; case CodecMode::FNC1_SECOND_POSITION: - // We do little with FNC1 except alter the parsed result a bit according to the spec - fc1InEffect = true; + fc1InEffect = true; // As above + symbologyIdModifier = 5; // As above + // ISO/IEC 18004:2015 7.4.8.3 AIM Application Indicator "00-99" or "A-Za-z" + appIndValue = bits.readBits(8); // Number 00-99 or ASCII value + 100; prefixed to data below break; case CodecMode::STRUCTURED_APPEND: if (bits.available() < 16) { @@ -405,8 +413,26 @@ DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCorrectionLevel return DecodeStatus::FormatError; } + if (appIndValue >= 0) { + if (appIndValue < 10) { // "00-09" + result.insert(0, L'0' + std::to_wstring(appIndValue)); + } + else if (appIndValue < 100) { // "10-99" + result.insert(0, std::to_wstring(appIndValue)); + } + else if ((appIndValue >= 165 && appIndValue <= 190) || (appIndValue >= 197 && appIndValue <= 222)) { // "A-Za-z" + result.insert(0, 1, static_cast(appIndValue - 100)); + } + else { + return DecodeStatus::FormatError; + } + } + + std::string symbologyIdentifier("]Q" + std::to_string(symbologyIdModifier)); + return DecoderResult(std::move(bytes), std::move(result)) .setEcLevel(ToString(ecLevel)) + .setSymbologyIdentifier(std::move(symbologyIdentifier)) .setStructuredAppend(structuredAppend); } @@ -428,10 +454,8 @@ DoDecode(const BitMatrix& bits, const Version& version, const std::string& hinte return DecodeStatus::FormatError; // Count total number of data bytes - int totalBytes = 0; - for (const auto& dataBlock : dataBlocks) { - totalBytes += dataBlock.numDataCodewords(); - } + const auto op = [](auto totalBytes, const auto& dataBlock){ return totalBytes + dataBlock.numDataCodewords();}; + const auto totalBytes = std::accumulate(std::begin(dataBlocks), std::end(dataBlocks), int{}, op); ByteArray resultBytes(totalBytes); auto resultIterator = resultBytes.begin(); @@ -462,7 +486,7 @@ DecoderResult Decode(const BitMatrix& bits, const std::string& hintedCharset) return res; if (auto resMirrored = DoDecode(bits, *version, hintedCharset, true); resMirrored.isValid()) { - resMirrored.setExtra(std::make_shared(true)); + resMirrored.setIsMirrored(true); return resMirrored; } diff --git a/core/src/qrcode/QRDecoderMetadata.h b/core/src/qrcode/QRDecoderMetadata.h deleted file mode 100644 index 6a23e8002f..0000000000 --- a/core/src/qrcode/QRDecoderMetadata.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once -/* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -* -* 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. -*/ - -#include "CustomData.h" - -namespace ZXing { -namespace QRCode { - -/** -* Meta-data container for QR Code decoding. Instances of this class may be used to convey information back to the -* decoding caller. Callers are expected to process this. -*/ - -class DecoderMetadata : public CustomData -{ - bool _mirrored; - -public: - explicit DecoderMetadata(bool mirrored) : _mirrored(mirrored) {} - - /** - * @return true if the QR Code was mirrored. - */ - bool isMirrored() const { - return _mirrored; - } -}; - -} // QRCode -} // ZXing diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index f0953b87c6..e2834481bb 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -24,8 +24,8 @@ #include "DetectorResult.h" #include "GridSampler.h" #include "LogMatrix.h" -#include "PerspectiveTransform.h" -#include "QRVersion.h" +#include "Pattern.h" +#include "Quadrilateral.h" #include "RegressionLine.h" #include "BitMatrixIO.h" @@ -33,9 +33,10 @@ #include #include #include -#include +#include #include #include +#include namespace ZXing::QRCode { @@ -73,6 +74,7 @@ static auto FindFinderPatterns(const BitMatrix& image, bool tryHarder) Reduce(next) * 3 / 2); // 1.5 for very skewed samples if (pattern) { log(*pattern, 3); + assert(image.get(pattern->x, pattern->y)); res.push_back(*pattern); } } @@ -86,13 +88,6 @@ static auto FindFinderPatterns(const BitMatrix& image, bool tryHarder) return res; } -struct FinderPatternSet -{ - ConcentricPattern bl, tl, tr; -}; - -using FinderPatternSets = std::vector; - /** * @brief GenerateFinderPatternSets * @param patterns list of ConcentricPattern objects, i.e. found finder pattern squares @@ -103,7 +98,13 @@ static FinderPatternSets GenerateFinderPatternSets(std::vector(); - auto squaredDistance = [](PointF a, PointF b) { return dot((a - b), (a - b)); }; + auto squaredDistance = [](const auto* a, const auto* b) { + // The scaling of the distance by the b/a size ratio is a very coarse compensation for the shortening effect of + // the camera projection on slanted symbols. The fact that the size of the finder pattern is proportional to the + // distance from the camera is used here. This approximation only works if a < b < 2*a (see below). + // Test image: fix-finderpattern-order.jpg + return dot((*a - *b), (*a - *b)) * std::pow(double(b->size) / a->size, 2); + }; int nbPatterns = Size(patterns); for (int i = 0; i < nbPatterns - 2; i++) { @@ -120,9 +121,9 @@ static FinderPatternSets GenerateFinderPatternSets(std::vector= distAB && distBC >= distAC) { std::swap(a, b); @@ -152,9 +153,10 @@ static FinderPatternSets GenerateFinderPatternSets(std::vectorfirst > d) { + const auto setSizeLimit = 16; + if (sets.size() < setSizeLimit || sets.crbegin()->first > d) { sets.emplace(d, FinderPatternSet{*a, *b, *c}); - if (sets.size() > 16) + if (sets.size() > setSizeLimit) sets.erase(std::prev(sets.end())); } } @@ -172,16 +174,18 @@ static FinderPatternSets GenerateFinderPatternSets(std::vector>(); - return Reduce(pattern) / 6.0 * length(cur.d); + auto pattern = cur.readPattern>(); + + return (2 * Reduce(pattern) - pattern[0] - pattern[4]) / 12.0 * length(cur.d); } struct DimensionEstimate @@ -195,6 +199,10 @@ static DimensionEstimate EstimateDimension(const BitMatrix& image, PointF a, Poi { auto ms_a = EstimateModuleSize(image, a, b); auto ms_b = EstimateModuleSize(image, b, a); + + if (ms_a < 0 || ms_b < 0) + return {}; + auto moduleSize = (ms_a + ms_b) / 2; int dimension = std::lround(distance(a, b) / moduleSize) + 7; @@ -209,26 +217,25 @@ static RegressionLine TraceLine(const BitMatrix& image, PointF p, PointF d, int RegressionLine line; line.setDirectionInward(cur.back()); - cur.stepToEdge(edge); - if (edge == 3) { - // collect points inside the black line -> go one step back + // collect points inside the black line -> backup on 3rd edge + cur.stepToEdge(edge, 0, edge == 3); + if (edge == 3) cur.turnBack(); - cur.step(); + + auto curI = BitMatrixCursorI(image, PointI(cur.p), PointI(mainDirection(cur.d))); + // make sure curI positioned such that the white->black edge is directly behind + // Test image: fix-traceline.jpg + while (!curI.edgeAtBack()) { + if (curI.edgeAtLeft()) + curI.turnRight(); + else if (curI.edgeAtRight()) + curI.turnLeft(); + else + curI.step(-1); } for (auto dir : {Direction::LEFT, Direction::RIGHT}) { - auto c = BitMatrixCursorI(image, PointI(cur.p), PointI(mainDirection(cur.direction(dir)))); - // if cur.d is near diagonal, it could be c.p is at a corner, i.e. c is not currently at an edge and hence, - // stepAlongEdge() would fail. Going either a step forward or backward should do the trick. - if (!c.edgeAt(dir)) { - c.step(); - if (!c.edgeAt(dir)) { - c.step(-2); - if (!c.edgeAt(dir)) - return {}; - } - } - + auto c = BitMatrixCursorI(image, curI.p, curI.direction(dir)); auto stepCount = static_cast(maxAbsComponent(cur.p - p)); do { line.add(centered(c.p)); @@ -243,10 +250,22 @@ static RegressionLine TraceLine(const BitMatrix& image, PointF p, PointF d, int return line; } -static DetectorResult SampleAtFinderPatternSet(const BitMatrix& image, const FinderPatternSet& fp) +// estimate how tilted the symbol is (return value between 1 and 2, see also above) +static double EstimateTilt(const FinderPatternSet& fp) +{ + int min = std::min({fp.bl.size, fp.tl.size, fp.tr.size}); + int max = std::max({fp.bl.size, fp.tl.size, fp.tr.size}); + return double(max) / min; +} + +DetectorResult SampleAtFinderPatternSet(const BitMatrix& image, const FinderPatternSet& fp) { auto top = EstimateDimension(image, fp.tl, fp.tr); auto left = EstimateDimension(image, fp.tl, fp.bl); + + if (!top.dim || !left.dim) + return {}; + auto best = top.err < left.err ? top : left; int dimension = best.dim; int moduleSize = static_cast(best.ms + 1); @@ -260,7 +279,7 @@ static DetectorResult SampleAtFinderPatternSet(const BitMatrix& image, const Fin }; // Everything except version 1 (21 modules) has an alignment pattern. Estimate the center of that by intersecting - // line extensions of the 1 module wide sqare around the finder patterns. This could also help with detecting + // line extensions of the 1 module wide square around the finder patterns. This could also help with detecting // slanted symbols of version 1. // generate 4 lines: outer and inner edge of the 1 module wide black line between the two outer and the inner @@ -275,26 +294,26 @@ static DetectorResult SampleAtFinderPatternSet(const BitMatrix& image, const Fin auto brInter = (intersect(bl2, tr2) + intersect(bl3, tr3)) / 2; log(brInter, 3); - // if the estimated alignment pattern position is outside of the image, stop here - if (!image.isIn(PointI(brInter), 3 * moduleSize)) - return {}; - - if (dimension > 21) { - // just in case we landed outside of the central black module of the alignment pattern, use the center - // of the next best circle (either outer or inner edge of the white part of the alignment pattern) - auto brCoR = CenterOfRing(image, PointI(brInter), moduleSize * 4, 1, false).value_or(brInter); - // if we did not land on a black pixel or the concentric pattern finder fails, - // leave the intersection of the lines as the best guess - if (image.get(brCoR)) { - if (auto brCP = LocateConcentricPattern(image, FixedPattern<3, 3>{1, 1, 1}, brCoR, moduleSize * 3)) - return sample(*brCP, quad[2] - PointF(3, 3)); + // check that the estimated alignment pattern position is inside of the image + if (image.isIn(PointI(brInter), 3 * moduleSize)) { + if (dimension > 21) { + // just in case we landed outside of the central black module of the alignment pattern, use the center + // of the next best circle (either outer or inner edge of the white part of the alignment pattern) + auto brCoR = CenterOfRing(image, PointI(brInter), moduleSize * 4, 1, false).value_or(brInter); + // if we did not land on a black pixel or the concentric pattern finder fails, + // leave the intersection of the lines as the best guess + if (image.get(brCoR)) { + if (auto brCP = + LocateConcentricPattern(image, FixedPattern<3, 3>{1, 1, 1}, brCoR, moduleSize * 3)) + return sample(*brCP, quad[2] - PointF(3, 3)); + } } - } - // if the resolution of the RegressionLines is sufficient, use their intersection as the best estimate - // (see discussion in #199, TODO: tune threshold in RegressionLine::isHighRes()) - if (bl2.isHighRes() && bl3.isHighRes() && tr2.isHighRes() && tr3.isHighRes()) - return sample(brInter, quad[2] - PointF(3, 3)); + // if the symbol is tilted or the resolution of the RegressionLines is sufficient, use their intersection + // as the best estimate (see discussion in #199 and test image estimate-tilt.jpg ) + if (EstimateTilt(fp) > 1.1 || (bl2.isHighRes() && bl3.isHighRes() && tr2.isHighRes() && tr3.isHighRes())) + return sample(brInter, quad[2] - PointF(3, 3)); + } } // otherwise the simple estimation used by upstream is used as a best guess fallback @@ -323,7 +342,7 @@ static DetectorResult DetectPure(const BitMatrix& image) PointI tl{left, top}, tr{right, top}, bl{left, bottom}; Pattern diagonal; - // allow corners be moved one pixel inside to accomodate for possible aliasing artifacts + // allow corners be moved one pixel inside to accommodate for possible aliasing artifacts for (auto [p, d] : {std::pair(tl, PointI{1, 1}), {tr, {-1, 1}}, {bl, {1, -1}}}) { diagonal = BitMatrixCursorI(image, p, d).readPatternFromBlack(1, width / 3); if (!IsPattern(diagonal, PATTERN)) @@ -352,6 +371,11 @@ static DetectorResult DetectPure(const BitMatrix& image) {{left, top}, {right, top}, {right, bottom}, {left, bottom}}}; } +FinderPatternSets FindFinderPatternSets(const BitMatrix& image, bool tryHarder) +{ + return GenerateFinderPatternSets(FindFinderPatterns(image, tryHarder)); +} + DetectorResult Detect(const BitMatrix& image, bool tryHarder, bool isPure) { #ifdef PRINT_DEBUG diff --git a/core/src/qrcode/QRDetector.h b/core/src/qrcode/QRDetector.h index 67c9d91110..b11975fffa 100644 --- a/core/src/qrcode/QRDetector.h +++ b/core/src/qrcode/QRDetector.h @@ -2,6 +2,7 @@ /* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors +* Copyright 2021 Axel Waggershauser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +17,10 @@ * limitations under the License. */ +#include "ConcentricFinder.h" + +#include + namespace ZXing { class DetectorResult; @@ -23,6 +28,16 @@ class BitMatrix; namespace QRCode { +struct FinderPatternSet +{ + ConcentricPattern bl, tl, tr; +}; + +using FinderPatternSets = std::vector; + +FinderPatternSets FindFinderPatternSets(const BitMatrix& image, bool tryHarder); +DetectorResult SampleAtFinderPatternSet(const BitMatrix& image, const FinderPatternSet& fp); + /** * @brief Detects a QR Code in an image. */ diff --git a/core/src/qrcode/QRECB.h b/core/src/qrcode/QRECB.h index 59a45ba858..5bb8017819 100644 --- a/core/src/qrcode/QRECB.h +++ b/core/src/qrcode/QRECB.h @@ -22,7 +22,7 @@ namespace ZXing { namespace QRCode { /** -*

Encapsualtes the parameters for one error-correction block in one symbol version. +*

Encapsulates the parameters for one error-correction block in one symbol version. * This includes the number of data codewords, and the number of times a block with these * parameters is used consecutively in the QR code version's format.

* diff --git a/core/src/qrcode/QREncoder.cpp b/core/src/qrcode/QREncoder.cpp index 9c05877482..297da96a31 100644 --- a/core/src/qrcode/QREncoder.cpp +++ b/core/src/qrcode/QREncoder.cpp @@ -250,20 +250,11 @@ ZXING_EXPORT_TEST_ONLY void AppendBytes(const std::wstring& content, CodecMode mode, CharacterSet encoding, BitArray& bits) { switch (mode) { - case CodecMode::NUMERIC: - AppendNumericBytes(content, bits); - break; - case CodecMode::ALPHANUMERIC: - AppendAlphanumericBytes(content, bits); - break; - case CodecMode::BYTE: - Append8BitBytes(content, encoding, bits); - break; - case CodecMode::KANJI: - AppendKanjiBytes(content, bits); - break; - default: - throw std::invalid_argument("Invalid mode: " + std::to_string(static_cast(mode))); + case CodecMode::NUMERIC: AppendNumericBytes(content, bits); break; + case CodecMode::ALPHANUMERIC: AppendAlphanumericBytes(content, bits); break; + case CodecMode::BYTE: Append8BitBytes(content, encoding, bits); break; + case CodecMode::KANJI: AppendKanjiBytes(content, bits); break; + default: throw std::invalid_argument("Invalid mode: " + std::to_string(static_cast(mode))); } } diff --git a/core/src/qrcode/QRErrorCorrectionLevel.cpp b/core/src/qrcode/QRErrorCorrectionLevel.cpp index 80890161f0..7ff749a515 100644 --- a/core/src/qrcode/QRErrorCorrectionLevel.cpp +++ b/core/src/qrcode/QRErrorCorrectionLevel.cpp @@ -30,13 +30,12 @@ const wchar_t* ToString(ErrorCorrectionLevel l) ErrorCorrectionLevel ECLevelFromString(const char* str) { - switch (str[0]) - { - case 'L': return ErrorCorrectionLevel::Low; - case 'M': return ErrorCorrectionLevel::Medium; - case 'Q': return ErrorCorrectionLevel::Quality; - case 'H': return ErrorCorrectionLevel::High; - default: return ErrorCorrectionLevel::Invalid; + switch (str[0]) { + case 'L': return ErrorCorrectionLevel::Low; + case 'M': return ErrorCorrectionLevel::Medium; + case 'Q': return ErrorCorrectionLevel::Quality; + case 'H': return ErrorCorrectionLevel::High; + default: return ErrorCorrectionLevel::Invalid; } } diff --git a/core/src/qrcode/QRMaskUtil.cpp b/core/src/qrcode/QRMaskUtil.cpp index 12d1a8a2f0..2f327e4f2a 100644 --- a/core/src/qrcode/QRMaskUtil.cpp +++ b/core/src/qrcode/QRMaskUtil.cpp @@ -21,6 +21,7 @@ #include #include #include +#include namespace ZXing::QRCode::MaskUtil { diff --git a/core/src/qrcode/QRMatrixUtil.cpp b/core/src/qrcode/QRMatrixUtil.cpp index e248a8dc3f..f937a08f5b 100644 --- a/core/src/qrcode/QRMatrixUtil.cpp +++ b/core/src/qrcode/QRMatrixUtil.cpp @@ -23,7 +23,6 @@ #include "QRErrorCorrectionLevel.h" #include "QRVersion.h" -#include #include #include diff --git a/core/src/qrcode/QRReader.cpp b/core/src/qrcode/QRReader.cpp index 7ce9180b1b..effd0e44e1 100644 --- a/core/src/qrcode/QRReader.cpp +++ b/core/src/qrcode/QRReader.cpp @@ -18,15 +18,13 @@ #include "QRReader.h" #include "BinaryBitmap.h" -#include "BitMatrix.h" #include "DecodeHints.h" #include "DecoderResult.h" #include "DetectorResult.h" +#include "LogMatrix.h" #include "QRDecoder.h" -#include "QRDecoderMetadata.h" #include "QRDetector.h" #include "Result.h" -#include "ResultPoint.h" #include @@ -40,7 +38,14 @@ Reader::Reader(const DecodeHints& hints) Result Reader::decode(const BinaryBitmap& image) const { - auto binImg = image.getBlackMatrix(); +#if 1 + if (!_isPure) { + auto res = decode(image, 1); + return res.empty() ? Result(DecodeStatus::NotFound) : res.front(); + } +#endif + + auto binImg = image.getBitMatrix(); if (binImg == nullptr) { return Result(DecodeStatus::NotFound); } @@ -52,10 +57,43 @@ Reader::decode(const BinaryBitmap& image) const auto decoderResult = Decode(detectorResult.bits(), _charset); auto position = detectorResult.position(); - // TODO: report the information that the symbol was mirrored back to the caller - //bool isMirrored = decoderResult.extra() && static_cast(decoderResult.extra().get())->isMirrored(); - return Result(std::move(decoderResult), std::move(position), BarcodeFormat::QRCode); } +Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const +{ + auto binImg = image.getBitMatrix(); + if (binImg == nullptr) + return {}; + +#ifdef PRINT_DEBUG + LogMatrixWriter lmw(log, *binImg, 5, "qr-log.pnm"); +#endif + + FinderPatternSets allFPSets = FindFinderPatternSets(*binImg, _tryHarder); + std::vector usedFPs; + Results results; + + for(auto& fpSet : allFPSets) { + if (Contains(usedFPs, fpSet.bl) || Contains(usedFPs, fpSet.tl) || Contains(usedFPs, fpSet.tr)) + continue; + + auto detectorResult = SampleAtFinderPatternSet(*binImg, fpSet); + if (detectorResult.isValid()) { + auto decoderResult = Decode(detectorResult.bits(), _charset); + auto position = detectorResult.position(); + if (decoderResult.isValid()) { + usedFPs.push_back(fpSet.bl); + usedFPs.push_back(fpSet.tl); + usedFPs.push_back(fpSet.tr); + results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::QRCode); + if (maxSymbols && Size(results) == maxSymbols) + break; + } + } + } + + return results; +} + } // namespace ZXing::QRCode diff --git a/core/src/qrcode/QRReader.h b/core/src/qrcode/QRReader.h index 78e5f50911..1df4e8f2b1 100644 --- a/core/src/qrcode/QRReader.h +++ b/core/src/qrcode/QRReader.h @@ -37,6 +37,8 @@ class Reader : public ZXing::Reader explicit Reader(const DecodeHints& hints); Result decode(const BinaryBitmap& image) const override; + Results decode(const BinaryBitmap& image, int maxSymbols) const override; + private: bool _tryHarder, _isPure; std::string _charset; diff --git a/core/src/textcodec/Big5MapTable.cpp b/core/src/textcodec/Big5MapTable.cpp index 3e4cb03874..ad80c85882 100644 --- a/core/src/textcodec/Big5MapTable.cpp +++ b/core/src/textcodec/Big5MapTable.cpp @@ -34,7 +34,7 @@ #include -// Big 5 to Unicode maping tables. +// Big 5 to Unicode mapping tables. // Tables are in sorted order on X(all table together) & Y(individually) static const B5Map b5_8140_to_uc_map[2041] = { {0x8140,0xeeb8}, {0x8141,0xeeb9}, {0x8142,0xeeba}, {0x8143,0xeebb}, {0x8144,0xeebc}, {0x8145,0xeebd}, {0x8146,0xeebe}, {0x8147,0xeebf}, {0x8148,0xeec0}, {0x8149,0xeec1}, diff --git a/core/src/textcodec/Big5TextDecoder.cpp b/core/src/textcodec/Big5TextDecoder.cpp index 04ffc71c40..48ec9bdf16 100644 --- a/core/src/textcodec/Big5TextDecoder.cpp +++ b/core/src/textcodec/Big5TextDecoder.cpp @@ -1747,7 +1747,7 @@ Big5TextDecoder::AppendBig5(std::vector& result, const uint8_t* bytes, { uint8_t buf[2] = { 0 }; int nbuf = 0; - int invalid = 0; +// int invalid = 0; result.reserve(length); for (size_t i = 0; i& result, const uint8_t* bytes, else { // Invalid result.push_back(REPLACEMENT); - ++invalid; +// ++invalid; } break; case 1: @@ -1779,13 +1779,13 @@ Big5TextDecoder::AppendBig5(std::vector& result, const uint8_t* bytes, else { // Error result.push_back(REPLACEMENT); - ++invalid; +// ++invalid; } } else { // Error result.push_back(REPLACEMENT); - ++invalid; +// ++invalid; } nbuf = 0; break; diff --git a/core/src/textcodec/GBTextDecoder.cpp b/core/src/textcodec/GBTextDecoder.cpp index cef0368897..e104b595e9 100644 --- a/core/src/textcodec/GBTextDecoder.cpp +++ b/core/src/textcodec/GBTextDecoder.cpp @@ -3433,7 +3433,7 @@ GBTextDecoder::AppendGB18030(std::vector& result, const uint8_t* bytes { uint8_t buf[4]; int nbuf = 0; - int invalid = 0; +// int invalid = 0; result.resize(length); int unicodeLen = 0; @@ -3454,7 +3454,7 @@ GBTextDecoder::AppendGB18030(std::vector& result, const uint8_t* bytes else { // Invalid result[unicodeLen++] = REPLACEMENT; - ++invalid; +// ++invalid; } break; case 1: @@ -3468,7 +3468,7 @@ GBTextDecoder::AppendGB18030(std::vector& result, const uint8_t* bytes } else { result[unicodeLen++] = REPLACEMENT; - ++invalid; +// ++invalid; } nbuf = 0; } @@ -3479,7 +3479,7 @@ GBTextDecoder::AppendGB18030(std::vector& result, const uint8_t* bytes else { // Error result[unicodeLen++] = REPLACEMENT; - ++invalid; +// ++invalid; nbuf = 0; } break; @@ -3491,7 +3491,7 @@ GBTextDecoder::AppendGB18030(std::vector& result, const uint8_t* bytes } else { result[unicodeLen++] = REPLACEMENT; - ++invalid; +// ++invalid; nbuf = 0; } break; @@ -3506,12 +3506,12 @@ GBTextDecoder::AppendGB18030(std::vector& result, const uint8_t* bytes } else { result[unicodeLen++] = REPLACEMENT; - ++invalid; +// ++invalid; } } else { result[unicodeLen++] = REPLACEMENT; - ++invalid; +// ++invalid; } nbuf = 0; break; @@ -3525,7 +3525,7 @@ GBTextDecoder::AppendGB2312(std::vector& result, const uint8_t* bytes, { uint8_t buf[2]; int nbuf = 0; - int invalid = 0; +// int invalid = 0; result.resize(length); int unicodeLen = 0; @@ -3545,7 +3545,7 @@ GBTextDecoder::AppendGB2312(std::vector& result, const uint8_t* bytes, else { // Invalid result[unicodeLen++] = REPLACEMENT; - ++invalid; +// ++invalid; } break; case 1: @@ -3559,14 +3559,14 @@ GBTextDecoder::AppendGB2312(std::vector& result, const uint8_t* bytes, } else { result[unicodeLen++] = REPLACEMENT; - ++invalid; +// ++invalid; } nbuf = 0; } else { // Error result[unicodeLen++] = REPLACEMENT; - ++invalid; +// ++invalid; nbuf = 0; } break; diff --git a/core/src/textcodec/GBTextEncoder.cpp b/core/src/textcodec/GBTextEncoder.cpp index 707ae60594..8bbb84826d 100644 --- a/core/src/textcodec/GBTextEncoder.cpp +++ b/core/src/textcodec/GBTextEncoder.cpp @@ -3977,7 +3977,7 @@ void GBTextEncoder::EncodeGB18030(const std::wstring& str, std::string& bytes) { static const char replacement = '?'; unsigned high = 0; - int invalid = 0; +// int invalid = 0; bytes.resize(4 * str.length() + 1); int index = 0; @@ -3998,14 +3998,14 @@ void GBTextEncoder::EncodeGB18030(const std::wstring& str, std::string& bytes) } else { bytes[index++] = replacement; - ++invalid; +// ++invalid; } high = 0; continue; } else { bytes[index++] = replacement; - ++invalid; +// ++invalid; high = 0; } } @@ -4026,7 +4026,7 @@ void GBTextEncoder::EncodeGB18030(const std::wstring& str, std::string& bytes) else { // Error bytes[index++] = replacement; - ++invalid; +// ++invalid; } } bytes.resize(index); diff --git a/core/src/textcodec/JPTextDecoder.cpp b/core/src/textcodec/JPTextDecoder.cpp index 1a3223212f..22874a4961 100644 --- a/core/src/textcodec/JPTextDecoder.cpp +++ b/core/src/textcodec/JPTextDecoder.cpp @@ -40,7 +40,7 @@ /* * This data is derived from Unicode 1.1, * JIS X 0208 (1990) to Unicode mapping table version 0.9 . -* (In addition NEC Vender Defined Char included) +* (In addition NEC Vendor Defined Char included) */ static unsigned short const jisx0208_to_unicode[] = { /* 0x2121 - 0x217e */ @@ -1272,7 +1272,7 @@ static unsigned short const jisx0208_to_unicode[] = { /* * This data is derived from Unicode 1.1, * JIS X 0212 (1990) to Unicode mapping table version 0.9 . -* (In addition IBM Vender Defined Char included) +* (In addition IBM Vendor Defined Char included) */ static unsigned short const jisx0212_to_unicode[] = { /* 0x2121 - 0x217e */ @@ -2634,7 +2634,7 @@ JPTextDecoder::AppendShiftJIS(std::vector& result, const uint8_t* byte { uint8_t buf[1] = { 0 }; int nbuf = 0; - int invalid = 0; +// int invalid = 0; unsigned u = 0; for (size_t i = 0; i& result, const uint8_t* byte else { // Invalid result.push_back(REPLACEMENT); - ++invalid; +// ++invalid; } break; case 1: @@ -2675,7 +2675,7 @@ JPTextDecoder::AppendShiftJIS(std::vector& result, const uint8_t* byte else { // Invalid result.push_back(REPLACEMENT); - ++invalid; +// ++invalid; } nbuf = 0; break; @@ -2696,7 +2696,7 @@ JPTextDecoder::AppendEUCJP(std::vector& result, const uint8_t* bytes, uint8_t buf[2] = { 0, 0 }; int nbuf = 0; - int invalid = 0; +// int invalid = 0; for (size_t i = 0; i& result, const uint8_t* bytes, else { // Invalid result.push_back(REPLACEMENT); - ++invalid; +// ++invalid; } break; case 1: @@ -2730,7 +2730,7 @@ JPTextDecoder::AppendEUCJP(std::vector& result, const uint8_t* bytes, } else { result.push_back(REPLACEMENT); - ++invalid; +// ++invalid; } nbuf = 0; } @@ -2743,7 +2743,7 @@ JPTextDecoder::AppendEUCJP(std::vector& result, const uint8_t* bytes, else { // Error result.push_back(REPLACEMENT); - ++invalid; +// ++invalid; nbuf = 0; } } @@ -2755,7 +2755,7 @@ JPTextDecoder::AppendEUCJP(std::vector& result, const uint8_t* bytes, else { // Error result.push_back(REPLACEMENT); - ++invalid; +// ++invalid; } nbuf = 0; } @@ -2767,7 +2767,7 @@ JPTextDecoder::AppendEUCJP(std::vector& result, const uint8_t* bytes, } else { result.push_back(REPLACEMENT); - ++invalid; +// ++invalid; } nbuf = 0; } diff --git a/core/src/textcodec/JPTextEncoder.cpp b/core/src/textcodec/JPTextEncoder.cpp index 8d8eee5e6f..26cf744d8f 100644 --- a/core/src/textcodec/JPTextEncoder.cpp +++ b/core/src/textcodec/JPTextEncoder.cpp @@ -40,7 +40,7 @@ /* * This data is derived from Unicode 1.1, * JIS X 0208 (1990) to Unicode mapping table version 0.9 . -* (In addition NEC Vender Defined Char included) +* (In addition NEC Vendor Defined Char included) */ static const uint16_t unicode_to_jisx0208_00[256] = { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, diff --git a/core/src/textcodec/KRTextDecoder.cpp b/core/src/textcodec/KRTextDecoder.cpp index 08e0b61895..dcbc0646ac 100644 --- a/core/src/textcodec/KRTextDecoder.cpp +++ b/core/src/textcodec/KRTextDecoder.cpp @@ -1029,7 +1029,7 @@ KRTextDecoder::AppendEucKr(std::vector& result, const uint8_t* bytes, { uint8_t buf[2] = { 0, 0 }; int nbuf = 0; - int invalid = 0; +// int invalid = 0; for (size_t i = 0; i& result, const uint8_t* bytes, else { // Invalid result.push_back(REPLACEMENT); - ++invalid; +// ++invalid; } break; case 1: @@ -1072,7 +1072,7 @@ KRTextDecoder::AppendEucKr(std::vector& result, const uint8_t* bytes, column = ch - 0x81 + 52; else { result.push_back(REPLACEMENT); - ++invalid; +// ++invalid; break; } @@ -1084,7 +1084,7 @@ KRTextDecoder::AppendEucKr(std::vector& result, const uint8_t* bytes, // check whether the conversion avialble in the table. if (internal_code < 0 || internal_code >= 8822) { result.push_back(REPLACEMENT); - ++invalid; +// ++invalid; break; } else @@ -1094,4 +1094,4 @@ KRTextDecoder::AppendEucKr(std::vector& result, const uint8_t* bytes, break; } } -} \ No newline at end of file +} diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index ba8b2db419..ec593db9ae 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,12 +1,12 @@ #show that it is possible to use the library (in its simples form) with a c++-11 compiler set (CMAKE_CXX_STANDARD 11) +zxing_add_package_stb() + if (BUILD_READERS) add_executable (ZXingReader ZXingReader.cpp) - target_include_directories (ZXingReader PRIVATE ../thirdparty/stb) - - target_link_libraries (ZXingReader ZXing::ZXing) + target_link_libraries (ZXingReader ZXing::ZXing stb::stb) add_test(NAME ZXingReaderTest COMMAND ZXingReader -fast -format qrcode "${CMAKE_SOURCE_DIR}/test/samples/qrcode-1/1.png") endif() @@ -14,23 +14,31 @@ endif() if (BUILD_WRITERS) add_executable (ZXingWriter ZXingWriter.cpp) - target_include_directories (ZXingWriter PRIVATE ../thirdparty/stb) - - target_link_libraries (ZXingWriter ZXing::ZXing) + target_link_libraries (ZXingWriter ZXing::ZXing stb::stb) add_test(NAME ZXingWriterTest COMMAND ZXingWriter qrcode "I have the best words." test.png) endif() -find_package(Qt5 COMPONENTS Gui Multimedia Quick) -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) -if (TARGET Qt5::Gui) - add_executable (ZXingQtReader ZXingQtReader.cpp ZXingQtReader.h) - target_link_libraries(ZXingQtReader ZXing::ZXing Qt5::Gui) -endif() - -if (TARGET Qt5::Multimedia) - add_executable(ZXingQtCamReader ZXingQtCamReader.cpp ZXingQtCamReader.qrc ZXingQtReader.h) - target_link_libraries(ZXingQtCamReader ZXing::ZXing Qt5::Gui Qt5::Multimedia Qt5::Quick) +if (BUILD_READERS) + find_package(Qt5 COMPONENTS Gui Multimedia Quick) + set(CMAKE_AUTOMOC ON) + set(CMAKE_AUTORCC ON) + + if (TARGET Qt5::Gui) + add_executable (ZXingQtReader ZXingQtReader.cpp ZXingQtReader.h) + target_link_libraries(ZXingQtReader ZXing::ZXing Qt5::Gui) + endif() + + if (TARGET Qt5::Multimedia) + add_executable(ZXingQtCamReader ZXingQtCamReader.cpp ZXingQtCamReader.qrc ZXingQtReader.h) + target_link_libraries(ZXingQtCamReader ZXing::ZXing Qt5::Gui Qt5::Multimedia Qt5::Quick) + endif() + + find_package(OpenCV) + if (OpenCV_FOUND) + add_executable (ZXingOpenCV ZXingOpenCV.cpp) + target_include_directories (ZXingOpenCV PRIVATE ${OpenCV_INCLUDE_DIRS}) + target_link_libraries (ZXingOpenCV ZXing::ZXing ${OpenCV_LIBS}) + endif() endif() diff --git a/example/ZXingOpenCV.cpp b/example/ZXingOpenCV.cpp new file mode 100644 index 0000000000..7507068ba7 --- /dev/null +++ b/example/ZXingOpenCV.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2021 Axel Waggershauser + * + * 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. + */ + +#include "ZXingOpenCV.h" + +#include + +using namespace cv; + +int main() +{ + namedWindow("Display window"); + + Mat image; + VideoCapture cap(0); + + if (!cap.isOpened()) + std::cout << "cannot open camera"; + else + while (waitKey(25) != 27) { + cap >> image; + auto res = ReadBarcode(image); + if (res.isValid()) + DrawResult(image, res); + imshow("Display window", image); + } + + return 0; +} diff --git a/example/ZXingOpenCV.h b/example/ZXingOpenCV.h new file mode 100644 index 0000000000..b096b0d770 --- /dev/null +++ b/example/ZXingOpenCV.h @@ -0,0 +1,56 @@ +#pragma once +/* + * Copyright 2021 Axel Waggershauser + * + * 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. + */ + +#include "ReadBarcode.h" +#include "TextUtfEncoding.h" + +#include + +inline ZXing::ImageView ImageViewFromMat(const cv::Mat& image) +{ + using ZXing::ImageFormat; + + auto fmt = ImageFormat::None; + switch (image.channels()) { + case 1: fmt = ImageFormat::Lum; break; + case 3: fmt = ImageFormat::BGR; break; + case 4: fmt = ImageFormat::BGRX; break; + } + + if (image.depth() != CV_8U || fmt == ImageFormat::None) + return {nullptr, 0, 0, ImageFormat::None}; + + return {image.data, image.cols, image.rows, fmt}; +} + +inline ZXing::Result ReadBarcode(const cv::Mat& image, const ZXing::DecodeHints& hints = {}) +{ + return ZXing::ReadBarcode(ImageViewFromMat(image), hints); +} + +inline void DrawResult(cv::Mat& img, ZXing::Result res) +{ + auto pos = res.position(); + auto zx2cv = [](ZXing::PointI p) { return cv::Point(p.x, p.y); }; + auto contour = std::vector{zx2cv(pos[0]), zx2cv(pos[1]), zx2cv(pos[2]), zx2cv(pos[3])}; + const auto* pts = contour.data(); + int npts = contour.size(); + + cv::polylines(img, &pts, &npts, 1, true, CV_RGB(0, 255, 0)); + cv::putText(img, ZXing::TextUtfEncoding::ToUtf8(res.text()), cv::Point(10, 20), cv::FONT_HERSHEY_DUPLEX, 0.5, + CV_RGB(0, 255, 0)); +} diff --git a/example/ZXingQtCamReader.qml b/example/ZXingQtCamReader.qml index 41e2b6bd95..9a90cf20d6 100644 --- a/example/ZXingQtCamReader.qml +++ b/example/ZXingQtCamReader.qml @@ -43,7 +43,7 @@ Window { tryRotate: tryRotateSwitch.checked tryHarder: tryHarderSwitch.checked - // callback with parameter 'result', called for every sucessfully processed frame + // callback with parameter 'result', called for every successfully processed frame // onFoundBarcode: {} // callback with parameter 'result', called for every processed frame diff --git a/example/ZXingQtReader.cpp b/example/ZXingQtReader.cpp index e64de9b538..dedd621780 100644 --- a/example/ZXingQtReader.cpp +++ b/example/ZXingQtReader.cpp @@ -22,14 +22,26 @@ using namespace ZXingQt; int main(int argc, char* argv[]) { + if (argc != 2) { + qDebug() << "Please supply exactly one image filename"; + return 1; + } + QString filePath = argv[1]; + QImage fileImage = QImage(filePath); + + if (fileImage.isNull()) { + qDebug() << "Could not load the filename as an image:" << filePath; + return 1; + } + auto hints = DecodeHints() .setFormats(BarcodeFormat::QRCode) .setTryRotate(false) .setBinarizer(Binarizer::FixedThreshold); - auto result = ReadBarcode(QImage(filePath), hints); + auto result = ReadBarcode(fileImage, hints); qDebug() << "Text: " << result.text(); qDebug() << "Format: " << result.format(); diff --git a/example/ZXingQtReader.h b/example/ZXingQtReader.h index 7d0ee8c054..8a316ca206 100644 --- a/example/ZXingQtReader.h +++ b/example/ZXingQtReader.h @@ -121,7 +121,7 @@ class Result : private ZXing::Result explicit Result(ZXing::Result&& r) : ZXing::Result(std::move(r)) { _text = QString::fromWCharArray(ZXing::Result::text().c_str()); _rawBytes = QByteArray(reinterpret_cast(ZXing::Result::rawBytes().data()), - ZXing::Result::rawBytes().size()); + Size(ZXing::Result::rawBytes())); auto& pos = ZXing::Result::position(); auto qp = [&pos](int i) { return QPoint(pos[i].x, pos[i].y); }; _position = {qp(0), qp(1), qp(2), qp(3)}; @@ -288,7 +288,7 @@ class VideoFilter : public QAbstractVideoFilter, private DecodeHints QVideoFilterRunnable* createFilterRunnable() override; - // TODO: find out how to properyl expose QFlags to QML + // TODO: find out how to properly expose QFlags to QML // simply using ZQ_PROPERTY(BarcodeFormats, formats, setFormats) // results in the runtime error "can't assign int to formats" Q_PROPERTY(int formats READ formats WRITE setFormats NOTIFY formatsChanged) diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index a16068155a..15150e2897 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -18,30 +18,34 @@ #include "TextUtfEncoding.h" #include "GTIN.h" -#include #include #include #include #include #include +#include #include #include #define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" +#include +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include using namespace ZXing; using namespace TextUtfEncoding; static void PrintUsage(const char* exePath) { - std::cout << "Usage: " << exePath << " [-fast] [-norotate] [-format ] [-ispure] [-1] ...\n" + std::cout << "Usage: " << exePath << " [-fast] [-norotate] [-format ] [-pngout ] [-ispure] [-1] ...\n" << " -fast Skip some lines/pixels during detection (faster)\n" << " -norotate Don't try rotated image during detection (faster)\n" + << " -noscale Don't try downscaled images during detection (faster)\n" << " -format Only detect given format(s) (faster)\n" << " -ispure Assume the image contains only a 'pure'/perfect code (faster)\n" - << " -1 Print only file name, text and status on one line per file\n" + << " -1 Print only file name, text and status on one line per file/barcode\n" << " -escape Escape non-graphical characters in angle brackets (ignored for -1 option, which always escapes)\n" + << " -pngout Write a copy of the input image with barcodes outlined by a green line\n" << "\n" << "Supported formats are:\n"; for (auto f : BarcodeFormats::all()) { @@ -50,20 +54,20 @@ static void PrintUsage(const char* exePath) std::cout << "Formats can be lowercase, with or without '-', separated by ',' and/or '|'\n"; } -static bool ParseOptions(int argc, char* argv[], DecodeHints& hints, bool& oneLine, bool& angleEscape, std::vector& filePaths) +static bool ParseOptions(int argc, char* argv[], DecodeHints& hints, bool& oneLine, bool& angleEscape, + std::vector& filePaths, std::string& outPath) { for (int i = 1; i < argc; ++i) { if (strcmp(argv[i], "-fast") == 0) { hints.setTryHarder(false); - } - else if (strcmp(argv[i], "-norotate") == 0) { + } else if (strcmp(argv[i], "-norotate") == 0) { hints.setTryRotate(false); - } - else if (strcmp(argv[i], "-ispure") == 0) { + } else if (strcmp(argv[i], "-noscale") == 0) { + hints.setDownscaleThreshold(0); + } else if (strcmp(argv[i], "-ispure") == 0) { hints.setIsPure(true); hints.setBinarizer(Binarizer::FixedThreshold); - } - else if (strcmp(argv[i], "-format") == 0) { + } else if (strcmp(argv[i], "-format") == 0) { if (++i == argc) return false; try { @@ -72,14 +76,15 @@ static bool ParseOptions(int argc, char* argv[], DecodeHints& hints, bool& oneLi std::cerr << e.what() << "\n"; return false; } - } - else if (strcmp(argv[i], "-1") == 0) { + } else if (strcmp(argv[i], "-1") == 0) { oneLine = true; - } - else if (strcmp(argv[i], "-escape") == 0) { + } else if (strcmp(argv[i], "-escape") == 0) { angleEscape = true; - } - else { + } else if (strcmp(argv[i], "-pngout") == 0) { + if (++i == argc) + return false; + outPath = argv[i]; + } else { filePaths.push_back(argv[i]); } } @@ -87,21 +92,43 @@ static bool ParseOptions(int argc, char* argv[], DecodeHints& hints, bool& oneLi return !filePaths.empty(); } -std::ostream& operator<<(std::ostream& os, const Position& points) { +std::ostream& operator<<(std::ostream& os, const Position& points) +{ for (const auto& p : points) os << p.x << "x" << p.y << " "; return os; } +void drawLine(const ImageView& iv, PointI a, PointI b) +{ + int steps = maxAbsComponent(b - a); + PointF dir = bresenhamDirection(PointF(b - a)); + int R = RedIndex(iv.format()), G = GreenIndex(iv.format()), B = BlueIndex(iv.format()); + for (int i = 0; i < steps; ++i) { + auto p = PointI(centered(a + i * dir)); + auto* dst = const_cast(iv.data(p.x, p.y)); + dst[R] = dst[B] = 0; + dst[G] = 0xff; + } +} + +void drawRect(const ImageView& image, const Position& pos) +{ + for (int i = 0; i < 4; ++i) + drawLine(image, pos[i], pos[(i + 1) % 4]); +} + int main(int argc, char* argv[]) { DecodeHints hints; std::vector filePaths; + std::string outPath; bool oneLine = false; bool angleEscape = false; int ret = 0; - if (!ParseOptions(argc, argv, hints, oneLine, angleEscape, filePaths)) { + + if (!ParseOptions(argc, argv, hints, oneLine, angleEscape, filePaths, outPath)) { PrintUsage(argv[0]); return -1; } @@ -116,60 +143,83 @@ int main(int argc, char* argv[]) for (const auto& filePath : filePaths) { int width, height, channels; - std::unique_ptr buffer(stbi_load(filePath.c_str(), &width, &height, &channels, 4), stbi_image_free); + std::unique_ptr buffer(stbi_load(filePath.c_str(), &width, &height, &channels, 3), stbi_image_free); if (buffer == nullptr) { std::cerr << "Failed to read image: " << filePath << "\n"; return -1; } - const auto& result = ReadBarcode({buffer.get(), width, height, ImageFormat::RGBX}, hints); + ImageView image{buffer.get(), width, height, ImageFormat::RGB}; + auto results = ReadBarcodes(image, hints); - ret |= static_cast(result.status()); + // if we did not find anything, insert a dummy to produce some output for each file + if (results.empty()) + results.emplace_back(DecodeStatus::NotFound); - if (oneLine) { - std::cout << filePath << " " << ToString(result.format()); - if (result.isValid()) - std::cout << " \"" << ToUtf8(result.text(), angleEscape) << "\""; - else if (result.format() != BarcodeFormat::None) - std::cout << " " << ToString(result.status()); - std::cout << "\n"; - continue; - } + for (auto&& result : results) { - if (filePaths.size() > 1) { - static bool firstFile = true; - if (!firstFile) + if (!outPath.empty()) + drawRect(image, result.position()); + + ret |= static_cast(result.status()); + + if (oneLine) { + std::cout << filePath << " " << ToString(result.format()); + if (result.isValid()) + std::cout << " \"" << ToUtf8(result.text(), angleEscape) << "\""; + else if (result.format() != BarcodeFormat::None) + std::cout << " " << ToString(result.status()); std::cout << "\n"; - std::cout << "File: " << filePath << "\n"; - firstFile = false; - } - std::cout << "Text: \"" << ToUtf8(result.text(), angleEscape) << "\"\n" - << "Format: " << ToString(result.format()) << "\n" - << "Position: " << result.position() << "\n" - << "Rotation: " << result.orientation() << " deg\n" - << "Error: " << ToString(result.status()) << "\n"; - - auto printOptional = [](const char* key, const std::string& v) { - if (!v.empty()) - std::cout << key << v << "\n"; - }; - - printOptional("EC Level: ", ToUtf8(result.ecLevel())); - - if ((BarcodeFormat::EAN13 | BarcodeFormat::EAN8 | BarcodeFormat::UPCA | BarcodeFormat::UPCE) - .testFlag(result.format())) { - printOptional("Country: ", GTIN::LookupCountryIdentifier(ToUtf8(result.text()))); - printOptional("Add-On: ", GTIN::EanAddOn(result)); - printOptional("Price: ", GTIN::Price(GTIN::EanAddOn(result))); - printOptional("Issue #: ", GTIN::IssueNr(GTIN::EanAddOn(result))); + continue; + } + + if (filePaths.size() > 1 || results.size() > 1) { + static bool firstFile = true; + if (!firstFile) + std::cout << "\n"; + if (filePaths.size() > 1) + std::cout << "File: " << filePath << "\n"; + firstFile = false; + } + std::cout << "Text: \"" << ToUtf8(result.text(), angleEscape) << "\"\n" + << "Format: " << ToString(result.format()) << "\n" + << "Identifier: " << result.symbologyIdentifier() << "\n" + << "Position: " << result.position() << "\n" + << "Rotation: " << result.orientation() << " deg\n" + << "IsMirrored: " << (result.isMirrored() ? "true" : "false") << "\n" + << "Error: " << ToString(result.status()) << "\n"; + + auto printOptional = [](const char* key, const std::string& v) { + if (!v.empty()) + std::cout << key << v << "\n"; + }; + + printOptional("EC Level: ", ToUtf8(result.ecLevel())); + + if (result.lineCount()) + std::cout << "Lines: " << result.lineCount() << "\n"; + + if ((BarcodeFormat::EAN13 | BarcodeFormat::EAN8 | BarcodeFormat::UPCA | BarcodeFormat::UPCE) + .testFlag(result.format())) { + printOptional("Country: ", GTIN::LookupCountryIdentifier(ToUtf8(result.text()), result.format())); + printOptional("Add-On: ", GTIN::EanAddOn(result)); + printOptional("Price: ", GTIN::Price(GTIN::EanAddOn(result))); + printOptional("Issue #: ", GTIN::IssueNr(GTIN::EanAddOn(result))); + } else if (result.format() == BarcodeFormat::ITF && result.text().length() == 14) { + printOptional("Country: ", GTIN::LookupCountryIdentifier(ToUtf8(result.text()), result.format())); + } + + if (result.isPartOfSequence()) + std::cout << "Structured Append: symbol " << result.sequenceIndex() + 1 << " of " + << result.sequenceSize() << " (parity/id: '" << result.sequenceId() << "')\n"; + + if (result.readerInit()) + std::cout << "Reader Initialisation/Programming\n"; } - if (result.isPartOfSequence()) - std::cout << "Structured Append: symbol " << result.sequenceIndex() + 1 << " of " << result.sequenceSize() - << " (parity/id: '" << result.sequenceId() << "')\n"; + if (Size(filePaths) == 1 && !outPath.empty()) + stbi_write_png(outPath.c_str(), image.width(), image.height(), 3, image.data(0, 0), image.rowStride()); - if (result.readerInit()) - std::cout << "Reader Initialisation/Programming\n"; } return ret; diff --git a/example/ZXingWriter.cpp b/example/ZXingWriter.cpp index b038365ee5..cd6188bcf8 100644 --- a/example/ZXingWriter.cpp +++ b/example/ZXingWriter.cpp @@ -16,20 +16,18 @@ #include "BarcodeFormat.h" #include "BitMatrix.h" -#include "ByteMatrix.h" #include "MultiFormatWriter.h" #include "TextUtfEncoding.h" +#include "CharacterSetECI.h" #include #include -#include #include #include -#include #include #define STB_IMAGE_WRITE_IMPLEMENTATION -#include "stb_image_write.h" +#include using namespace ZXing; @@ -53,14 +51,15 @@ static bool ParseSize(std::string str, int* width, int* height) std::transform(str.begin(), str.end(), str.begin(), [](char c) { return (char)std::tolower(c); }); auto xPos = str.find('x'); if (xPos != std::string::npos) { - *width = std::stoi(str.substr(0, xPos)); + *width = std::stoi(str.substr(0, xPos)); *height = std::stoi(str.substr(xPos + 1)); return true; } return false; } -static bool ParseOptions(int argc, char* argv[], int* width, int* height, int* margin, int* eccLevel, BarcodeFormat* format, std::string* text, std::string* filePath) +static bool ParseOptions(int argc, char* argv[], int* width, int* height, int* margin, CharacterSet* encoding, + int* eccLevel, BarcodeFormat* format, std::string* text, std::string* filePath) { int nonOptArgCount = 0; for (int i = 1; i < argc; ++i) { @@ -71,34 +70,32 @@ static bool ParseOptions(int argc, char* argv[], int* width, int* height, int* m std::cerr << "Invalid size specification: " << argv[i] << std::endl; return false; } - } - else if (strcmp(argv[i], "-margin") == 0) { + } else if (strcmp(argv[i], "-margin") == 0) { if (++i == argc) return false; *margin = std::stoi(argv[i]); - } - else if (strcmp(argv[i], "-ecc") == 0) { + } else if (strcmp(argv[i], "-ecc") == 0) { if (++i == argc) return false; *eccLevel = std::stoi(argv[i]); - } - else if (nonOptArgCount == 0) { + } else if (strcmp(argv[i], "-encoding") == 0) { + if (++i == argc) + return false; + *encoding = CharacterSetECI::CharsetFromName(argv[i]); + } else if (nonOptArgCount == 0) { *format = BarcodeFormatFromString(argv[i]); if (*format == BarcodeFormat::None) { std::cerr << "Unrecognized format: " << argv[i] << std::endl; return false; } ++nonOptArgCount; - } - else if (nonOptArgCount == 1) { + } else if (nonOptArgCount == 1) { *text = argv[i]; ++nonOptArgCount; - } - else if (nonOptArgCount == 2) { + } else if (nonOptArgCount == 2) { *filePath = argv[i]; ++nonOptArgCount; - } - else { + } else { return false; } } @@ -121,24 +118,24 @@ int main(int argc, char* argv[]) int width = 100, height = 100; int margin = 10; int eccLevel = -1; + CharacterSet encoding = CharacterSet::Unknown; std::string text, filePath; BarcodeFormat format; - if (!ParseOptions(argc, argv, &width, &height, &margin, &eccLevel, &format, &text, &filePath)) { + if (!ParseOptions(argc, argv, &width, &height, &margin, &encoding, &eccLevel, &format, &text, &filePath)) { PrintUsage(argv[0]); return -1; } try { - auto writer = MultiFormatWriter(format).setMargin(margin).setEccLevel(eccLevel); + auto writer = MultiFormatWriter(format).setMargin(margin).setEncoding(encoding).setEccLevel(eccLevel); auto bitmap = ToMatrix(writer.encode(TextUtfEncoding::FromUtf8(text), width, height)); auto ext = GetExtension(filePath); int success = 0; if (ext == "" || ext == "png") { success = stbi_write_png(filePath.c_str(), bitmap.width(), bitmap.height(), 1, bitmap.data(), 0); - } - else if (ext == "jpg" || ext == "jpeg") { + } else if (ext == "jpg" || ext == "jpeg") { success = stbi_write_jpg(filePath.c_str(), bitmap.width(), bitmap.height(), 1, bitmap.data(), 0); } @@ -146,8 +143,7 @@ int main(int argc, char* argv[]) std::cerr << "Failed to write image: " << filePath << std::endl; return -1; } - } - catch (const std::exception& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return -1; } diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 0df58ba4a4..9899cef628 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,65 @@ namespace { }; } +// Helper for `compareResult()` - map `key` to Result property, converting value to std::string +static std::string getResultValue(const Result& result, const std::string& key) +{ + if (key == "ecLevel") + return TextUtfEncoding::ToUtf8(result.ecLevel()); + if (key == "orientation") + return std::to_string(result.orientation()); + if (key == "numBits") + return std::to_string(result.numBits()); + if (key == "symbologyIdentifier") + return result.symbologyIdentifier(); + if (key == "sequenceSize") + return std::to_string(result.sequenceSize()); + if (key == "sequenceIndex") + return std::to_string(result.sequenceIndex()); + if (key == "sequenceId") + return result.sequenceId(); + if (key == "isLastInSequence") + return result.isLastInSequence() ? "true" : "false"; + if (key == "isPartOfSequence") + return result.isPartOfSequence() ? "true" : "false"; + if (key == "isMirrored") + return result.isMirrored() ? "true" : "false"; + if (key == "readerInit") + return result.readerInit() ? "true" : "false"; + + return fmt::format("***Unknown key '{}'***", key); +} + +// Read ".result.txt" file contents `expected` with lines "key=value" and compare to `actual` +static bool compareResult(const Result& result, const std::string& expected, std::string& actual) +{ + bool ret = true; + + actual.clear(); + actual.reserve(expected.size()); + + std::stringstream expectedLines(expected); + std::string expectedLine; + while (std::getline(expectedLines, expectedLine)) { + if (expectedLine.empty() || expectedLine[0] == '#') + continue; + auto equals = expectedLine.find('='); + if (equals == std::string::npos) { + actual += "***Bad format, missing equals***\n"; + return false; + } + std::string key = expectedLine.substr(0, equals); + std::string expectedValue = expectedLine.substr(equals + 1); + std::string actualValue = getResultValue(result, key); + if (actualValue != expectedValue) { + ret = false; + actualValue += " ***Mismatch***"; + } + actual += key + '=' + actualValue + '\n'; + } + return ret; +} + static std::string checkResult(const fs::path& imgPath, std::string_view expectedFormat, const Result& result) { if (auto format = ToString(result.format()); expectedFormat != format) @@ -82,6 +142,12 @@ static std::string checkResult(const fs::path& imgPath, std::string_view expecte return ifs ? std::optional(std::string(std::istreambuf_iterator(ifs), std::istreambuf_iterator())) : std::nullopt; }; + if (auto expected = readFile(".result.txt")) { + std::string actual; + if (!compareResult(result, *expected, actual)) + return fmt::format("Result mismatch: expected\n{} but got\n{}", *expected, actual); + } + if (auto expected = readFile(".txt")) { auto utf8Result = TextUtfEncoding::ToUtf8(result.text()); return utf8Result != *expected ? fmt::format("Content mismatch: expected '{}' but got '{}'", *expected, utf8Result) : ""; @@ -191,15 +257,13 @@ static void doRunTests( } } -static auto readPDF417s = [](const BinaryBitmap& image) { return Pdf417::Reader({}).decodeMultiple(image); }; -static auto readQRCodes = [](const BinaryBitmap& image) { return std::list{QRCode::Reader({}).decode(image)}; }; - -template -static Result readMultiple(const std::vector& imgPaths, int rotation, READER read) +static Result readMultiple(const std::vector& imgPaths, std::string_view format) { std::list allResults; for (const auto& imgPath : imgPaths) { - auto results = read(ThresholdBinarizer(ImageLoader::load(imgPath), 127)); + auto results = + ReadBarcodes(ImageLoader::load(imgPath), + DecodeHints().setFormats(BarcodeFormatFromString(format.data())).setTryDownscale(false)); allResults.insert(allResults.end(), results.begin(), results.end()); } @@ -217,7 +281,10 @@ static Result readMultiple(const std::vector& imgPaths, int rotation, for (const auto& r : allResults) text.append(r.text()); - return {std::move(text), {}, allResults.front().format()}; + const auto& first = allResults.front(); + StructuredAppendInfo sai{ first.sequenceIndex(), first.sequenceSize(), first.sequenceId() }; + return Result(std::move(text), {}, first.format(), std::string(first.symbologyIdentifier()), {}, std::move(sai), + first.readerInit()); } static void doRunStructuredAppendTest( @@ -243,8 +310,7 @@ static void doRunStructuredAppendTest( auto startTime = std::chrono::steady_clock::now(); for (const auto& [testPath, testImgPaths] : imageGroups) { - auto result = format == "QRCode" ? readMultiple(testImgPaths, test.rotation, readQRCodes) - : readMultiple(testImgPaths, test.rotation, readPDF417s); + auto result = readMultiple(testImgPaths, format); if (result.isValid()) { auto error = checkResult(testPath, format, result); if (!error.empty()) @@ -283,12 +349,12 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set auto startTime = std::chrono::steady_clock::now(); // clang-format off - runTests("aztec-1", "Aztec", 21, { - { 20, 20, 0 }, - { 20, 20, 90 }, - { 20, 20, 180 }, - { 20, 20, 270 }, - { 21, 0, pure }, + runTests("aztec-1", "Aztec", 22, { + { 21, 21, 0 }, + { 21, 21, 90 }, + { 21, 21, 180 }, + { 21, 21, 270 }, + { 22, 0, pure }, }); runTests("aztec-2", "Aztec", 22, { @@ -298,12 +364,12 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 3, 3, 270 }, }); - runTests("datamatrix-1", "DataMatrix", 25, { - { 25, 25, 0 }, - { 0, 25, 90 }, - { 0, 25, 180 }, - { 0, 25, 270 }, - { 24, 0, pure }, + runTests("datamatrix-1", "DataMatrix", 26, { + { 26, 26, 0 }, + { 0, 26, 90 }, + { 0, 26, 180 }, + { 0, 26, 270 }, + { 25, 0, pure }, }); runTests("datamatrix-2", "DataMatrix", 13, { @@ -320,12 +386,12 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 0, 19, 270 }, }); - runTests("datamatrix-4", "DataMatrix", 20, { - { 20, 20, 0 }, - { 0, 20, 90 }, - { 0, 20, 180 }, - { 0, 20, 270 }, - { 18, 0, pure }, + runTests("datamatrix-4", "DataMatrix", 21, { + { 21, 21, 0 }, + { 0, 21, 90 }, + { 0, 21, 180 }, + { 0, 21, 270 }, + { 19, 0, pure }, }); runTests("codabar-1", "Codabar", 11, { @@ -334,8 +400,8 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set }); runTests("codabar-2", "Codabar", 4, { - { 3, 3, 0 }, - { 3, 3, 180 }, + { 2, 3, 0 }, + { 2, 3, 180 }, }); runTests("code39-1", "Code39", 4, { @@ -364,7 +430,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set }); runTests("code128-2", "Code128", 21, { - { 19, 21, 0 }, + { 18, 21, 0 }, { 19, 21, 180 }, }); @@ -376,10 +442,11 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set runTests("ean8-1", "EAN-8", 8, { { 8, 8, 0 }, { 8, 8, 180 }, + { 7, 0, pure }, }); runTests("ean13-1", "EAN-13", 31, { - { 26, 29, 0 }, + { 24, 29, 0 }, { 23, 29, 180 }, }); @@ -394,18 +461,18 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set }); runTests("ean13-4", "EAN-13", 22, { - { 7, 14, 0 }, - { 8, 14, 180 }, + { 6, 13, 0 }, + { 8, 13, 180 }, }); runTests("ean13-extension-1", "EAN-13", 5, { - { 4, 5, 0 }, + { 3, 5, 0 }, { 3, 5, 180 }, }, DecodeHints().setEanAddOnSymbol(EanAddOnSymbol::Require)); - runTests("itf-1", "ITF", 10, { - { 10, 10, 0 }, - { 10, 10, 180 }, + runTests("itf-1", "ITF", 11, { + { 10, 11, 0 }, + { 10, 11, 180 }, }); runTests("itf-2", "ITF", 6, { @@ -422,48 +489,49 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set }); runTests("upca-1", "UPC-A", 12, { - { 9, 12, 0, 1, 0 }, - { 11, 12, 0, 1, 180 }, + { 9, 12, 0 }, + { 11, 12, 180 }, }); runTests("upca-2", "UPC-A", 36, { { 17, 22, 0 }, - { 18, 22, 180 }, + { 16, 22, 180 }, }); runTests("upca-3", "UPC-A", 21, { - { 7, 10, 0, 1, 0 }, - { 8, 10, 0, 1, 180 }, + { 7, 10, 0 }, + { 8, 10, 180 }, }); runTests("upca-4", "UPC-A", 19, { - { 9, 11, 0, 1, 0 }, - { 9, 11, 0, 1, 180 }, + { 8, 12, 0 }, + { 9, 12, 180 }, }); runTests("upca-5", "UPC-A", 32, { { 17, 20, 0 }, - { 19, 20, 180 }, + { 18, 20, 180 }, }); runTests("upca-extension-1", "UPC-A", 6, { - { 3, 6, 0 }, - { 4, 6, 180 }, + { 4, 4, 0 }, + { 3, 4, 180 }, }, DecodeHints().setEanAddOnSymbol(EanAddOnSymbol::Require)); runTests("upce-1", "UPC-E", 3, { { 3, 3, 0 }, { 3, 3, 180 }, + { 3, 0, pure }, }); runTests("upce-2", "UPC-E", 28, { - { 19, 22, 0, 1, 0 }, + { 17, 22, 0, 1, 0 }, { 20, 22, 1, 1, 180 }, }); runTests("upce-3", "UPC-E", 11, { - { 6, 8, 0 }, - { 6, 8, 180 }, + { 5, 7, 0 }, + { 6, 7, 180 }, }); runTests("rss14-1", "DataBar", 6, { @@ -472,13 +540,14 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set }); runTests("rss14-2", "DataBar", 16, { - { 8 , 10, 0 }, - { 10, 10, 180 }, + { 8, 10, 0 }, + { 9, 10, 180 }, }); runTests("rssexpanded-1", "DataBarExpanded", 33, { { 33, 33, 0 }, { 33, 33, 180 }, + { 33, 0, pure }, }); runTests("rssexpanded-2", "DataBarExpanded", 15, { @@ -489,11 +558,13 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set runTests("rssexpanded-3", "DataBarExpanded", 118, { { 118, 118, 0 }, { 118, 118, 180 }, + { 118, 0, pure }, }); runTests("rssexpandedstacked-1", "DataBarExpanded", 65, { { 60, 65, 0 }, { 60, 65, 180 }, + { 60, 0, pure }, }); runTests("rssexpandedstacked-2", "DataBarExpanded", 7, { @@ -508,12 +579,12 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 16, 16, 270 }, }); - runTests("qrcode-2", "QRCode", 40, { - { 38, 38, 0 }, - { 38, 38, 90 }, - { 38, 38, 180 }, - { 38, 38, 270 }, - { 20, 1, pure }, // the misread is the 'outer' symbol in 16.png + runTests("qrcode-2", "QRCode", 46, { + { 44, 44, 0 }, + { 44, 44, 90 }, + { 44, 44, 180 }, + { 44, 44, 270 }, + { 21, 1, pure }, // the misread is the 'outer' symbol in 16.png }); runTests("qrcode-3", "QRCode", 28, { @@ -572,7 +643,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 2, 2, 0 }, }); - runTests("falsepositives-1", "None", 24, { + runTests("falsepositives-1", "None", 25, { { 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 90 }, { 0, 0, 0, 0, 180 }, diff --git a/test/blackbox/CMakeLists.txt b/test/blackbox/CMakeLists.txt index cf24f0c96c..f20f4127c7 100644 --- a/test/blackbox/CMakeLists.txt +++ b/test/blackbox/CMakeLists.txt @@ -1,10 +1,5 @@ -cmake_minimum_required(VERSION 3.14) - -include(FetchContent) -FetchContent_Declare (fmtlib - GIT_REPOSITORY https://github.com/fmtlib/fmt.git - GIT_TAG 7.1.2) -FetchContent_MakeAvailable (fmtlib) # Adds fmt::fmt +zxing_add_package_stb() +zxing_add_package(fmt fmtlib https://github.com/fmtlib/fmt.git 8.1.1) if (BUILD_READERS) add_executable (ReaderTest @@ -16,28 +11,18 @@ if (BUILD_READERS) ZXFilesystem.h ) - target_include_directories (ReaderTest PRIVATE ../../thirdparty/stb) - target_link_libraries(ReaderTest - ZXing::ZXing fmt::fmt + ZXing::ZXing fmt::fmt stb::stb $<$,$,9.0>>:stdc++fs> ) -if (MSVC) - target_compile_options(ReaderTest PRIVATE - -D_CRT_SECURE_NO_WARNINGS - ) -endif() - add_test(NAME ReaderTest COMMAND ReaderTest ${CMAKE_CURRENT_SOURCE_DIR}/../samples) endif() if (BUILD_WRITERS) add_executable (WriterTest TestWriterMain.cpp) - target_include_directories (WriterTest PRIVATE ../../thirdparty/stb) - - target_link_libraries (WriterTest ZXing::ZXing) + target_link_libraries (WriterTest ZXing::ZXing stb::stb) add_test(NAME WriterTest COMMAND WriterTest) endif() diff --git a/test/blackbox/ImageLoader.cpp b/test/blackbox/ImageLoader.cpp index 2375c978d2..51e0066886 100644 --- a/test/blackbox/ImageLoader.cpp +++ b/test/blackbox/ImageLoader.cpp @@ -18,17 +18,14 @@ #include "ImageLoader.h" #include "BinaryBitmap.h" -#include "GenericLuminanceSource.h" -#include "HybridBinarizer.h" #include "ReadBarcode.h" -#include "ThresholdBinarizer.h" #include #include #include #define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" +#include namespace ZXing::Test { @@ -42,10 +39,17 @@ class STBImage : public ImageView void load(const fs::path& imgPath) { int width, height, colors; - _memory.reset(stbi_load(imgPath.string().c_str(), &width, &height, &colors, 1)); + _memory.reset(stbi_load(imgPath.string().c_str(), &width, &height, &colors, 3)); if (_memory == nullptr) throw std::runtime_error("Failed to read image"); +#if 1 + auto* img = _memory.get(); + for (int i = 0; i < width * height; ++i ) + img[i] = RGBToLum(img[3 * i + 0], img[3 * i + 1], img[3 * i + 2]); ImageView::operator=({_memory.get(), width, height, ImageFormat::Lum}); +#else + ImageView::operator=({_memory.get(), width, height, ImageFormat::RGB}); +#endif } operator bool() const { return _data; } diff --git a/test/blackbox/TestReaderMain.cpp b/test/blackbox/TestReaderMain.cpp index 4efc7b566e..cabc70267f 100644 --- a/test/blackbox/TestReaderMain.cpp +++ b/test/blackbox/TestReaderMain.cpp @@ -16,7 +16,6 @@ */ #include "BlackboxTestRunner.h" -#include "ByteArray.h" #include "ImageLoader.h" #include "TextUtfEncoding.h" #include "ZXContainerAlgorithms.h" diff --git a/test/blackbox/TestWriterMain.cpp b/test/blackbox/TestWriterMain.cpp index 041f7c2645..8dfc63e841 100644 --- a/test/blackbox/TestWriterMain.cpp +++ b/test/blackbox/TestWriterMain.cpp @@ -16,7 +16,6 @@ */ #include "BitMatrix.h" -#include "ByteMatrix.h" #include "MultiFormatWriter.h" #include @@ -25,7 +24,7 @@ using namespace ZXing; using namespace std::literals; #define STB_IMAGE_WRITE_IMPLEMENTATION -#include "stb_image_write.h" +#include void savePng(const BitMatrix& matrix, BarcodeFormat format) { diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt index 73ab9f71d0..a416ea791e 100644 --- a/test/fuzz/CMakeLists.txt +++ b/test/fuzz/CMakeLists.txt @@ -9,10 +9,12 @@ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -march=native -fsanitize=address,fuz set (BUILD_WRITERS ON) set (BUILD_READERS ON) +add_definitions (-DZXING_BUILD_FOR_TEST) add_subdirectory (${CMAKE_CURRENT_SOURCE_DIR}/../../core ${CMAKE_BINARY_DIR}/ZXing) set (TESTS DBEDecoder + DMDecoder DMEncoder ODDecoders ) diff --git a/test/fuzz/fuzzDBEDecoder.cpp b/test/fuzz/fuzzDBEDecoder.cpp index e9d624c93c..4a14761611 100644 --- a/test/fuzz/fuzzDBEDecoder.cpp +++ b/test/fuzz/fuzzDBEDecoder.cpp @@ -5,7 +5,6 @@ #include "oned/rss/ODRSSExpandedBinaryDecoder.h" using namespace ZXing; -using namespace ZXing::OneD::RSS; extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { @@ -16,7 +15,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) for (size_t i = 0; i < size; ++i) bits.appendBits(data[i], 8); - ExpandedBinaryDecoder::Decode(bits); + OneD::DataBar::DecodeExpandedBits(bits); return 0; } diff --git a/test/fuzz/fuzzDMDecoder.cpp b/test/fuzz/fuzzDMDecoder.cpp new file mode 100644 index 0000000000..455679c70c --- /dev/null +++ b/test/fuzz/fuzzDMDecoder.cpp @@ -0,0 +1,26 @@ +#include +#include + +#include "ByteArray.h" +#include "DecoderResult.h" + +using namespace ZXing; + +namespace ZXing::DataMatrix::DecodedBitStreamParser { +DecoderResult Decode(ByteArray&& bytes, const std::string& characterSet); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + if (size < 2) + return 0; + + ByteArray ba; + ba.insert(ba.begin(), data, data + size); + try { + DataMatrix::DecodedBitStreamParser::Decode(std::move(ba), ""); + } catch (...) { + } + + return 0; +} diff --git a/test/fuzz/fuzzODDecoders.cpp b/test/fuzz/fuzzODDecoders.cpp index 4426c1317f..df39599041 100644 --- a/test/fuzz/fuzzODDecoders.cpp +++ b/test/fuzz/fuzzODDecoders.cpp @@ -46,8 +46,15 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) } row.back() = 0; - for (size_t r = 0; r < readers.size(); ++r) - readers[r]->decodePattern(0, row, decodingState[r]); + for (size_t r = 0; r < readers.size(); ++r) { + PatternView next(row); + while (next.isValid()) { + readers[r]->decodePattern(0, next, decodingState[r]); + // make sure we make progress and we start the next try on a bar + next.shift(2 - (next.index() % 2)); + next.extend(); + } + } return 0; } diff --git a/test/samples/aztec-1/7.result.txt b/test/samples/aztec-1/7.result.txt new file mode 100644 index 0000000000..a431263155 --- /dev/null +++ b/test/samples/aztec-1/7.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]z0 diff --git a/test/samples/aztec-1/Z1-sequence4of7.result.txt b/test/samples/aztec-1/Z1-sequence4of7.result.txt new file mode 100644 index 0000000000..7ca53f9eeb --- /dev/null +++ b/test/samples/aztec-1/Z1-sequence4of7.result.txt @@ -0,0 +1,4 @@ +symbologyIdentifier=]z6 +sequenceSize=7 +sequenceIndex=3 +sequenceId=Z1.txt diff --git a/test/samples/aztec-1/gs1-figure-4.15.1-2-31x31.png b/test/samples/aztec-1/gs1-figure-4.15.1-2-31x31.png new file mode 100644 index 0000000000..a6d8e064bf Binary files /dev/null and b/test/samples/aztec-1/gs1-figure-4.15.1-2-31x31.png differ diff --git a/test/samples/aztec-1/gs1-figure-4.15.1-2-31x31.result.txt b/test/samples/aztec-1/gs1-figure-4.15.1-2-31x31.result.txt new file mode 100644 index 0000000000..dfe1e78971 --- /dev/null +++ b/test/samples/aztec-1/gs1-figure-4.15.1-2-31x31.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]z1 diff --git a/test/samples/aztec-1/gs1-figure-4.15.1-2-31x31.txt b/test/samples/aztec-1/gs1-figure-4.15.1-2-31x31.txt new file mode 100644 index 0000000000..b2c7f1364d --- /dev/null +++ b/test/samples/aztec-1/gs1-figure-4.15.1-2-31x31.txt @@ -0,0 +1 @@ +01095040000591012112345678p901101234567p171411208200http://www.gs1.org/demo/ \ No newline at end of file diff --git a/test/samples/aztec-1/readerinit-compact-15x15.result.txt b/test/samples/aztec-1/readerinit-compact-15x15.result.txt new file mode 100644 index 0000000000..d85407b994 --- /dev/null +++ b/test/samples/aztec-1/readerinit-compact-15x15.result.txt @@ -0,0 +1,2 @@ +symbologyIdentifier=]z0 +readerInit=true diff --git a/test/samples/aztec-1/readerinit-full-19x19.result.txt b/test/samples/aztec-1/readerinit-full-19x19.result.txt new file mode 100644 index 0000000000..d85407b994 --- /dev/null +++ b/test/samples/aztec-1/readerinit-full-19x19.result.txt @@ -0,0 +1,2 @@ +symbologyIdentifier=]z0 +readerInit=true diff --git a/test/samples/codabar-1/01.result.txt b/test/samples/codabar-1/01.result.txt new file mode 100644 index 0000000000..e941e9e1aa --- /dev/null +++ b/test/samples/codabar-1/01.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]F0 diff --git a/test/samples/code128-1/1.result.txt b/test/samples/code128-1/1.result.txt new file mode 100644 index 0000000000..90af6c5d09 --- /dev/null +++ b/test/samples/code128-1/1.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]C1 diff --git a/test/samples/code128-1/2.result.txt b/test/samples/code128-1/2.result.txt new file mode 100644 index 0000000000..ecb6da6d8b --- /dev/null +++ b/test/samples/code128-1/2.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]C0 diff --git a/test/samples/code128-1/3.result.txt b/test/samples/code128-1/3.result.txt new file mode 100644 index 0000000000..ecb6da6d8b --- /dev/null +++ b/test/samples/code128-1/3.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]C0 diff --git a/test/samples/code128-1/4.result.txt b/test/samples/code128-1/4.result.txt new file mode 100644 index 0000000000..90af6c5d09 --- /dev/null +++ b/test/samples/code128-1/4.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]C1 diff --git a/test/samples/code128-1/5.result.txt b/test/samples/code128-1/5.result.txt new file mode 100644 index 0000000000..90af6c5d09 --- /dev/null +++ b/test/samples/code128-1/5.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]C1 diff --git a/test/samples/code128-1/6.result.txt b/test/samples/code128-1/6.result.txt new file mode 100644 index 0000000000..ecb6da6d8b --- /dev/null +++ b/test/samples/code128-1/6.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]C0 diff --git a/test/samples/code39-1/1.result.txt b/test/samples/code39-1/1.result.txt new file mode 100644 index 0000000000..0655e0d7ce --- /dev/null +++ b/test/samples/code39-1/1.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]A0 diff --git a/test/samples/code39-2/1.result.txt b/test/samples/code39-2/1.result.txt new file mode 100644 index 0000000000..162e215ee3 --- /dev/null +++ b/test/samples/code39-2/1.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]A4 diff --git a/test/samples/code93-1/1.result.txt b/test/samples/code93-1/1.result.txt new file mode 100644 index 0000000000..5615ece03a --- /dev/null +++ b/test/samples/code93-1/1.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]G0 diff --git a/test/samples/datamatrix-1/0123456789.result.txt b/test/samples/datamatrix-1/0123456789.result.txt new file mode 100644 index 0000000000..771cae78da --- /dev/null +++ b/test/samples/datamatrix-1/0123456789.result.txt @@ -0,0 +1,2 @@ +symbologyIdentifier=]d1 +isMirrored=false diff --git a/test/samples/datamatrix-1/abcd-36x12-mirrored.result.txt b/test/samples/datamatrix-1/abcd-36x12-mirrored.result.txt new file mode 100644 index 0000000000..f32b8d9e10 --- /dev/null +++ b/test/samples/datamatrix-1/abcd-36x12-mirrored.result.txt @@ -0,0 +1 @@ +isMirrored=true diff --git a/test/samples/datamatrix-1/gs1-figure-4.15.1-2-32x32.png b/test/samples/datamatrix-1/gs1-figure-4.15.1-2-32x32.png new file mode 100644 index 0000000000..b799569503 Binary files /dev/null and b/test/samples/datamatrix-1/gs1-figure-4.15.1-2-32x32.png differ diff --git a/test/samples/datamatrix-1/gs1-figure-4.15.1-2-32x32.result.txt b/test/samples/datamatrix-1/gs1-figure-4.15.1-2-32x32.result.txt new file mode 100644 index 0000000000..c31c096dbb --- /dev/null +++ b/test/samples/datamatrix-1/gs1-figure-4.15.1-2-32x32.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]d2 diff --git a/test/samples/datamatrix-1/gs1-figure-4.15.1-2-32x32.txt b/test/samples/datamatrix-1/gs1-figure-4.15.1-2-32x32.txt new file mode 100644 index 0000000000..b2c7f1364d --- /dev/null +++ b/test/samples/datamatrix-1/gs1-figure-4.15.1-2-32x32.txt @@ -0,0 +1 @@ +01095040000591012112345678p901101234567p171411208200http://www.gs1.org/demo/ \ No newline at end of file diff --git a/test/samples/datamatrix-1/readerinit.png b/test/samples/datamatrix-1/readerinit.png index 8f776867c1..eeb3823bb1 100644 Binary files a/test/samples/datamatrix-1/readerinit.png and b/test/samples/datamatrix-1/readerinit.png differ diff --git a/test/samples/datamatrix-1/readerinit.result.txt b/test/samples/datamatrix-1/readerinit.result.txt new file mode 100644 index 0000000000..b3503a6a1b --- /dev/null +++ b/test/samples/datamatrix-1/readerinit.result.txt @@ -0,0 +1,2 @@ +symbologyIdentifier=]d1 +readerInit=true diff --git a/test/samples/datamatrix-4/abcd-36x20.result.txt b/test/samples/datamatrix-4/abcd-36x20.result.txt new file mode 100644 index 0000000000..3f647f5777 --- /dev/null +++ b/test/samples/datamatrix-4/abcd-36x20.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]d7 diff --git a/test/samples/datamatrix-4/gs1-figure-4.15.1-2-44x20.png b/test/samples/datamatrix-4/gs1-figure-4.15.1-2-44x20.png new file mode 100644 index 0000000000..9323d9e562 Binary files /dev/null and b/test/samples/datamatrix-4/gs1-figure-4.15.1-2-44x20.png differ diff --git a/test/samples/datamatrix-4/gs1-figure-4.15.1-2-44x20.result.txt b/test/samples/datamatrix-4/gs1-figure-4.15.1-2-44x20.result.txt new file mode 100644 index 0000000000..f665b7a0d5 --- /dev/null +++ b/test/samples/datamatrix-4/gs1-figure-4.15.1-2-44x20.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]d8 diff --git a/test/samples/datamatrix-4/gs1-figure-4.15.1-2-44x20.txt b/test/samples/datamatrix-4/gs1-figure-4.15.1-2-44x20.txt new file mode 100644 index 0000000000..b2c7f1364d --- /dev/null +++ b/test/samples/datamatrix-4/gs1-figure-4.15.1-2-44x20.txt @@ -0,0 +1 @@ +01095040000591012112345678p901101234567p171411208200http://www.gs1.org/demo/ \ No newline at end of file diff --git a/test/samples/ean13-1/1.result.txt b/test/samples/ean13-1/1.result.txt new file mode 100644 index 0000000000..602e892842 --- /dev/null +++ b/test/samples/ean13-1/1.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]E0 diff --git a/test/samples/ean13-extension-1/1.result.txt b/test/samples/ean13-extension-1/1.result.txt new file mode 100644 index 0000000000..98a472dd1d --- /dev/null +++ b/test/samples/ean13-extension-1/1.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]E3 diff --git a/test/samples/ean8-1/1.result.txt b/test/samples/ean8-1/1.result.txt new file mode 100644 index 0000000000..9c6d49446e --- /dev/null +++ b/test/samples/ean8-1/1.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]E4 diff --git a/test/samples/falsepositives-1/#245.png b/test/samples/falsepositives-1/#245.png new file mode 100644 index 0000000000..c09a2f34ca Binary files /dev/null and b/test/samples/falsepositives-1/#245.png differ diff --git a/test/samples/itf-1/#220.png b/test/samples/itf-1/#220.png new file mode 100644 index 0000000000..67e8eda6ea Binary files /dev/null and b/test/samples/itf-1/#220.png differ diff --git a/test/samples/itf-1/#220.txt b/test/samples/itf-1/#220.txt new file mode 100644 index 0000000000..025bac2483 --- /dev/null +++ b/test/samples/itf-1/#220.txt @@ -0,0 +1 @@ +0000091897 \ No newline at end of file diff --git a/test/samples/itf-1/1.result.txt b/test/samples/itf-1/1.result.txt new file mode 100644 index 0000000000..f7de2b88cb --- /dev/null +++ b/test/samples/itf-1/1.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]I1 diff --git a/test/samples/itf-1/2.result.txt b/test/samples/itf-1/2.result.txt new file mode 100644 index 0000000000..f7de2b88cb --- /dev/null +++ b/test/samples/itf-1/2.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]I1 diff --git a/test/samples/itf-1/3.result.txt b/test/samples/itf-1/3.result.txt new file mode 100644 index 0000000000..fc3f2e68c2 --- /dev/null +++ b/test/samples/itf-1/3.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]I0 diff --git a/test/samples/itf-1/5.result.txt b/test/samples/itf-1/5.result.txt new file mode 100644 index 0000000000..fc3f2e68c2 --- /dev/null +++ b/test/samples/itf-1/5.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]I0 diff --git a/test/samples/maxicode-1/MODE2.result.txt b/test/samples/maxicode-1/MODE2.result.txt new file mode 100644 index 0000000000..bc7855ebd5 --- /dev/null +++ b/test/samples/maxicode-1/MODE2.result.txt @@ -0,0 +1,2 @@ +symbologyIdentifier=]U1 +ecLevel=2 diff --git a/test/samples/maxicode-1/Wikipedia.result.txt b/test/samples/maxicode-1/Wikipedia.result.txt new file mode 100644 index 0000000000..871838d832 --- /dev/null +++ b/test/samples/maxicode-1/Wikipedia.result.txt @@ -0,0 +1,2 @@ +symbologyIdentifier=]U0 +ecLevel=4 diff --git a/test/samples/maxicode-1/mode4-mixed-ecis.result.txt b/test/samples/maxicode-1/mode4-mixed-ecis.result.txt new file mode 100644 index 0000000000..871838d832 --- /dev/null +++ b/test/samples/maxicode-1/mode4-mixed-ecis.result.txt @@ -0,0 +1,2 @@ +symbologyIdentifier=]U0 +ecLevel=4 diff --git a/test/samples/maxicode-1/mode5-sequence2of3.result.txt b/test/samples/maxicode-1/mode5-sequence2of3.result.txt new file mode 100644 index 0000000000..fb7308103b --- /dev/null +++ b/test/samples/maxicode-1/mode5-sequence2of3.result.txt @@ -0,0 +1,5 @@ +symbologyIdentifier=]U0 +ecLevel=5 +sequenceSize=3 +sequenceIndex=1 +sequenceId= diff --git a/test/samples/multi-1/3xean-8.png b/test/samples/multi-1/3xean-8.png new file mode 100644 index 0000000000..77d2fd730f Binary files /dev/null and b/test/samples/multi-1/3xean-8.png differ diff --git a/test/samples/qrcode-2/#253.jpg b/test/samples/qrcode-2/#253.jpg new file mode 100644 index 0000000000..7bc934fcbe Binary files /dev/null and b/test/samples/qrcode-2/#253.jpg differ diff --git a/test/samples/qrcode-2/#253.txt b/test/samples/qrcode-2/#253.txt new file mode 100644 index 0000000000..066646f2c0 --- /dev/null +++ b/test/samples/qrcode-2/#253.txt @@ -0,0 +1 @@ +http://cli.im \ No newline at end of file diff --git a/test/samples/qrcode-2/#258.jpg b/test/samples/qrcode-2/#258.jpg new file mode 100644 index 0000000000..0aa8fa91a6 Binary files /dev/null and b/test/samples/qrcode-2/#258.jpg differ diff --git a/test/samples/qrcode-2/#258.txt b/test/samples/qrcode-2/#258.txt new file mode 100644 index 0000000000..ff0b6a83d0 --- /dev/null +++ b/test/samples/qrcode-2/#258.txt @@ -0,0 +1 @@ +https://vt.tiktok.com/ZGJUXpy5e/ \ No newline at end of file diff --git a/test/samples/qrcode-2/1.result.txt b/test/samples/qrcode-2/1.result.txt new file mode 100644 index 0000000000..ee6eefe619 --- /dev/null +++ b/test/samples/qrcode-2/1.result.txt @@ -0,0 +1,2 @@ +symbologyIdentifier=]Q1 +ecLevel=M diff --git a/test/samples/qrcode-2/estimate-tilt.jpg b/test/samples/qrcode-2/estimate-tilt.jpg new file mode 100644 index 0000000000..e30b300588 Binary files /dev/null and b/test/samples/qrcode-2/estimate-tilt.jpg differ diff --git a/test/samples/qrcode-2/estimate-tilt.txt b/test/samples/qrcode-2/estimate-tilt.txt new file mode 100644 index 0000000000..a837ad36a0 --- /dev/null +++ b/test/samples/qrcode-2/estimate-tilt.txt @@ -0,0 +1 @@ +VERSION 1 6CM \ No newline at end of file diff --git a/test/samples/qrcode-2/fix-finderpattern-order.jpg b/test/samples/qrcode-2/fix-finderpattern-order.jpg new file mode 100644 index 0000000000..0250a0e5be Binary files /dev/null and b/test/samples/qrcode-2/fix-finderpattern-order.jpg differ diff --git a/test/samples/qrcode-2/fix-finderpattern-order.txt b/test/samples/qrcode-2/fix-finderpattern-order.txt new file mode 100644 index 0000000000..97268c5c8a --- /dev/null +++ b/test/samples/qrcode-2/fix-finderpattern-order.txt @@ -0,0 +1 @@ +VERSION 2 8CM \ No newline at end of file diff --git a/test/samples/qrcode-2/fix-traceline.jpg b/test/samples/qrcode-2/fix-traceline.jpg new file mode 100644 index 0000000000..4bea7b8a99 Binary files /dev/null and b/test/samples/qrcode-2/fix-traceline.jpg differ diff --git a/test/samples/qrcode-2/fix-traceline.txt b/test/samples/qrcode-2/fix-traceline.txt new file mode 100644 index 0000000000..97268c5c8a --- /dev/null +++ b/test/samples/qrcode-2/fix-traceline.txt @@ -0,0 +1 @@ +VERSION 2 8CM \ No newline at end of file diff --git a/test/samples/qrcode-2/gs1-figure-4.15.1-2.png b/test/samples/qrcode-2/gs1-figure-4.15.1-2.png new file mode 100644 index 0000000000..d5d83a0393 Binary files /dev/null and b/test/samples/qrcode-2/gs1-figure-4.15.1-2.png differ diff --git a/test/samples/qrcode-2/gs1-figure-4.15.1-2.result.txt b/test/samples/qrcode-2/gs1-figure-4.15.1-2.result.txt new file mode 100644 index 0000000000..06b3826bfc --- /dev/null +++ b/test/samples/qrcode-2/gs1-figure-4.15.1-2.result.txt @@ -0,0 +1,2 @@ +symbologyIdentifier=]Q3 +ecLevel=M diff --git a/test/samples/qrcode-2/gs1-figure-4.15.1-2.txt b/test/samples/qrcode-2/gs1-figure-4.15.1-2.txt new file mode 100644 index 0000000000..b2c7f1364d --- /dev/null +++ b/test/samples/qrcode-2/gs1-figure-4.15.1-2.txt @@ -0,0 +1 @@ +01095040000591012112345678p901101234567p171411208200http://www.gs1.org/demo/ \ No newline at end of file diff --git a/test/samples/qrcode-7/01.result.txt b/test/samples/qrcode-7/01.result.txt new file mode 100644 index 0000000000..7867b23fec --- /dev/null +++ b/test/samples/qrcode-7/01.result.txt @@ -0,0 +1,6 @@ +symbologyIdentifier=]Q1 +# ecLevel not set when "runStructuredAppendTest" +sequenceSize=4 +# sequenceIndex set to first when "runStructuredAppendTest" +sequenceIndex=0 +sequenceId=95 diff --git a/test/samples/rss14-1/1.result.txt b/test/samples/rss14-1/1.result.txt new file mode 100644 index 0000000000..76dbad08fe --- /dev/null +++ b/test/samples/rss14-1/1.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]e0 diff --git a/test/samples/rssexpanded-1/1.result.txt b/test/samples/rssexpanded-1/1.result.txt new file mode 100644 index 0000000000..76dbad08fe --- /dev/null +++ b/test/samples/rssexpanded-1/1.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]e0 diff --git a/test/samples/rssexpandedstacked-1/1.result.txt b/test/samples/rssexpandedstacked-1/1.result.txt new file mode 100644 index 0000000000..76dbad08fe --- /dev/null +++ b/test/samples/rssexpandedstacked-1/1.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]e0 diff --git a/test/samples/upca-1/2.result.txt b/test/samples/upca-1/2.result.txt new file mode 100644 index 0000000000..602e892842 --- /dev/null +++ b/test/samples/upca-1/2.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]E0 diff --git a/test/samples/upca-extension-1/8.result.txt b/test/samples/upca-extension-1/8.result.txt new file mode 100644 index 0000000000..98a472dd1d --- /dev/null +++ b/test/samples/upca-extension-1/8.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]E3 diff --git a/test/samples/upce-1/1.result.txt b/test/samples/upce-1/1.result.txt new file mode 100644 index 0000000000..602e892842 --- /dev/null +++ b/test/samples/upce-1/1.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]E0 diff --git a/test/unit/BitArrayUtility.cpp b/test/unit/BitArrayUtility.cpp index 45a61bd5f5..a5d67f6586 100644 --- a/test/unit/BitArrayUtility.cpp +++ b/test/unit/BitArrayUtility.cpp @@ -37,10 +37,8 @@ std::string ToString(const BitArray& arr, char one, char zero) BitArray ParseBitArray(std::string_view str, char one) { BitArray result(Size(str)); - for (int i = 0; i < Size(str); ++i) { - if (str[i] == one) - result.set(i); - } + for (int i = 0; i < Size(str); ++i) + result.set(i, str[i] == one); return result; } diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 00e51f260a..1c33901a39 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -1,17 +1,9 @@ -cmake_minimum_required(VERSION 3.14) +zxing_add_package(GTest googletest https://github.com/google/googletest.git release-1.11.0) -# Download and unpack googletest at configure time -include(FetchContent) -#set(FETCHCONTENT_QUIET Off) -FetchContent_Declare (googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG v1.10.x) -# Prevent overriding the parent project's compiler/linker settings on Windows -set (gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable (googletest) - -# don't install gtest stuff on "make install" -set (INSTALL_GTEST OFF CACHE BOOL "" FORCE) +if (GTest_POPULATED) + # don't install gtest stuff on "make install" + set (INSTALL_GTEST OFF CACHE BOOL "" FORCE) +endif() # Our executable add_executable (UnitTest @@ -21,9 +13,11 @@ add_executable (UnitTest PseudoRandom.h BitHacksTest.cpp CharacterSetECITest.cpp + GTINTest.cpp ReedSolomonTest.cpp TextDecoderTest.cpp TextUtfEncodingTest.cpp + ThresholdBinarizerTest.cpp aztec/AZDetectorTest.cpp aztec/AZDecoderTest.cpp aztec/AZEncoderTest.cpp @@ -38,6 +32,7 @@ add_executable (UnitTest maxicode/MCDecoderTest.cpp oned/ODCodaBarWriterTest.cpp oned/ODCode39ExtendedModeTest.cpp + oned/ODCode39ReaderTest.cpp oned/ODCode39WriterTest.cpp oned/ODCode93ReaderTest.cpp oned/ODCode93WriterTest.cpp @@ -49,6 +44,7 @@ add_executable (UnitTest oned/ODUPCAWriterTest.cpp oned/ODUPCEWriterTest.cpp oned/rss/ODRSSExpandedBinaryDecoderTest.cpp + oned/rss/ODRSSFieldParserTest.cpp qrcode/QRDataMaskTest.cpp qrcode/QRDecodedBitStreamParserTest.cpp qrcode/QREncoderTest.cpp @@ -65,6 +61,6 @@ add_executable (UnitTest target_include_directories (UnitTest PRIVATE .) -target_link_libraries (UnitTest ZXing::ZXing gtest_main gmock) +target_link_libraries (UnitTest ZXing::ZXing GTest::gtest_main GTest::gmock) add_test(NAME UnitTest COMMAND UnitTest) diff --git a/test/unit/GTINTest.cpp b/test/unit/GTINTest.cpp new file mode 100644 index 0000000000..ddaf02db2b --- /dev/null +++ b/test/unit/GTINTest.cpp @@ -0,0 +1,223 @@ +/* +* Copyright 2022 gitlost +* +* 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. +*/ + +#include "GTIN.h" + +#include "Result.h" +#include "oned/ODUPCEANCommon.h" + +#include "gtest/gtest.h" + +using namespace ZXing; +using namespace ZXing::GTIN; + +TEST(GTINTest, CountryIdentifierEAN13) +{ + // From test/samples/ean13-*/ + EXPECT_EQ(LookupCountryIdentifier("8413000065504"), "ES"); + EXPECT_EQ(LookupCountryIdentifier("8413000065504 12"), "ES"); + EXPECT_EQ(LookupCountryIdentifier("8413000065504 51299"), "ES"); + EXPECT_EQ(LookupCountryIdentifier("5449000039231"), "BE/LU"); + EXPECT_TRUE(LookupCountryIdentifier("9788430532674").empty()); // Bookland (ISBN) + EXPECT_EQ(LookupCountryIdentifier("8480017507990"), "ES"); + EXPECT_EQ(LookupCountryIdentifier("3166298099809"), "FR"); + EXPECT_EQ(LookupCountryIdentifier("5201815331227"), "GR"); + EXPECT_EQ(LookupCountryIdentifier("3560070169443"), "FR"); + EXPECT_EQ(LookupCountryIdentifier("4045787034318"), "DE"); + EXPECT_EQ(LookupCountryIdentifier("3086126100326"), "FR"); + EXPECT_EQ(LookupCountryIdentifier("4820024790635"), "UA"); + EXPECT_EQ(LookupCountryIdentifier("7622200008018"), "CH"); + EXPECT_EQ(LookupCountryIdentifier("5603667020517"), "PT"); + EXPECT_EQ(LookupCountryIdentifier("5709262942503"), "DK"); + EXPECT_EQ(LookupCountryIdentifier("4901780188352"), "JP"); + EXPECT_EQ(LookupCountryIdentifier("4007817327098"), "DE"); + EXPECT_EQ(LookupCountryIdentifier("5025121072311"), "GB"); + EXPECT_EQ(LookupCountryIdentifier("5025121072311 12"), "GB"); + EXPECT_EQ(LookupCountryIdentifier("5025121072311 51299"), "GB"); + EXPECT_EQ(LookupCountryIdentifier("5030159003930"), "GB"); + EXPECT_EQ(LookupCountryIdentifier("5000213002834"), "GB"); + EXPECT_TRUE(LookupCountryIdentifier("1920081045006").empty()); // 140-199 unassigned + EXPECT_TRUE(LookupCountryIdentifier("9780735200449 51299").empty()); // Bookland (ISBN) + + // Other + EXPECT_TRUE(LookupCountryIdentifier("0000000001465").empty()); // 0000000 Restricted Circulation Numbers + EXPECT_TRUE(LookupCountryIdentifier("0000000111461 12").empty()); + EXPECT_TRUE(LookupCountryIdentifier("0000001991469").empty()); // 0000001-0000099 unused to avoid GTIN-8 collision + EXPECT_TRUE(LookupCountryIdentifier("0000099991463").empty()); + EXPECT_EQ(LookupCountryIdentifier("0000102301463"), "US"); // 00001-00009 US + EXPECT_EQ(LookupCountryIdentifier("0000102301463 51299"), "US"); + EXPECT_EQ(LookupCountryIdentifier("0000902301465"), "US"); + EXPECT_EQ(LookupCountryIdentifier("0001602301465"), "US"); // 0001-0009 US + EXPECT_EQ(LookupCountryIdentifier("0009602301461 12"), "US"); + EXPECT_EQ(LookupCountryIdentifier("0016602301469"), "US/CA"); // 001-019 US/CA + EXPECT_EQ(LookupCountryIdentifier("0036602301467"), "US/CA"); + EXPECT_EQ(LookupCountryIdentifier("0196602301468 51299"), "US/CA"); + EXPECT_TRUE(LookupCountryIdentifier("0206602301464").empty()); // 020-029 Restricted Circulation Numbers + EXPECT_TRUE(LookupCountryIdentifier("0296602301465").empty()); + EXPECT_EQ(LookupCountryIdentifier("0306602301461"), "US"); // 030-039 US + EXPECT_EQ(LookupCountryIdentifier("0396602301462"), "US"); + EXPECT_TRUE(LookupCountryIdentifier("0406602301468").empty()); // 040-049 Restricted Circulation Numbers + EXPECT_TRUE(LookupCountryIdentifier("0496602301469").empty()); + EXPECT_TRUE(LookupCountryIdentifier("0506602301465").empty()); // 050-059 reserved for future use + EXPECT_TRUE(LookupCountryIdentifier("0596602301466").empty()); + EXPECT_EQ(LookupCountryIdentifier("0606602301462"), "US/CA"); // 060-099 US/CA + EXPECT_EQ(LookupCountryIdentifier("0996602301464"), "US/CA"); + EXPECT_EQ(LookupCountryIdentifier("1006602301469"), "US"); // 100-139 US + EXPECT_EQ(LookupCountryIdentifier("1396602301461"), "US"); + EXPECT_TRUE(LookupCountryIdentifier("1406602301467").empty()); // 140-199 unassigned + EXPECT_TRUE(LookupCountryIdentifier("1996602301463").empty()); + EXPECT_TRUE(LookupCountryIdentifier("2006602301468").empty()); // 200-299 Restricted Circulation Numbers + EXPECT_TRUE(LookupCountryIdentifier("2996602301462").empty()); + EXPECT_EQ(LookupCountryIdentifier("9586602301468"), "MO"); + EXPECT_EQ(LookupCountryIdentifier("9586602301468 12"), "MO"); + EXPECT_EQ(LookupCountryIdentifier("9586602301468 51299"), "MO"); + + // Additions/updates + EXPECT_EQ(LookupCountryIdentifier("3890102301467"), "ME"); + //EXPECT_EQ(LookupCountryIdentifier("3900102301463"), "XK"); // Kosovo according to Wikipedia - awaiting GS1 confirmation + EXPECT_EQ(LookupCountryIdentifier("4700102301468"), "KG"); + EXPECT_EQ(LookupCountryIdentifier("4830102301462"), "TM"); + EXPECT_EQ(LookupCountryIdentifier("4880102301467"), "TJ"); + EXPECT_EQ(LookupCountryIdentifier("5210102301461"), "GR"); + EXPECT_EQ(LookupCountryIdentifier("5300102301469"), "AL"); + EXPECT_EQ(LookupCountryIdentifier("6040102301463"), "SN"); + EXPECT_EQ(LookupCountryIdentifier("6150102301469"), "NG"); + EXPECT_EQ(LookupCountryIdentifier("6170102301467"), "CM"); + EXPECT_EQ(LookupCountryIdentifier("6200102301461"), "TZ"); + EXPECT_EQ(LookupCountryIdentifier("6230102301468"), "BN"); + EXPECT_EQ(LookupCountryIdentifier("6300102301468"), "QA"); + EXPECT_EQ(LookupCountryIdentifier("6310102301467"), "NA"); + EXPECT_EQ(LookupCountryIdentifier("6990102301461"), "CN"); + EXPECT_EQ(LookupCountryIdentifier("7710102301464"), "CO"); + EXPECT_EQ(LookupCountryIdentifier("7780102301467"), "AR"); + EXPECT_TRUE(LookupCountryIdentifier("7850102301467").empty()); + EXPECT_EQ(LookupCountryIdentifier("8600102301467"), "RS"); + EXPECT_EQ(LookupCountryIdentifier("8830102301468"), "MM"); + EXPECT_EQ(LookupCountryIdentifier("8840102301467"), "KH"); + EXPECT_EQ(LookupCountryIdentifier("9400102301462"), "NZ"); +} + +TEST(GTINTest, CountryIdentifierUPCA) +{ + // From test/samples/upca-*/ + EXPECT_EQ(LookupCountryIdentifier("036602301467"), "US/CA"); // 001-019 US/CA + EXPECT_EQ(LookupCountryIdentifier("036602301467 12"), "US/CA"); // 001-019 US/CA + EXPECT_EQ(LookupCountryIdentifier("036602301467 51299"), "US/CA"); // 001-019 US/CA + EXPECT_EQ(LookupCountryIdentifier("070097025088"), "US/CA"); + EXPECT_EQ(LookupCountryIdentifier("781735802045"), "US/CA"); // 060-099 US/CA + EXPECT_TRUE(LookupCountryIdentifier("456314319671").empty()); // 040-049 Restricted Circulation Numbers + EXPECT_TRUE(LookupCountryIdentifier("434704791429").empty()); + EXPECT_EQ(LookupCountryIdentifier("752919460009"), "US/CA"); // 060-099 US/CA + EXPECT_EQ(LookupCountryIdentifier("606949762520"), "US/CA"); + EXPECT_EQ(LookupCountryIdentifier("890444000335"), "US/CA"); + EXPECT_EQ(LookupCountryIdentifier("181497000879"), "US/CA"); // 001-019 US/CA + EXPECT_EQ(LookupCountryIdentifier("012546619592"), "US/CA"); + EXPECT_EQ(LookupCountryIdentifier("854818000116"), "US/CA"); // 060-099 US/CA + EXPECT_EQ(LookupCountryIdentifier("312547701310"), "US"); // 030-039 US + EXPECT_EQ(LookupCountryIdentifier("071831007995 19868"), "US/CA"); // 001-019 US/CA + EXPECT_EQ(LookupCountryIdentifier("027011006951 02601"), "US/CA"); + EXPECT_EQ(LookupCountryIdentifier("024543136538 00"), "US/CA"); + + // Other + EXPECT_TRUE(LookupCountryIdentifier("000000001465").empty()); // 0000000 Restricted Circulation Numbers + EXPECT_TRUE(LookupCountryIdentifier("000000111461 12").empty()); + EXPECT_TRUE(LookupCountryIdentifier("000001991468").empty()); // 0000001-0000099 unused to avoid GTIN-8 collision + EXPECT_TRUE(LookupCountryIdentifier("000099991463").empty()); + EXPECT_EQ(LookupCountryIdentifier("000102301463"), "US"); // 00001-00009 US + EXPECT_EQ(LookupCountryIdentifier("000102301463 51299"), "US"); + EXPECT_EQ(LookupCountryIdentifier("000902301465"), "US"); + EXPECT_EQ(LookupCountryIdentifier("001602301465"), "US"); // 0001-0009 US + EXPECT_EQ(LookupCountryIdentifier("009602301461 12"), "US"); + EXPECT_EQ(LookupCountryIdentifier("016602301469"), "US/CA"); // 001-019 US/CA + EXPECT_EQ(LookupCountryIdentifier("036602301467"), "US/CA"); + EXPECT_EQ(LookupCountryIdentifier("196602301468 51299"), "US/CA"); + EXPECT_TRUE(LookupCountryIdentifier("206602301464").empty()); // 020-029 Restricted Circulation Numbers + EXPECT_TRUE(LookupCountryIdentifier("296602301465").empty()); + EXPECT_EQ(LookupCountryIdentifier("306602301461"), "US"); // 030-039 US + EXPECT_EQ(LookupCountryIdentifier("396602301462"), "US"); + EXPECT_TRUE(LookupCountryIdentifier("406602301468").empty()); // 040-049 Restricted Circulation Numbers + EXPECT_TRUE(LookupCountryIdentifier("496602301469").empty()); + EXPECT_TRUE(LookupCountryIdentifier("506602301465").empty()); // 050-059 reserved for future use + EXPECT_TRUE(LookupCountryIdentifier("596602301466").empty()); + EXPECT_EQ(LookupCountryIdentifier("606602301462"), "US/CA"); // 060-099 US/CA + EXPECT_EQ(LookupCountryIdentifier("996602301464"), "US/CA"); +} + +TEST(GTINTest, CountryIdentifierUPCE) +{ + // From test/samples/upce-*/ + EXPECT_EQ(LookupCountryIdentifier("01234565"), "US/CA"); // 001-019 US/CA + EXPECT_EQ(LookupCountryIdentifier("01234565", BarcodeFormat::UPCE), "US/CA"); // 001-019 US/CA + EXPECT_EQ(LookupCountryIdentifier("00123457"), "US"); // 0001-0009 US + EXPECT_EQ(LookupCountryIdentifier("00123457", BarcodeFormat::UPCE), "US"); // 0001-0009 US + EXPECT_EQ(LookupCountryIdentifier("05096893"), "US/CA"); // 001-019 US/CA + EXPECT_EQ(LookupCountryIdentifier("05096893", BarcodeFormat::UPCE), "US/CA"); // 001-019 US/CA + EXPECT_EQ(LookupCountryIdentifier("04963406 01"), "US/CA"); // 001-019 US/CA + EXPECT_EQ(LookupCountryIdentifier("04963406 01", BarcodeFormat::UPCE), "US/CA"); // 001-019 US/CA + + // Other + // 0000000, 0000001-0000099 and 00001-00009 not possible for UPC-E + EXPECT_EQ(LookupCountryIdentifier("00021357"), "US"); // 0001-0009 US + EXPECT_EQ(LookupCountryIdentifier("00021357 01"), "US"); + EXPECT_EQ(LookupCountryIdentifier("11621355"), "US/CA"); // 001-019 US/CA + EXPECT_TRUE(LookupCountryIdentifier("22221111").empty()); // 020-029 Restricted Circulation Numbers + EXPECT_EQ(LookupCountryIdentifier("31621358"), "US"); // 030-039 US/CA + EXPECT_TRUE(LookupCountryIdentifier("40621359").empty()); // 040-049 Restricted Circulation Numbers + EXPECT_TRUE(LookupCountryIdentifier("50621359").empty()); // 050-059 reserved for future use + EXPECT_EQ(LookupCountryIdentifier("61621358"), "US/CA"); // 060-099 US/CA + EXPECT_EQ(LookupCountryIdentifier("99621350"), "US/CA"); +} + +TEST(GTINTest, CountryIdentifierEAN8) +{ + auto format = BarcodeFormat::EAN8; // Require BarcodeFormat for EAN-8 to be distinguished from UPC-E + + // From test/samples/ean8-*/ + EXPECT_EQ(LookupCountryIdentifier("48512343", format), "AM"); + EXPECT_EQ(LookupCountryIdentifier("12345670", format), "US"); + EXPECT_TRUE(LookupCountryIdentifier("67678983", format).empty()); // 650-689 unassigned + EXPECT_EQ(LookupCountryIdentifier("80674313", format), "IT"); + EXPECT_EQ(LookupCountryIdentifier("59001270", format), "PL"); + EXPECT_EQ(LookupCountryIdentifier("50487066", format), "GB"); + EXPECT_TRUE(LookupCountryIdentifier("55123457", format).empty()); // 550-559 unassigned + EXPECT_TRUE(LookupCountryIdentifier("95012346", format).empty()); // 950 GS1 Global Office + + // Other (GS1 General Specifications 1.4.3 Figure 1.4.3-1 + EXPECT_TRUE(LookupCountryIdentifier("00045674", format).empty()); // 000-099 EAN-8 Restricted Circulation Numbers + EXPECT_TRUE(LookupCountryIdentifier("09945678", format).empty()); + EXPECT_EQ(LookupCountryIdentifier("10045671", format), "US"); // 100-139 US + EXPECT_EQ(LookupCountryIdentifier("13945671", format), "US"); + EXPECT_TRUE(LookupCountryIdentifier("14045677", format).empty()); // 140-199 unassigned + EXPECT_TRUE(LookupCountryIdentifier("19945675", format).empty()); + EXPECT_TRUE(LookupCountryIdentifier("20045678", format).empty()); // 200-299 Restricted Circulation Numbers + EXPECT_TRUE(LookupCountryIdentifier("29945672", format).empty()); + EXPECT_EQ(LookupCountryIdentifier("30045675", format), "FR"); + EXPECT_EQ(LookupCountryIdentifier("95845678", format), "MO"); + EXPECT_TRUE(LookupCountryIdentifier("97645672", format).empty()); // Unassigned + EXPECT_TRUE(LookupCountryIdentifier("97745679", format).empty()); // 977-999 Reserved for future use + EXPECT_TRUE(LookupCountryIdentifier("99945671", format).empty()); +} + +TEST(GTINTest, CountryIdentifierGTIN14) +{ + // From test/samples/itf-*/ + EXPECT_EQ(LookupCountryIdentifier("30712345000010"), "US/CA"); + EXPECT_EQ(LookupCountryIdentifier("00012345678905"), "US/CA"); + + // Other + EXPECT_TRUE(LookupCountryIdentifier("12345678901231").empty()); // 200-299 Restricted Circulation Numbers + EXPECT_EQ(LookupCountryIdentifier("13005678901233"), "FR"); +} diff --git a/test/unit/ThresholdBinarizerTest.cpp b/test/unit/ThresholdBinarizerTest.cpp new file mode 100644 index 0000000000..0a9e54f189 --- /dev/null +++ b/test/unit/ThresholdBinarizerTest.cpp @@ -0,0 +1,115 @@ +/* +* Copyright 2022 gitlost +* +* 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. +*/ + +#include "ThresholdBinarizer.h" +#include "oned/ODReader.h" + +#include "gtest/gtest.h" + +using namespace ZXing; + +// Helper to parse a 0/1 string into a BitMatrix +static BitMatrix ParseBitMatrix(const std::string& str, const int width) +{ + const int height = static_cast(str.length() / width); + + BitMatrix mat(width, height); + for (int y = 0; y < height; ++y) { + size_t offset = y * width; + for (int x = 0; x < width; ++x, offset++) { + if (str[offset] == '1') { + mat.set(x, y); + } + } + } + return mat; +} + +// Helper to convert a BitMatrix into a black/white ImageView +static ImageView getImageView(std::vector &buf, const BitMatrix &bits) +{ + buf.resize(bits.width() * bits.height()); + for (int r = 0; r < bits.height(); r++) { + const int k = r * bits.width(); + for (int c = 0; c < bits.width(); c++) { + buf[k + c] = bits.get(c, r) ? 0x00 : 0xFF; + } + } + return ImageView(buf.data(), bits.width(), bits.height(), ImageFormat::Lum); +} + +TEST(ThresholdBinarizerTest, PatternRowClear) +{ + std::string bitstream; + BitMatrix bits; + DecodeHints hints; + std::vector buf; + Result result(DecodeStatus::NotFound); + + // Test that ThresholdBinarizer::getPatternRow() clears row first (same as GlobalHistogramBinarizer) + // Following was failing due to OneD::DoDecode() accumulating bars in loop when using ThresholdBinarizer + + // DataBarExpanded Stacked with 11 rows (11 + 10 * 3 separators), 1 column (2 data segments) wide + bitstream = "01010101111011111110111111110000101100100000010100010" + "00001010000100000001000000001010010011011111101010000" + "00000101010101010101010101010101010101010101010100000" + "00001000001110100010100001010101001110000101100110000" + "10100111110001011101011110000000010001111010011000101" + "00001000001110100010100001010101001110000101100110000" + "00000101010101010101010101010101010101010101010100000" + "00000001110100100001010000001010010101100111000110000" + "01011110001011011110001111110000101010011000111000010" + "00000001110100100001010000001010010101100111000110000" + "00000101010101010101010101010101010101010101010100000" + "00000110001111101110100001010100001110001110111010000" + "10101001110000010001011110000001110001110001000100101" + "00000110001111101110100001010100001110001110111010000" + "00000101010101010101010101010101010101010101010100000" + "00001000011011000101010000101010010011100010001100000" + "01000111100100111010001111000000101100011101110010010" + "00001000011011000101010000101010010011100010001100000" + "00000101010101010101010101010101010101010101010100000" + "00001000110001110110100000000100001011110100001000000" + "10100111001110001001011111111001110100001011110111101" + "00001000110001110110100000000100001011110100001000000" + "00000101010101010101010101010101010101010101010100000" + "00000010011101111101010010101010010100110000100000000" + "01011101100010000010001100000000101011001111011110010" + "00000010011101111101010010101010010100110000100000000" + "00000101010101010101010101010101010101010101010100000" + "00000100010111101110100000101010001100000110011010000" + "10111011101000010001011111000000110011111001100101101" + "00000100010111101110100000101010001100000110011010000" + "00000101010101010101010101010101010101010101010100000" + "00000011000110001001000000010101010000011110011010000" + "01011100111001110110011111100000101111100001100101010" + "00000011000110001001000000010101010000011110011010000" + "00000101010101010101010101010101010101010101010100000" + "00001011100010001110100000000010001011111010001100000" + "10100100011101110001011111111100110100000101110011101" + "00001011100010001110100000000010001011111010001100000" + "00000101010101010101010101010101010101010101010100000" + "00001000111010000101000101010101010100001011000110000" + "01000111000101111010011000000000101011110100111000010"; + + bits = ParseBitMatrix(bitstream, 53 /*width*/); + hints.setFormats(BarcodeFormat::DataBarExpanded); + OneD::Reader reader(hints); + + result = reader.decode(ThresholdBinarizer(getImageView(buf, bits), 0x7F)); + EXPECT_TRUE(result.isValid()); + EXPECT_EQ(result.text(), L"(91)12345678901234567890123456789012345678901234567890123456789012345678"); +} diff --git a/test/unit/aztec/AZDecoderTest.cpp b/test/unit/aztec/AZDecoderTest.cpp index eaebd7711d..242348897c 100644 --- a/test/unit/aztec/AZDecoderTest.cpp +++ b/test/unit/aztec/AZDecoderTest.cpp @@ -16,18 +16,26 @@ */ #include "aztec/AZDecoder.h" +#include "BitArray.h" #include "BitMatrixIO.h" #include "DecodeStatus.h" #include "DecoderResult.h" #include "aztec/AZDetectorResult.h" #include "gtest/gtest.h" +#include #include namespace ZXing::Aztec { -std::wstring GetEncodedData(const std::vector& correctedBits, const std::string& characterSet, - StructuredAppendInfo& sai); +struct AztecData +{ + std::wstring text; + std::string symbologyIdentifier; + StructuredAppendInfo sai; +}; + +AztecData GetEncodedData(const BitArray& bits, const std::string& characterSet = ""); } @@ -36,7 +44,7 @@ using namespace ZXing; // Shorthand to call Decode() static DecoderResult parse(BitMatrix&& bits, bool compact, int nbDatablocks, int nbLayers) { - return Aztec::Decoder::Decode({{std::move(bits), {}}, compact, nbDatablocks, nbLayers, false /*readerInit*/}, ""); + return Aztec::Decode({{std::move(bits), {}}, compact, nbDatablocks, nbLayers, false /*readerInit*/}); } TEST(AZDecoderTest, AztecResult) @@ -75,6 +83,7 @@ TEST(AZDecoderTest, AztecResult) 0x5a, 0xd6, 0xb5, 0xad, 0x6b, 0x5a, 0xd6, 0xb5, 0xad, 0x6b, 0x5a, 0xd6, 0xb0 })); EXPECT_EQ(result.numBits(), 180); + EXPECT_EQ(result.symbologyIdentifier(), "]z0"); } TEST(AZDecoderTest, DecodeTooManyErrors) @@ -149,135 +158,218 @@ TEST(AZDecoderTest, DecodeTooManyErrors2) EXPECT_EQ(result.errorCode(), DecodeStatus::FormatError); } -// Helper to call GetEncodedData() -static std::wstring getData(const ByteArray& bytes, StructuredAppendInfo& sai) +// Helper taking bit string to call GetEncodedData() +static Aztec::AztecData getData(std::string_view bitStr) { - std::vector correctedBits; - correctedBits.reserve(bytes.size() * 5); // 5-bit words (assuming no digits/binary) + BitArray bits; + + for (auto b : bitStr) + bits.appendBit(b == '1'); + + return Aztec::GetEncodedData(bits); +} - for (int i = 0; i < bytes.size(); i++) { - for (int j = 5 - 1; j >= 0; j--) { - correctedBits.push_back((bytes[i] & (1 << j)) != 0); - } +TEST(AZDecoderTest, SymbologyIdentifier) +{ + { + // Plain + auto data = getData("00010"); + EXPECT_EQ(data.symbologyIdentifier, "]z0"); + EXPECT_EQ(data.text, L"A"); + } + + { + // GS1 ("PS FLGN(0) DL (20)01") + auto data = getData("0000000000000111100100001000100011"); + EXPECT_EQ(data.symbologyIdentifier, "]z1"); + EXPECT_EQ(data.text, L"2001"); } - return Aztec::GetEncodedData(correctedBits, "", sai); + { + // AIM ("A PS FLGN(0) B") + auto data = getData("00010000000000000000011"); + EXPECT_EQ(data.symbologyIdentifier, "]z2"); + EXPECT_EQ(data.text, L"AB"); + } + + { + // AIM ("DL 99 UL PS FLGN(0) B") + auto data = getData("11110101110111110000000000000000011"); + EXPECT_EQ(data.symbologyIdentifier, "]z2"); + EXPECT_EQ(data.text, L"99B"); + } + + { + // Structured Append ("UL ML A D A") + auto data = getData("1110111101000100010100010"); + EXPECT_EQ(data.symbologyIdentifier, "]z6"); + EXPECT_EQ(data.text, L"A"); + EXPECT_EQ(data.sai.index, 0); + EXPECT_EQ(data.sai.count, 4); + } + + { + // Structured Append with GS1 ("UL ML A D PS FLGN(0) DL (20)01") + auto data = getData("111011110100010001010000000000000111100100001000100011"); + EXPECT_EQ(data.symbologyIdentifier, "]z7"); + EXPECT_EQ(data.text, L"2001"); + EXPECT_EQ(data.sai.index, 0); + EXPECT_EQ(data.sai.count, 4); + } + + { + // Structured Append with AIM ("UL ML A D A PS FLGN(0) B") + auto data = getData("1110111101000100010100010000000000000000011"); + EXPECT_EQ(data.symbologyIdentifier, "]z8"); + EXPECT_EQ(data.text, L"AB"); + EXPECT_EQ(data.sai.index, 0); + EXPECT_EQ(data.sai.count, 4); + } + + { + // Plain with FNC1 not in first/second position ("A B PS FLGN(0) C") + auto data = getData("0001000011000000000000000100"); + EXPECT_EQ(data.symbologyIdentifier, "]z0"); + EXPECT_EQ(data.text, L"AB\u001DC"); // "ABC" + } + + { + // Plain with FNC1 not in first/second position ("A B C PS FLGN(0) D") + auto data = getData("000100001100100000000000000000101"); + EXPECT_EQ(data.symbologyIdentifier, "]z0"); + EXPECT_EQ(data.text, L"ABC\u001DD"); // "ABCD" + } + + { + // Plain with FNC1 not in first/second position ("DL 1 UL PS FLGN(0) A") + auto data = getData("1111000111110000000000000000010"); + EXPECT_EQ(data.symbologyIdentifier, "]z0"); + EXPECT_EQ(data.text, L"1\u001DA"); // "1D" + } } -// Shorthand to return Structured Append -static StructuredAppendInfo info(ByteArray bytes) +// Helper taking 5-bit word array to call GetEncodedData() +static Aztec::AztecData getData(const ByteArray& bytes) { - StructuredAppendInfo sai; - (void)getData(bytes, sai); - return sai; + BitArray bits; // 5-bit words (assuming no digits/binary) + + for (auto b : bytes) + bits.appendBits(b, 5); + + return Aztec::GetEncodedData(bits); } -// Shorthand to return string result -static std::wstring data(ByteArray bytes) +// Shorthand to return Structured Append given 5-bit word array +static StructuredAppendInfo sai(const ByteArray& bytes) { - StructuredAppendInfo sai; - return getData(bytes, sai); + return getData(bytes).sai; +} + +// Shorthand to return string result given 5-bit word array +static std::wstring text(const ByteArray& bytes) +{ + return getData(bytes).text; } TEST(AZDecoderTest, StructuredAppend) { // Null - EXPECT_EQ(info({2}).index, -1); - EXPECT_EQ(info({2}).count, -1); - EXPECT_TRUE(info({2}).id.empty()); - EXPECT_EQ(data({2}), L"A"); + EXPECT_EQ(sai({2}).index, -1); + EXPECT_EQ(sai({2}).count, -1); + EXPECT_TRUE(sai({2}).id.empty()); + EXPECT_EQ(text({2}), L"A"); // Example from ISO/IEC 24778:2008 Section 8 - EXPECT_EQ(info({29, 29, 2, 5, 2}).index, 0); // AD - EXPECT_EQ(info({29, 29, 2, 5, 2}).count, 4); - EXPECT_TRUE(info({29, 29, 2, 5, 2}).id.empty()); - EXPECT_EQ(data({29, 29, 2, 5, 2}), L"A"); - - EXPECT_EQ(info({29, 29, 3, 5, 2}).index, 1); // BD - EXPECT_EQ(info({29, 29, 3, 5, 2}).count, 4); - EXPECT_TRUE(info({29, 29, 3, 5, 2}).id.empty()); - EXPECT_EQ(data({29, 29, 3, 5, 2}), L"A"); - - EXPECT_EQ(info({29, 29, 4, 5, 2}).index, 2); // CD - EXPECT_EQ(info({29, 29, 4, 5, 2}).count, 4); - EXPECT_TRUE(info({29, 29, 4, 5, 2}).id.empty()); - EXPECT_EQ(data({29, 29, 4, 5, 2}), L"A"); - - EXPECT_EQ(info({29, 29, 5, 5, 2}).index, 3); // DD - EXPECT_EQ(info({29, 29, 5, 5, 2}).count, 4); - EXPECT_TRUE(info({29, 29, 5, 5, 2}).id.empty()); - EXPECT_EQ(data({29, 29, 5, 5, 2}), L"A"); + EXPECT_EQ(sai({29, 29, 2, 5, 2}).index, 0); // AD + EXPECT_EQ(sai({29, 29, 2, 5, 2}).count, 4); + EXPECT_TRUE(sai({29, 29, 2, 5, 2}).id.empty()); + EXPECT_EQ(text({29, 29, 2, 5, 2}), L"A"); + + EXPECT_EQ(sai({29, 29, 3, 5, 2}).index, 1); // BD + EXPECT_EQ(sai({29, 29, 3, 5, 2}).count, 4); + EXPECT_TRUE(sai({29, 29, 3, 5, 2}).id.empty()); + EXPECT_EQ(text({29, 29, 3, 5, 2}), L"A"); + + EXPECT_EQ(sai({29, 29, 4, 5, 2}).index, 2); // CD + EXPECT_EQ(sai({29, 29, 4, 5, 2}).count, 4); + EXPECT_TRUE(sai({29, 29, 4, 5, 2}).id.empty()); + EXPECT_EQ(text({29, 29, 4, 5, 2}), L"A"); + + EXPECT_EQ(sai({29, 29, 5, 5, 2}).index, 3); // DD + EXPECT_EQ(sai({29, 29, 5, 5, 2}).count, 4); + EXPECT_TRUE(sai({29, 29, 5, 5, 2}).id.empty()); + EXPECT_EQ(text({29, 29, 5, 5, 2}), L"A"); // Sequencing field - EXPECT_EQ(info({29, 29, 2, 27, 2}).index, 0); // AZ - EXPECT_EQ(info({29, 29, 2, 27, 2}).count, 26); + EXPECT_EQ(sai({29, 29, 2, 27, 2}).index, 0); // AZ + EXPECT_EQ(sai({29, 29, 2, 27, 2}).count, 26); - EXPECT_EQ(info({29, 29, 14, 27, 2}).index, 12); // MZ - EXPECT_EQ(info({29, 29, 14, 27, 2}).count, 26); + EXPECT_EQ(sai({29, 29, 14, 27, 2}).index, 12); // MZ + EXPECT_EQ(sai({29, 29, 14, 27, 2}).count, 26); - EXPECT_EQ(info({29, 29, 27, 27, 2}).index, 25); // ZZ - EXPECT_EQ(info({29, 29, 27, 27, 2}).count, 26); + EXPECT_EQ(sai({29, 29, 27, 27, 2}).index, 25); // ZZ + EXPECT_EQ(sai({29, 29, 27, 27, 2}).count, 26); // Id - EXPECT_EQ(info({29, 29, 1, 10, 5, 1, 2, 5, 2}).id, "ID"); - EXPECT_EQ(data({29, 29, 1, 10, 5, 1, 2, 5, 2}), L"A"); + EXPECT_EQ(sai({29, 29, 1, 10, 5, 1, 2, 5, 2}).id, "ID"); + EXPECT_EQ(text({29, 29, 1, 10, 5, 1, 2, 5, 2}), L"A"); // Invalid sequencing - EXPECT_EQ(info({29, 29, 2, 2, 2}).index, 0); // AA - EXPECT_EQ(info({29, 29, 2, 2, 2}).count, 0); // Count 1 so set to 0 - EXPECT_EQ(data({29, 29, 2, 2, 2}), L"A"); + EXPECT_EQ(sai({29, 29, 2, 2, 2}).index, 0); // AA + EXPECT_EQ(sai({29, 29, 2, 2, 2}).count, 0); // Count 1 so set to 0 + EXPECT_EQ(text({29, 29, 2, 2, 2}), L"A"); - EXPECT_EQ(info({29, 29, 6, 5, 2}).index, 4); // ED - EXPECT_EQ(info({29, 29, 6, 5, 2}).count, 0); // Count 4 <= index 4 so set to 0 - EXPECT_EQ(data({29, 29, 6, 5, 2}), L"A"); + EXPECT_EQ(sai({29, 29, 6, 5, 2}).index, 4); // ED + EXPECT_EQ(sai({29, 29, 6, 5, 2}).count, 0); // Count 4 <= index 4 so set to 0 + EXPECT_EQ(text({29, 29, 6, 5, 2}), L"A"); - EXPECT_EQ(info({29, 29, 1, 5, 2}).index, -1); // Index < 'A' - EXPECT_EQ(info({29, 29, 1, 5, 2}).count, -1); - EXPECT_EQ(data({29, 29, 1, 5, 2}), L" DA"); // Bad sequencing left in result + EXPECT_EQ(sai({29, 29, 1, 5, 2}).index, -1); // Index < 'A' + EXPECT_EQ(sai({29, 29, 1, 5, 2}).count, -1); + EXPECT_EQ(text({29, 29, 1, 5, 2}), L" DA"); // Bad sequencing left in result - EXPECT_EQ(info({29, 29, 28, 5, 2}).index, -1); // Index > 'Z' (LL) - EXPECT_EQ(info({29, 29, 28, 5, 2}).count, -1); - EXPECT_EQ(data({29, 29, 28, 5, 2}), L"da"); + EXPECT_EQ(sai({29, 29, 28, 5, 2}).index, -1); // Index > 'Z' (LL) + EXPECT_EQ(sai({29, 29, 28, 5, 2}).count, -1); + EXPECT_EQ(text({29, 29, 28, 5, 2}), L"da"); - EXPECT_EQ(info({29, 29, 2, 1, 2}).index, -1); // Count < 'A' - EXPECT_EQ(info({29, 29, 2, 1, 2}).count, -1); - EXPECT_EQ(data({29, 29, 2, 1, 2}), L"A A"); + EXPECT_EQ(sai({29, 29, 2, 1, 2}).index, -1); // Count < 'A' + EXPECT_EQ(sai({29, 29, 2, 1, 2}).count, -1); + EXPECT_EQ(text({29, 29, 2, 1, 2}), L"A A"); - EXPECT_EQ(info({29, 29, 2, 28, 2}).index, -1); // Count > 'Z' - EXPECT_EQ(info({29, 29, 2, 28, 2}).count, -1); - EXPECT_EQ(data({29, 29, 2, 28, 2}), L"Aa"); + EXPECT_EQ(sai({29, 29, 2, 28, 2}).index, -1); // Count > 'Z' + EXPECT_EQ(sai({29, 29, 2, 28, 2}).count, -1); + EXPECT_EQ(text({29, 29, 2, 28, 2}), L"Aa"); - EXPECT_EQ(info({29, 29, 2, 5}).index, -1); // Sequencing but no data - EXPECT_EQ(info({29, 29, 2, 5}).count, -1); - EXPECT_EQ(data({29, 29, 2, 5}), L"AD"); + EXPECT_EQ(sai({29, 29, 2, 5}).index, -1); // Sequencing but no data + EXPECT_EQ(sai({29, 29, 2, 5}).count, -1); + EXPECT_EQ(text({29, 29, 2, 5}), L"AD"); // Invalid Ids { - ByteArray bytes({29, 29, 1, 10, 5, 2, 5, 2}); // No terminating space - EXPECT_TRUE(info(bytes).id.empty()); - EXPECT_EQ(info(bytes).index, -1); // Not recognized as sequence - EXPECT_EQ(info(bytes).count, -1); - EXPECT_EQ(data(bytes), L" IDADA"); // Bad ID and sequencing left in result + auto data = getData({29, 29, 1, 10, 5, 2, 5, 2}); // No terminating space + EXPECT_TRUE(data.sai.id.empty()); + EXPECT_EQ(data.sai.index, -1); // Not recognized as sequence + EXPECT_EQ(data.sai.count, -1); + EXPECT_EQ(data.text, L" IDADA"); // Bad ID and sequencing left in result } { - ByteArray bytes({29, 29, 1, 1, 2, 5, 2}); // Blank - EXPECT_TRUE(info(bytes).id.empty()); - EXPECT_EQ(info(bytes).index, 0); // Recognized as sequence - EXPECT_EQ(info(bytes).count, 4); - EXPECT_EQ(data(bytes), L"A"); + auto data = getData({29, 29, 1, 1, 2, 5, 2}); // Blank + EXPECT_TRUE(data.sai.id.empty()); + EXPECT_EQ(data.sai.index, 0); // Recognized as sequence + EXPECT_EQ(data.sai.count, 4); + EXPECT_EQ(data.text, L"A"); } { - ByteArray bytes({29, 29, 1, 10, 1, 5, 1, 2, 5, 2}); // Space in "I D" - EXPECT_TRUE(info(bytes).id.empty()); - EXPECT_EQ(info(bytes).index, -1); // Not recognized as sequence as sequence count invalid (space) - EXPECT_EQ(info(bytes).count, -1); - EXPECT_EQ(data(bytes), L" I D ADA"); // Bad ID and sequencing left in result + auto data = getData({29, 29, 1, 10, 1, 5, 1, 2, 5, 2}); // Space in "I D" + EXPECT_TRUE(data.sai.id.empty()); + EXPECT_EQ(data.sai.index, -1); // Not recognized as sequence as sequence count invalid (space) + EXPECT_EQ(data.sai.count, -1); + EXPECT_EQ(data.text, L" I D ADA"); // Bad ID and sequencing left in result } { - ByteArray bytes({29, 29, 1, 10, 1, 2, 5, 1, 2, 5, 2}); // "I AD" (happens to have valid sequencing at end) - EXPECT_EQ(info(bytes).id, "I"); - EXPECT_EQ(info(bytes).index, 0); - EXPECT_EQ(info(bytes).count, 4); - EXPECT_EQ(data(bytes), L" ADA"); // Trailing space and "real" sequencing left in result + auto data = getData({29, 29, 1, 10, 1, 2, 5, 1, 2, 5, 2}); // "I AD" (happens to have valid sequencing at end) + EXPECT_EQ(data.sai.id, "I"); + EXPECT_EQ(data.sai.index, 0); + EXPECT_EQ(data.sai.count, 4); + EXPECT_EQ(data.text, L" ADA"); // Trailing space and "real" sequencing left in result } } diff --git a/test/unit/aztec/AZDetectorTest.cpp b/test/unit/aztec/AZDetectorTest.cpp index ee4ab2c700..42a9885ee9 100644 --- a/test/unit/aztec/AZDetectorTest.cpp +++ b/test/unit/aztec/AZDetectorTest.cpp @@ -16,8 +16,12 @@ */ #include "aztec/AZDetector.h" + #include "BitMatrixIO.h" +#include "DecoderResult.h" #include "PseudoRandom.h" +#include "TextUtfEncoding.h" +#include "aztec/AZDecoder.h" #include "aztec/AZDetectorResult.h" #include "gtest/gtest.h" @@ -48,11 +52,6 @@ namespace { return result; } - // Zooms a bit matrix so that each bit is factor x factor - BitMatrix MakeLarger(const BitMatrix& input, int factor) { - return Inflate(input.copy(), factor * input.width(), factor * input.height(), 0); - } - // Test that we can tolerate errors in the parameter locator bits void TestErrorInParameterLocator(std::string_view data, int nbLayers, bool isCompact, const BitMatrix &matrix_) { @@ -73,13 +72,11 @@ namespace { // if error2 == error1, we only test a single error copy.flip(orientationPoints[error2].x, orientationPoints[error2].y); } - // The detector doesn't seem to work when matrix bits are only 1x1. So magnify. - Aztec::DetectorResult r = Aztec::Detector::Detect(MakeLarger(copy, 3), isMirror, true); + Aztec::DetectorResult r = Aztec::Detect(copy, isMirror, true); EXPECT_EQ(r.isValid(), true); EXPECT_EQ(r.nbLayers(), nbLayers); EXPECT_EQ(r.isCompact(), isCompact); - //DecoderResult res = new Decoder().decode(r); - //assertEquals(data, res.getText()); + EXPECT_EQ(data, TextUtfEncoding::ToUtf8(Aztec::Decode(r).text())); } } // Try a few random three-bit errors; @@ -92,7 +89,7 @@ namespace { for (auto error : errors) { copy.flip(orientationPoints[error].x, orientationPoints[error].y); } - Aztec::DetectorResult r = Aztec::Detector::Detect(MakeLarger(copy, 3), false, true); + Aztec::DetectorResult r = Aztec::Detect(copy, false, true); EXPECT_EQ(r.isValid(), false); } @@ -209,7 +206,7 @@ TEST(AZDetectorTest, ReaderInitFull2Layers) { { // Null (not set) - auto r = Aztec::Detector::Detect(ParseBitMatrix( + auto r = Aztec::Detect(ParseBitMatrix( " X X X X X X X X X X X X X X X \n" " X X X X X X X X X X X X \n" " X X X X X X X X X X X X X X X \n" @@ -241,7 +238,7 @@ TEST(AZDetectorTest, ReaderInitFull2Layers) } { // Set - auto r = Aztec::Detector::Detect(ParseBitMatrix( + auto r = Aztec::Detect(ParseBitMatrix( " X X X X X X X X X X X X X X X \n" " X X X X X X X X X X X X \n" " X X X X X X X X X X X X X X X \n" @@ -276,7 +273,7 @@ TEST(AZDetectorTest, ReaderInitFull2Layers) TEST(AZDetectorTest, ReaderInitFull22Layers) { // Set - auto r = Aztec::Detector::Detect(ParseBitMatrix( + auto r = Aztec::Detect(ParseBitMatrix( " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" " X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" @@ -397,7 +394,7 @@ TEST(AZDetectorTest, ReaderInitCompact) { { // Null (not set) - auto r = Aztec::Detector::Detect(ParseBitMatrix( + auto r = Aztec::Detect(ParseBitMatrix( " X X X X \n" " X X X X X X X X X \n" " X X X X \n" @@ -421,7 +418,7 @@ TEST(AZDetectorTest, ReaderInitCompact) } { // Set - auto r = Aztec::Detector::Detect(ParseBitMatrix( + auto r = Aztec::Detect(ParseBitMatrix( " X X X X \n" " X X X X X X X X X \n" " X X X X X \n" diff --git a/test/unit/aztec/AZEncodeDecodeTest.cpp b/test/unit/aztec/AZEncodeDecodeTest.cpp index ed6d21e2d6..d09f0bd6f9 100644 --- a/test/unit/aztec/AZEncodeDecodeTest.cpp +++ b/test/unit/aztec/AZEncodeDecodeTest.cpp @@ -47,8 +47,7 @@ namespace { // Shorthand to call Decode() static DecoderResult parse(BitMatrix&& bits, bool compact, int nbDatablocks, int nbLayers) { - return Aztec::Decoder::Decode({{std::move(bits), {}}, compact, nbDatablocks, nbLayers, false /*readerInit*/}, - ""); + return Aztec::Decode({{std::move(bits), {}}, compact, nbDatablocks, nbLayers, false /*readerInit*/}); } void TestEncodeDecode(const std::string& data, bool compact, int layers) { diff --git a/test/unit/aztec/AZHighLevelEncoderTest.cpp b/test/unit/aztec/AZHighLevelEncoderTest.cpp index c8cd87f9a8..2b55de86a9 100644 --- a/test/unit/aztec/AZHighLevelEncoderTest.cpp +++ b/test/unit/aztec/AZHighLevelEncoderTest.cpp @@ -24,23 +24,22 @@ #include "gtest/gtest.h" #include -namespace ZXing { - namespace Aztec { - std::wstring GetEncodedData(const std::vector& correctedBits, const std::string& characterSet, - StructuredAppendInfo& sai); - } +namespace ZXing::Aztec { + +struct AztecData +{ + std::wstring text; + std::string symbologyIdentifier; + StructuredAppendInfo sai; +}; + +AztecData GetEncodedData(const BitArray& bits, const std::string& characterSet = ""); + } using namespace ZXing; namespace { - - std::vector ToBoolArray(const BitArray& arr) { - std::vector result(arr.size()); - std::copy_n(arr.begin(), arr.size(), result.begin()); - return result; - } - std::string StripSpaces(std::string str) { str.erase(std::remove_if(str.begin(), str.end(), isspace), str.end()); return str; @@ -49,16 +48,16 @@ namespace { void TestHighLevelEncodeString(const std::string& s, const std::string& expectedBits) { BitArray bits = Aztec::HighLevelEncoder::Encode(s); EXPECT_EQ(Utility::ToString(bits), StripSpaces(expectedBits)) << "highLevelEncode() failed for input string: " + s; - StructuredAppendInfo sai; - EXPECT_EQ(TextDecoder::FromLatin1(s), Aztec::GetEncodedData(ToBoolArray(bits), "", sai)); + EXPECT_EQ(TextDecoder::FromLatin1(s), Aztec::GetEncodedData(bits).text); } void TestHighLevelEncodeString(const std::string& s, int expectedReceivedBits) { BitArray bits = Aztec::HighLevelEncoder::Encode(s); int receivedBitCount = Size(Utility::ToString(bits)); EXPECT_EQ(receivedBitCount, expectedReceivedBits) << "highLevelEncode() failed for input string: " + s; + std::string symbologyIdentifier; StructuredAppendInfo sai; - EXPECT_EQ(TextDecoder::FromLatin1(s), Aztec::GetEncodedData(ToBoolArray(bits), "", sai)); + EXPECT_EQ(TextDecoder::FromLatin1(s), Aztec::GetEncodedData(bits).text); } } diff --git a/test/unit/datamatrix/DMDecodedBitStreamParserTest.cpp b/test/unit/datamatrix/DMDecodedBitStreamParserTest.cpp index 7fd05597cb..ad3db44716 100644 --- a/test/unit/datamatrix/DMDecodedBitStreamParserTest.cpp +++ b/test/unit/datamatrix/DMDecodedBitStreamParserTest.cpp @@ -23,21 +23,34 @@ namespace ZXing::DataMatrix::DecodedBitStreamParser { -DecoderResult Decode(ByteArray&& bytes, const std::string& characterSet); +DecoderResult Decode(ByteArray&& bytes, const std::string& characterSet, const bool isDMRE); } using namespace ZXing; -// Shorthand for Decode() -static DecoderResult parse(ByteArray bytes) +// Helper to call Decode() +static DecoderResult parse(ByteArray bytes, const bool isDMRE = false) { - return DataMatrix::DecodedBitStreamParser::Decode(std::move(bytes), ""); + return DataMatrix::DecodedBitStreamParser::Decode(std::move(bytes), "", isDMRE); } -static std::wstring decode(ByteArray bytes) +// Shorthand to return text +static std::wstring decode(ByteArray bytes, const bool isDMRE = false) { - return parse(std::move(bytes)).text(); + return parse(std::move(bytes), isDMRE).text(); +} + +// Shorthand to return symbology identifier +static std::string id(ByteArray bytes, const bool isDMRE = false) +{ + return parse(std::move(bytes), isDMRE).symbologyIdentifier(); +} + +// Shorthand to return Structured Append +static StructuredAppendInfo info(ByteArray bytes, const bool isDMRE = false) +{ + return parse(std::move(bytes), isDMRE).structuredAppend(); } TEST(DMDecodeTest, Ascii) @@ -106,9 +119,90 @@ TEST(DMDecodeTest, X12) // EXPECT_EQ(decode({}), L""); } -static StructuredAppendInfo info(ByteArray bytes) +TEST(DMDecodeTest, SymbologyIdentifier) +{ + // Plain + EXPECT_EQ(id({50}), "]d1"); + EXPECT_EQ(decode({50}), L"1"); + + // GS1 "FNC1 (20)01" + EXPECT_EQ(id({232, 150, 131}), "]d2"); + EXPECT_EQ(decode({232, 150, 131}), L"2001"); + + // "LatchC40 Shift2 FNC1 LatchASCII 2001" not recognized as FNC1 in first position + EXPECT_EQ(id({230, 0x0A, 0x79, 254, 150, 131}), "]d1"); // shift2FNC1 = (1600 * 1) + (40 * 27) + 0 + 1 == 0x0A79 + EXPECT_EQ(decode({230, 0x0A, 0x79, 254, 150, 131}), L"\u001D2001"); + + // AIM "A FNC1 B" + EXPECT_EQ(id({66, 232, 67}), "]d3"); + EXPECT_EQ(decode({66, 232, 67}), L"AB"); + + // AIM "9 FNC1 A" + EXPECT_EQ(id({58, 232, 66}), "]d3"); + EXPECT_EQ(decode({58, 232, 66}), L"9A"); + + // AIM "99 FNC1 A" (double digit + 130) + EXPECT_EQ(id({99 + 130, 232, 66}), "]d3"); + EXPECT_EQ(decode({99 + 130, 232, 66}), L"99A"); + + // AIM "? FNC1 A" (ISO/IEC 16022:2006 11.2 does not specify any restrictions on single first character) + EXPECT_EQ(id({64, 232, 66}), "]d3"); + EXPECT_EQ(decode({64, 232, 66}), L"?A"); + + // "LatchC40 A Shift2 FNC1 B" not recognized as FNC1 in second position + EXPECT_EQ(id({230, 0x57, 0xC4, 254, 67}), "]d1"); // shift2FNC1 = 1600 * 14 + (40 * 1) + 27 + 1 == 0x57C4 + EXPECT_EQ(decode({230, 0x57, 0xC4, 254, 67}), L"A\u001DB"); + + // "99 FNC1 A" (2 single digits before FNC1 not recognized as AIM) + EXPECT_EQ(id({58, 58, 232, 66}), "]d1"); + EXPECT_EQ(decode({58, 58, 232, 66}), L"99\u001DA"); + + // GS1 "StructuredAppend FNC1 (20)01" + EXPECT_EQ(id({233, 42, 1, 1, 232, 150, 131}), "]d2"); + EXPECT_EQ(decode({233, 42, 1, 1, 232, 150, 131}), L"2001"); + + // AIM "StructuredAppend A FNC1 B" + EXPECT_EQ(id({233, 42, 1, 1, 66, 232, 67}), "]d3"); + EXPECT_EQ(decode({233, 42, 1, 1, 66, 232, 67}), L"AB"); +} + +TEST(DMDecodeTest, DMRESymbologyIdentifier) { - return parse(std::move(bytes)).structuredAppend(); + // Plain + EXPECT_EQ(id({50}, true /*isDMRE*/), "]d7"); + EXPECT_EQ(decode({50}, true /*isDMRE*/), L"1"); + + // GS1 "FNC1 (20)01" + EXPECT_EQ(id({232, 150, 131}, true /*isDMRE*/), "]d8"); + EXPECT_EQ(decode({232, 150, 131}, true /*isDMRE*/), L"2001"); + + // AIM "A FNC1 B" + EXPECT_EQ(id({66, 232, 67}, true /*isDMRE*/), "]d9"); + EXPECT_EQ(decode({66, 232, 67}, true /*isDMRE*/), L"AB"); + + // AIM "9 FNC1 A" + EXPECT_EQ(id({58, 232, 66}, true /*isDMRE*/), "]d9"); + EXPECT_EQ(decode({58, 232, 66}, true /*isDMRE*/), L"9A"); + + // AIM "99 FNC1 A" (double digit + 130) + EXPECT_EQ(id({99 + 130, 232, 66}, true /*isDMRE*/), "]d9"); + EXPECT_EQ(decode({99 + 130, 232, 66}, true /*isDMRE*/), L"99A"); + + // AIM "? FNC1 A" (ISO/IEC 16022:2006 11.2 does not specify any restrictions on single first character) + EXPECT_EQ(id({64, 232, 66}, true /*isDMRE*/), "]d9"); + EXPECT_EQ(decode({64, 232, 66}, true /*isDMRE*/), L"?A"); + + // "99 FNC1 A" (2 single digits before FNC1 not recognized as AIM) + EXPECT_EQ(id({58, 58, 232, 66}, true /*isDMRE*/), "]d7"); + EXPECT_EQ(decode({58, 58, 232, 66}, true /*isDMRE*/), L"99\u001DA"); + + // GS1 "StructuredAppend FNC1 (20)01" + EXPECT_EQ(id({233, 42, 1, 1, 232, 150, 131}, true /*isDMRE*/), "]d8"); + EXPECT_EQ(decode({233, 42, 1, 1, 232, 150, 131}, true /*isDMRE*/), L"2001"); + + // AIM "StructuredAppend A FNC1 B" + EXPECT_EQ(id({233, 42, 1, 1, 66, 232, 67}, true /*isDMRE*/), "]d9"); + EXPECT_EQ(decode({233, 42, 1, 1, 66, 232, 67}, true /*isDMRE*/), L"AB"); } TEST(DMDecodeTest, StructuredAppend) @@ -117,38 +211,44 @@ TEST(DMDecodeTest, StructuredAppend) EXPECT_EQ(info({50}).index, -1); EXPECT_EQ(info({50}).count, -1); EXPECT_TRUE(info({50}).id.empty()); + EXPECT_EQ(id({50}), "]d1"); + + // Structured Append "233" must be first ISO 16022:2006 5.6.1 + EXPECT_FALSE(parse({50, 233, 42, 1, 1}).isValid()); + EXPECT_EQ(info({50, 233, 42, 1, 1}).index, -1); // ISO/IEC 16022:2006 5.6.2 sequence indicator example - EXPECT_EQ(info({50, 233, 42, 1, 1}).index, 2); // 1-based position 3 == index 2 - EXPECT_EQ(info({50, 233, 42, 1, 1}).count, 7); - EXPECT_EQ(info({50, 233, 42, 1, 1}).id, "257"); + EXPECT_EQ(info({233, 42, 1, 1, 50}).index, 2); // 1-based position 3 == index 2 + EXPECT_EQ(info({233, 42, 1, 1, 50}).count, 7); + EXPECT_EQ(info({233, 42, 1, 1, 50}).id, "257"); + EXPECT_EQ(id({233, 42, 1, 1, 50}), "]d1"); // Sequence indicator - EXPECT_EQ(info({50, 233, 0, 1, 1}).index, 0); - EXPECT_EQ(info({50, 233, 0, 1, 1}).count, 0); // Count 17 set to 0 + EXPECT_EQ(info({233, 0, 1, 1, 50}).index, 0); + EXPECT_EQ(info({233, 0, 1, 1, 50}).count, 0); // Count 17 set to 0 - EXPECT_EQ(info({50, 233, 1, 1, 1}).index, 0); - EXPECT_EQ(info({50, 233, 1, 1, 1}).count, 16); + EXPECT_EQ(info({233, 1, 1, 1, 50}).index, 0); + EXPECT_EQ(info({233, 1, 1, 1, 50}).count, 16); - EXPECT_EQ(info({50, 233, 0x81, 1, 1}).index, 8); - EXPECT_EQ(info({50, 233, 0x81, 1, 1}).count, 16); + EXPECT_EQ(info({233, 0x81, 1, 1, 50}).index, 8); + EXPECT_EQ(info({233, 0x81, 1, 1, 50}).count, 16); - EXPECT_EQ(info({50, 233, 0xFF, 1, 1}).index, 15); - EXPECT_EQ(info({50, 233, 0xFF, 1, 1}).count, 0); // Count 2 <= index so set to 0 + EXPECT_EQ(info({233, 0xFF, 1, 1, 50}).index, 15); + EXPECT_EQ(info({233, 0xFF, 1, 1, 50}).count, 0); // Count 2 <= index so set to 0 - EXPECT_EQ(info({50, 233, 0xF1, 1, 1}).index, 15); - EXPECT_EQ(info({50, 233, 0xF1, 1, 1}).count, 16); + EXPECT_EQ(info({233, 0xF1, 1, 1, 50}).index, 15); + EXPECT_EQ(info({233, 0xF1, 1, 1, 50}).count, 16); // File identification - EXPECT_EQ(info({50, 233, 42, 1, 12}).id, "268"); - EXPECT_EQ(info({50, 233, 42, 12, 34}).id, "3106"); - EXPECT_EQ(info({50, 233, 42, 12, 123}).id, "3195"); - EXPECT_EQ(info({50, 233, 42, 254, 254}).id, "65278"); + EXPECT_EQ(info({233, 42, 1, 12, 50}).id, "268"); + EXPECT_EQ(info({233, 42, 12, 34, 50}).id, "3106"); + EXPECT_EQ(info({233, 42, 12, 123, 50}).id, "3195"); + EXPECT_EQ(info({233, 42, 254, 254, 50}).id, "65278"); // Values outside 1-254 allowed (i.e. tolerated) - EXPECT_EQ(info({50, 233, 42, 0, 0}).id, "0"); - EXPECT_EQ(info({50, 233, 42, 0, 255}).id, "255"); - EXPECT_EQ(info({50, 233, 42, 255, 0}).id, "65280"); - EXPECT_EQ(info({50, 233, 42, 255, 255}).id, "65535"); + EXPECT_EQ(info({233, 42, 0, 0, 50}).id, "0"); + EXPECT_EQ(info({233, 42, 0, 255, 50}).id, "255"); + EXPECT_EQ(info({233, 42, 255, 0, 50}).id, "65280"); + EXPECT_EQ(info({233, 42, 255, 255, 50}).id, "65535"); } TEST(DMDecodeTest, ReaderInit) @@ -157,19 +257,19 @@ TEST(DMDecodeTest, ReaderInit) EXPECT_FALSE(parse({50}).readerInit()); EXPECT_TRUE(parse({50}).isValid()); + // Reader Programming "234" must be first ISO 16022:2006 5.2.4.9 + EXPECT_FALSE(parse({50, 234}).readerInit()); + EXPECT_FALSE(parse({50, 234}).isValid()); + // Set EXPECT_TRUE(parse({234, 50}).readerInit()); EXPECT_TRUE(parse({234, 50}).isValid()); - // Must be first - EXPECT_FALSE(parse({50, 234}).readerInit()); - EXPECT_FALSE(parse({50, 234}).isValid()); - EXPECT_FALSE(parse({235, 234, 50}).readerInit()); // Upper Shift first EXPECT_FALSE(parse({235, 234, 50}).isValid()); - // Can't be used with Structured Append - EXPECT_TRUE(parse({50, 233, 42, 1, 1}).isValid()); // Null - EXPECT_FALSE(parse({234, 50, 233, 42, 1, 1}).readerInit()); - EXPECT_FALSE(parse({234, 50, 233, 42, 1, 1}).isValid()); + // Can't be used with Structured Append "233" + EXPECT_TRUE(parse({233, 42, 1, 1, 50}).isValid()); // Null + EXPECT_FALSE(parse({233, 42, 1, 1, 234, 50}).readerInit()); + EXPECT_FALSE(parse({233, 42, 1, 1, 234, 50}).isValid()); } diff --git a/test/unit/maxicode/MCDecoderTest.cpp b/test/unit/maxicode/MCDecoderTest.cpp index c973bbe7bf..150f4542ec 100644 --- a/test/unit/maxicode/MCDecoderTest.cpp +++ b/test/unit/maxicode/MCDecoderTest.cpp @@ -61,28 +61,33 @@ static StructuredAppendInfo info(ByteArray bytes, const int mode) return parse(bytes, mode).structuredAppend(); } -TEST(MCDecodeTest, StructuredAppend) +TEST(MCDecodeTest, StructuredAppendSymbologyIdentifier) { // Null EXPECT_EQ(info({49}, 2).index, -1); // Mode 2 EXPECT_EQ(info({49}, 2).count, -1); EXPECT_TRUE(info({49}, 2).id.empty()); + EXPECT_EQ(parse({49}, 2).symbologyIdentifier(), "]U1"); EXPECT_EQ(info({49}, 3).index, -1); // Mode 3 EXPECT_EQ(info({49}, 3).count, -1); EXPECT_TRUE(info({49}, 3).id.empty()); + EXPECT_EQ(parse({49}, 3).symbologyIdentifier(), "]U1"); EXPECT_EQ(info({49}, 4).index, -1); // Mode 4 EXPECT_EQ(info({49}, 4).count, -1); EXPECT_TRUE(info({49}, 4).id.empty()); + EXPECT_EQ(parse({49}, 4).symbologyIdentifier(), "]U0"); EXPECT_EQ(info({49}, 5).index, -1); // Mode 5 EXPECT_EQ(info({49}, 5).count, -1); EXPECT_TRUE(info({49}, 5).id.empty()); + EXPECT_EQ(parse({49}, 5).symbologyIdentifier(), "]U0"); EXPECT_EQ(info({49}, 6).index, -1); // Mode 6 EXPECT_EQ(info({49}, 6).count, -1); EXPECT_TRUE(info({49}, 6).id.empty()); + EXPECT_TRUE(parse({49}, 6).symbologyIdentifier().empty()); // Not defined for reader initialisation/programming // ISO/IEC 16023:2000 4.9.1 example EXPECT_EQ(info({33, 22, 49}, 2).index, 2); // Mode 2 - 3rd position 1-based == index 2 diff --git a/test/unit/oned/ODCode128ReaderTest.cpp b/test/unit/oned/ODCode128ReaderTest.cpp index 04332bf2be..87af89dbfb 100644 --- a/test/unit/oned/ODCode128ReaderTest.cpp +++ b/test/unit/oned/ODCode128ReaderTest.cpp @@ -16,7 +16,6 @@ #include "oned/ODCode128Reader.h" -#include "DecodeHints.h" #include "Result.h" #include "gtest/gtest.h" @@ -37,9 +36,60 @@ static Result parse(const int startPattern, PatternRow row) row.insert(row.end(), { 2, 3, 3, 1, 1, 1, 2, 0 }); // Stop pattern std::unique_ptr state; - DecodeHints hints; - Code128Reader reader(hints); - return reader.decodePattern(0, row, state); + Code128Reader reader; + PatternView next(row); + return reader.decodePattern(0, next, state); +} + +TEST(ODCode128ReaderTest, SymbologyIdentifier) +{ + { + // Plain "2001" + PatternRow row({ 2, 2, 1, 2, 3, 1, 2, 2, 2, 1, 2, 2, 3, 1, 1, 2, 2, 2 }); + auto result = parse('C', row); + EXPECT_EQ(result.symbologyIdentifier(), "]C0"); + EXPECT_EQ(result.text(), L"2001"); + } + + { + // GS1 "(20)01" + PatternRow row({ 4, 1, 1, 1, 3, 1, 2, 2, 1, 2, 3, 1, 2, 2, 2, 1, 2, 2, 1, 3, 2, 1, 3, 1 }); + auto result = parse('C', row); + EXPECT_EQ(result.symbologyIdentifier(), "]C1"); + EXPECT_EQ(result.text(), L"2001"); + } + + { + // AIM "A FNC1 B" + PatternRow row({ 1, 1, 1, 3, 2, 3, 4, 1, 1, 1, 3, 1, 1, 3, 1, 1, 2, 3, 2, 1, 2, 3, 2, 1 }); + auto result = parse('B', row); + EXPECT_EQ(result.symbologyIdentifier(), "]C2"); + EXPECT_EQ(result.text(), L"AB"); + } + + { + // AIM "z FNC1 B" + PatternRow row({ 2, 1, 4, 1, 2, 1, 4, 1, 1, 1, 3, 1, 1, 3, 1, 1, 2, 3, 4, 2, 1, 2, 1, 1 }); + auto result = parse('B', row); + EXPECT_EQ(result.symbologyIdentifier(), "]C2"); + EXPECT_EQ(result.text(), L"zB"); + } + + { + // AIM "99 FNC1 A" + PatternRow row({ 1, 1, 3, 1, 4, 1, 4, 1, 1, 1, 3, 1, 1, 1, 4, 1, 3, 1, 1, 1, 1, 3, 2, 3, 1, 2, 3, 1, 2, 2 }); + auto result = parse('C', row); + EXPECT_EQ(result.symbologyIdentifier(), "]C2"); + EXPECT_EQ(result.text(), L"99A"); + } + + { + // Bad AIM Application Indicator "? FNC1 B" + PatternRow row({ 2, 1, 2, 3, 2, 1, 4, 1, 1, 1, 3, 1, 1, 3, 1, 1, 2, 3, 3, 2, 2, 2, 1, 1 }); + auto result = parse('B', row); + EXPECT_EQ(result.symbologyIdentifier(), "]C0"); // Just ignoring, not giving FormatError + EXPECT_EQ(result.text(), L"?\u001DB"); + } } TEST(ODCode128ReaderTest, ReaderInit) @@ -47,21 +97,24 @@ TEST(ODCode128ReaderTest, ReaderInit) { // Null PatternRow row({ 1, 1, 1, 1, 4, 3, 1, 3, 1, 1, 4, 1 }); - EXPECT_FALSE(parse('C', row).readerInit()); - EXPECT_EQ(parse('C', row).text(), L"92"); + auto result = parse('C', row); + EXPECT_FALSE(result.readerInit()); + EXPECT_EQ(result.text(), L"92"); } { // Set (FNC3 first) PatternRow row({ 1, 1, 4, 3, 1, 1, 1, 1, 3, 1, 4, 1, 1, 1, 1, 1, 4, 3, 3, 3, 1, 1, 2, 1 }); - EXPECT_TRUE(parse('B', row).readerInit()); - EXPECT_EQ(parse('B', row).text(), L"92"); + auto result = parse('B', row); + EXPECT_TRUE(result.readerInit()); + EXPECT_EQ(result.text(), L"92"); } { // Set (FNC3 between "9" and "2" ) PatternRow row({ 3, 2, 1, 1, 2, 2, 1, 1, 4, 3, 1, 1, 2, 2, 3, 2, 1, 1, 1, 2, 1, 4, 2, 1 }); - EXPECT_TRUE(parse('B', row).readerInit()); - EXPECT_EQ(parse('B', row).text(), L"92"); + auto result = parse('B', row); + EXPECT_TRUE(result.readerInit()); + EXPECT_EQ(result.text(), L"92"); } } diff --git a/test/unit/oned/ODCode128WriterTest.cpp b/test/unit/oned/ODCode128WriterTest.cpp index ee3c1e4507..65636d64ec 100644 --- a/test/unit/oned/ODCode128WriterTest.cpp +++ b/test/unit/oned/ODCode128WriterTest.cpp @@ -17,7 +17,6 @@ #include "oned/ODCode128Writer.h" #include "BitMatrixIO.h" -#include "DecodeHints.h" #include "Result.h" #include "oned/ODCode128Reader.h" @@ -50,13 +49,13 @@ static ZXing::Result Decode(const BitMatrix &matrix) { BitArray row; matrix.getRow(0, row); - return Code128Reader(DecodeHints()).decodeSingleRow(0, row); + return Code128Reader().decodeSingleRow(0, row); } TEST(ODCode128Writer, EncodeWithFunc1) { auto toEncode = L"\xf1""123"; - // "12" "3" check digit 92 + // "12" "3" check digit 92 auto expected = QUIET_SPACE + START_CODE_C + FNC1 + "10110011100" + SWITCH_CODE_B + "11001011100" + "10101111000" + STOP + QUIET_SPACE; auto actual = LineMatrixToString(Code128Writer().encode(toEncode, 0, 0)); @@ -101,14 +100,28 @@ TEST(ODCode128Writer, EncodeWithFncsAndNumberInCodesetA) EXPECT_EQ(actual, expected); } -TEST(ODCode128Writer, Roundtrip) +TEST(ODCode128Writer, RoundtripGS1) { auto toEncode = L"\xf1" "10958" "\xf1" "17160526"; - auto expected = L"1095817160526"; + auto expected = L"10958\u001D17160526"; - auto encResult = Code128Writer().encode(toEncode, 0, 0); - auto actual = Decode(encResult).text(); + auto encResult = Code128Writer().encode(toEncode, 0, 0); + auto decResult = Decode(encResult); + auto actual = decResult.text(); EXPECT_EQ(actual, expected); + EXPECT_EQ(decResult.symbologyIdentifier(), "]C1"); +} + +TEST(ODCode128Writer, RoundtripFNC1) +{ + auto toEncode = L"1\xf1" "0958" "\xf1" "17160526"; + auto expected = L"1\u001D0958\u001D17160526"; + + auto encResult = Code128Writer().encode(toEncode, 0, 0); + auto decResult = Decode(encResult); + auto actual = decResult.text(); + EXPECT_EQ(actual, expected); + EXPECT_EQ(decResult.symbologyIdentifier(), "]C0"); } TEST(ODCode128Writer, EncodeSwitchCodesetFromAToB) diff --git a/test/unit/oned/ODCode39ReaderTest.cpp b/test/unit/oned/ODCode39ReaderTest.cpp new file mode 100644 index 0000000000..c92d88202f --- /dev/null +++ b/test/unit/oned/ODCode39ReaderTest.cpp @@ -0,0 +1,82 @@ +/* +* Copyright 2022 gitlost +* +* 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. +*/ + +#include "oned/ODCode39Reader.h" + +#include "DecodeHints.h" +#include "Result.h" + +#include "gtest/gtest.h" + +using namespace ZXing; +using namespace ZXing::OneD; + +// Helper to call decodePattern() +static Result parse(PatternRow row, DecodeHints hints = {}) +{ + Code39Reader reader(hints); + + row.insert(row.begin(), { 0, 1, 2, 1, 1, 2, 1, 2, 1, 1, 0 }); + row.insert(row.end(), { 0, 1, 2, 1, 1, 2, 1, 2, 1, 1, 0 }); + + std::unique_ptr state; + PatternView next(row); + return reader.decodePattern(0, next, state); +} + +TEST(ODCode39ReaderTest, SymbologyIdentifier) +{ + { + // Plain "A" + PatternRow row({ 2, 1, 1, 1, 1, 2, 1, 1, 2 }); + auto result = parse(row); + EXPECT_EQ(result.symbologyIdentifier(), "]A0"); + EXPECT_EQ(result.text(), L"A"); + } + { + // "A" with checksum + PatternRow row({ 2, 1, 1, 1, 1, 2, 1, 1, 2, 0, 2, 1, 1, 1, 1, 2, 1, 1, 2 }); + auto result = parse(row, DecodeHints().setValidateCode39CheckSum(true)); + EXPECT_EQ(result.symbologyIdentifier(), "]A3"); + EXPECT_EQ(result.text(), L"A"); + + result = parse(row); + EXPECT_EQ(result.symbologyIdentifier(), "]A0"); + EXPECT_EQ(result.text(), L"AA"); + } + { + // Extended "a" + PatternRow row({ 1, 2, 1, 1, 1, 2, 1, 2, 1, 0, 2, 1, 1, 1, 1, 2, 1, 1, 2 }); + auto result = parse(row, DecodeHints().setTryCode39ExtendedMode(true)); + EXPECT_EQ(result.symbologyIdentifier(), "]A4"); + EXPECT_EQ(result.text(), L"a"); + + result = parse(row); + EXPECT_EQ(result.symbologyIdentifier(), "]A0"); + EXPECT_EQ(result.text(), L"+A"); + } + { + // Extended "a" with checksum + PatternRow row({ 1, 2, 1, 1, 1, 2, 1, 2, 1, 0, 2, 1, 1, 1, 1, 2, 1, 1, 2, 0, 2, 1, 1, 2, 1, 1, 2, 1, 1 }); + auto result = parse(row, DecodeHints().setTryCode39ExtendedMode(true).setValidateCode39CheckSum(true)); + EXPECT_EQ(result.symbologyIdentifier(), "]A7"); + EXPECT_EQ(result.text(), L"a"); + + result = parse(row); + EXPECT_EQ(result.symbologyIdentifier(), "]A0"); + EXPECT_EQ(result.text(), L"+A8"); + } +} diff --git a/test/unit/oned/rss/ODRSSExpandedBinaryDecoderTest.cpp b/test/unit/oned/rss/ODRSSExpandedBinaryDecoderTest.cpp index 4b0841522a..12df29d4bb 100644 --- a/test/unit/oned/rss/ODRSSExpandedBinaryDecoderTest.cpp +++ b/test/unit/oned/rss/ODRSSExpandedBinaryDecoderTest.cpp @@ -47,3 +47,37 @@ TEST(ODRSSExpandedBinaryDecoderTest, FNC1NumericLatch) result = parse("0001000100110010101000000100111011010111100001101100011111010000100000010000100"); EXPECT_EQ(result, "(10)12((422)123"); } + +TEST(ODRSSExpandedBinaryDecoderTest, DecodeAI01392x) +{ + std::string result; + + // Parse AIs following AI01392x + result = parse("00110000000000000100111010101000110111110111101000001110100111100001001"); + EXPECT_EQ(result, "(01)90012345678908(3920)1(20)01"); + + result = parse("0011000000000000010011101010100011011111011110101010111101000100111100000010000010"); + EXPECT_EQ(result, "(01)90012345678908(3922)7955(20)01"); + + result = parse("0100100100000000010011101010100011011111011110100110010010011100101010101101100010110111011101011001" + "01010101101111100000010000011101"); + EXPECT_EQ(result, "(01)90012345678908(3929)12345678901234(20)01"); +} + +TEST(ODRSSExpandedBinaryDecoderTest, DecodeAI01393x) +{ + std::string result; + + // Parse AIs following AI01393x + result = parse("0011010000000000010011101010100011011111011110100000000011000011101001111000010010010011000010000010" + "000100110"); + EXPECT_EQ(result, "(01)90012345678908(3930)0121(20)01(10)AB1"); + + result = parse("0011010000000000010011101010100011011111011110101000000010000010101010110111110000001000001010000000" + "010110000010000100110"); + EXPECT_EQ(result, "(01)90012345678908(3932)0081234(20)01(10)AB1"); + + result = parse("0011010000000000010011101010100011011111011110101111111001010010101010110110001011011101110101100101" + "0101011011001001001111000010010010011000010000010000100110"); + EXPECT_EQ(result, "(01)90012345678908(3933)997123456789012345(20)01(10)AB1"); +} diff --git a/test/unit/oned/rss/ODRSSFieldParserTest.cpp b/test/unit/oned/rss/ODRSSFieldParserTest.cpp new file mode 100644 index 0000000000..dab4897508 --- /dev/null +++ b/test/unit/oned/rss/ODRSSFieldParserTest.cpp @@ -0,0 +1,431 @@ +/* +* Copyright 2022 gitlost +* +* 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. +*/ + +#include "oned/rss/ODRSSFieldParser.h" + +#include "DecodeStatus.h" + +#include "gtest/gtest.h" + +using namespace ZXing; +using namespace ZXing::OneD::DataBar; + +TEST(ODRSSFieldParserTest, ParseFieldsInGeneralPurpose) +{ + std::string result; + + // 2-digit AIs + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("00123456789012345678", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(00)123456789012345678"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("0012345678901234567", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("001234567890123456789", result), DecodeStatus::NotFound); + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("16123456", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(16)123456"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("1612345", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("161234567", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("2212345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(22)12345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("221234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(22)1234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("22123456789012345678901", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("9112345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(91)123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("9112345678901234567890123456789012345678901234567890" + "123456789012345678901234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(91)12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("9112345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("9912345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(99)123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("9912345678901234567890123456789012345678901234567890" + "123456789012345678901234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(99)12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("9912345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901", result), DecodeStatus::NotFound); + + // 3-digit AIs + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("2351234567890123456789012345678", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(235)1234567890123456789012345678"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("235123456789012345678901234567", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(235)123456789012345678901234567"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("23512345678901234567890123456789", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("24312345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(243)12345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("2431234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(243)1234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("243123456789012345678901", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("253123456789012345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(253)123456789012345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("25312345678901234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(253)12345678901234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("2531234567890123456789012345678901", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("2551234567890123456789012345", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(255)1234567890123456789012345"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("255123456789012345678901234", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(255)123456789012345678901234"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("25512345678901234567890123456", result), DecodeStatus::NotFound); + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("4151234567890123", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(415)1234567890123"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("415123456789012", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("41512345678901234", result), DecodeStatus::NotFound); + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("4171234567890123", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(417)1234567890123"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("417123456789012", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("41712345678901234", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("421123456789012", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(421)123456789012"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("42112345678901", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(421)12345678901"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("4211234567890123", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("425123456789012345", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(425)123456789012345"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("42512345678901234", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(425)12345678901234"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("4251234567890123456", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("427123", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(427)123"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("42712", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(427)12"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("4271234", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("71012345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(710)12345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("7101234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(710)1234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("710123456789012345678901", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("71512345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(715)12345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("7151234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(715)1234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("715123456789012345678901", result), DecodeStatus::NotFound); + + // 4-digit variable 4th + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("3370123456", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(3370)123456"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("337012345", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("33701234567", result), DecodeStatus::NotFound); + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("3375123456", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(3375)123456"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("33751234567", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("337512345", result), DecodeStatus::NotFound); + + EXPECT_EQ(ParseFieldsInGeneralPurpose("3376123456", result), DecodeStatus::NoError); // Allow although > max 3375 + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("39401234", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(3940)1234"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("394012345", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("3940123", result), DecodeStatus::NotFound); + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("39431234", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(3943)1234"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("394312345", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("3943123", result), DecodeStatus::NotFound); + + EXPECT_EQ(ParseFieldsInGeneralPurpose("39441234", result), DecodeStatus::NoError); // Allow although > max 3943 + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("3950123456", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(3950)123456"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("39501234567", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("395012345", result), DecodeStatus::NotFound); + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("3955123456", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(3955)123456"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("39551234567", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("395512345", result), DecodeStatus::NotFound); + + EXPECT_EQ(ParseFieldsInGeneralPurpose("3956123456", result), DecodeStatus::NoError); // Allow although > max 3955 + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("7230123456789012345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7230)123456789012345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("723012345678901234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7230)12345678901234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("72301234567890123456789012345678901", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("7239123456789012345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7239)123456789012345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("723912345678901234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7239)12345678901234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("72391234567890123456789012345678901", result), DecodeStatus::NotFound); + + // 4-digit AIs + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("430012345678901234567890123456789012345", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(4300)12345678901234567890123456789012345"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("43001234567890123456789012345678901234", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(4300)1234567890123456789012345678901234"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("4300123456789012345678901234567890123456", result), DecodeStatus::NotFound); + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("430712", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(4307)12"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("4307123", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("43071", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("4308123456789012345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(4308)123456789012345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("430812345678901234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(4308)12345678901234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("43081234567890123456789012345678901", result), DecodeStatus::NotFound); + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("431712", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(4317)12"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("4317123", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("43171", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("431812345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(4318)12345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("43181234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(4318)1234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("4318123456789012345678901", result), DecodeStatus::NotFound); + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("43211", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(4321)1"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("432112", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("4321", result), DecodeStatus::NotFound); + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("4326123456", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(4326)123456"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("43261234567", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("432612345", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("70041234", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7004)1234"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("7004123", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7004)123"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("700412345", result), DecodeStatus::NotFound); + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("7006123456", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7006)123456"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("70061234567", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("700612345", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("701012", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7010)12"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("70101", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7010)1"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("7010123", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("702012345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7020)12345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("70201234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7020)1234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("7020123456789012345678901", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("7023123456789012345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7023)123456789012345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("702312345678901234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7023)12345678901234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("70231234567890123456789012345678901", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("70401234", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7040)1234"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("704012345", result), DecodeStatus::NotFound); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("7040123", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("724012345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7240)12345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("72401234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(7240)1234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("7240123456789012345678901", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("80071234567890123456789012345678901234", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8007)1234567890123456789012345678901234"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("8007123456789012345678901234567890123", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8007)123456789012345678901234567890123"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("800712345678901234567890123456789012345", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("800912345678901234567890123456789012345678901234567890", result), + DecodeStatus::NoError); + EXPECT_EQ(result, "(8009)12345678901234567890123456789012345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("80091234567890123456789012345678901234567890123456789", result), + DecodeStatus::NoError); + EXPECT_EQ(result, "(8009)1234567890123456789012345678901234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("8009123456789012345678901234567890123456789012345678901", result), + DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("80131234567890123456789012345", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8013)1234567890123456789012345"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("8013123456789012345678901234", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8013)123456789012345678901234"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("801312345678901234567890123456", result), DecodeStatus::NotFound); + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("8017123456789012345678", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8017)123456789012345678"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("80171234567890123456789", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("801712345678901234567", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("80191234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8019)1234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("8019123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8019)123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("801912345678901", result), DecodeStatus::NotFound); + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("8026123456789012345678", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8026)123456789012345678"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("80261234567890123456789", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("802612345678901234567", result), DecodeStatus::NotFound); + + // Non-existing + EXPECT_EQ(ParseFieldsInGeneralPurpose("8100123456", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("81011234567890", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("810212", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("811012345678901234567890123456789012345678901234567890" + "12345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8110)1234567890123456789012345678901234567890123456789012345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("811012345678901234567890123456789012345678901234567890" + "1234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8110)123456789012345678901234567890123456789012345678901234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("811012345678901234567890123456789012345678901234567890" + "123456789012345678901", result), DecodeStatus::NotFound); + + // Fixed length + EXPECT_EQ(ParseFieldsInGeneralPurpose("81111234", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8111)1234"); + // Incorrect lengths + EXPECT_EQ(ParseFieldsInGeneralPurpose("811112345", result), DecodeStatus::NotFound); + EXPECT_EQ(ParseFieldsInGeneralPurpose("8111123", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("811212345678901234567890123456789012345678901234567890" + "12345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8112)1234567890123456789012345678901234567890123456789012345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("811212345678901234567890123456789012345678901234567890" + "1234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8112)123456789012345678901234567890123456789012345678901234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("811212345678901234567890123456789012345678901234567890" + "123456789012345678901", result), DecodeStatus::NotFound); + + // Max length + EXPECT_EQ(ParseFieldsInGeneralPurpose("820012345678901234567890123456789012345678901234567890" + "12345678901234567890", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8200)1234567890123456789012345678901234567890123456789012345678901234567890"); + EXPECT_EQ(ParseFieldsInGeneralPurpose("820012345678901234567890123456789012345678901234567890" + "1234567890123456789", result), DecodeStatus::NoError); + EXPECT_EQ(result, "(8200)123456789012345678901234567890123456789012345678901234567890123456789"); + // Too long + EXPECT_EQ(ParseFieldsInGeneralPurpose("820012345678901234567890123456789012345678901234567890" + "123456789012345678901", result), DecodeStatus::NotFound); +} diff --git a/test/unit/pdf417/PDF417WriterTest.cpp b/test/unit/pdf417/PDF417WriterTest.cpp index ac6395261c..c88b732490 100644 --- a/test/unit/pdf417/PDF417WriterTest.cpp +++ b/test/unit/pdf417/PDF417WriterTest.cpp @@ -31,28 +31,36 @@ TEST(PDF417WriterTest, DataMatrixImageWriter) BitMatrix matrix = writer.encode(L"Hello Google", size, size); auto actual = ToString(matrix, 'X', ' ', true); EXPECT_EQ(actual, - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" - "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n"); + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n" + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n"); } diff --git a/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp b/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp index ab94c74d2f..4888138a10 100644 --- a/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp +++ b/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp @@ -96,3 +96,49 @@ TEST(QRDecodedBitStreamParserTest, HanziLevel1) auto result = DecodeBitStream(ba.toBytes(), *Version::VersionForNumber(1), ErrorCorrectionLevel::Medium, "").text(); EXPECT_EQ(L"\u30a2", result); } + +TEST(QRDecodedBitStreamParserTest, SymbologyIdentifier) +{ + const Version& version = *Version::VersionForNumber(1); + const ErrorCorrectionLevel ecLevel = ErrorCorrectionLevel::Medium; + DecoderResult result; + + // Plain "ANUM(1) A" + result = DecodeBitStream({0x20, 0x09, 0x40}, version, ecLevel, ""); + EXPECT_EQ(result.symbologyIdentifier(), "]Q1"); + EXPECT_EQ(result.text(), L"A"); + + // GS1 "FNC1(1st) NUM(4) 2001" + result = DecodeBitStream({0x51, 0x01, 0x0C, 0x81, 0x00}, version, ecLevel, ""); + EXPECT_EQ(result.symbologyIdentifier(), "]Q3"); + EXPECT_EQ(result.text(), L"2001"); // "(20)01" + + // GS1 "NUM(4) 2001 FNC1(1st) 301" - FNC1(1st) can occur anywhere + result = DecodeBitStream({0x10, 0x10, 0xC8, 0x15, 0x10, 0x0D, 0x2D, 0x00}, version, ecLevel, ""); + EXPECT_EQ(result.symbologyIdentifier(), "]Q3"); + EXPECT_EQ(result.text(), L"2001301"); // "(20)01(30)1" + + // AIM "FNC1(2nd) 99 (0x63) ANUM(1) A" + result = DecodeBitStream({0x96, 0x32, 0x00, 0x94, 0x00}, version, ecLevel, ""); + EXPECT_EQ(result.symbologyIdentifier(), "]Q5"); + EXPECT_EQ(result.text(), L"99A"); + + // AIM "BYTE(1) A FNC1(2nd) 99 (0x63) BYTE(1) B" - FNC1(2nd) can occur anywhere + result = DecodeBitStream({0x40, 0x14, 0x19, 0x63, 0x40, 0x14, 0x20, 0x00}, version, ecLevel, ""); + EXPECT_EQ(result.symbologyIdentifier(), "]Q5"); + EXPECT_EQ(result.text(), L"99AB"); // Application Indicator prefixed to data + + // AIM "FNC1(2nd) A (100 + 61 = 0xA5) ANUM(1) B" + result = DecodeBitStream({0x9A, 0x52, 0x00, 0x96, 0x00}, version, ecLevel, ""); + EXPECT_EQ(result.symbologyIdentifier(), "]Q5"); + EXPECT_EQ(result.text(), L"AB"); + + // AIM "FNC1(2nd) a (100 + 97 = 0xC5) ANUM(1) B" + result = DecodeBitStream({0x9C, 0x52, 0x00, 0x96, 0x00}, version, ecLevel, ""); + EXPECT_EQ(result.symbologyIdentifier(), "]Q5"); + EXPECT_EQ(result.text(), L"aB"); + + // Bad AIM Application Indicator "FNC1(2nd) @ (0xA4) ANUM(1) B" + result = DecodeBitStream({0x9A, 0x42, 0x00, 0x96, 0x00}, version, ecLevel, ""); + EXPECT_FALSE(result.isValid()); +} diff --git a/thirdparty/stb/stb_image.h b/thirdparty/stb/stb_image.h deleted file mode 100644 index bf283b7bf7..0000000000 --- a/thirdparty/stb/stb_image.h +++ /dev/null @@ -1,7660 +0,0 @@ -/* stb_image - v2.25 - public domain image loader - http://nothings.org/stb - no warranty implied; use at your own risk - - Do this: - #define STB_IMAGE_IMPLEMENTATION - before you include this file in *one* C or C++ file to create the implementation. - - // i.e. it should look like this: - #include ... - #include ... - #include ... - #define STB_IMAGE_IMPLEMENTATION - #include "stb_image.h" - - You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. - And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free - - - QUICK NOTES: - Primarily of interest to game developers and other people who can - avoid problematic images and only need the trivial interface - - JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) - PNG 1/2/4/8/16-bit-per-channel - - TGA (not sure what subset, if a subset) - BMP non-1bpp, non-RLE - PSD (composited view only, no extra channels, 8/16 bit-per-channel) - - GIF (*comp always reports as 4-channel) - HDR (radiance rgbE format) - PIC (Softimage PIC) - PNM (PPM and PGM binary only) - - Animated GIF still needs a proper API, but here's one way to do it: - http://gist.github.com/urraka/685d9a6340b26b830d49 - - - decode from memory or through FILE (define STBI_NO_STDIO to remove code) - - decode from arbitrary I/O callbacks - - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) - - Full documentation under "DOCUMENTATION" below. - - -LICENSE - - See end of file for license information. - -RECENT REVISION HISTORY: - - 2.25 (2020-02-02) fix warnings - 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically - 2.23 (2019-08-11) fix clang static analysis warning - 2.22 (2019-03-04) gif fixes, fix warnings - 2.21 (2019-02-25) fix typo in comment - 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs - 2.19 (2018-02-11) fix warning - 2.18 (2018-01-30) fix warnings - 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings - 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes - 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC - 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs - 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes - 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes - 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 - RGB-format JPEG; remove white matting in PSD; - allocate large structures on the stack; - correct channel count for PNG & BMP - 2.10 (2016-01-22) avoid warning introduced in 2.09 - 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED - - See end of file for full revision history. - - - ============================ Contributors ========================= - - Image formats Extensions, features - Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) - Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) - Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) - Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) - Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) - Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) - Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) - github:urraka (animated gif) Junggon Kim (PNM comments) - Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) - socks-the-fox (16-bit PNG) - Jeremy Sawicki (handle all ImageNet JPGs) - Optimizations & bugfixes Mikhail Morozov (1-bit BMP) - Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) - Arseny Kapoulkine - John-Mark Allen - Carmelo J Fdez-Aguera - - Bug & warning fixes - Marc LeBlanc David Woo Guillaume George Martins Mozeiko - Christpher Lloyd Jerry Jansson Joseph Thomson Phil Jordan - Dave Moore Roy Eltham Hayaki Saito Nathan Reed - Won Chun Luke Graham Johan Duparc Nick Verigakis - the Horde3D community Thomas Ruf Ronny Chevalier github:rlyeh - Janez Zemva John Bartholomew Michal Cichon github:romigrou - Jonathan Blow Ken Hamada Tero Hanninen github:svdijk - Laurent Gomila Cort Stratton Sergio Gonzalez github:snagar - Aruelien Pocheville Thibault Reuille Cass Everitt github:Zelex - Ryamond Barbiero Paul Du Bois Engin Manap github:grim210 - Aldo Culquicondor Philipp Wiesemann Dale Weiler github:sammyhw - Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:phprus - Julian Raschke Gregory Mullen Baldur Karlsson github:poppolopoppo - Christian Floisand Kevin Schmidt JR Smith github:darealshinji - Brad Weinberger Matvey Cherevko github:Michaelangel007 - Blazej Dariusz Roszkowski Alexander Veselov -*/ - -#ifndef STBI_INCLUDE_STB_IMAGE_H -#define STBI_INCLUDE_STB_IMAGE_H - -// DOCUMENTATION -// -// Limitations: -// - no 12-bit-per-channel JPEG -// - no JPEGs with arithmetic coding -// - GIF always returns *comp=4 -// -// Basic usage (see HDR discussion below for HDR usage): -// int x,y,n; -// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); -// // ... process data if not NULL ... -// // ... x = width, y = height, n = # 8-bit components per pixel ... -// // ... replace '0' with '1'..'4' to force that many components per pixel -// // ... but 'n' will always be the number that it would have been if you said 0 -// stbi_image_free(data) -// -// Standard parameters: -// int *x -- outputs image width in pixels -// int *y -- outputs image height in pixels -// int *channels_in_file -- outputs # of image components in image file -// int desired_channels -- if non-zero, # of image components requested in result -// -// The return value from an image loader is an 'unsigned char *' which points -// to the pixel data, or NULL on an allocation failure or if the image is -// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, -// with each pixel consisting of N interleaved 8-bit components; the first -// pixel pointed to is top-left-most in the image. There is no padding between -// image scanlines or between pixels, regardless of format. The number of -// components N is 'desired_channels' if desired_channels is non-zero, or -// *channels_in_file otherwise. If desired_channels is non-zero, -// *channels_in_file has the number of components that _would_ have been -// output otherwise. E.g. if you set desired_channels to 4, you will always -// get RGBA output, but you can check *channels_in_file to see if it's trivially -// opaque because e.g. there were only 3 channels in the source image. -// -// An output image with N components has the following components interleaved -// in this order in each pixel: -// -// N=#comp components -// 1 grey -// 2 grey, alpha -// 3 red, green, blue -// 4 red, green, blue, alpha -// -// If image loading fails for any reason, the return value will be NULL, -// and *x, *y, *channels_in_file will be unchanged. The function -// stbi_failure_reason() can be queried for an extremely brief, end-user -// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS -// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly -// more user-friendly ones. -// -// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. -// -// =========================================================================== -// -// UNICODE: -// -// If compiling for Windows and you wish to use Unicode filenames, compile -// with -// #define STBI_WINDOWS_UTF8 -// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert -// Windows wchar_t filenames to utf8. -// -// =========================================================================== -// -// Philosophy -// -// stb libraries are designed with the following priorities: -// -// 1. easy to use -// 2. easy to maintain -// 3. good performance -// -// Sometimes I let "good performance" creep up in priority over "easy to maintain", -// and for best performance I may provide less-easy-to-use APIs that give higher -// performance, in addition to the easy-to-use ones. Nevertheless, it's important -// to keep in mind that from the standpoint of you, a client of this library, -// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. -// -// Some secondary priorities arise directly from the first two, some of which -// provide more explicit reasons why performance can't be emphasized. -// -// - Portable ("ease of use") -// - Small source code footprint ("easy to maintain") -// - No dependencies ("ease of use") -// -// =========================================================================== -// -// I/O callbacks -// -// I/O callbacks allow you to read from arbitrary sources, like packaged -// files or some other source. Data read from callbacks are processed -// through a small internal buffer (currently 128 bytes) to try to reduce -// overhead. -// -// The three functions you must define are "read" (reads some bytes of data), -// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). -// -// =========================================================================== -// -// SIMD support -// -// The JPEG decoder will try to automatically use SIMD kernels on x86 when -// supported by the compiler. For ARM Neon support, you must explicitly -// request it. -// -// (The old do-it-yourself SIMD API is no longer supported in the current -// code.) -// -// On x86, SSE2 will automatically be used when available based on a run-time -// test; if not, the generic C versions are used as a fall-back. On ARM targets, -// the typical path is to have separate builds for NEON and non-NEON devices -// (at least this is true for iOS and Android). Therefore, the NEON support is -// toggled by a build flag: define STBI_NEON to get NEON loops. -// -// If for some reason you do not want to use any of SIMD code, or if -// you have issues compiling it, you can disable it entirely by -// defining STBI_NO_SIMD. -// -// =========================================================================== -// -// HDR image support (disable by defining STBI_NO_HDR) -// -// stb_image supports loading HDR images in general, and currently the Radiance -// .HDR file format specifically. You can still load any file through the existing -// interface; if you attempt to load an HDR file, it will be automatically remapped -// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; -// both of these constants can be reconfigured through this interface: -// -// stbi_hdr_to_ldr_gamma(2.2f); -// stbi_hdr_to_ldr_scale(1.0f); -// -// (note, do not use _inverse_ constants; stbi_image will invert them -// appropriately). -// -// Additionally, there is a new, parallel interface for loading files as -// (linear) floats to preserve the full dynamic range: -// -// float *data = stbi_loadf(filename, &x, &y, &n, 0); -// -// If you load LDR images through this interface, those images will -// be promoted to floating point values, run through the inverse of -// constants corresponding to the above: -// -// stbi_ldr_to_hdr_scale(1.0f); -// stbi_ldr_to_hdr_gamma(2.2f); -// -// Finally, given a filename (or an open file or memory block--see header -// file for details) containing image data, you can query for the "most -// appropriate" interface to use (that is, whether the image is HDR or -// not), using: -// -// stbi_is_hdr(char *filename); -// -// =========================================================================== -// -// iPhone PNG support: -// -// By default we convert iphone-formatted PNGs back to RGB, even though -// they are internally encoded differently. You can disable this conversion -// by calling stbi_convert_iphone_png_to_rgb(0), in which case -// you will always just get the native iphone "format" through (which -// is BGR stored in RGB). -// -// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per -// pixel to remove any premultiplied alpha *only* if the image file explicitly -// says there's premultiplied data (currently only happens in iPhone images, -// and only if iPhone convert-to-rgb processing is on). -// -// =========================================================================== -// -// ADDITIONAL CONFIGURATION -// -// - You can suppress implementation of any of the decoders to reduce -// your code footprint by #defining one or more of the following -// symbols before creating the implementation. -// -// STBI_NO_JPEG -// STBI_NO_PNG -// STBI_NO_BMP -// STBI_NO_PSD -// STBI_NO_TGA -// STBI_NO_GIF -// STBI_NO_HDR -// STBI_NO_PIC -// STBI_NO_PNM (.ppm and .pgm) -// -// - You can request *only* certain decoders and suppress all other ones -// (this will be more forward-compatible, as addition of new decoders -// doesn't require you to disable them explicitly): -// -// STBI_ONLY_JPEG -// STBI_ONLY_PNG -// STBI_ONLY_BMP -// STBI_ONLY_PSD -// STBI_ONLY_TGA -// STBI_ONLY_GIF -// STBI_ONLY_HDR -// STBI_ONLY_PIC -// STBI_ONLY_PNM (.ppm and .pgm) -// -// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still -// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB -// - - -#ifndef STBI_NO_STDIO -#include -#endif // STBI_NO_STDIO - -#define STBI_VERSION 1 - -enum -{ - STBI_default = 0, // only used for desired_channels - - STBI_grey = 1, - STBI_grey_alpha = 2, - STBI_rgb = 3, - STBI_rgb_alpha = 4 -}; - -#include -typedef unsigned char stbi_uc; -typedef unsigned short stbi_us; - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef STBIDEF -#ifdef STB_IMAGE_STATIC -#define STBIDEF static -#else -#define STBIDEF extern -#endif -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// PRIMARY API - works on images of any type -// - -// -// load image by filename, open file, or memory buffer -// - -typedef struct -{ - int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read - void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative - int (*eof) (void *user); // returns nonzero if we are at end of file/data -} stbi_io_callbacks; - -//////////////////////////////////// -// -// 8-bits-per-channel interface -// - -STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); - -#ifndef STBI_NO_STDIO -STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); -// for stbi_load_from_file, file pointer is left pointing immediately after image -#endif - -#ifndef STBI_NO_GIF -STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); -#endif - -#ifdef STBI_WINDOWS_UTF8 -STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); -#endif - -//////////////////////////////////// -// -// 16-bits-per-channel interface -// - -STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); - -#ifndef STBI_NO_STDIO -STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); -#endif - -//////////////////////////////////// -// -// float-per-channel interface -// -#ifndef STBI_NO_LINEAR - STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); - STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); - - #ifndef STBI_NO_STDIO - STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); - STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); - #endif -#endif - -#ifndef STBI_NO_HDR - STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); - STBIDEF void stbi_hdr_to_ldr_scale(float scale); -#endif // STBI_NO_HDR - -#ifndef STBI_NO_LINEAR - STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); - STBIDEF void stbi_ldr_to_hdr_scale(float scale); -#endif // STBI_NO_LINEAR - -// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename); -STBIDEF int stbi_is_hdr_from_file(FILE *f); -#endif // STBI_NO_STDIO - - -// get a VERY brief reason for failure -// on most compilers (and ALL modern mainstream compilers) this is threadsafe -STBIDEF const char *stbi_failure_reason (void); - -// free the loaded image -- this is just free() -STBIDEF void stbi_image_free (void *retval_from_stbi_load); - -// get image dimensions & components without fully decoding -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); -STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); -STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); -STBIDEF int stbi_is_16_bit (char const *filename); -STBIDEF int stbi_is_16_bit_from_file(FILE *f); -#endif - - - -// for image formats that explicitly notate that they have premultiplied alpha, -// we just return the colors as stored in the file. set this flag to force -// unpremultiplication. results are undefined if the unpremultiply overflow. -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); - -// indicate whether we should process iphone images back to canonical format, -// or just pass them through "as-is" -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); - -// flip the image vertically, so the first pixel in the output array is the bottom left -STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); - -// as above, but only applies to images loaded on the thread that calls the function -// this function is only available if your compiler supports thread-local variables; -// calling it will fail to link if your compiler doesn't -STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); - -// ZLIB client - used by PNG, available for other purposes - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); -STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - -STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - - -#ifdef __cplusplus -} -#endif - -// -// -//// end header file ///////////////////////////////////////////////////// -#endif // STBI_INCLUDE_STB_IMAGE_H - -#ifdef STB_IMAGE_IMPLEMENTATION - -#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ - || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ - || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ - || defined(STBI_ONLY_ZLIB) - #ifndef STBI_ONLY_JPEG - #define STBI_NO_JPEG - #endif - #ifndef STBI_ONLY_PNG - #define STBI_NO_PNG - #endif - #ifndef STBI_ONLY_BMP - #define STBI_NO_BMP - #endif - #ifndef STBI_ONLY_PSD - #define STBI_NO_PSD - #endif - #ifndef STBI_ONLY_TGA - #define STBI_NO_TGA - #endif - #ifndef STBI_ONLY_GIF - #define STBI_NO_GIF - #endif - #ifndef STBI_ONLY_HDR - #define STBI_NO_HDR - #endif - #ifndef STBI_ONLY_PIC - #define STBI_NO_PIC - #endif - #ifndef STBI_ONLY_PNM - #define STBI_NO_PNM - #endif -#endif - -#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) -#define STBI_NO_ZLIB -#endif - - -#include -#include // ptrdiff_t on osx -#include -#include -#include - -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -#include // ldexp, pow -#endif - -#ifndef STBI_NO_STDIO -#include -#endif - -#ifndef STBI_ASSERT -#include -#define STBI_ASSERT(x) assert(x) -#endif - -#ifdef __cplusplus -#define STBI_EXTERN extern "C" -#else -#define STBI_EXTERN extern -#endif - - -#ifndef _MSC_VER - #ifdef __cplusplus - #define stbi_inline inline - #else - #define stbi_inline - #endif -#else - #define stbi_inline __forceinline -#endif - -#ifndef STBI_NO_THREAD_LOCALS - #if defined(__cplusplus) && __cplusplus >= 201103L - #define STBI_THREAD_LOCAL thread_local - #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L - #define STBI_THREAD_LOCAL _Thread_local - #elif defined(__GNUC__) - #define STBI_THREAD_LOCAL __thread - #elif defined(_MSC_VER) - #define STBI_THREAD_LOCAL __declspec(thread) -#endif -#endif - -#ifdef _MSC_VER -typedef unsigned short stbi__uint16; -typedef signed short stbi__int16; -typedef unsigned int stbi__uint32; -typedef signed int stbi__int32; -#else -#include -typedef uint16_t stbi__uint16; -typedef int16_t stbi__int16; -typedef uint32_t stbi__uint32; -typedef int32_t stbi__int32; -#endif - -// should produce compiler error if size is wrong -typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; - -#ifdef _MSC_VER -#define STBI_NOTUSED(v) (void)(v) -#else -#define STBI_NOTUSED(v) (void)sizeof(v) -#endif - -#ifdef _MSC_VER -#define STBI_HAS_LROTL -#endif - -#ifdef STBI_HAS_LROTL - #define stbi_lrot(x,y) _lrotl(x,y) -#else - #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) -#endif - -#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) -// ok -#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) -// ok -#else -#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." -#endif - -#ifndef STBI_MALLOC -#define STBI_MALLOC(sz) malloc(sz) -#define STBI_REALLOC(p,newsz) realloc(p,newsz) -#define STBI_FREE(p) free(p) -#endif - -#ifndef STBI_REALLOC_SIZED -#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) -#endif - -// x86/x64 detection -#if defined(__x86_64__) || defined(_M_X64) -#define STBI__X64_TARGET -#elif defined(__i386) || defined(_M_IX86) -#define STBI__X86_TARGET -#endif - -#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) -// gcc doesn't support sse2 intrinsics unless you compile with -msse2, -// which in turn means it gets to use SSE2 everywhere. This is unfortunate, -// but previous attempts to provide the SSE2 functions with runtime -// detection caused numerous issues. The way architecture extensions are -// exposed in GCC/Clang is, sadly, not really suited for one-file libs. -// New behavior: if compiled with -msse2, we use SSE2 without any -// detection; if not, we don't use it at all. -#define STBI_NO_SIMD -#endif - -#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) -// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET -// -// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the -// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. -// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not -// simultaneously enabling "-mstackrealign". -// -// See https://github.com/nothings/stb/issues/81 for more information. -// -// So default to no SSE2 on 32-bit MinGW. If you've read this far and added -// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. -#define STBI_NO_SIMD -#endif - -#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) -#define STBI_SSE2 -#include - -#ifdef _MSC_VER - -#if _MSC_VER >= 1400 // not VC6 -#include // __cpuid -static int stbi__cpuid3(void) -{ - int info[4]; - __cpuid(info,1); - return info[3]; -} -#else -static int stbi__cpuid3(void) -{ - int res; - __asm { - mov eax,1 - cpuid - mov res,edx - } - return res; -} -#endif - -#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name - -#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) -static int stbi__sse2_available(void) -{ - int info3 = stbi__cpuid3(); - return ((info3 >> 26) & 1) != 0; -} -#endif - -#else // assume GCC-style if not VC++ -#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) - -#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) -static int stbi__sse2_available(void) -{ - // If we're even attempting to compile this on GCC/Clang, that means - // -msse2 is on, which means the compiler is allowed to use SSE2 - // instructions at will, and so are we. - return 1; -} -#endif - -#endif -#endif - -// ARM NEON -#if defined(STBI_NO_SIMD) && defined(STBI_NEON) -#undef STBI_NEON -#endif - -#ifdef STBI_NEON -#include -// assume GCC or Clang on ARM targets -#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) -#endif - -#ifndef STBI_SIMD_ALIGN -#define STBI_SIMD_ALIGN(type, name) type name -#endif - -/////////////////////////////////////////////// -// -// stbi__context struct and start_xxx functions - -// stbi__context structure is our basic context used by all images, so it -// contains all the IO context, plus some basic image information -typedef struct -{ - stbi__uint32 img_x, img_y; - int img_n, img_out_n; - - stbi_io_callbacks io; - void *io_user_data; - - int read_from_callbacks; - int buflen; - stbi_uc buffer_start[128]; - - stbi_uc *img_buffer, *img_buffer_end; - stbi_uc *img_buffer_original, *img_buffer_original_end; -} stbi__context; - - -static void stbi__refill_buffer(stbi__context *s); - -// initialize a memory-decode context -static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) -{ - s->io.read = NULL; - s->read_from_callbacks = 0; - s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; - s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; -} - -// initialize a callback-based context -static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) -{ - s->io = *c; - s->io_user_data = user; - s->buflen = sizeof(s->buffer_start); - s->read_from_callbacks = 1; - s->img_buffer_original = s->buffer_start; - stbi__refill_buffer(s); - s->img_buffer_original_end = s->img_buffer_end; -} - -#ifndef STBI_NO_STDIO - -static int stbi__stdio_read(void *user, char *data, int size) -{ - return (int) fread(data,1,size,(FILE*) user); -} - -static void stbi__stdio_skip(void *user, int n) -{ - fseek((FILE*) user, n, SEEK_CUR); -} - -static int stbi__stdio_eof(void *user) -{ - return feof((FILE*) user); -} - -static stbi_io_callbacks stbi__stdio_callbacks = -{ - stbi__stdio_read, - stbi__stdio_skip, - stbi__stdio_eof, -}; - -static void stbi__start_file(stbi__context *s, FILE *f) -{ - stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); -} - -//static void stop_file(stbi__context *s) { } - -#endif // !STBI_NO_STDIO - -static void stbi__rewind(stbi__context *s) -{ - // conceptually rewind SHOULD rewind to the beginning of the stream, - // but we just rewind to the beginning of the initial buffer, because - // we only use it after doing 'test', which only ever looks at at most 92 bytes - s->img_buffer = s->img_buffer_original; - s->img_buffer_end = s->img_buffer_original_end; -} - -enum -{ - STBI_ORDER_RGB, - STBI_ORDER_BGR -}; - -typedef struct -{ - int bits_per_channel; - int num_channels; - int channel_order; -} stbi__result_info; - -#ifndef STBI_NO_JPEG -static int stbi__jpeg_test(stbi__context *s); -static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PNG -static int stbi__png_test(stbi__context *s); -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__png_is16(stbi__context *s); -#endif - -#ifndef STBI_NO_BMP -static int stbi__bmp_test(stbi__context *s); -static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_TGA -static int stbi__tga_test(stbi__context *s); -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PSD -static int stbi__psd_test(stbi__context *s); -static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__psd_is16(stbi__context *s); -#endif - -#ifndef STBI_NO_HDR -static int stbi__hdr_test(stbi__context *s); -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PIC -static int stbi__pic_test(stbi__context *s); -static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_GIF -static int stbi__gif_test(stbi__context *s); -static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PNM -static int stbi__pnm_test(stbi__context *s); -static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -static -#ifdef STBI_THREAD_LOCAL -STBI_THREAD_LOCAL -#endif -const char *stbi__g_failure_reason; - -STBIDEF const char *stbi_failure_reason(void) -{ - return stbi__g_failure_reason; -} - -#ifndef STBI_NO_FAILURE_STRINGS -static int stbi__err(const char *str) -{ - stbi__g_failure_reason = str; - return 0; -} -#endif - -static void *stbi__malloc(size_t size) -{ - return STBI_MALLOC(size); -} - -// stb_image uses ints pervasively, including for offset calculations. -// therefore the largest decoded image size we can support with the -// current code, even on 64-bit targets, is INT_MAX. this is not a -// significant limitation for the intended use case. -// -// we do, however, need to make sure our size calculations don't -// overflow. hence a few helper functions for size calculations that -// multiply integers together, making sure that they're non-negative -// and no overflow occurs. - -// return 1 if the sum is valid, 0 on overflow. -// negative terms are considered invalid. -static int stbi__addsizes_valid(int a, int b) -{ - if (b < 0) return 0; - // now 0 <= b <= INT_MAX, hence also - // 0 <= INT_MAX - b <= INTMAX. - // And "a + b <= INT_MAX" (which might overflow) is the - // same as a <= INT_MAX - b (no overflow) - return a <= INT_MAX - b; -} - -// returns 1 if the product is valid, 0 on overflow. -// negative factors are considered invalid. -static int stbi__mul2sizes_valid(int a, int b) -{ - if (a < 0 || b < 0) return 0; - if (b == 0) return 1; // mul-by-0 is always safe - // portable way to check for no overflows in a*b - return a <= INT_MAX/b; -} - -#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) -// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow -static int stbi__mad2sizes_valid(int a, int b, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); -} -#endif - -// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow -static int stbi__mad3sizes_valid(int a, int b, int c, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && - stbi__addsizes_valid(a*b*c, add); -} - -// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && - stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); -} -#endif - -#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) -// mallocs with size overflow checking -static void *stbi__malloc_mad2(int a, int b, int add) -{ - if (!stbi__mad2sizes_valid(a, b, add)) return NULL; - return stbi__malloc(a*b + add); -} -#endif - -static void *stbi__malloc_mad3(int a, int b, int c, int add) -{ - if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; - return stbi__malloc(a*b*c + add); -} - -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) -{ - if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; - return stbi__malloc(a*b*c*d + add); -} -#endif - -// stbi__err - error -// stbi__errpf - error returning pointer to float -// stbi__errpuc - error returning pointer to unsigned char - -#ifdef STBI_NO_FAILURE_STRINGS - #define stbi__err(x,y) 0 -#elif defined(STBI_FAILURE_USERMSG) - #define stbi__err(x,y) stbi__err(y) -#else - #define stbi__err(x,y) stbi__err(x) -#endif - -#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) -#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) - -STBIDEF void stbi_image_free(void *retval_from_stbi_load) -{ - STBI_FREE(retval_from_stbi_load); -} - -#ifndef STBI_NO_LINEAR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); -#endif - -#ifndef STBI_NO_HDR -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); -#endif - -static int stbi__vertically_flip_on_load_global = 0; - -STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) -{ - stbi__vertically_flip_on_load_global = flag_true_if_should_flip; -} - -#ifndef STBI_THREAD_LOCAL -#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global -#else -static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; - -STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) -{ - stbi__vertically_flip_on_load_local = flag_true_if_should_flip; - stbi__vertically_flip_on_load_set = 1; -} - -#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ - ? stbi__vertically_flip_on_load_local \ - : stbi__vertically_flip_on_load_global) -#endif // STBI_THREAD_LOCAL - -static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) -{ - memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields - ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed - ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order - ri->num_channels = 0; - - #ifndef STBI_NO_JPEG - if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PNG - if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_BMP - if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_GIF - if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PSD - if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); - #else - STBI_NOTUSED(bpc); - #endif - #ifndef STBI_NO_PIC - if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PNM - if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); - #endif - - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); - return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); - } - #endif - - #ifndef STBI_NO_TGA - // test tga last because it's a crappy test! - if (stbi__tga_test(s)) - return stbi__tga_load(s,x,y,comp,req_comp, ri); - #endif - - return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); -} - -static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) -{ - int i; - int img_len = w * h * channels; - stbi_uc *reduced; - - reduced = (stbi_uc *) stbi__malloc(img_len); - if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); - - for (i = 0; i < img_len; ++i) - reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling - - STBI_FREE(orig); - return reduced; -} - -static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) -{ - int i; - int img_len = w * h * channels; - stbi__uint16 *enlarged; - - enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); - if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); - - for (i = 0; i < img_len; ++i) - enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff - - STBI_FREE(orig); - return enlarged; -} - -static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) -{ - int row; - size_t bytes_per_row = (size_t)w * bytes_per_pixel; - stbi_uc temp[2048]; - stbi_uc *bytes = (stbi_uc *)image; - - for (row = 0; row < (h>>1); row++) { - stbi_uc *row0 = bytes + row*bytes_per_row; - stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; - // swap row0 with row1 - size_t bytes_left = bytes_per_row; - while (bytes_left) { - size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); - memcpy(temp, row0, bytes_copy); - memcpy(row0, row1, bytes_copy); - memcpy(row1, temp, bytes_copy); - row0 += bytes_copy; - row1 += bytes_copy; - bytes_left -= bytes_copy; - } - } -} - -#ifndef STBI_NO_GIF -static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) -{ - int slice; - int slice_size = w * h * bytes_per_pixel; - - stbi_uc *bytes = (stbi_uc *)image; - for (slice = 0; slice < z; ++slice) { - stbi__vertical_flip(bytes, w, h, bytes_per_pixel); - bytes += slice_size; - } -} -#endif - -static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); - - if (result == NULL) - return NULL; - - if (ri.bits_per_channel != 8) { - STBI_ASSERT(ri.bits_per_channel == 16); - result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); - ri.bits_per_channel = 8; - } - - // @TODO: move stbi__convert_format to here - - if (stbi__vertically_flip_on_load) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); - } - - return (unsigned char *) result; -} - -static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); - - if (result == NULL) - return NULL; - - if (ri.bits_per_channel != 16) { - STBI_ASSERT(ri.bits_per_channel == 8); - result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); - ri.bits_per_channel = 16; - } - - // @TODO: move stbi__convert_format16 to here - // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision - - if (stbi__vertically_flip_on_load) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); - } - - return (stbi__uint16 *) result; -} - -#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) -static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) -{ - if (stbi__vertically_flip_on_load && result != NULL) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); - } -} -#endif - -#ifndef STBI_NO_STDIO - -#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) -STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); -STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); -#endif - -#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) -STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) -{ - return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); -} -#endif - -static FILE *stbi__fopen(char const *filename, char const *mode) -{ - FILE *f; -#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) - wchar_t wMode[64]; - wchar_t wFilename[1024]; - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) - return 0; - - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) - return 0; - -#if _MSC_VER >= 1400 - if (0 != _wfopen_s(&f, wFilename, wMode)) - f = 0; -#else - f = _wfopen(wFilename, wMode); -#endif - -#elif defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != fopen_s(&f, filename, mode)) - f=0; -#else - f = fopen(filename, mode); -#endif - return f; -} - - -STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - unsigned char *result; - if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); - } - return result; -} - -STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__uint16 *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); - } - return result; -} - -STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - stbi__uint16 *result; - if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file_16(f,x,y,comp,req_comp); - fclose(f); - return result; -} - - -#endif //!STBI_NO_STDIO - -STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); -} - -STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); - return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); -} - -STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); -} - -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_GIF -STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) -{ - unsigned char *result; - stbi__context s; - stbi__start_mem(&s,buffer,len); - - result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); - if (stbi__vertically_flip_on_load) { - stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); - } - - return result; -} -#endif - -#ifndef STBI_NO_LINEAR -static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *data; - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - stbi__result_info ri; - float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); - if (hdr_data) - stbi__float_postprocess(hdr_data,x,y,comp,req_comp); - return hdr_data; - } - #endif - data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); - if (data) - return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); - return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); -} - -STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} - -STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_STDIO -STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - float *result; - FILE *f = stbi__fopen(filename, "rb"); - if (!f) return stbi__errpf("can't fopen", "Unable to open file"); - result = stbi_loadf_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_file(&s,f); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} -#endif // !STBI_NO_STDIO - -#endif // !STBI_NO_LINEAR - -// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is -// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always -// reports false! - -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(buffer); - STBI_NOTUSED(len); - return 0; - #endif -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result=0; - if (f) { - result = stbi_is_hdr_from_file(f); - fclose(f); - } - return result; -} - -STBIDEF int stbi_is_hdr_from_file(FILE *f) -{ - #ifndef STBI_NO_HDR - long pos = ftell(f); - int res; - stbi__context s; - stbi__start_file(&s,f); - res = stbi__hdr_test(&s); - fseek(f, pos, SEEK_SET); - return res; - #else - STBI_NOTUSED(f); - return 0; - #endif -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(clbk); - STBI_NOTUSED(user); - return 0; - #endif -} - -#ifndef STBI_NO_LINEAR -static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; - -STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } -STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } -#endif - -static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; - -STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } -STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } - - -////////////////////////////////////////////////////////////////////////////// -// -// Common code used by all image loaders -// - -enum -{ - STBI__SCAN_load=0, - STBI__SCAN_type, - STBI__SCAN_header -}; - -static void stbi__refill_buffer(stbi__context *s) -{ - int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); - if (n == 0) { - // at end of file, treat same as if from memory, but need to handle case - // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file - s->read_from_callbacks = 0; - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start+1; - *s->img_buffer = 0; - } else { - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start + n; - } -} - -stbi_inline static stbi_uc stbi__get8(stbi__context *s) -{ - if (s->img_buffer < s->img_buffer_end) - return *s->img_buffer++; - if (s->read_from_callbacks) { - stbi__refill_buffer(s); - return *s->img_buffer++; - } - return 0; -} - -#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) -// nothing -#else -stbi_inline static int stbi__at_eof(stbi__context *s) -{ - if (s->io.read) { - if (!(s->io.eof)(s->io_user_data)) return 0; - // if feof() is true, check if buffer = end - // special case: we've only got the special 0 character at the end - if (s->read_from_callbacks == 0) return 1; - } - - return s->img_buffer >= s->img_buffer_end; -} -#endif - -#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) -// nothing -#else -static void stbi__skip(stbi__context *s, int n) -{ - if (n < 0) { - s->img_buffer = s->img_buffer_end; - return; - } - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - s->img_buffer = s->img_buffer_end; - (s->io.skip)(s->io_user_data, n - blen); - return; - } - } - s->img_buffer += n; -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) -// nothing -#else -static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) -{ - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - int res, count; - - memcpy(buffer, s->img_buffer, blen); - - count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); - res = (count == (n-blen)); - s->img_buffer = s->img_buffer_end; - return res; - } - } - - if (s->img_buffer+n <= s->img_buffer_end) { - memcpy(buffer, s->img_buffer, n); - s->img_buffer += n; - return 1; - } else - return 0; -} -#endif - -#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) -// nothing -#else -static int stbi__get16be(stbi__context *s) -{ - int z = stbi__get8(s); - return (z << 8) + stbi__get8(s); -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) -// nothing -#else -static stbi__uint32 stbi__get32be(stbi__context *s) -{ - stbi__uint32 z = stbi__get16be(s); - return (z << 16) + stbi__get16be(s); -} -#endif - -#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) -// nothing -#else -static int stbi__get16le(stbi__context *s) -{ - int z = stbi__get8(s); - return z + (stbi__get8(s) << 8); -} -#endif - -#ifndef STBI_NO_BMP -static stbi__uint32 stbi__get32le(stbi__context *s) -{ - stbi__uint32 z = stbi__get16le(s); - return z + (stbi__get16le(s) << 16); -} -#endif - -#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings - -#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) -// nothing -#else -////////////////////////////////////////////////////////////////////////////// -// -// generic converter from built-in img_n to req_comp -// individual types do this automatically as much as possible (e.g. jpeg -// does all cases internally since it needs to colorspace convert anyway, -// and it never has alpha, so very few cases ). png can automatically -// interleave an alpha=255 channel, but falls back to this for other cases -// -// assume data buffer is malloced, so malloc a new one and free that one -// only failure mode is malloc failing - -static stbi_uc stbi__compute_y(int r, int g, int b) -{ -#if 0 // ori - return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); -#else // zxing (see GenericLuminanceSource.cpp:RGBToGray) - return (stbi_uc) ((306 * r + 601 * g + 117 * b + 0x200) >> 10); -#endif -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) -// nothing -#else -static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - unsigned char *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); - if (good == NULL) { - STBI_FREE(data); - return stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - unsigned char *src = data + j * x * img_n ; - unsigned char *dest = good + j * x * req_comp; - - #define STBI__COMBO(a,b) ((a)*8+(b)) - #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; - STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; - STBI__CASE(2,1) { dest[0]=src[0]; } break; - STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; - STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; - STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; - default: STBI_ASSERT(0); - } - #undef STBI__CASE - } - - STBI_FREE(data); - return good; -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) -// nothing -#else -static stbi__uint16 stbi__compute_y_16(int r, int g, int b) -{ - return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) -// nothing -#else -static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - stbi__uint16 *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); - if (good == NULL) { - STBI_FREE(data); - return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - stbi__uint16 *src = data + j * x * img_n ; - stbi__uint16 *dest = good + j * x * req_comp; - - #define STBI__COMBO(a,b) ((a)*8+(b)) - #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; - STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; - STBI__CASE(2,1) { dest[0]=src[0]; } break; - STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; - STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; - STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; - default: STBI_ASSERT(0); - } - #undef STBI__CASE - } - - STBI_FREE(data); - return good; -} -#endif - -#ifndef STBI_NO_LINEAR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) -{ - int i,k,n; - float *output; - if (!data) return NULL; - output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); - if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); - } - } - if (n < comp) { - for (i=0; i < x*y; ++i) { - output[i*comp + n] = data[i*comp + n]/255.0f; - } - } - STBI_FREE(data); - return output; -} -#endif - -#ifndef STBI_NO_HDR -#define stbi__float2int(x) ((int) (x)) -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) -{ - int i,k,n; - stbi_uc *output; - if (!data) return NULL; - output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); - if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - if (k < comp) { - float z = data[i*comp+k] * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - } - STBI_FREE(data); - return output; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// "baseline" JPEG/JFIF decoder -// -// simple implementation -// - doesn't support delayed output of y-dimension -// - simple interface (only one output format: 8-bit interleaved RGB) -// - doesn't try to recover corrupt jpegs -// - doesn't allow partial loading, loading multiple at once -// - still fast on x86 (copying globals into locals doesn't help x86) -// - allocates lots of intermediate memory (full size of all components) -// - non-interleaved case requires this anyway -// - allows good upsampling (see next) -// high-quality -// - upsampled channels are bilinearly interpolated, even across blocks -// - quality integer IDCT derived from IJG's 'slow' -// performance -// - fast huffman; reasonable integer IDCT -// - some SIMD kernels for common paths on targets with SSE2/NEON -// - uses a lot of intermediate memory, could cache poorly - -#ifndef STBI_NO_JPEG - -// huffman decoding acceleration -#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache - -typedef struct -{ - stbi_uc fast[1 << FAST_BITS]; - // weirdly, repacking this into AoS is a 10% speed loss, instead of a win - stbi__uint16 code[256]; - stbi_uc values[256]; - stbi_uc size[257]; - unsigned int maxcode[18]; - int delta[17]; // old 'firstsymbol' - old 'firstcode' -} stbi__huffman; - -typedef struct -{ - stbi__context *s; - stbi__huffman huff_dc[4]; - stbi__huffman huff_ac[4]; - stbi__uint16 dequant[4][64]; - stbi__int16 fast_ac[4][1 << FAST_BITS]; - -// sizes for components, interleaved MCUs - int img_h_max, img_v_max; - int img_mcu_x, img_mcu_y; - int img_mcu_w, img_mcu_h; - -// definition of jpeg image component - struct - { - int id; - int h,v; - int tq; - int hd,ha; - int dc_pred; - - int x,y,w2,h2; - stbi_uc *data; - void *raw_data, *raw_coeff; - stbi_uc *linebuf; - short *coeff; // progressive only - int coeff_w, coeff_h; // number of 8x8 coefficient blocks - } img_comp[4]; - - stbi__uint32 code_buffer; // jpeg entropy-coded buffer - int code_bits; // number of valid bits - unsigned char marker; // marker seen while filling entropy buffer - int nomore; // flag if we saw a marker so must stop - - int progressive; - int spec_start; - int spec_end; - int succ_high; - int succ_low; - int eob_run; - int jfif; - int app14_color_transform; // Adobe APP14 tag - int rgb; - - int scan_n, order[4]; - int restart_interval, todo; - -// kernels - void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); - void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); - stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); -} stbi__jpeg; - -static int stbi__build_huffman(stbi__huffman *h, int *count) -{ - int i,j,k=0; - unsigned int code; - // build size list for each symbol (from JPEG spec) - for (i=0; i < 16; ++i) - for (j=0; j < count[i]; ++j) - h->size[k++] = (stbi_uc) (i+1); - h->size[k] = 0; - - // compute actual symbols (from jpeg spec) - code = 0; - k = 0; - for(j=1; j <= 16; ++j) { - // compute delta to add to code to compute symbol id - h->delta[j] = k - code; - if (h->size[k] == j) { - while (h->size[k] == j) - h->code[k++] = (stbi__uint16) (code++); - if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); - } - // compute largest code + 1 for this size, preshifted as needed later - h->maxcode[j] = code << (16-j); - code <<= 1; - } - h->maxcode[j] = 0xffffffff; - - // build non-spec acceleration table; 255 is flag for not-accelerated - memset(h->fast, 255, 1 << FAST_BITS); - for (i=0; i < k; ++i) { - int s = h->size[i]; - if (s <= FAST_BITS) { - int c = h->code[i] << (FAST_BITS-s); - int m = 1 << (FAST_BITS-s); - for (j=0; j < m; ++j) { - h->fast[c+j] = (stbi_uc) i; - } - } - } - return 1; -} - -// build a table that decodes both magnitude and value of small ACs in -// one go. -static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) -{ - int i; - for (i=0; i < (1 << FAST_BITS); ++i) { - stbi_uc fast = h->fast[i]; - fast_ac[i] = 0; - if (fast < 255) { - int rs = h->values[fast]; - int run = (rs >> 4) & 15; - int magbits = rs & 15; - int len = h->size[fast]; - - if (magbits && len + magbits <= FAST_BITS) { - // magnitude code followed by receive_extend code - int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); - int m = 1 << (magbits - 1); - if (k < m) k += (~0U << magbits) + 1; - // if the result is small enough, we can fit it in fast_ac table - if (k >= -128 && k <= 127) - fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); - } - } - } -} - -static void stbi__grow_buffer_unsafe(stbi__jpeg *j) -{ - do { - unsigned int b = j->nomore ? 0 : stbi__get8(j->s); - if (b == 0xff) { - int c = stbi__get8(j->s); - while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes - if (c != 0) { - j->marker = (unsigned char) c; - j->nomore = 1; - return; - } - } - j->code_buffer |= b << (24 - j->code_bits); - j->code_bits += 8; - } while (j->code_bits <= 24); -} - -// (1 << n) - 1 -static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; - -// decode a jpeg huffman value from the bitstream -stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) -{ - unsigned int temp; - int c,k; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - // look at the top FAST_BITS and determine what symbol ID it is, - // if the code is <= FAST_BITS - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - k = h->fast[c]; - if (k < 255) { - int s = h->size[k]; - if (s > j->code_bits) - return -1; - j->code_buffer <<= s; - j->code_bits -= s; - return h->values[k]; - } - - // naive test is to shift the code_buffer down so k bits are - // valid, then test against maxcode. To speed this up, we've - // preshifted maxcode left so that it has (16-k) 0s at the - // end; in other words, regardless of the number of bits, it - // wants to be compared against something shifted to have 16; - // that way we don't need to shift inside the loop. - temp = j->code_buffer >> 16; - for (k=FAST_BITS+1 ; ; ++k) - if (temp < h->maxcode[k]) - break; - if (k == 17) { - // error! code not found - j->code_bits -= 16; - return -1; - } - - if (k > j->code_bits) - return -1; - - // convert the huffman code to the symbol id - c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; - STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); - - // convert the id to a symbol - j->code_bits -= k; - j->code_buffer <<= k; - return h->values[c]; -} - -// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); - - sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB - k = stbi_lrot(j->code_buffer, n); - STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - return k + (stbi__jbias[n] & ~sgn); -} - -// get some unsigned bits -stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) -{ - unsigned int k; - if (j->code_bits < n) stbi__grow_buffer_unsafe(j); - k = stbi_lrot(j->code_buffer, n); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - return k; -} - -stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) -{ - unsigned int k; - if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); - k = j->code_buffer; - j->code_buffer <<= 1; - --j->code_bits; - return k & 0x80000000; -} - -// given a value that's at position X in the zigzag stream, -// where does it appear in the 8x8 matrix coded as row-major? -static const stbi_uc stbi__jpeg_dezigzag[64+15] = -{ - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, - // let corrupt input sample past end - 63, 63, 63, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63 -}; - -// decode one 64-entry block-- -static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) -{ - int diff,dc,k; - int t; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - t = stbi__jpeg_huff_decode(j, hdc); - if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - - // 0 all the ac values now so we can do it 32-bits at a time - memset(data,0,64*sizeof(data[0])); - - diff = t ? stbi__extend_receive(j, t) : 0; - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc * dequant[0]); - - // decode AC components, see JPEG spec - k = 1; - do { - unsigned int zig; - int c,r,s; - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - r = fac[c]; - if (r) { // fast-AC path - k += (r >> 4) & 15; // run - s = r & 15; // combined length - j->code_buffer <<= s; - j->code_bits -= s; - // decode into unzigzag'd location - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) * dequant[zig]); - } else { - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (rs != 0xf0) break; // end block - k += 16; - } else { - k += r; - // decode into unzigzag'd location - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); - } - } - } while (k < 64); - return 1; -} - -static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) -{ - int diff,dc; - int t; - if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - if (j->succ_high == 0) { - // first scan for DC coefficient, must be first - memset(data,0,64*sizeof(data[0])); // 0 all the ac values now - t = stbi__jpeg_huff_decode(j, hdc); - diff = t ? stbi__extend_receive(j, t) : 0; - - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc << j->succ_low); - } else { - // refinement scan for DC coefficient - if (stbi__jpeg_get_bit(j)) - data[0] += (short) (1 << j->succ_low); - } - return 1; -} - -// @OPTIMIZE: store non-zigzagged during the decode passes, -// and only de-zigzag when dequantizing -static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) -{ - int k; - if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - - if (j->succ_high == 0) { - int shift = j->succ_low; - - if (j->eob_run) { - --j->eob_run; - return 1; - } - - k = j->spec_start; - do { - unsigned int zig; - int c,r,s; - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - r = fac[c]; - if (r) { // fast-AC path - k += (r >> 4) & 15; // run - s = r & 15; // combined length - j->code_buffer <<= s; - j->code_bits -= s; - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) << shift); - } else { - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (r < 15) { - j->eob_run = (1 << r); - if (r) - j->eob_run += stbi__jpeg_get_bits(j, r); - --j->eob_run; - break; - } - k += 16; - } else { - k += r; - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) << shift); - } - } - } while (k <= j->spec_end); - } else { - // refinement scan for these AC coefficients - - short bit = (short) (1 << j->succ_low); - - if (j->eob_run) { - --j->eob_run; - for (k = j->spec_start; k <= j->spec_end; ++k) { - short *p = &data[stbi__jpeg_dezigzag[k]]; - if (*p != 0) - if (stbi__jpeg_get_bit(j)) - if ((*p & bit)==0) { - if (*p > 0) - *p += bit; - else - *p -= bit; - } - } - } else { - k = j->spec_start; - do { - int r,s; - int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (r < 15) { - j->eob_run = (1 << r) - 1; - if (r) - j->eob_run += stbi__jpeg_get_bits(j, r); - r = 64; // force end of block - } else { - // r=15 s=0 should write 16 0s, so we just do - // a run of 15 0s and then write s (which is 0), - // so we don't have to do anything special here - } - } else { - if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); - // sign bit - if (stbi__jpeg_get_bit(j)) - s = bit; - else - s = -bit; - } - - // advance by r - while (k <= j->spec_end) { - short *p = &data[stbi__jpeg_dezigzag[k++]]; - if (*p != 0) { - if (stbi__jpeg_get_bit(j)) - if ((*p & bit)==0) { - if (*p > 0) - *p += bit; - else - *p -= bit; - } - } else { - if (r == 0) { - *p = (short) s; - break; - } - --r; - } - } - } while (k <= j->spec_end); - } - } - return 1; -} - -// take a -128..127 value and stbi__clamp it and convert to 0..255 -stbi_inline static stbi_uc stbi__clamp(int x) -{ - // trick to use a single test to catch both cases - if ((unsigned int) x > 255) { - if (x < 0) return 0; - if (x > 255) return 255; - } - return (stbi_uc) x; -} - -#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) -#define stbi__fsh(x) ((x) * 4096) - -// derived from jidctint -- DCT_ISLOW -#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ - int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ - p2 = s2; \ - p3 = s6; \ - p1 = (p2+p3) * stbi__f2f(0.5411961f); \ - t2 = p1 + p3*stbi__f2f(-1.847759065f); \ - t3 = p1 + p2*stbi__f2f( 0.765366865f); \ - p2 = s0; \ - p3 = s4; \ - t0 = stbi__fsh(p2+p3); \ - t1 = stbi__fsh(p2-p3); \ - x0 = t0+t3; \ - x3 = t0-t3; \ - x1 = t1+t2; \ - x2 = t1-t2; \ - t0 = s7; \ - t1 = s5; \ - t2 = s3; \ - t3 = s1; \ - p3 = t0+t2; \ - p4 = t1+t3; \ - p1 = t0+t3; \ - p2 = t1+t2; \ - p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ - t0 = t0*stbi__f2f( 0.298631336f); \ - t1 = t1*stbi__f2f( 2.053119869f); \ - t2 = t2*stbi__f2f( 3.072711026f); \ - t3 = t3*stbi__f2f( 1.501321110f); \ - p1 = p5 + p1*stbi__f2f(-0.899976223f); \ - p2 = p5 + p2*stbi__f2f(-2.562915447f); \ - p3 = p3*stbi__f2f(-1.961570560f); \ - p4 = p4*stbi__f2f(-0.390180644f); \ - t3 += p1+p4; \ - t2 += p2+p3; \ - t1 += p2+p4; \ - t0 += p1+p3; - -static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) -{ - int i,val[64],*v=val; - stbi_uc *o; - short *d = data; - - // columns - for (i=0; i < 8; ++i,++d, ++v) { - // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing - if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 - && d[40]==0 && d[48]==0 && d[56]==0) { - // no shortcut 0 seconds - // (1|2|3|4|5|6|7)==0 0 seconds - // all separate -0.047 seconds - // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds - int dcterm = d[0]*4; - v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; - } else { - STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) - // constants scaled things up by 1<<12; let's bring them back - // down, but keep 2 extra bits of precision - x0 += 512; x1 += 512; x2 += 512; x3 += 512; - v[ 0] = (x0+t3) >> 10; - v[56] = (x0-t3) >> 10; - v[ 8] = (x1+t2) >> 10; - v[48] = (x1-t2) >> 10; - v[16] = (x2+t1) >> 10; - v[40] = (x2-t1) >> 10; - v[24] = (x3+t0) >> 10; - v[32] = (x3-t0) >> 10; - } - } - - for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { - // no fast case since the first 1D IDCT spread components out - STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) - // constants scaled things up by 1<<12, plus we had 1<<2 from first - // loop, plus horizontal and vertical each scale by sqrt(8) so together - // we've got an extra 1<<3, so 1<<17 total we need to remove. - // so we want to round that, which means adding 0.5 * 1<<17, - // aka 65536. Also, we'll end up with -128 to 127 that we want - // to encode as 0..255 by adding 128, so we'll add that before the shift - x0 += 65536 + (128<<17); - x1 += 65536 + (128<<17); - x2 += 65536 + (128<<17); - x3 += 65536 + (128<<17); - // tried computing the shifts into temps, or'ing the temps to see - // if any were out of range, but that was slower - o[0] = stbi__clamp((x0+t3) >> 17); - o[7] = stbi__clamp((x0-t3) >> 17); - o[1] = stbi__clamp((x1+t2) >> 17); - o[6] = stbi__clamp((x1-t2) >> 17); - o[2] = stbi__clamp((x2+t1) >> 17); - o[5] = stbi__clamp((x2-t1) >> 17); - o[3] = stbi__clamp((x3+t0) >> 17); - o[4] = stbi__clamp((x3-t0) >> 17); - } -} - -#ifdef STBI_SSE2 -// sse2 integer IDCT. not the fastest possible implementation but it -// produces bit-identical results to the generic C version so it's -// fully "transparent". -static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) -{ - // This is constructed to match our regular (generic) integer IDCT exactly. - __m128i row0, row1, row2, row3, row4, row5, row6, row7; - __m128i tmp; - - // dot product constant: even elems=x, odd elems=y - #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) - - // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) - // out(1) = c1[even]*x + c1[odd]*y - #define dct_rot(out0,out1, x,y,c0,c1) \ - __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ - __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ - __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ - __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ - __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ - __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) - - // out = in << 12 (in 16-bit, out 32-bit) - #define dct_widen(out, in) \ - __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ - __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) - - // wide add - #define dct_wadd(out, a, b) \ - __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ - __m128i out##_h = _mm_add_epi32(a##_h, b##_h) - - // wide sub - #define dct_wsub(out, a, b) \ - __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ - __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) - - // butterfly a/b, add bias, then shift by "s" and pack - #define dct_bfly32o(out0, out1, a,b,bias,s) \ - { \ - __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ - __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ - dct_wadd(sum, abiased, b); \ - dct_wsub(dif, abiased, b); \ - out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ - out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ - } - - // 8-bit interleave step (for transposes) - #define dct_interleave8(a, b) \ - tmp = a; \ - a = _mm_unpacklo_epi8(a, b); \ - b = _mm_unpackhi_epi8(tmp, b) - - // 16-bit interleave step (for transposes) - #define dct_interleave16(a, b) \ - tmp = a; \ - a = _mm_unpacklo_epi16(a, b); \ - b = _mm_unpackhi_epi16(tmp, b) - - #define dct_pass(bias,shift) \ - { \ - /* even part */ \ - dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ - __m128i sum04 = _mm_add_epi16(row0, row4); \ - __m128i dif04 = _mm_sub_epi16(row0, row4); \ - dct_widen(t0e, sum04); \ - dct_widen(t1e, dif04); \ - dct_wadd(x0, t0e, t3e); \ - dct_wsub(x3, t0e, t3e); \ - dct_wadd(x1, t1e, t2e); \ - dct_wsub(x2, t1e, t2e); \ - /* odd part */ \ - dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ - dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ - __m128i sum17 = _mm_add_epi16(row1, row7); \ - __m128i sum35 = _mm_add_epi16(row3, row5); \ - dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ - dct_wadd(x4, y0o, y4o); \ - dct_wadd(x5, y1o, y5o); \ - dct_wadd(x6, y2o, y5o); \ - dct_wadd(x7, y3o, y4o); \ - dct_bfly32o(row0,row7, x0,x7,bias,shift); \ - dct_bfly32o(row1,row6, x1,x6,bias,shift); \ - dct_bfly32o(row2,row5, x2,x5,bias,shift); \ - dct_bfly32o(row3,row4, x3,x4,bias,shift); \ - } - - __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); - __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); - __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); - __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); - __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); - __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); - __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); - __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); - - // rounding biases in column/row passes, see stbi__idct_block for explanation. - __m128i bias_0 = _mm_set1_epi32(512); - __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); - - // load - row0 = _mm_load_si128((const __m128i *) (data + 0*8)); - row1 = _mm_load_si128((const __m128i *) (data + 1*8)); - row2 = _mm_load_si128((const __m128i *) (data + 2*8)); - row3 = _mm_load_si128((const __m128i *) (data + 3*8)); - row4 = _mm_load_si128((const __m128i *) (data + 4*8)); - row5 = _mm_load_si128((const __m128i *) (data + 5*8)); - row6 = _mm_load_si128((const __m128i *) (data + 6*8)); - row7 = _mm_load_si128((const __m128i *) (data + 7*8)); - - // column pass - dct_pass(bias_0, 10); - - { - // 16bit 8x8 transpose pass 1 - dct_interleave16(row0, row4); - dct_interleave16(row1, row5); - dct_interleave16(row2, row6); - dct_interleave16(row3, row7); - - // transpose pass 2 - dct_interleave16(row0, row2); - dct_interleave16(row1, row3); - dct_interleave16(row4, row6); - dct_interleave16(row5, row7); - - // transpose pass 3 - dct_interleave16(row0, row1); - dct_interleave16(row2, row3); - dct_interleave16(row4, row5); - dct_interleave16(row6, row7); - } - - // row pass - dct_pass(bias_1, 17); - - { - // pack - __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 - __m128i p1 = _mm_packus_epi16(row2, row3); - __m128i p2 = _mm_packus_epi16(row4, row5); - __m128i p3 = _mm_packus_epi16(row6, row7); - - // 8bit 8x8 transpose pass 1 - dct_interleave8(p0, p2); // a0e0a1e1... - dct_interleave8(p1, p3); // c0g0c1g1... - - // transpose pass 2 - dct_interleave8(p0, p1); // a0c0e0g0... - dct_interleave8(p2, p3); // b0d0f0h0... - - // transpose pass 3 - dct_interleave8(p0, p2); // a0b0c0d0... - dct_interleave8(p1, p3); // a4b4c4d4... - - // store - _mm_storel_epi64((__m128i *) out, p0); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p2); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p1); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p3); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); - } - -#undef dct_const -#undef dct_rot -#undef dct_widen -#undef dct_wadd -#undef dct_wsub -#undef dct_bfly32o -#undef dct_interleave8 -#undef dct_interleave16 -#undef dct_pass -} - -#endif // STBI_SSE2 - -#ifdef STBI_NEON - -// NEON integer IDCT. should produce bit-identical -// results to the generic C version. -static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) -{ - int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; - - int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); - int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); - int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); - int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); - int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); - int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); - int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); - int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); - int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); - int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); - int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); - int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); - -#define dct_long_mul(out, inq, coeff) \ - int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ - int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) - -#define dct_long_mac(out, acc, inq, coeff) \ - int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ - int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) - -#define dct_widen(out, inq) \ - int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ - int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) - -// wide add -#define dct_wadd(out, a, b) \ - int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ - int32x4_t out##_h = vaddq_s32(a##_h, b##_h) - -// wide sub -#define dct_wsub(out, a, b) \ - int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ - int32x4_t out##_h = vsubq_s32(a##_h, b##_h) - -// butterfly a/b, then shift using "shiftop" by "s" and pack -#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ - { \ - dct_wadd(sum, a, b); \ - dct_wsub(dif, a, b); \ - out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ - out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ - } - -#define dct_pass(shiftop, shift) \ - { \ - /* even part */ \ - int16x8_t sum26 = vaddq_s16(row2, row6); \ - dct_long_mul(p1e, sum26, rot0_0); \ - dct_long_mac(t2e, p1e, row6, rot0_1); \ - dct_long_mac(t3e, p1e, row2, rot0_2); \ - int16x8_t sum04 = vaddq_s16(row0, row4); \ - int16x8_t dif04 = vsubq_s16(row0, row4); \ - dct_widen(t0e, sum04); \ - dct_widen(t1e, dif04); \ - dct_wadd(x0, t0e, t3e); \ - dct_wsub(x3, t0e, t3e); \ - dct_wadd(x1, t1e, t2e); \ - dct_wsub(x2, t1e, t2e); \ - /* odd part */ \ - int16x8_t sum15 = vaddq_s16(row1, row5); \ - int16x8_t sum17 = vaddq_s16(row1, row7); \ - int16x8_t sum35 = vaddq_s16(row3, row5); \ - int16x8_t sum37 = vaddq_s16(row3, row7); \ - int16x8_t sumodd = vaddq_s16(sum17, sum35); \ - dct_long_mul(p5o, sumodd, rot1_0); \ - dct_long_mac(p1o, p5o, sum17, rot1_1); \ - dct_long_mac(p2o, p5o, sum35, rot1_2); \ - dct_long_mul(p3o, sum37, rot2_0); \ - dct_long_mul(p4o, sum15, rot2_1); \ - dct_wadd(sump13o, p1o, p3o); \ - dct_wadd(sump24o, p2o, p4o); \ - dct_wadd(sump23o, p2o, p3o); \ - dct_wadd(sump14o, p1o, p4o); \ - dct_long_mac(x4, sump13o, row7, rot3_0); \ - dct_long_mac(x5, sump24o, row5, rot3_1); \ - dct_long_mac(x6, sump23o, row3, rot3_2); \ - dct_long_mac(x7, sump14o, row1, rot3_3); \ - dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ - dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ - dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ - dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ - } - - // load - row0 = vld1q_s16(data + 0*8); - row1 = vld1q_s16(data + 1*8); - row2 = vld1q_s16(data + 2*8); - row3 = vld1q_s16(data + 3*8); - row4 = vld1q_s16(data + 4*8); - row5 = vld1q_s16(data + 5*8); - row6 = vld1q_s16(data + 6*8); - row7 = vld1q_s16(data + 7*8); - - // add DC bias - row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); - - // column pass - dct_pass(vrshrn_n_s32, 10); - - // 16bit 8x8 transpose - { -// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. -// whether compilers actually get this is another story, sadly. -#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } -#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } -#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } - - // pass 1 - dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 - dct_trn16(row2, row3); - dct_trn16(row4, row5); - dct_trn16(row6, row7); - - // pass 2 - dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 - dct_trn32(row1, row3); - dct_trn32(row4, row6); - dct_trn32(row5, row7); - - // pass 3 - dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 - dct_trn64(row1, row5); - dct_trn64(row2, row6); - dct_trn64(row3, row7); - -#undef dct_trn16 -#undef dct_trn32 -#undef dct_trn64 - } - - // row pass - // vrshrn_n_s32 only supports shifts up to 16, we need - // 17. so do a non-rounding shift of 16 first then follow - // up with a rounding shift by 1. - dct_pass(vshrn_n_s32, 16); - - { - // pack and round - uint8x8_t p0 = vqrshrun_n_s16(row0, 1); - uint8x8_t p1 = vqrshrun_n_s16(row1, 1); - uint8x8_t p2 = vqrshrun_n_s16(row2, 1); - uint8x8_t p3 = vqrshrun_n_s16(row3, 1); - uint8x8_t p4 = vqrshrun_n_s16(row4, 1); - uint8x8_t p5 = vqrshrun_n_s16(row5, 1); - uint8x8_t p6 = vqrshrun_n_s16(row6, 1); - uint8x8_t p7 = vqrshrun_n_s16(row7, 1); - - // again, these can translate into one instruction, but often don't. -#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } -#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } -#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } - - // sadly can't use interleaved stores here since we only write - // 8 bytes to each scan line! - - // 8x8 8-bit transpose pass 1 - dct_trn8_8(p0, p1); - dct_trn8_8(p2, p3); - dct_trn8_8(p4, p5); - dct_trn8_8(p6, p7); - - // pass 2 - dct_trn8_16(p0, p2); - dct_trn8_16(p1, p3); - dct_trn8_16(p4, p6); - dct_trn8_16(p5, p7); - - // pass 3 - dct_trn8_32(p0, p4); - dct_trn8_32(p1, p5); - dct_trn8_32(p2, p6); - dct_trn8_32(p3, p7); - - // store - vst1_u8(out, p0); out += out_stride; - vst1_u8(out, p1); out += out_stride; - vst1_u8(out, p2); out += out_stride; - vst1_u8(out, p3); out += out_stride; - vst1_u8(out, p4); out += out_stride; - vst1_u8(out, p5); out += out_stride; - vst1_u8(out, p6); out += out_stride; - vst1_u8(out, p7); - -#undef dct_trn8_8 -#undef dct_trn8_16 -#undef dct_trn8_32 - } - -#undef dct_long_mul -#undef dct_long_mac -#undef dct_widen -#undef dct_wadd -#undef dct_wsub -#undef dct_bfly32o -#undef dct_pass -} - -#endif // STBI_NEON - -#define STBI__MARKER_none 0xff -// if there's a pending marker from the entropy stream, return that -// otherwise, fetch from the stream and get a marker. if there's no -// marker, return 0xff, which is never a valid marker value -static stbi_uc stbi__get_marker(stbi__jpeg *j) -{ - stbi_uc x; - if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } - x = stbi__get8(j->s); - if (x != 0xff) return STBI__MARKER_none; - while (x == 0xff) - x = stbi__get8(j->s); // consume repeated 0xff fill bytes - return x; -} - -// in each scan, we'll have scan_n components, and the order -// of the components is specified by order[] -#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) - -// after a restart interval, stbi__jpeg_reset the entropy decoder and -// the dc prediction -static void stbi__jpeg_reset(stbi__jpeg *j) -{ - j->code_bits = 0; - j->code_buffer = 0; - j->nomore = 0; - j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; - j->marker = STBI__MARKER_none; - j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; - j->eob_run = 0; - // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, - // since we don't even allow 1<<30 pixels -} - -static int stbi__parse_entropy_coded_data(stbi__jpeg *z) -{ - stbi__jpeg_reset(z); - if (!z->progressive) { - if (z->scan_n == 1) { - int i,j; - STBI_SIMD_ALIGN(short, data[64]); - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - // if it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } else { // interleaved - int i,j,k,x,y; - STBI_SIMD_ALIGN(short, data[64]); - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x)*8; - int y2 = (j*z->img_comp[n].v + y)*8; - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } - } else { - if (z->scan_n == 1) { - int i,j; - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); - if (z->spec_start == 0) { - if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) - return 0; - } else { - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) - return 0; - } - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } else { // interleaved - int i,j,k,x,y; - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x); - int y2 = (j*z->img_comp[n].v + y); - short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); - if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) - return 0; - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } - } -} - -static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) -{ - int i; - for (i=0; i < 64; ++i) - data[i] *= dequant[i]; -} - -static void stbi__jpeg_finish(stbi__jpeg *z) -{ - if (z->progressive) { - // dequantize and idct the data - int i,j,n; - for (n=0; n < z->s->img_n; ++n) { - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); - stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); - } - } - } - } -} - -static int stbi__process_marker(stbi__jpeg *z, int m) -{ - int L; - switch (m) { - case STBI__MARKER_none: // no marker found - return stbi__err("expected marker","Corrupt JPEG"); - - case 0xDD: // DRI - specify restart interval - if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); - z->restart_interval = stbi__get16be(z->s); - return 1; - - case 0xDB: // DQT - define quantization table - L = stbi__get16be(z->s)-2; - while (L > 0) { - int q = stbi__get8(z->s); - int p = q >> 4, sixteen = (p != 0); - int t = q & 15,i; - if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); - if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); - - for (i=0; i < 64; ++i) - z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); - L -= (sixteen ? 129 : 65); - } - return L==0; - - case 0xC4: // DHT - define huffman table - L = stbi__get16be(z->s)-2; - while (L > 0) { - stbi_uc *v; - int sizes[16],i,n=0; - int q = stbi__get8(z->s); - int tc = q >> 4; - int th = q & 15; - if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); - for (i=0; i < 16; ++i) { - sizes[i] = stbi__get8(z->s); - n += sizes[i]; - } - L -= 17; - if (tc == 0) { - if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; - v = z->huff_dc[th].values; - } else { - if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; - v = z->huff_ac[th].values; - } - for (i=0; i < n; ++i) - v[i] = stbi__get8(z->s); - if (tc != 0) - stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); - L -= n; - } - return L==0; - } - - // check for comment block or APP blocks - if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { - L = stbi__get16be(z->s); - if (L < 2) { - if (m == 0xFE) - return stbi__err("bad COM len","Corrupt JPEG"); - else - return stbi__err("bad APP len","Corrupt JPEG"); - } - L -= 2; - - if (m == 0xE0 && L >= 5) { // JFIF APP0 segment - static const unsigned char tag[5] = {'J','F','I','F','\0'}; - int ok = 1; - int i; - for (i=0; i < 5; ++i) - if (stbi__get8(z->s) != tag[i]) - ok = 0; - L -= 5; - if (ok) - z->jfif = 1; - } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment - static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; - int ok = 1; - int i; - for (i=0; i < 6; ++i) - if (stbi__get8(z->s) != tag[i]) - ok = 0; - L -= 6; - if (ok) { - stbi__get8(z->s); // version - stbi__get16be(z->s); // flags0 - stbi__get16be(z->s); // flags1 - z->app14_color_transform = stbi__get8(z->s); // color transform - L -= 6; - } - } - - stbi__skip(z->s, L); - return 1; - } - - return stbi__err("unknown marker","Corrupt JPEG"); -} - -// after we see SOS -static int stbi__process_scan_header(stbi__jpeg *z) -{ - int i; - int Ls = stbi__get16be(z->s); - z->scan_n = stbi__get8(z->s); - if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); - if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); - for (i=0; i < z->scan_n; ++i) { - int id = stbi__get8(z->s), which; - int q = stbi__get8(z->s); - for (which = 0; which < z->s->img_n; ++which) - if (z->img_comp[which].id == id) - break; - if (which == z->s->img_n) return 0; // no match - z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); - z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); - z->order[i] = which; - } - - { - int aa; - z->spec_start = stbi__get8(z->s); - z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 - aa = stbi__get8(z->s); - z->succ_high = (aa >> 4); - z->succ_low = (aa & 15); - if (z->progressive) { - if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) - return stbi__err("bad SOS", "Corrupt JPEG"); - } else { - if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); - if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); - z->spec_end = 63; - } - } - - return 1; -} - -static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) -{ - int i; - for (i=0; i < ncomp; ++i) { - if (z->img_comp[i].raw_data) { - STBI_FREE(z->img_comp[i].raw_data); - z->img_comp[i].raw_data = NULL; - z->img_comp[i].data = NULL; - } - if (z->img_comp[i].raw_coeff) { - STBI_FREE(z->img_comp[i].raw_coeff); - z->img_comp[i].raw_coeff = 0; - z->img_comp[i].coeff = 0; - } - if (z->img_comp[i].linebuf) { - STBI_FREE(z->img_comp[i].linebuf); - z->img_comp[i].linebuf = NULL; - } - } - return why; -} - -static int stbi__process_frame_header(stbi__jpeg *z, int scan) -{ - stbi__context *s = z->s; - int Lf,p,i,q, h_max=1,v_max=1,c; - Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG - p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline - s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG - s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires - c = stbi__get8(s); - if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); - s->img_n = c; - for (i=0; i < c; ++i) { - z->img_comp[i].data = NULL; - z->img_comp[i].linebuf = NULL; - } - - if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); - - z->rgb = 0; - for (i=0; i < s->img_n; ++i) { - static const unsigned char rgb[3] = { 'R', 'G', 'B' }; - z->img_comp[i].id = stbi__get8(s); - if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) - ++z->rgb; - q = stbi__get8(s); - z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); - z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); - z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); - } - - if (scan != STBI__SCAN_load) return 1; - - if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); - - for (i=0; i < s->img_n; ++i) { - if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; - if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; - } - - // compute interleaved mcu info - z->img_h_max = h_max; - z->img_v_max = v_max; - z->img_mcu_w = h_max * 8; - z->img_mcu_h = v_max * 8; - // these sizes can't be more than 17 bits - z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; - z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; - - for (i=0; i < s->img_n; ++i) { - // number of effective pixels (e.g. for non-interleaved MCU) - z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; - z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; - // to simplify generation, we'll allocate enough memory to decode - // the bogus oversized data from using interleaved MCUs and their - // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't - // discard the extra data until colorspace conversion - // - // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) - // so these muls can't overflow with 32-bit ints (which we require) - z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; - z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; - z->img_comp[i].coeff = 0; - z->img_comp[i].raw_coeff = 0; - z->img_comp[i].linebuf = NULL; - z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); - if (z->img_comp[i].raw_data == NULL) - return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); - // align blocks for idct using mmx/sse - z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); - if (z->progressive) { - // w2, h2 are multiples of 8 (see above) - z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; - z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; - z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); - if (z->img_comp[i].raw_coeff == NULL) - return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); - z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); - } - } - - return 1; -} - -// use comparisons since in some cases we handle more than one case (e.g. SOF) -#define stbi__DNL(x) ((x) == 0xdc) -#define stbi__SOI(x) ((x) == 0xd8) -#define stbi__EOI(x) ((x) == 0xd9) -#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) -#define stbi__SOS(x) ((x) == 0xda) - -#define stbi__SOF_progressive(x) ((x) == 0xc2) - -static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) -{ - int m; - z->jfif = 0; - z->app14_color_transform = -1; // valid values are 0,1,2 - z->marker = STBI__MARKER_none; // initialize cached marker to empty - m = stbi__get_marker(z); - if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); - if (scan == STBI__SCAN_type) return 1; - m = stbi__get_marker(z); - while (!stbi__SOF(m)) { - if (!stbi__process_marker(z,m)) return 0; - m = stbi__get_marker(z); - while (m == STBI__MARKER_none) { - // some files have extra padding after their blocks, so ok, we'll scan - if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); - m = stbi__get_marker(z); - } - } - z->progressive = stbi__SOF_progressive(m); - if (!stbi__process_frame_header(z, scan)) return 0; - return 1; -} - -// decode image to YCbCr format -static int stbi__decode_jpeg_image(stbi__jpeg *j) -{ - int m; - for (m = 0; m < 4; m++) { - j->img_comp[m].raw_data = NULL; - j->img_comp[m].raw_coeff = NULL; - } - j->restart_interval = 0; - if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; - m = stbi__get_marker(j); - while (!stbi__EOI(m)) { - if (stbi__SOS(m)) { - if (!stbi__process_scan_header(j)) return 0; - if (!stbi__parse_entropy_coded_data(j)) return 0; - if (j->marker == STBI__MARKER_none ) { - // handle 0s at the end of image data from IP Kamera 9060 - while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - if (x == 255) { - j->marker = stbi__get8(j->s); - break; - } - } - // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 - } - } else if (stbi__DNL(m)) { - int Ld = stbi__get16be(j->s); - stbi__uint32 NL = stbi__get16be(j->s); - if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); - if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); - } else { - if (!stbi__process_marker(j, m)) return 0; - } - m = stbi__get_marker(j); - } - if (j->progressive) - stbi__jpeg_finish(j); - return 1; -} - -// static jfif-centered resampling (across block boundaries) - -typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, - int w, int hs); - -#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) - -static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - STBI_NOTUSED(out); - STBI_NOTUSED(in_far); - STBI_NOTUSED(w); - STBI_NOTUSED(hs); - return in_near; -} - -static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples vertically for every one in input - int i; - STBI_NOTUSED(hs); - for (i=0; i < w; ++i) - out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); - return out; -} - -static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples horizontally for every one in input - int i; - stbi_uc *input = in_near; - - if (w == 1) { - // if only one sample, can't do any interpolation - out[0] = out[1] = input[0]; - return out; - } - - out[0] = input[0]; - out[1] = stbi__div4(input[0]*3 + input[1] + 2); - for (i=1; i < w-1; ++i) { - int n = 3*input[i]+2; - out[i*2+0] = stbi__div4(n+input[i-1]); - out[i*2+1] = stbi__div4(n+input[i+1]); - } - out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); - out[i*2+1] = input[w-1]; - - STBI_NOTUSED(in_far); - STBI_NOTUSED(hs); - - return out; -} - -#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) - -static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i,t0,t1; - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - out[0] = stbi__div4(t1+2); - for (i=1; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} - -#if defined(STBI_SSE2) || defined(STBI_NEON) -static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i=0,t0,t1; - - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - // process groups of 8 pixels for as long as we can. - // note we can't handle the last pixel in a row in this loop - // because we need to handle the filter boundary conditions. - for (; i < ((w-1) & ~7); i += 8) { -#if defined(STBI_SSE2) - // load and perform the vertical filtering pass - // this uses 3*x + y = 4*x + (y - x) - __m128i zero = _mm_setzero_si128(); - __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); - __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); - __m128i farw = _mm_unpacklo_epi8(farb, zero); - __m128i nearw = _mm_unpacklo_epi8(nearb, zero); - __m128i diff = _mm_sub_epi16(farw, nearw); - __m128i nears = _mm_slli_epi16(nearw, 2); - __m128i curr = _mm_add_epi16(nears, diff); // current row - - // horizontal filter works the same based on shifted vers of current - // row. "prev" is current row shifted right by 1 pixel; we need to - // insert the previous pixel value (from t1). - // "next" is current row shifted left by 1 pixel, with first pixel - // of next block of 8 pixels added in. - __m128i prv0 = _mm_slli_si128(curr, 2); - __m128i nxt0 = _mm_srli_si128(curr, 2); - __m128i prev = _mm_insert_epi16(prv0, t1, 0); - __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); - - // horizontal filter, polyphase implementation since it's convenient: - // even pixels = 3*cur + prev = cur*4 + (prev - cur) - // odd pixels = 3*cur + next = cur*4 + (next - cur) - // note the shared term. - __m128i bias = _mm_set1_epi16(8); - __m128i curs = _mm_slli_epi16(curr, 2); - __m128i prvd = _mm_sub_epi16(prev, curr); - __m128i nxtd = _mm_sub_epi16(next, curr); - __m128i curb = _mm_add_epi16(curs, bias); - __m128i even = _mm_add_epi16(prvd, curb); - __m128i odd = _mm_add_epi16(nxtd, curb); - - // interleave even and odd pixels, then undo scaling. - __m128i int0 = _mm_unpacklo_epi16(even, odd); - __m128i int1 = _mm_unpackhi_epi16(even, odd); - __m128i de0 = _mm_srli_epi16(int0, 4); - __m128i de1 = _mm_srli_epi16(int1, 4); - - // pack and write output - __m128i outv = _mm_packus_epi16(de0, de1); - _mm_storeu_si128((__m128i *) (out + i*2), outv); -#elif defined(STBI_NEON) - // load and perform the vertical filtering pass - // this uses 3*x + y = 4*x + (y - x) - uint8x8_t farb = vld1_u8(in_far + i); - uint8x8_t nearb = vld1_u8(in_near + i); - int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); - int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); - int16x8_t curr = vaddq_s16(nears, diff); // current row - - // horizontal filter works the same based on shifted vers of current - // row. "prev" is current row shifted right by 1 pixel; we need to - // insert the previous pixel value (from t1). - // "next" is current row shifted left by 1 pixel, with first pixel - // of next block of 8 pixels added in. - int16x8_t prv0 = vextq_s16(curr, curr, 7); - int16x8_t nxt0 = vextq_s16(curr, curr, 1); - int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); - int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); - - // horizontal filter, polyphase implementation since it's convenient: - // even pixels = 3*cur + prev = cur*4 + (prev - cur) - // odd pixels = 3*cur + next = cur*4 + (next - cur) - // note the shared term. - int16x8_t curs = vshlq_n_s16(curr, 2); - int16x8_t prvd = vsubq_s16(prev, curr); - int16x8_t nxtd = vsubq_s16(next, curr); - int16x8_t even = vaddq_s16(curs, prvd); - int16x8_t odd = vaddq_s16(curs, nxtd); - - // undo scaling and round, then store with even/odd phases interleaved - uint8x8x2_t o; - o.val[0] = vqrshrun_n_s16(even, 4); - o.val[1] = vqrshrun_n_s16(odd, 4); - vst2_u8(out + i*2, o); -#endif - - // "previous" value for next iter - t1 = 3*in_near[i+7] + in_far[i+7]; - } - - t0 = t1; - t1 = 3*in_near[i] + in_far[i]; - out[i*2] = stbi__div16(3*t1 + t0 + 8); - - for (++i; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} -#endif - -static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // resample with nearest-neighbor - int i,j; - STBI_NOTUSED(in_far); - for (i=0; i < w; ++i) - for (j=0; j < hs; ++j) - out[i*hs+j] = in_near[i]; - return out; -} - -// this is a reduced-precision calculation of YCbCr-to-RGB introduced -// to make sure the code produces the same results in both SIMD and scalar -#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) -static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) -{ - int i; - for (i=0; i < count; ++i) { - int y_fixed = (y[i] << 20) + (1<<19); // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr* stbi__float2fixed(1.40200f); - g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* stbi__float2fixed(1.77200f); - r >>= 20; - g >>= 20; - b >>= 20; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} - -#if defined(STBI_SSE2) || defined(STBI_NEON) -static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) -{ - int i = 0; - -#ifdef STBI_SSE2 - // step == 3 is pretty ugly on the final interleave, and i'm not convinced - // it's useful in practice (you wouldn't use it for textures, for example). - // so just accelerate step == 4 case. - if (step == 4) { - // this is a fairly straightforward implementation and not super-optimized. - __m128i signflip = _mm_set1_epi8(-0x80); - __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); - __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); - __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); - __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); - __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); - __m128i xw = _mm_set1_epi16(255); // alpha channel - - for (; i+7 < count; i += 8) { - // load - __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); - __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); - __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); - __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 - __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 - - // unpack to short (and left-shift cr, cb by 8) - __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); - __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); - __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); - - // color transform - __m128i yws = _mm_srli_epi16(yw, 4); - __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); - __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); - __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); - __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); - __m128i rws = _mm_add_epi16(cr0, yws); - __m128i gwt = _mm_add_epi16(cb0, yws); - __m128i bws = _mm_add_epi16(yws, cb1); - __m128i gws = _mm_add_epi16(gwt, cr1); - - // descale - __m128i rw = _mm_srai_epi16(rws, 4); - __m128i bw = _mm_srai_epi16(bws, 4); - __m128i gw = _mm_srai_epi16(gws, 4); - - // back to byte, set up for transpose - __m128i brb = _mm_packus_epi16(rw, bw); - __m128i gxb = _mm_packus_epi16(gw, xw); - - // transpose to interleave channels - __m128i t0 = _mm_unpacklo_epi8(brb, gxb); - __m128i t1 = _mm_unpackhi_epi8(brb, gxb); - __m128i o0 = _mm_unpacklo_epi16(t0, t1); - __m128i o1 = _mm_unpackhi_epi16(t0, t1); - - // store - _mm_storeu_si128((__m128i *) (out + 0), o0); - _mm_storeu_si128((__m128i *) (out + 16), o1); - out += 32; - } - } -#endif - -#ifdef STBI_NEON - // in this version, step=3 support would be easy to add. but is there demand? - if (step == 4) { - // this is a fairly straightforward implementation and not super-optimized. - uint8x8_t signflip = vdup_n_u8(0x80); - int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); - int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); - int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); - int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); - - for (; i+7 < count; i += 8) { - // load - uint8x8_t y_bytes = vld1_u8(y + i); - uint8x8_t cr_bytes = vld1_u8(pcr + i); - uint8x8_t cb_bytes = vld1_u8(pcb + i); - int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); - int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); - - // expand to s16 - int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); - int16x8_t crw = vshll_n_s8(cr_biased, 7); - int16x8_t cbw = vshll_n_s8(cb_biased, 7); - - // color transform - int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); - int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); - int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); - int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); - int16x8_t rws = vaddq_s16(yws, cr0); - int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); - int16x8_t bws = vaddq_s16(yws, cb1); - - // undo scaling, round, convert to byte - uint8x8x4_t o; - o.val[0] = vqrshrun_n_s16(rws, 4); - o.val[1] = vqrshrun_n_s16(gws, 4); - o.val[2] = vqrshrun_n_s16(bws, 4); - o.val[3] = vdup_n_u8(255); - - // store, interleaving r/g/b/a - vst4_u8(out, o); - out += 8*4; - } - } -#endif - - for (; i < count; ++i) { - int y_fixed = (y[i] << 20) + (1<<19); // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr* stbi__float2fixed(1.40200f); - g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* stbi__float2fixed(1.77200f); - r >>= 20; - g >>= 20; - b >>= 20; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} -#endif - -// set up the kernels -static void stbi__setup_jpeg(stbi__jpeg *j) -{ - j->idct_block_kernel = stbi__idct_block; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; - -#ifdef STBI_SSE2 - if (stbi__sse2_available()) { - j->idct_block_kernel = stbi__idct_simd; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; - } -#endif - -#ifdef STBI_NEON - j->idct_block_kernel = stbi__idct_simd; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; -#endif -} - -// clean up the temporary component buffers -static void stbi__cleanup_jpeg(stbi__jpeg *j) -{ - stbi__free_jpeg_components(j, j->s->img_n, 0); -} - -typedef struct -{ - resample_row_func resample; - stbi_uc *line0,*line1; - int hs,vs; // expansion factor in each axis - int w_lores; // horizontal pixels pre-expansion - int ystep; // how far through vertical expansion we are - int ypos; // which pre-expansion row we're on -} stbi__resample; - -// fast 0..255 * 0..255 => 0..255 rounded multiplication -static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) -{ - unsigned int t = x*y + 128; - return (stbi_uc) ((t + (t >>8)) >> 8); -} - -static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) -{ - int n, decode_n, is_rgb; - z->s->img_n = 0; // make stbi__cleanup_jpeg safe - - // validate req_comp - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - - // load a jpeg image from whichever source, but leave in YCbCr format - if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } - - // determine actual number of components to generate - n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; - - is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); - - if (z->s->img_n == 3 && n < 3 && !is_rgb) - decode_n = 1; - else - decode_n = z->s->img_n; - - // resample and color-convert - { - int k; - unsigned int i,j; - stbi_uc *output; - stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; - - stbi__resample res_comp[4]; - - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - - // allocate line buffer big enough for upsampling off the edges - // with upsample factor of 4 - z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); - if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - r->hs = z->img_h_max / z->img_comp[k].h; - r->vs = z->img_v_max / z->img_comp[k].v; - r->ystep = r->vs >> 1; - r->w_lores = (z->s->img_x + r->hs-1) / r->hs; - r->ypos = 0; - r->line0 = r->line1 = z->img_comp[k].data; - - if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; - else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; - else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; - else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; - else r->resample = stbi__resample_row_generic; - } - - // can't error after this so, this is safe - output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); - if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - // now go ahead and resample - for (j=0; j < z->s->img_y; ++j) { - stbi_uc *out = output + n * z->s->img_x * j; - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - int y_bot = r->ystep >= (r->vs >> 1); - coutput[k] = r->resample(z->img_comp[k].linebuf, - y_bot ? r->line1 : r->line0, - y_bot ? r->line0 : r->line1, - r->w_lores, r->hs); - if (++r->ystep >= r->vs) { - r->ystep = 0; - r->line0 = r->line1; - if (++r->ypos < z->img_comp[k].y) - r->line1 += z->img_comp[k].w2; - } - } - if (n >= 3) { - stbi_uc *y = coutput[0]; - if (z->s->img_n == 3) { - if (is_rgb) { - for (i=0; i < z->s->img_x; ++i) { - out[0] = y[i]; - out[1] = coutput[1][i]; - out[2] = coutput[2][i]; - out[3] = 255; - out += n; - } - } else { - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } - } else if (z->s->img_n == 4) { - if (z->app14_color_transform == 0) { // CMYK - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - out[0] = stbi__blinn_8x8(coutput[0][i], m); - out[1] = stbi__blinn_8x8(coutput[1][i], m); - out[2] = stbi__blinn_8x8(coutput[2][i], m); - out[3] = 255; - out += n; - } - } else if (z->app14_color_transform == 2) { // YCCK - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - out[0] = stbi__blinn_8x8(255 - out[0], m); - out[1] = stbi__blinn_8x8(255 - out[1], m); - out[2] = stbi__blinn_8x8(255 - out[2], m); - out += n; - } - } else { // YCbCr + alpha? Ignore the fourth channel for now - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } - } else - for (i=0; i < z->s->img_x; ++i) { - out[0] = out[1] = out[2] = y[i]; - out[3] = 255; // not used if n==3 - out += n; - } - } else { - if (is_rgb) { - if (n == 1) - for (i=0; i < z->s->img_x; ++i) - *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); - else { - for (i=0; i < z->s->img_x; ++i, out += 2) { - out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); - out[1] = 255; - } - } - } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); - stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); - stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); - out[0] = stbi__compute_y(r, g, b); - out[1] = 255; - out += n; - } - } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { - for (i=0; i < z->s->img_x; ++i) { - out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); - out[1] = 255; - out += n; - } - } else { - stbi_uc *y = coutput[0]; - if (n == 1) - for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; - else - for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } - } - } - } - stbi__cleanup_jpeg(z); - *out_x = z->s->img_x; - *out_y = z->s->img_y; - if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output - return output; - } -} - -static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - unsigned char* result; - stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); - STBI_NOTUSED(ri); - j->s = s; - stbi__setup_jpeg(j); - result = load_jpeg_image(j, x,y,comp,req_comp); - STBI_FREE(j); - return result; -} - -static int stbi__jpeg_test(stbi__context *s) -{ - int r; - stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); - j->s = s; - stbi__setup_jpeg(j); - r = stbi__decode_jpeg_header(j, STBI__SCAN_type); - stbi__rewind(s); - STBI_FREE(j); - return r; -} - -static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) -{ - if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { - stbi__rewind( j->s ); - return 0; - } - if (x) *x = j->s->img_x; - if (y) *y = j->s->img_y; - if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; - return 1; -} - -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) -{ - int result; - stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); - j->s = s; - result = stbi__jpeg_info_raw(j, x, y, comp); - STBI_FREE(j); - return result; -} -#endif - -// public domain zlib decode v0.2 Sean Barrett 2006-11-18 -// simple implementation -// - all input must be provided in an upfront buffer -// - all output is written to a single output buffer (can malloc/realloc) -// performance -// - fast huffman - -#ifndef STBI_NO_ZLIB - -// fast-way is faster to check than jpeg huffman, but slow way is slower -#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables -#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) - -// zlib-style huffman encoding -// (jpegs packs from left, zlib from right, so can't share code) -typedef struct -{ - stbi__uint16 fast[1 << STBI__ZFAST_BITS]; - stbi__uint16 firstcode[16]; - int maxcode[17]; - stbi__uint16 firstsymbol[16]; - stbi_uc size[288]; - stbi__uint16 value[288]; -} stbi__zhuffman; - -stbi_inline static int stbi__bitreverse16(int n) -{ - n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); - n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); - n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); - n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); - return n; -} - -stbi_inline static int stbi__bit_reverse(int v, int bits) -{ - STBI_ASSERT(bits <= 16); - // to bit reverse n bits, reverse 16 and shift - // e.g. 11 bits, bit reverse and shift away 5 - return stbi__bitreverse16(v) >> (16-bits); -} - -static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) -{ - int i,k=0; - int code, next_code[16], sizes[17]; - - // DEFLATE spec for generating codes - memset(sizes, 0, sizeof(sizes)); - memset(z->fast, 0, sizeof(z->fast)); - for (i=0; i < num; ++i) - ++sizes[sizelist[i]]; - sizes[0] = 0; - for (i=1; i < 16; ++i) - if (sizes[i] > (1 << i)) - return stbi__err("bad sizes", "Corrupt PNG"); - code = 0; - for (i=1; i < 16; ++i) { - next_code[i] = code; - z->firstcode[i] = (stbi__uint16) code; - z->firstsymbol[i] = (stbi__uint16) k; - code = (code + sizes[i]); - if (sizes[i]) - if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); - z->maxcode[i] = code << (16-i); // preshift for inner loop - code <<= 1; - k += sizes[i]; - } - z->maxcode[16] = 0x10000; // sentinel - for (i=0; i < num; ++i) { - int s = sizelist[i]; - if (s) { - int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; - stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); - z->size [c] = (stbi_uc ) s; - z->value[c] = (stbi__uint16) i; - if (s <= STBI__ZFAST_BITS) { - int j = stbi__bit_reverse(next_code[s],s); - while (j < (1 << STBI__ZFAST_BITS)) { - z->fast[j] = fastv; - j += (1 << s); - } - } - ++next_code[s]; - } - } - return 1; -} - -// zlib-from-memory implementation for PNG reading -// because PNG allows splitting the zlib stream arbitrarily, -// and it's annoying structurally to have PNG call ZLIB call PNG, -// we require PNG read all the IDATs and combine them into a single -// memory buffer - -typedef struct -{ - stbi_uc *zbuffer, *zbuffer_end; - int num_bits; - stbi__uint32 code_buffer; - - char *zout; - char *zout_start; - char *zout_end; - int z_expandable; - - stbi__zhuffman z_length, z_distance; -} stbi__zbuf; - -stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) -{ - if (z->zbuffer >= z->zbuffer_end) return 0; - return *z->zbuffer++; -} - -static void stbi__fill_bits(stbi__zbuf *z) -{ - do { - STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); - z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; - z->num_bits += 8; - } while (z->num_bits <= 24); -} - -stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) -{ - unsigned int k; - if (z->num_bits < n) stbi__fill_bits(z); - k = z->code_buffer & ((1 << n) - 1); - z->code_buffer >>= n; - z->num_bits -= n; - return k; -} - -static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s,k; - // not resolved by fast table, so compute it the slow way - // use jpeg approach, which requires MSbits at top - k = stbi__bit_reverse(a->code_buffer, 16); - for (s=STBI__ZFAST_BITS+1; ; ++s) - if (k < z->maxcode[s]) - break; - if (s == 16) return -1; // invalid code! - // code size is s, so: - b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; - STBI_ASSERT(z->size[b] == s); - a->code_buffer >>= s; - a->num_bits -= s; - return z->value[b]; -} - -stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s; - if (a->num_bits < 16) stbi__fill_bits(a); - b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; - if (b) { - s = b >> 9; - a->code_buffer >>= s; - a->num_bits -= s; - return b & 511; - } - return stbi__zhuffman_decode_slowpath(a, z); -} - -static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes -{ - char *q; - int cur, limit, old_limit; - z->zout = zout; - if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); - cur = (int) (z->zout - z->zout_start); - limit = old_limit = (int) (z->zout_end - z->zout_start); - while (cur + n > limit) - limit *= 2; - q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); - STBI_NOTUSED(old_limit); - if (q == NULL) return stbi__err("outofmem", "Out of memory"); - z->zout_start = q; - z->zout = q + cur; - z->zout_end = q + limit; - return 1; -} - -static const int stbi__zlength_base[31] = { - 3,4,5,6,7,8,9,10,11,13, - 15,17,19,23,27,31,35,43,51,59, - 67,83,99,115,131,163,195,227,258,0,0 }; - -static const int stbi__zlength_extra[31]= -{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; - -static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, -257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; - -static const int stbi__zdist_extra[32] = -{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; - -static int stbi__parse_huffman_block(stbi__zbuf *a) -{ - char *zout = a->zout; - for(;;) { - int z = stbi__zhuffman_decode(a, &a->z_length); - if (z < 256) { - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes - if (zout >= a->zout_end) { - if (!stbi__zexpand(a, zout, 1)) return 0; - zout = a->zout; - } - *zout++ = (char) z; - } else { - stbi_uc *p; - int len,dist; - if (z == 256) { - a->zout = zout; - return 1; - } - z -= 257; - len = stbi__zlength_base[z]; - if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); - z = stbi__zhuffman_decode(a, &a->z_distance); - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); - dist = stbi__zdist_base[z]; - if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); - if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); - if (zout + len > a->zout_end) { - if (!stbi__zexpand(a, zout, len)) return 0; - zout = a->zout; - } - p = (stbi_uc *) (zout - dist); - if (dist == 1) { // run of one byte; common in images. - stbi_uc v = *p; - if (len) { do *zout++ = v; while (--len); } - } else { - if (len) { do *zout++ = *p++; while (--len); } - } - } - } -} - -static int stbi__compute_huffman_codes(stbi__zbuf *a) -{ - static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; - stbi__zhuffman z_codelength; - stbi_uc lencodes[286+32+137];//padding for maximum single op - stbi_uc codelength_sizes[19]; - int i,n; - - int hlit = stbi__zreceive(a,5) + 257; - int hdist = stbi__zreceive(a,5) + 1; - int hclen = stbi__zreceive(a,4) + 4; - int ntot = hlit + hdist; - - memset(codelength_sizes, 0, sizeof(codelength_sizes)); - for (i=0; i < hclen; ++i) { - int s = stbi__zreceive(a,3); - codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; - } - if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; - - n = 0; - while (n < ntot) { - int c = stbi__zhuffman_decode(a, &z_codelength); - if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); - if (c < 16) - lencodes[n++] = (stbi_uc) c; - else { - stbi_uc fill = 0; - if (c == 16) { - c = stbi__zreceive(a,2)+3; - if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); - fill = lencodes[n-1]; - } else if (c == 17) - c = stbi__zreceive(a,3)+3; - else { - STBI_ASSERT(c == 18); - c = stbi__zreceive(a,7)+11; - } - if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); - memset(lencodes+n, fill, c); - n += c; - } - } - if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); - if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; - return 1; -} - -static int stbi__parse_uncompressed_block(stbi__zbuf *a) -{ - stbi_uc header[4]; - int len,nlen,k; - if (a->num_bits & 7) - stbi__zreceive(a, a->num_bits & 7); // discard - // drain the bit-packed data into header - k = 0; - while (a->num_bits > 0) { - header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check - a->code_buffer >>= 8; - a->num_bits -= 8; - } - STBI_ASSERT(a->num_bits == 0); - // now fill header the normal way - while (k < 4) - header[k++] = stbi__zget8(a); - len = header[1] * 256 + header[0]; - nlen = header[3] * 256 + header[2]; - if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); - if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); - if (a->zout + len > a->zout_end) - if (!stbi__zexpand(a, a->zout, len)) return 0; - memcpy(a->zout, a->zbuffer, len); - a->zbuffer += len; - a->zout += len; - return 1; -} - -static int stbi__parse_zlib_header(stbi__zbuf *a) -{ - int cmf = stbi__zget8(a); - int cm = cmf & 15; - /* int cinfo = cmf >> 4; */ - int flg = stbi__zget8(a); - if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec - if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png - if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png - // window = 1 << (8 + cinfo)... but who cares, we fully buffer output - return 1; -} - -static const stbi_uc stbi__zdefault_length[288] = -{ - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 -}; -static const stbi_uc stbi__zdefault_distance[32] = -{ - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 -}; -/* -Init algorithm: -{ - int i; // use <= to match clearly with spec - for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; - for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; - for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; - for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; - - for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; -} -*/ - -static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) -{ - int final, type; - if (parse_header) - if (!stbi__parse_zlib_header(a)) return 0; - a->num_bits = 0; - a->code_buffer = 0; - do { - final = stbi__zreceive(a,1); - type = stbi__zreceive(a,2); - if (type == 0) { - if (!stbi__parse_uncompressed_block(a)) return 0; - } else if (type == 3) { - return 0; - } else { - if (type == 1) { - // use fixed code lengths - if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; - } else { - if (!stbi__compute_huffman_codes(a)) return 0; - } - if (!stbi__parse_huffman_block(a)) return 0; - } - } while (!final); - return 1; -} - -static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) -{ - a->zout_start = obuf; - a->zout = obuf; - a->zout_end = obuf + olen; - a->z_expandable = exp; - - return stbi__parse_zlib(a, parse_header); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) -{ - return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) - return (int) (a.zout - a.zout_start); - else - return -1; -} - -STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(16384); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer+len; - if (stbi__do_zlib(&a, p, 16384, 1, 0)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) - return (int) (a.zout - a.zout_start); - else - return -1; -} -#endif - -// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 -// simple implementation -// - only 8-bit samples -// - no CRC checking -// - allocates lots of intermediate memory -// - avoids problem of streaming data between subsystems -// - avoids explicit window management -// performance -// - uses stb_zlib, a PD zlib implementation with fast huffman decoding - -#ifndef STBI_NO_PNG -typedef struct -{ - stbi__uint32 length; - stbi__uint32 type; -} stbi__pngchunk; - -static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) -{ - stbi__pngchunk c; - c.length = stbi__get32be(s); - c.type = stbi__get32be(s); - return c; -} - -static int stbi__check_png_header(stbi__context *s) -{ - static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; - int i; - for (i=0; i < 8; ++i) - if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); - return 1; -} - -typedef struct -{ - stbi__context *s; - stbi_uc *idata, *expanded, *out; - int depth; -} stbi__png; - - -enum { - STBI__F_none=0, - STBI__F_sub=1, - STBI__F_up=2, - STBI__F_avg=3, - STBI__F_paeth=4, - // synthetic filters used for first scanline to avoid needing a dummy row of 0s - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static stbi_uc first_row_filter[5] = -{ - STBI__F_none, - STBI__F_sub, - STBI__F_none, - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static int stbi__paeth(int a, int b, int c) -{ - int p = a + b - c; - int pa = abs(p-a); - int pb = abs(p-b); - int pc = abs(p-c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; -} - -static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; - -// create the png data from post-deflated data -static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) -{ - int bytes = (depth == 16? 2 : 1); - stbi__context *s = a->s; - stbi__uint32 i,j,stride = x*out_n*bytes; - stbi__uint32 img_len, img_width_bytes; - int k; - int img_n = s->img_n; // copy it into a local for later - - int output_bytes = out_n*bytes; - int filter_bytes = img_n*bytes; - int width = x; - - STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); - a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into - if (!a->out) return stbi__err("outofmem", "Out of memory"); - - if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); - img_width_bytes = (((img_n * x * depth) + 7) >> 3); - img_len = (img_width_bytes + 1) * y; - - // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, - // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), - // so just check for raw_len < img_len always. - if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); - - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *prior; - int filter = *raw++; - - if (filter > 4) - return stbi__err("invalid filter","Corrupt PNG"); - - if (depth < 8) { - STBI_ASSERT(img_width_bytes <= x); - cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place - filter_bytes = 1; - width = img_width_bytes; - } - prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above - - // if first row, use special filter that doesn't sample previous row - if (j == 0) filter = first_row_filter[filter]; - - // handle first byte explicitly - for (k=0; k < filter_bytes; ++k) { - switch (filter) { - case STBI__F_none : cur[k] = raw[k]; break; - case STBI__F_sub : cur[k] = raw[k]; break; - case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; - case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; - case STBI__F_avg_first : cur[k] = raw[k]; break; - case STBI__F_paeth_first: cur[k] = raw[k]; break; - } - } - - if (depth == 8) { - if (img_n != out_n) - cur[img_n] = 255; // first pixel - raw += img_n; - cur += out_n; - prior += out_n; - } else if (depth == 16) { - if (img_n != out_n) { - cur[filter_bytes] = 255; // first pixel top byte - cur[filter_bytes+1] = 255; // first pixel bottom byte - } - raw += filter_bytes; - cur += output_bytes; - prior += output_bytes; - } else { - raw += 1; - cur += 1; - prior += 1; - } - - // this is a little gross, so that we don't switch per-pixel or per-component - if (depth < 8 || img_n == out_n) { - int nk = (width - 1)*filter_bytes; - #define STBI__CASE(f) \ - case f: \ - for (k=0; k < nk; ++k) - switch (filter) { - // "none" filter turns into a memcpy here; make that explicit. - case STBI__F_none: memcpy(cur, raw, nk); break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; - } - #undef STBI__CASE - raw += nk; - } else { - STBI_ASSERT(img_n+1 == out_n); - #define STBI__CASE(f) \ - case f: \ - for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ - for (k=0; k < filter_bytes; ++k) - switch (filter) { - STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; - } - #undef STBI__CASE - - // the loop above sets the high byte of the pixels' alpha, but for - // 16 bit png files we also need the low byte set. we'll do that here. - if (depth == 16) { - cur = a->out + stride*j; // start at the beginning of the row again - for (i=0; i < x; ++i,cur+=output_bytes) { - cur[filter_bytes+1] = 255; - } - } - } - } - - // we make a separate pass to expand bits to pixels; for performance, - // this could run two scanlines behind the above code, so it won't - // intefere with filtering but will still be in the cache. - if (depth < 8) { - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; - // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit - // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop - stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range - - // note that the final byte might overshoot and write more data than desired. - // we can allocate enough data that this never writes out of memory, but it - // could also overwrite the next scanline. can it overwrite non-empty data - // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. - // so we need to explicitly clamp the final ones - - if (depth == 4) { - for (k=x*img_n; k >= 2; k-=2, ++in) { - *cur++ = scale * ((*in >> 4) ); - *cur++ = scale * ((*in ) & 0x0f); - } - if (k > 0) *cur++ = scale * ((*in >> 4) ); - } else if (depth == 2) { - for (k=x*img_n; k >= 4; k-=4, ++in) { - *cur++ = scale * ((*in >> 6) ); - *cur++ = scale * ((*in >> 4) & 0x03); - *cur++ = scale * ((*in >> 2) & 0x03); - *cur++ = scale * ((*in ) & 0x03); - } - if (k > 0) *cur++ = scale * ((*in >> 6) ); - if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); - if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); - } else if (depth == 1) { - for (k=x*img_n; k >= 8; k-=8, ++in) { - *cur++ = scale * ((*in >> 7) ); - *cur++ = scale * ((*in >> 6) & 0x01); - *cur++ = scale * ((*in >> 5) & 0x01); - *cur++ = scale * ((*in >> 4) & 0x01); - *cur++ = scale * ((*in >> 3) & 0x01); - *cur++ = scale * ((*in >> 2) & 0x01); - *cur++ = scale * ((*in >> 1) & 0x01); - *cur++ = scale * ((*in ) & 0x01); - } - if (k > 0) *cur++ = scale * ((*in >> 7) ); - if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); - if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); - if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); - if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); - if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); - if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); - } - if (img_n != out_n) { - int q; - // insert alpha = 255 - cur = a->out + stride*j; - if (img_n == 1) { - for (q=x-1; q >= 0; --q) { - cur[q*2+1] = 255; - cur[q*2+0] = cur[q]; - } - } else { - STBI_ASSERT(img_n == 3); - for (q=x-1; q >= 0; --q) { - cur[q*4+3] = 255; - cur[q*4+2] = cur[q*3+2]; - cur[q*4+1] = cur[q*3+1]; - cur[q*4+0] = cur[q*3+0]; - } - } - } - } - } else if (depth == 16) { - // force the image data from big-endian to platform-native. - // this is done in a separate pass due to the decoding relying - // on the data being untouched, but could probably be done - // per-line during decode if care is taken. - stbi_uc *cur = a->out; - stbi__uint16 *cur16 = (stbi__uint16*)cur; - - for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { - *cur16 = (cur[0] << 8) | cur[1]; - } - } - - return 1; -} - -static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) -{ - int bytes = (depth == 16 ? 2 : 1); - int out_bytes = out_n * bytes; - stbi_uc *final; - int p; - if (!interlaced) - return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); - - // de-interlacing - final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); - for (p=0; p < 7; ++p) { - int xorig[] = { 0,4,0,2,0,1,0 }; - int yorig[] = { 0,0,4,0,2,0,1 }; - int xspc[] = { 8,8,4,4,2,2,1 }; - int yspc[] = { 8,8,8,4,4,2,2 }; - int i,j,x,y; - // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 - x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; - y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; - if (x && y) { - stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; - if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { - STBI_FREE(final); - return 0; - } - for (j=0; j < y; ++j) { - for (i=0; i < x; ++i) { - int out_y = j*yspc[p]+yorig[p]; - int out_x = i*xspc[p]+xorig[p]; - memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, - a->out + (j*x+i)*out_bytes, out_bytes); - } - } - STBI_FREE(a->out); - image_data += img_len; - image_data_len -= img_len; - } - } - a->out = final; - - return 1; -} - -static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - // compute color-based transparency, assuming we've - // already got 255 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i=0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 255); - p += 2; - } - } else { - for (i=0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi__uint16 *p = (stbi__uint16*) z->out; - - // compute color-based transparency, assuming we've - // already got 65535 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i = 0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 65535); - p += 2; - } - } else { - for (i = 0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) -{ - stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; - stbi_uc *p, *temp_out, *orig = a->out; - - p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); - if (p == NULL) return stbi__err("outofmem", "Out of memory"); - - // between here and free(out) below, exitting would leak - temp_out = p; - - if (pal_img_n == 3) { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p += 3; - } - } else { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p[3] = palette[n+3]; - p += 4; - } - } - STBI_FREE(a->out); - a->out = temp_out; - - STBI_NOTUSED(len); - - return 1; -} - -static int stbi__unpremultiply_on_load = 0; -static int stbi__de_iphone_flag = 0; - -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) -{ - stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; -} - -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) -{ - stbi__de_iphone_flag = flag_true_if_should_convert; -} - -static void stbi__de_iphone(stbi__png *z) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - if (s->img_out_n == 3) { // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 3; - } - } else { - STBI_ASSERT(s->img_out_n == 4); - if (stbi__unpremultiply_on_load) { - // convert bgr to rgb and unpremultiply - for (i=0; i < pixel_count; ++i) { - stbi_uc a = p[3]; - stbi_uc t = p[0]; - if (a) { - stbi_uc half = a / 2; - p[0] = (p[2] * 255 + half) / a; - p[1] = (p[1] * 255 + half) / a; - p[2] = ( t * 255 + half) / a; - } else { - p[0] = p[2]; - p[2] = t; - } - p += 4; - } - } else { - // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 4; - } - } - } -} - -#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) - -static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) -{ - stbi_uc palette[1024], pal_img_n=0; - stbi_uc has_trans=0, tc[3]={0}; - stbi__uint16 tc16[3]; - stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; - int first=1,k,interlace=0, color=0, is_iphone=0; - stbi__context *s = z->s; - - z->expanded = NULL; - z->idata = NULL; - z->out = NULL; - - if (!stbi__check_png_header(s)) return 0; - - if (scan == STBI__SCAN_type) return 1; - - for (;;) { - stbi__pngchunk c = stbi__get_chunk_header(s); - switch (c.type) { - case STBI__PNG_TYPE('C','g','B','I'): - is_iphone = 1; - stbi__skip(s, c.length); - break; - case STBI__PNG_TYPE('I','H','D','R'): { - int comp,filter; - if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); - first = 0; - if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); - s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); - color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); - comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); - filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); - interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); - if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); - if (!pal_img_n) { - s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); - if (scan == STBI__SCAN_header) return 1; - } else { - // if paletted, then pal_n is our final components, and - // img_n is # components to decompress/filter. - s->img_n = 1; - if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); - // if SCAN_header, have to scan to see if we have a tRNS - } - break; - } - - case STBI__PNG_TYPE('P','L','T','E'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); - pal_len = c.length / 3; - if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); - for (i=0; i < pal_len; ++i) { - palette[i*4+0] = stbi__get8(s); - palette[i*4+1] = stbi__get8(s); - palette[i*4+2] = stbi__get8(s); - palette[i*4+3] = 255; - } - break; - } - - case STBI__PNG_TYPE('t','R','N','S'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); - if (pal_img_n) { - if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } - if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); - if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); - pal_img_n = 4; - for (i=0; i < c.length; ++i) - palette[i*4+3] = stbi__get8(s); - } else { - if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); - if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); - has_trans = 1; - if (z->depth == 16) { - for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is - } else { - for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger - } - } - break; - } - - case STBI__PNG_TYPE('I','D','A','T'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); - if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } - if ((int)(ioff + c.length) < (int)ioff) return 0; - if (ioff + c.length > idata_limit) { - stbi__uint32 idata_limit_old = idata_limit; - stbi_uc *p; - if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; - while (ioff + c.length > idata_limit) - idata_limit *= 2; - STBI_NOTUSED(idata_limit_old); - p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); - z->idata = p; - } - if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); - ioff += c.length; - break; - } - - case STBI__PNG_TYPE('I','E','N','D'): { - stbi__uint32 raw_len, bpl; - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (scan != STBI__SCAN_load) return 1; - if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); - // initial guess for decoded data size to avoid unnecessary reallocs - bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component - raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; - z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); - if (z->expanded == NULL) return 0; // zlib should set error - STBI_FREE(z->idata); z->idata = NULL; - if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) - s->img_out_n = s->img_n+1; - else - s->img_out_n = s->img_n; - if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; - if (has_trans) { - if (z->depth == 16) { - if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; - } else { - if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; - } - } - if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) - stbi__de_iphone(z); - if (pal_img_n) { - // pal_img_n == 3 or 4 - s->img_n = pal_img_n; // record the actual colors we had - s->img_out_n = pal_img_n; - if (req_comp >= 3) s->img_out_n = req_comp; - if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) - return 0; - } else if (has_trans) { - // non-paletted image with tRNS -> source image has (constant) alpha - ++s->img_n; - } - STBI_FREE(z->expanded); z->expanded = NULL; - // end of PNG chunk, read and skip CRC - stbi__get32be(s); - return 1; - } - - default: - // if critical, fail - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if ((c.type & (1 << 29)) == 0) { - #ifndef STBI_NO_FAILURE_STRINGS - // not threadsafe - static char invalid_chunk[] = "XXXX PNG chunk not known"; - invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); - invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); - invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); - invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); - #endif - return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); - } - stbi__skip(s, c.length); - break; - } - // end of PNG chunk, read and skip CRC - stbi__get32be(s); - } -} - -static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) -{ - void *result=NULL; - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { - if (p->depth < 8) - ri->bits_per_channel = 8; - else - ri->bits_per_channel = p->depth; - result = p->out; - p->out = NULL; - if (req_comp && req_comp != p->s->img_out_n) { - if (ri->bits_per_channel == 8) - result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - else - result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - p->s->img_out_n = req_comp; - if (result == NULL) return result; - } - *x = p->s->img_x; - *y = p->s->img_y; - if (n) *n = p->s->img_n; - } - STBI_FREE(p->out); p->out = NULL; - STBI_FREE(p->expanded); p->expanded = NULL; - STBI_FREE(p->idata); p->idata = NULL; - - return result; -} - -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi__png p; - p.s = s; - return stbi__do_png(&p, x,y,comp,req_comp, ri); -} - -static int stbi__png_test(stbi__context *s) -{ - int r; - r = stbi__check_png_header(s); - stbi__rewind(s); - return r; -} - -static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) -{ - if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { - stbi__rewind( p->s ); - return 0; - } - if (x) *x = p->s->img_x; - if (y) *y = p->s->img_y; - if (comp) *comp = p->s->img_n; - return 1; -} - -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__png p; - p.s = s; - return stbi__png_info_raw(&p, x, y, comp); -} - -static int stbi__png_is16(stbi__context *s) -{ - stbi__png p; - p.s = s; - if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) - return 0; - if (p.depth != 16) { - stbi__rewind(p.s); - return 0; - } - return 1; -} -#endif - -// Microsoft/Windows BMP image - -#ifndef STBI_NO_BMP -static int stbi__bmp_test_raw(stbi__context *s) -{ - int r; - int sz; - if (stbi__get8(s) != 'B') return 0; - if (stbi__get8(s) != 'M') return 0; - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - stbi__get32le(s); // discard data offset - sz = stbi__get32le(s); - r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); - return r; -} - -static int stbi__bmp_test(stbi__context *s) -{ - int r = stbi__bmp_test_raw(s); - stbi__rewind(s); - return r; -} - - -// returns 0..31 for the highest set bit -static int stbi__high_bit(unsigned int z) -{ - int n=0; - if (z == 0) return -1; - if (z >= 0x10000) { n += 16; z >>= 16; } - if (z >= 0x00100) { n += 8; z >>= 8; } - if (z >= 0x00010) { n += 4; z >>= 4; } - if (z >= 0x00004) { n += 2; z >>= 2; } - if (z >= 0x00002) { n += 1;/* >>= 1;*/ } - return n; -} - -static int stbi__bitcount(unsigned int a) -{ - a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 - a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 - a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits - a = (a + (a >> 8)); // max 16 per 8 bits - a = (a + (a >> 16)); // max 32 per 8 bits - return a & 0xff; -} - -// extract an arbitrarily-aligned N-bit value (N=bits) -// from v, and then make it 8-bits long and fractionally -// extend it to full full range. -static int stbi__shiftsigned(unsigned int v, int shift, int bits) -{ - static unsigned int mul_table[9] = { - 0, - 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, - 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, - }; - static unsigned int shift_table[9] = { - 0, 0,0,1,0,2,4,6,0, - }; - if (shift < 0) - v <<= -shift; - else - v >>= shift; - STBI_ASSERT(v < 256); - v >>= (8-bits); - STBI_ASSERT(bits >= 0 && bits <= 8); - return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; -} - -typedef struct -{ - int bpp, offset, hsz; - unsigned int mr,mg,mb,ma, all_a; - int extra_read; -} stbi__bmp_data; - -static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) -{ - int hsz; - if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - info->offset = stbi__get32le(s); - info->hsz = hsz = stbi__get32le(s); - info->mr = info->mg = info->mb = info->ma = 0; - info->extra_read = 14; - - if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); - if (hsz == 12) { - s->img_x = stbi__get16le(s); - s->img_y = stbi__get16le(s); - } else { - s->img_x = stbi__get32le(s); - s->img_y = stbi__get32le(s); - } - if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); - info->bpp = stbi__get16le(s); - if (hsz != 12) { - int compress = stbi__get32le(s); - if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); - stbi__get32le(s); // discard sizeof - stbi__get32le(s); // discard hres - stbi__get32le(s); // discard vres - stbi__get32le(s); // discard colorsused - stbi__get32le(s); // discard max important - if (hsz == 40 || hsz == 56) { - if (hsz == 56) { - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - } - if (info->bpp == 16 || info->bpp == 32) { - if (compress == 0) { - if (info->bpp == 32) { - info->mr = 0xffu << 16; - info->mg = 0xffu << 8; - info->mb = 0xffu << 0; - info->ma = 0xffu << 24; - info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 - } else { - info->mr = 31u << 10; - info->mg = 31u << 5; - info->mb = 31u << 0; - } - } else if (compress == 3) { - info->mr = stbi__get32le(s); - info->mg = stbi__get32le(s); - info->mb = stbi__get32le(s); - info->extra_read += 12; - // not documented, but generated by photoshop and handled by mspaint - if (info->mr == info->mg && info->mg == info->mb) { - // ?!?!? - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else { - int i; - if (hsz != 108 && hsz != 124) - return stbi__errpuc("bad BMP", "bad BMP"); - info->mr = stbi__get32le(s); - info->mg = stbi__get32le(s); - info->mb = stbi__get32le(s); - info->ma = stbi__get32le(s); - stbi__get32le(s); // discard color space - for (i=0; i < 12; ++i) - stbi__get32le(s); // discard color space parameters - if (hsz == 124) { - stbi__get32le(s); // discard rendering intent - stbi__get32le(s); // discard offset of profile data - stbi__get32le(s); // discard size of profile data - stbi__get32le(s); // discard reserved - } - } - } - return (void *) 1; -} - - -static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *out; - unsigned int mr=0,mg=0,mb=0,ma=0, all_a; - stbi_uc pal[256][4]; - int psize=0,i,j,width; - int flip_vertically, pad, target; - stbi__bmp_data info; - STBI_NOTUSED(ri); - - info.all_a = 255; - if (stbi__bmp_parse_header(s, &info) == NULL) - return NULL; // error code already set - - flip_vertically = ((int) s->img_y) > 0; - s->img_y = abs((int) s->img_y); - - mr = info.mr; - mg = info.mg; - mb = info.mb; - ma = info.ma; - all_a = info.all_a; - - if (info.hsz == 12) { - if (info.bpp < 24) - psize = (info.offset - info.extra_read - 24) / 3; - } else { - if (info.bpp < 16) - psize = (info.offset - info.extra_read - info.hsz) >> 2; - } - if (psize == 0) { - STBI_ASSERT(info.offset == (s->img_buffer - s->buffer_start)); - } - - if (info.bpp == 24 && ma == 0xff000000) - s->img_n = 3; - else - s->img_n = ma ? 4 : 3; - if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 - target = req_comp; - else - target = s->img_n; // if they want monochrome, we'll post-convert - - // sanity-check size - if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) - return stbi__errpuc("too large", "Corrupt BMP"); - - out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - if (info.bpp < 16) { - int z=0; - if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } - for (i=0; i < psize; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - if (info.hsz != 12) stbi__get8(s); - pal[i][3] = 255; - } - stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); - if (info.bpp == 1) width = (s->img_x + 7) >> 3; - else if (info.bpp == 4) width = (s->img_x + 1) >> 1; - else if (info.bpp == 8) width = s->img_x; - else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } - pad = (-width)&3; - if (info.bpp == 1) { - for (j=0; j < (int) s->img_y; ++j) { - int bit_offset = 7, v = stbi__get8(s); - for (i=0; i < (int) s->img_x; ++i) { - int color = (v>>bit_offset)&0x1; - out[z++] = pal[color][0]; - out[z++] = pal[color][1]; - out[z++] = pal[color][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - if((--bit_offset) < 0) { - bit_offset = 7; - v = stbi__get8(s); - } - } - stbi__skip(s, pad); - } - } else { - for (j=0; j < (int) s->img_y; ++j) { - for (i=0; i < (int) s->img_x; i += 2) { - int v=stbi__get8(s),v2=0; - if (info.bpp == 4) { - v2 = v & 15; - v >>= 4; - } - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - v = (info.bpp == 8) ? stbi__get8(s) : v2; - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - } - stbi__skip(s, pad); - } - } - } else { - int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; - int z = 0; - int easy=0; - stbi__skip(s, info.offset - info.extra_read - info.hsz); - if (info.bpp == 24) width = 3 * s->img_x; - else if (info.bpp == 16) width = 2*s->img_x; - else /* bpp = 32 and pad = 0 */ width=0; - pad = (-width) & 3; - if (info.bpp == 24) { - easy = 1; - } else if (info.bpp == 32) { - if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) - easy = 2; - } - if (!easy) { - if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } - // right shift amt to put high bit in position #7 - rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); - gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); - bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); - ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); - } - for (j=0; j < (int) s->img_y; ++j) { - if (easy) { - for (i=0; i < (int) s->img_x; ++i) { - unsigned char a; - out[z+2] = stbi__get8(s); - out[z+1] = stbi__get8(s); - out[z+0] = stbi__get8(s); - z += 3; - a = (easy == 2 ? stbi__get8(s) : 255); - all_a |= a; - if (target == 4) out[z++] = a; - } - } else { - int bpp = info.bpp; - for (i=0; i < (int) s->img_x; ++i) { - stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); - unsigned int a; - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); - a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); - all_a |= a; - if (target == 4) out[z++] = STBI__BYTECAST(a); - } - } - stbi__skip(s, pad); - } - } - - // if alpha channel is all 0s, replace with all 255s - if (target == 4 && all_a == 0) - for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) - out[i] = 255; - - if (flip_vertically) { - stbi_uc t; - for (j=0; j < (int) s->img_y>>1; ++j) { - stbi_uc *p1 = out + j *s->img_x*target; - stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; - for (i=0; i < (int) s->img_x*target; ++i) { - t = p1[i]; p1[i] = p2[i]; p2[i] = t; - } - } - } - - if (req_comp && req_comp != target) { - out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = s->img_n; - return out; -} -#endif - -// Targa Truevision - TGA -// by Jonathan Dummer -#ifndef STBI_NO_TGA -// returns STBI_rgb or whatever, 0 on error -static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) -{ - // only RGB or RGBA (incl. 16bit) or grey allowed - if (is_rgb16) *is_rgb16 = 0; - switch(bits_per_pixel) { - case 8: return STBI_grey; - case 16: if(is_grey) return STBI_grey_alpha; - // fallthrough - case 15: if(is_rgb16) *is_rgb16 = 1; - return STBI_rgb; - case 24: // fallthrough - case 32: return bits_per_pixel/8; - default: return 0; - } -} - -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) -{ - int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; - int sz, tga_colormap_type; - stbi__get8(s); // discard Offset - tga_colormap_type = stbi__get8(s); // colormap type - if( tga_colormap_type > 1 ) { - stbi__rewind(s); - return 0; // only RGB or indexed allowed - } - tga_image_type = stbi__get8(s); // image type - if ( tga_colormap_type == 1 ) { // colormapped (paletted) image - if (tga_image_type != 1 && tga_image_type != 9) { - stbi__rewind(s); - return 0; - } - stbi__skip(s,4); // skip index of first colormap entry and number of entries - sz = stbi__get8(s); // check bits per palette color entry - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { - stbi__rewind(s); - return 0; - } - stbi__skip(s,4); // skip image x and y origin - tga_colormap_bpp = sz; - } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE - if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { - stbi__rewind(s); - return 0; // only RGB or grey allowed, +/- RLE - } - stbi__skip(s,9); // skip colormap specification and image x/y origin - tga_colormap_bpp = 0; - } - tga_w = stbi__get16le(s); - if( tga_w < 1 ) { - stbi__rewind(s); - return 0; // test width - } - tga_h = stbi__get16le(s); - if( tga_h < 1 ) { - stbi__rewind(s); - return 0; // test height - } - tga_bits_per_pixel = stbi__get8(s); // bits per pixel - stbi__get8(s); // ignore alpha bits - if (tga_colormap_bpp != 0) { - if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { - // when using a colormap, tga_bits_per_pixel is the size of the indexes - // I don't think anything but 8 or 16bit indexes makes sense - stbi__rewind(s); - return 0; - } - tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); - } else { - tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); - } - if(!tga_comp) { - stbi__rewind(s); - return 0; - } - if (x) *x = tga_w; - if (y) *y = tga_h; - if (comp) *comp = tga_comp; - return 1; // seems to have passed everything -} - -static int stbi__tga_test(stbi__context *s) -{ - int res = 0; - int sz, tga_color_type; - stbi__get8(s); // discard Offset - tga_color_type = stbi__get8(s); // color type - if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed - sz = stbi__get8(s); // image type - if ( tga_color_type == 1 ) { // colormapped (paletted) image - if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 - stbi__skip(s,4); // skip index of first colormap entry and number of entries - sz = stbi__get8(s); // check bits per palette color entry - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; - stbi__skip(s,4); // skip image x and y origin - } else { // "normal" image w/o colormap - if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE - stbi__skip(s,9); // skip colormap specification and image x/y origin - } - if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width - if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height - sz = stbi__get8(s); // bits per pixel - if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; - - res = 1; // if we got this far, everything's good and we can return 1 instead of 0 - -errorEnd: - stbi__rewind(s); - return res; -} - -// read 16bit value and convert to 24bit RGB -static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) -{ - stbi__uint16 px = (stbi__uint16)stbi__get16le(s); - stbi__uint16 fiveBitMask = 31; - // we have 3 channels with 5bits each - int r = (px >> 10) & fiveBitMask; - int g = (px >> 5) & fiveBitMask; - int b = px & fiveBitMask; - // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later - out[0] = (stbi_uc)((r * 255)/31); - out[1] = (stbi_uc)((g * 255)/31); - out[2] = (stbi_uc)((b * 255)/31); - - // some people claim that the most significant bit might be used for alpha - // (possibly if an alpha-bit is set in the "image descriptor byte") - // but that only made 16bit test images completely translucent.. - // so let's treat all 15 and 16bit TGAs as RGB with no alpha. -} - -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - // read in the TGA header stuff - int tga_offset = stbi__get8(s); - int tga_indexed = stbi__get8(s); - int tga_image_type = stbi__get8(s); - int tga_is_RLE = 0; - int tga_palette_start = stbi__get16le(s); - int tga_palette_len = stbi__get16le(s); - int tga_palette_bits = stbi__get8(s); - int tga_x_origin = stbi__get16le(s); - int tga_y_origin = stbi__get16le(s); - int tga_width = stbi__get16le(s); - int tga_height = stbi__get16le(s); - int tga_bits_per_pixel = stbi__get8(s); - int tga_comp, tga_rgb16=0; - int tga_inverted = stbi__get8(s); - // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) - // image data - unsigned char *tga_data; - unsigned char *tga_palette = NULL; - int i, j; - unsigned char raw_data[4] = {0}; - int RLE_count = 0; - int RLE_repeating = 0; - int read_next_pixel = 1; - STBI_NOTUSED(ri); - STBI_NOTUSED(tga_x_origin); // @TODO - STBI_NOTUSED(tga_y_origin); // @TODO - - // do a tiny bit of precessing - if ( tga_image_type >= 8 ) - { - tga_image_type -= 8; - tga_is_RLE = 1; - } - tga_inverted = 1 - ((tga_inverted >> 5) & 1); - - // If I'm paletted, then I'll use the number of bits from the palette - if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); - else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); - - if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency - return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); - - // tga info - *x = tga_width; - *y = tga_height; - if (comp) *comp = tga_comp; - - if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) - return stbi__errpuc("too large", "Corrupt TGA"); - - tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); - if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); - - // skip to the data's starting position (offset usually = 0) - stbi__skip(s, tga_offset ); - - if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { - for (i=0; i < tga_height; ++i) { - int row = tga_inverted ? tga_height -i - 1 : i; - stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; - stbi__getn(s, tga_row, tga_width * tga_comp); - } - } else { - // do I need to load a palette? - if ( tga_indexed) - { - // any data to skip? (offset usually = 0) - stbi__skip(s, tga_palette_start ); - // load the palette - tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); - if (!tga_palette) { - STBI_FREE(tga_data); - return stbi__errpuc("outofmem", "Out of memory"); - } - if (tga_rgb16) { - stbi_uc *pal_entry = tga_palette; - STBI_ASSERT(tga_comp == STBI_rgb); - for (i=0; i < tga_palette_len; ++i) { - stbi__tga_read_rgb16(s, pal_entry); - pal_entry += tga_comp; - } - } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { - STBI_FREE(tga_data); - STBI_FREE(tga_palette); - return stbi__errpuc("bad palette", "Corrupt TGA"); - } - } - // load the data - for (i=0; i < tga_width * tga_height; ++i) - { - // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? - if ( tga_is_RLE ) - { - if ( RLE_count == 0 ) - { - // yep, get the next byte as a RLE command - int RLE_cmd = stbi__get8(s); - RLE_count = 1 + (RLE_cmd & 127); - RLE_repeating = RLE_cmd >> 7; - read_next_pixel = 1; - } else if ( !RLE_repeating ) - { - read_next_pixel = 1; - } - } else - { - read_next_pixel = 1; - } - // OK, if I need to read a pixel, do it now - if ( read_next_pixel ) - { - // load however much data we did have - if ( tga_indexed ) - { - // read in index, then perform the lookup - int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); - if ( pal_idx >= tga_palette_len ) { - // invalid index - pal_idx = 0; - } - pal_idx *= tga_comp; - for (j = 0; j < tga_comp; ++j) { - raw_data[j] = tga_palette[pal_idx+j]; - } - } else if(tga_rgb16) { - STBI_ASSERT(tga_comp == STBI_rgb); - stbi__tga_read_rgb16(s, raw_data); - } else { - // read in the data raw - for (j = 0; j < tga_comp; ++j) { - raw_data[j] = stbi__get8(s); - } - } - // clear the reading flag for the next pixel - read_next_pixel = 0; - } // end of reading a pixel - - // copy data - for (j = 0; j < tga_comp; ++j) - tga_data[i*tga_comp+j] = raw_data[j]; - - // in case we're in RLE mode, keep counting down - --RLE_count; - } - // do I need to invert the image? - if ( tga_inverted ) - { - for (j = 0; j*2 < tga_height; ++j) - { - int index1 = j * tga_width * tga_comp; - int index2 = (tga_height - 1 - j) * tga_width * tga_comp; - for (i = tga_width * tga_comp; i > 0; --i) - { - unsigned char temp = tga_data[index1]; - tga_data[index1] = tga_data[index2]; - tga_data[index2] = temp; - ++index1; - ++index2; - } - } - } - // clear my palette, if I had one - if ( tga_palette != NULL ) - { - STBI_FREE( tga_palette ); - } - } - - // swap RGB - if the source data was RGB16, it already is in the right order - if (tga_comp >= 3 && !tga_rgb16) - { - unsigned char* tga_pixel = tga_data; - for (i=0; i < tga_width * tga_height; ++i) - { - unsigned char temp = tga_pixel[0]; - tga_pixel[0] = tga_pixel[2]; - tga_pixel[2] = temp; - tga_pixel += tga_comp; - } - } - - // convert to target component count - if (req_comp && req_comp != tga_comp) - tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); - - // the things I do to get rid of an error message, and yet keep - // Microsoft's C compilers happy... [8^( - tga_palette_start = tga_palette_len = tga_palette_bits = - tga_x_origin = tga_y_origin = 0; - STBI_NOTUSED(tga_palette_start); - // OK, done - return tga_data; -} -#endif - -// ************************************************************************************************* -// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB - -#ifndef STBI_NO_PSD -static int stbi__psd_test(stbi__context *s) -{ - int r = (stbi__get32be(s) == 0x38425053); - stbi__rewind(s); - return r; -} - -static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) -{ - int count, nleft, len; - - count = 0; - while ((nleft = pixelCount - count) > 0) { - len = stbi__get8(s); - if (len == 128) { - // No-op. - } else if (len < 128) { - // Copy next len+1 bytes literally. - len++; - if (len > nleft) return 0; // corrupt data - count += len; - while (len) { - *p = stbi__get8(s); - p += 4; - len--; - } - } else if (len > 128) { - stbi_uc val; - // Next -len+1 bytes in the dest are replicated from next source byte. - // (Interpret len as a negative 8-bit int.) - len = 257 - len; - if (len > nleft) return 0; // corrupt data - val = stbi__get8(s); - count += len; - while (len) { - *p = val; - p += 4; - len--; - } - } - } - - return 1; -} - -static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) -{ - int pixelCount; - int channelCount, compression; - int channel, i; - int bitdepth; - int w,h; - stbi_uc *out; - STBI_NOTUSED(ri); - - // Check identifier - if (stbi__get32be(s) != 0x38425053) // "8BPS" - return stbi__errpuc("not PSD", "Corrupt PSD image"); - - // Check file type version. - if (stbi__get16be(s) != 1) - return stbi__errpuc("wrong version", "Unsupported version of PSD image"); - - // Skip 6 reserved bytes. - stbi__skip(s, 6 ); - - // Read the number of channels (R, G, B, A, etc). - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) - return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); - - // Read the rows and columns of the image. - h = stbi__get32be(s); - w = stbi__get32be(s); - - // Make sure the depth is 8 bits. - bitdepth = stbi__get16be(s); - if (bitdepth != 8 && bitdepth != 16) - return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); - - // Make sure the color mode is RGB. - // Valid options are: - // 0: Bitmap - // 1: Grayscale - // 2: Indexed color - // 3: RGB color - // 4: CMYK color - // 7: Multichannel - // 8: Duotone - // 9: Lab color - if (stbi__get16be(s) != 3) - return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); - - // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) - stbi__skip(s,stbi__get32be(s) ); - - // Skip the image resources. (resolution, pen tool paths, etc) - stbi__skip(s, stbi__get32be(s) ); - - // Skip the reserved data. - stbi__skip(s, stbi__get32be(s) ); - - // Find out if the data is compressed. - // Known values: - // 0: no compression - // 1: RLE compressed - compression = stbi__get16be(s); - if (compression > 1) - return stbi__errpuc("bad compression", "PSD has an unknown compression format"); - - // Check size - if (!stbi__mad3sizes_valid(4, w, h, 0)) - return stbi__errpuc("too large", "Corrupt PSD"); - - // Create the destination image. - - if (!compression && bitdepth == 16 && bpc == 16) { - out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); - ri->bits_per_channel = 16; - } else - out = (stbi_uc *) stbi__malloc(4 * w*h); - - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - pixelCount = w*h; - - // Initialize the data to zero. - //memset( out, 0, pixelCount * 4 ); - - // Finally, the image data. - if (compression) { - // RLE as used by .PSD and .TIFF - // Loop until you get the number of unpacked bytes you are expecting: - // Read the next source byte into n. - // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. - // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. - // Else if n is 128, noop. - // Endloop - - // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, - // which we're going to just skip. - stbi__skip(s, h * channelCount * 2 ); - - // Read the RLE data by channel. - for (channel = 0; channel < 4; channel++) { - stbi_uc *p; - - p = out+channel; - if (channel >= channelCount) { - // Fill this channel with default data. - for (i = 0; i < pixelCount; i++, p += 4) - *p = (channel == 3 ? 255 : 0); - } else { - // Read the RLE data. - if (!stbi__psd_decode_rle(s, p, pixelCount)) { - STBI_FREE(out); - return stbi__errpuc("corrupt", "bad RLE data"); - } - } - } - - } else { - // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) - // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. - - // Read the data by channel. - for (channel = 0; channel < 4; channel++) { - if (channel >= channelCount) { - // Fill this channel with default data. - if (bitdepth == 16 && bpc == 16) { - stbi__uint16 *q = ((stbi__uint16 *) out) + channel; - stbi__uint16 val = channel == 3 ? 65535 : 0; - for (i = 0; i < pixelCount; i++, q += 4) - *q = val; - } else { - stbi_uc *p = out+channel; - stbi_uc val = channel == 3 ? 255 : 0; - for (i = 0; i < pixelCount; i++, p += 4) - *p = val; - } - } else { - if (ri->bits_per_channel == 16) { // output bpc - stbi__uint16 *q = ((stbi__uint16 *) out) + channel; - for (i = 0; i < pixelCount; i++, q += 4) - *q = (stbi__uint16) stbi__get16be(s); - } else { - stbi_uc *p = out+channel; - if (bitdepth == 16) { // input bpc - for (i = 0; i < pixelCount; i++, p += 4) - *p = (stbi_uc) (stbi__get16be(s) >> 8); - } else { - for (i = 0; i < pixelCount; i++, p += 4) - *p = stbi__get8(s); - } - } - } - } - } - - // remove weird white matte from PSD - if (channelCount >= 4) { - if (ri->bits_per_channel == 16) { - for (i=0; i < w*h; ++i) { - stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; - if (pixel[3] != 0 && pixel[3] != 65535) { - float a = pixel[3] / 65535.0f; - float ra = 1.0f / a; - float inv_a = 65535.0f * (1 - ra); - pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); - pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); - pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); - } - } - } else { - for (i=0; i < w*h; ++i) { - unsigned char *pixel = out + 4*i; - if (pixel[3] != 0 && pixel[3] != 255) { - float a = pixel[3] / 255.0f; - float ra = 1.0f / a; - float inv_a = 255.0f * (1 - ra); - pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); - pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); - pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); - } - } - } - } - - // convert to desired output format - if (req_comp && req_comp != 4) { - if (ri->bits_per_channel == 16) - out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); - else - out = stbi__convert_format(out, 4, req_comp, w, h); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - if (comp) *comp = 4; - *y = h; - *x = w; - - return out; -} -#endif - -// ************************************************************************************************* -// Softimage PIC loader -// by Tom Seddon -// -// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format -// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ - -#ifndef STBI_NO_PIC -static int stbi__pic_is4(stbi__context *s,const char *str) -{ - int i; - for (i=0; i<4; ++i) - if (stbi__get8(s) != (stbi_uc)str[i]) - return 0; - - return 1; -} - -static int stbi__pic_test_core(stbi__context *s) -{ - int i; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) - return 0; - - for(i=0;i<84;++i) - stbi__get8(s); - - if (!stbi__pic_is4(s,"PICT")) - return 0; - - return 1; -} - -typedef struct -{ - stbi_uc size,type,channel; -} stbi__pic_packet; - -static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) -{ - int mask=0x80, i; - - for (i=0; i<4; ++i, mask>>=1) { - if (channel & mask) { - if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); - dest[i]=stbi__get8(s); - } - } - - return dest; -} - -static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) -{ - int mask=0x80,i; - - for (i=0;i<4; ++i, mask>>=1) - if (channel&mask) - dest[i]=src[i]; -} - -static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) -{ - int act_comp=0,num_packets=0,y,chained; - stbi__pic_packet packets[10]; - - // this will (should...) cater for even some bizarre stuff like having data - // for the same channel in multiple packets. - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return stbi__errpuc("bad format","too many packets"); - - packet = &packets[num_packets++]; - - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - - act_comp |= packet->channel; - - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); - if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? - - for(y=0; ytype) { - default: - return stbi__errpuc("bad format","packet has bad compression type"); - - case 0: {//uncompressed - int x; - - for(x=0;xchannel,dest)) - return 0; - break; - } - - case 1://Pure RLE - { - int left=width, i; - - while (left>0) { - stbi_uc count,value[4]; - - count=stbi__get8(s); - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); - - if (count > left) - count = (stbi_uc) left; - - if (!stbi__readval(s,packet->channel,value)) return 0; - - for(i=0; ichannel,dest,value); - left -= count; - } - } - break; - - case 2: {//Mixed RLE - int left=width; - while (left>0) { - int count = stbi__get8(s), i; - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); - - if (count >= 128) { // Repeated - stbi_uc value[4]; - - if (count==128) - count = stbi__get16be(s); - else - count -= 127; - if (count > left) - return stbi__errpuc("bad file","scanline overrun"); - - if (!stbi__readval(s,packet->channel,value)) - return 0; - - for(i=0;ichannel,dest,value); - } else { // Raw - ++count; - if (count>left) return stbi__errpuc("bad file","scanline overrun"); - - for(i=0;ichannel,dest)) - return 0; - } - left-=count; - } - break; - } - } - } - } - - return result; -} - -static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) -{ - stbi_uc *result; - int i, x,y, internal_comp; - STBI_NOTUSED(ri); - - if (!comp) comp = &internal_comp; - - for (i=0; i<92; ++i) - stbi__get8(s); - - x = stbi__get16be(s); - y = stbi__get16be(s); - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); - if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); - - stbi__get32be(s); //skip `ratio' - stbi__get16be(s); //skip `fields' - stbi__get16be(s); //skip `pad' - - // intermediate buffer is RGBA - result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); - memset(result, 0xff, x*y*4); - - if (!stbi__pic_load_core(s,x,y,comp, result)) { - STBI_FREE(result); - result=0; - } - *px = x; - *py = y; - if (req_comp == 0) req_comp = *comp; - result=stbi__convert_format(result,4,req_comp,x,y); - - return result; -} - -static int stbi__pic_test(stbi__context *s) -{ - int r = stbi__pic_test_core(s); - stbi__rewind(s); - return r; -} -#endif - -// ************************************************************************************************* -// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb - -#ifndef STBI_NO_GIF -typedef struct -{ - stbi__int16 prefix; - stbi_uc first; - stbi_uc suffix; -} stbi__gif_lzw; - -typedef struct -{ - int w,h; - stbi_uc *out; // output buffer (always 4 components) - stbi_uc *background; // The current "background" as far as a gif is concerned - stbi_uc *history; - int flags, bgindex, ratio, transparent, eflags; - stbi_uc pal[256][4]; - stbi_uc lpal[256][4]; - stbi__gif_lzw codes[8192]; - stbi_uc *color_table; - int parse, step; - int lflags; - int start_x, start_y; - int max_x, max_y; - int cur_x, cur_y; - int line_size; - int delay; -} stbi__gif; - -static int stbi__gif_test_raw(stbi__context *s) -{ - int sz; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; - sz = stbi__get8(s); - if (sz != '9' && sz != '7') return 0; - if (stbi__get8(s) != 'a') return 0; - return 1; -} - -static int stbi__gif_test(stbi__context *s) -{ - int r = stbi__gif_test_raw(s); - stbi__rewind(s); - return r; -} - -static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) -{ - int i; - for (i=0; i < num_entries; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - pal[i][3] = transp == i ? 0 : 255; - } -} - -static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) -{ - stbi_uc version; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') - return stbi__err("not GIF", "Corrupt GIF"); - - version = stbi__get8(s); - if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); - if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); - - stbi__g_failure_reason = ""; - g->w = stbi__get16le(s); - g->h = stbi__get16le(s); - g->flags = stbi__get8(s); - g->bgindex = stbi__get8(s); - g->ratio = stbi__get8(s); - g->transparent = -1; - - if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments - - if (is_info) return 1; - - if (g->flags & 0x80) - stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); - - return 1; -} - -static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); - if (!stbi__gif_header(s, g, comp, 1)) { - STBI_FREE(g); - stbi__rewind( s ); - return 0; - } - if (x) *x = g->w; - if (y) *y = g->h; - STBI_FREE(g); - return 1; -} - -static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) -{ - stbi_uc *p, *c; - int idx; - - // recurse to decode the prefixes, since the linked-list is backwards, - // and working backwards through an interleaved image would be nasty - if (g->codes[code].prefix >= 0) - stbi__out_gif_code(g, g->codes[code].prefix); - - if (g->cur_y >= g->max_y) return; - - idx = g->cur_x + g->cur_y; - p = &g->out[idx]; - g->history[idx / 4] = 1; - - c = &g->color_table[g->codes[code].suffix * 4]; - if (c[3] > 128) { // don't render transparent pixels; - p[0] = c[2]; - p[1] = c[1]; - p[2] = c[0]; - p[3] = c[3]; - } - g->cur_x += 4; - - if (g->cur_x >= g->max_x) { - g->cur_x = g->start_x; - g->cur_y += g->step; - - while (g->cur_y >= g->max_y && g->parse > 0) { - g->step = (1 << g->parse) * g->line_size; - g->cur_y = g->start_y + (g->step >> 1); - --g->parse; - } - } -} - -static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) -{ - stbi_uc lzw_cs; - stbi__int32 len, init_code; - stbi__uint32 first; - stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; - stbi__gif_lzw *p; - - lzw_cs = stbi__get8(s); - if (lzw_cs > 12) return NULL; - clear = 1 << lzw_cs; - first = 1; - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - bits = 0; - valid_bits = 0; - for (init_code = 0; init_code < clear; init_code++) { - g->codes[init_code].prefix = -1; - g->codes[init_code].first = (stbi_uc) init_code; - g->codes[init_code].suffix = (stbi_uc) init_code; - } - - // support no starting clear code - avail = clear+2; - oldcode = -1; - - len = 0; - for(;;) { - if (valid_bits < codesize) { - if (len == 0) { - len = stbi__get8(s); // start new block - if (len == 0) - return g->out; - } - --len; - bits |= (stbi__int32) stbi__get8(s) << valid_bits; - valid_bits += 8; - } else { - stbi__int32 code = bits & codemask; - bits >>= codesize; - valid_bits -= codesize; - // @OPTIMIZE: is there some way we can accelerate the non-clear path? - if (code == clear) { // clear code - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - avail = clear + 2; - oldcode = -1; - first = 0; - } else if (code == clear + 1) { // end of stream code - stbi__skip(s, len); - while ((len = stbi__get8(s)) > 0) - stbi__skip(s,len); - return g->out; - } else if (code <= avail) { - if (first) { - return stbi__errpuc("no clear code", "Corrupt GIF"); - } - - if (oldcode >= 0) { - p = &g->codes[avail++]; - if (avail > 8192) { - return stbi__errpuc("too many codes", "Corrupt GIF"); - } - - p->prefix = (stbi__int16) oldcode; - p->first = g->codes[oldcode].first; - p->suffix = (code == avail) ? p->first : g->codes[code].first; - } else if (code == avail) - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - - stbi__out_gif_code(g, (stbi__uint16) code); - - if ((avail & codemask) == 0 && avail <= 0x0FFF) { - codesize++; - codemask = (1 << codesize) - 1; - } - - oldcode = code; - } else { - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - } - } - } -} - -// this function is designed to support animated gifs, although stb_image doesn't support it -// two back is the image from two frames ago, used for a very specific disposal format -static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) -{ - int dispose; - int first_frame; - int pi; - int pcount; - STBI_NOTUSED(req_comp); - - // on first frame, any non-written pixels get the background colour (non-transparent) - first_frame = 0; - if (g->out == 0) { - if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header - if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) - return stbi__errpuc("too large", "GIF image is too large"); - pcount = g->w * g->h; - g->out = (stbi_uc *) stbi__malloc(4 * pcount); - g->background = (stbi_uc *) stbi__malloc(4 * pcount); - g->history = (stbi_uc *) stbi__malloc(pcount); - if (!g->out || !g->background || !g->history) - return stbi__errpuc("outofmem", "Out of memory"); - - // image is treated as "transparent" at the start - ie, nothing overwrites the current background; - // background colour is only used for pixels that are not rendered first frame, after that "background" - // color refers to the color that was there the previous frame. - memset(g->out, 0x00, 4 * pcount); - memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) - memset(g->history, 0x00, pcount); // pixels that were affected previous frame - first_frame = 1; - } else { - // second frame - how do we dispoase of the previous one? - dispose = (g->eflags & 0x1C) >> 2; - pcount = g->w * g->h; - - if ((dispose == 3) && (two_back == 0)) { - dispose = 2; // if I don't have an image to revert back to, default to the old background - } - - if (dispose == 3) { // use previous graphic - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi]) { - memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); - } - } - } else if (dispose == 2) { - // restore what was changed last frame to background before that frame; - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi]) { - memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); - } - } - } else { - // This is a non-disposal case eithe way, so just - // leave the pixels as is, and they will become the new background - // 1: do not dispose - // 0: not specified. - } - - // background is what out is after the undoing of the previou frame; - memcpy( g->background, g->out, 4 * g->w * g->h ); - } - - // clear my history; - memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame - - for (;;) { - int tag = stbi__get8(s); - switch (tag) { - case 0x2C: /* Image Descriptor */ - { - stbi__int32 x, y, w, h; - stbi_uc *o; - - x = stbi__get16le(s); - y = stbi__get16le(s); - w = stbi__get16le(s); - h = stbi__get16le(s); - if (((x + w) > (g->w)) || ((y + h) > (g->h))) - return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); - - g->line_size = g->w * 4; - g->start_x = x * 4; - g->start_y = y * g->line_size; - g->max_x = g->start_x + w * 4; - g->max_y = g->start_y + h * g->line_size; - g->cur_x = g->start_x; - g->cur_y = g->start_y; - - // if the width of the specified rectangle is 0, that means - // we may not see *any* pixels or the image is malformed; - // to make sure this is caught, move the current y down to - // max_y (which is what out_gif_code checks). - if (w == 0) - g->cur_y = g->max_y; - - g->lflags = stbi__get8(s); - - if (g->lflags & 0x40) { - g->step = 8 * g->line_size; // first interlaced spacing - g->parse = 3; - } else { - g->step = g->line_size; - g->parse = 0; - } - - if (g->lflags & 0x80) { - stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); - g->color_table = (stbi_uc *) g->lpal; - } else if (g->flags & 0x80) { - g->color_table = (stbi_uc *) g->pal; - } else - return stbi__errpuc("missing color table", "Corrupt GIF"); - - o = stbi__process_gif_raster(s, g); - if (!o) return NULL; - - // if this was the first frame, - pcount = g->w * g->h; - if (first_frame && (g->bgindex > 0)) { - // if first frame, any pixel not drawn to gets the background color - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi] == 0) { - g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; - memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); - } - } - } - - return o; - } - - case 0x21: // Comment Extension. - { - int len; - int ext = stbi__get8(s); - if (ext == 0xF9) { // Graphic Control Extension. - len = stbi__get8(s); - if (len == 4) { - g->eflags = stbi__get8(s); - g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. - - // unset old transparent - if (g->transparent >= 0) { - g->pal[g->transparent][3] = 255; - } - if (g->eflags & 0x01) { - g->transparent = stbi__get8(s); - if (g->transparent >= 0) { - g->pal[g->transparent][3] = 0; - } - } else { - // don't need transparent - stbi__skip(s, 1); - g->transparent = -1; - } - } else { - stbi__skip(s, len); - break; - } - } - while ((len = stbi__get8(s)) != 0) { - stbi__skip(s, len); - } - break; - } - - case 0x3B: // gif stream termination code - return (stbi_uc *) s; // using '1' causes warning on some compilers - - default: - return stbi__errpuc("unknown code", "Corrupt GIF"); - } - } -} - -static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) -{ - if (stbi__gif_test(s)) { - int layers = 0; - stbi_uc *u = 0; - stbi_uc *out = 0; - stbi_uc *two_back = 0; - stbi__gif g; - int stride; - memset(&g, 0, sizeof(g)); - if (delays) { - *delays = 0; - } - - do { - u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); - if (u == (stbi_uc *) s) u = 0; // end of animated gif marker - - if (u) { - *x = g.w; - *y = g.h; - ++layers; - stride = g.w * g.h * 4; - - if (out) { - void *tmp = (stbi_uc*) STBI_REALLOC( out, layers * stride ); - if (NULL == tmp) { - STBI_FREE(g.out); - STBI_FREE(g.history); - STBI_FREE(g.background); - return stbi__errpuc("outofmem", "Out of memory"); - } - else - out = (stbi_uc*) tmp; - if (delays) { - *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers ); - } - } else { - out = (stbi_uc*)stbi__malloc( layers * stride ); - if (delays) { - *delays = (int*) stbi__malloc( layers * sizeof(int) ); - } - } - memcpy( out + ((layers - 1) * stride), u, stride ); - if (layers >= 2) { - two_back = out - 2 * stride; - } - - if (delays) { - (*delays)[layers - 1U] = g.delay; - } - } - } while (u != 0); - - // free temp buffer; - STBI_FREE(g.out); - STBI_FREE(g.history); - STBI_FREE(g.background); - - // do the final conversion after loading everything; - if (req_comp && req_comp != 4) - out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); - - *z = layers; - return out; - } else { - return stbi__errpuc("not GIF", "Image was not as a gif type."); - } -} - -static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *u = 0; - stbi__gif g; - memset(&g, 0, sizeof(g)); - STBI_NOTUSED(ri); - - u = stbi__gif_load_next(s, &g, comp, req_comp, 0); - if (u == (stbi_uc *) s) u = 0; // end of animated gif marker - if (u) { - *x = g.w; - *y = g.h; - - // moved conversion to after successful load so that the same - // can be done for multiple frames. - if (req_comp && req_comp != 4) - u = stbi__convert_format(u, 4, req_comp, g.w, g.h); - } else if (g.out) { - // if there was an error and we allocated an image buffer, free it! - STBI_FREE(g.out); - } - - // free buffers needed for multiple frame loading; - STBI_FREE(g.history); - STBI_FREE(g.background); - - return u; -} - -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) -{ - return stbi__gif_info_raw(s,x,y,comp); -} -#endif - -// ************************************************************************************************* -// Radiance RGBE HDR loader -// originally by Nicolas Schulz -#ifndef STBI_NO_HDR -static int stbi__hdr_test_core(stbi__context *s, const char *signature) -{ - int i; - for (i=0; signature[i]; ++i) - if (stbi__get8(s) != signature[i]) - return 0; - stbi__rewind(s); - return 1; -} - -static int stbi__hdr_test(stbi__context* s) -{ - int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); - stbi__rewind(s); - if(!r) { - r = stbi__hdr_test_core(s, "#?RGBE\n"); - stbi__rewind(s); - } - return r; -} - -#define STBI__HDR_BUFLEN 1024 -static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) -{ - int len=0; - char c = '\0'; - - c = (char) stbi__get8(z); - - while (!stbi__at_eof(z) && c != '\n') { - buffer[len++] = c; - if (len == STBI__HDR_BUFLEN-1) { - // flush to end of line - while (!stbi__at_eof(z) && stbi__get8(z) != '\n') - ; - break; - } - c = (char) stbi__get8(z); - } - - buffer[len] = 0; - return buffer; -} - -static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) -{ - if ( input[3] != 0 ) { - float f1; - // Exponent - f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); - if (req_comp <= 2) - output[0] = (input[0] + input[1] + input[2]) * f1 / 3; - else { - output[0] = input[0] * f1; - output[1] = input[1] * f1; - output[2] = input[2] * f1; - } - if (req_comp == 2) output[1] = 1; - if (req_comp == 4) output[3] = 1; - } else { - switch (req_comp) { - case 4: output[3] = 1; /* fallthrough */ - case 3: output[0] = output[1] = output[2] = 0; - break; - case 2: output[1] = 1; /* fallthrough */ - case 1: output[0] = 0; - break; - } - } -} - -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - int width, height; - stbi_uc *scanline; - float *hdr_data; - int len; - unsigned char count, value; - int i, j, k, c1,c2, z; - const char *headerToken; - STBI_NOTUSED(ri); - - // Check identifier - headerToken = stbi__hdr_gettoken(s,buffer); - if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) - return stbi__errpf("not HDR", "Corrupt HDR image"); - - // Parse header - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); - - // Parse width and height - // can't use sscanf() if we're not using stdio! - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - height = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - width = (int) strtol(token, NULL, 10); - - *x = width; - *y = height; - - if (comp) *comp = 3; - if (req_comp == 0) req_comp = 3; - - if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) - return stbi__errpf("too large", "HDR image is too large"); - - // Read data - hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); - if (!hdr_data) - return stbi__errpf("outofmem", "Out of memory"); - - // Load image data - // image data is stored as some number of sca - if ( width < 8 || width >= 32768) { - // Read flat data - for (j=0; j < height; ++j) { - for (i=0; i < width; ++i) { - stbi_uc rgbe[4]; - main_decode_loop: - stbi__getn(s, rgbe, 4); - stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); - } - } - } else { - // Read RLE-encoded data - scanline = NULL; - - for (j = 0; j < height; ++j) { - c1 = stbi__get8(s); - c2 = stbi__get8(s); - len = stbi__get8(s); - if (c1 != 2 || c2 != 2 || (len & 0x80)) { - // not run-length encoded, so we have to actually use THIS data as a decoded - // pixel (note this can't be a valid pixel--one of RGB must be >= 128) - stbi_uc rgbe[4]; - rgbe[0] = (stbi_uc) c1; - rgbe[1] = (stbi_uc) c2; - rgbe[2] = (stbi_uc) len; - rgbe[3] = (stbi_uc) stbi__get8(s); - stbi__hdr_convert(hdr_data, rgbe, req_comp); - i = 1; - j = 0; - STBI_FREE(scanline); - goto main_decode_loop; // yes, this makes no sense - } - len <<= 8; - len |= stbi__get8(s); - if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } - if (scanline == NULL) { - scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); - if (!scanline) { - STBI_FREE(hdr_data); - return stbi__errpf("outofmem", "Out of memory"); - } - } - - for (k = 0; k < 4; ++k) { - int nleft; - i = 0; - while ((nleft = width - i) > 0) { - count = stbi__get8(s); - if (count > 128) { - // Run - value = stbi__get8(s); - count -= 128; - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = value; - } else { - // Dump - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = stbi__get8(s); - } - } - } - for (i=0; i < width; ++i) - stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); - } - if (scanline) - STBI_FREE(scanline); - } - - return hdr_data; -} - -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - int dummy; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - if (stbi__hdr_test(s) == 0) { - stbi__rewind( s ); - return 0; - } - - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) { - stbi__rewind( s ); - return 0; - } - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *y = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *x = (int) strtol(token, NULL, 10); - *comp = 3; - return 1; -} -#endif // STBI_NO_HDR - -#ifndef STBI_NO_BMP -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) -{ - void *p; - stbi__bmp_data info; - - info.all_a = 255; - p = stbi__bmp_parse_header(s, &info); - stbi__rewind( s ); - if (p == NULL) - return 0; - if (x) *x = s->img_x; - if (y) *y = s->img_y; - if (comp) { - if (info.bpp == 24 && info.ma == 0xff000000) - *comp = 3; - else - *comp = info.ma ? 4 : 3; - } - return 1; -} -#endif - -#ifndef STBI_NO_PSD -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) -{ - int channelCount, dummy, depth; - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - if (stbi__get32be(s) != 0x38425053) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 1) { - stbi__rewind( s ); - return 0; - } - stbi__skip(s, 6); - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) { - stbi__rewind( s ); - return 0; - } - *y = stbi__get32be(s); - *x = stbi__get32be(s); - depth = stbi__get16be(s); - if (depth != 8 && depth != 16) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 3) { - stbi__rewind( s ); - return 0; - } - *comp = 4; - return 1; -} - -static int stbi__psd_is16(stbi__context *s) -{ - int channelCount, depth; - if (stbi__get32be(s) != 0x38425053) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 1) { - stbi__rewind( s ); - return 0; - } - stbi__skip(s, 6); - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) { - stbi__rewind( s ); - return 0; - } - (void) stbi__get32be(s); - (void) stbi__get32be(s); - depth = stbi__get16be(s); - if (depth != 16) { - stbi__rewind( s ); - return 0; - } - return 1; -} -#endif - -#ifndef STBI_NO_PIC -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) -{ - int act_comp=0,num_packets=0,chained,dummy; - stbi__pic_packet packets[10]; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { - stbi__rewind(s); - return 0; - } - - stbi__skip(s, 88); - - *x = stbi__get16be(s); - *y = stbi__get16be(s); - if (stbi__at_eof(s)) { - stbi__rewind( s); - return 0; - } - if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { - stbi__rewind( s ); - return 0; - } - - stbi__skip(s, 8); - - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return 0; - - packet = &packets[num_packets++]; - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - act_comp |= packet->channel; - - if (stbi__at_eof(s)) { - stbi__rewind( s ); - return 0; - } - if (packet->size != 8) { - stbi__rewind( s ); - return 0; - } - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); - - return 1; -} -#endif - -// ************************************************************************************************* -// Portable Gray Map and Portable Pixel Map loader -// by Ken Miller -// -// PGM: http://netpbm.sourceforge.net/doc/pgm.html -// PPM: http://netpbm.sourceforge.net/doc/ppm.html -// -// Known limitations: -// Does not support comments in the header section -// Does not support ASCII image data (formats P2 and P3) -// Does not support 16-bit-per-channel - -#ifndef STBI_NO_PNM - -static int stbi__pnm_test(stbi__context *s) -{ - char p, t; - p = (char) stbi__get8(s); - t = (char) stbi__get8(s); - if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind( s ); - return 0; - } - return 1; -} - -static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *out; - STBI_NOTUSED(ri); - - if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) - return 0; - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = s->img_n; - - if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) - return stbi__errpuc("too large", "PNM too large"); - - out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - stbi__getn(s, out, s->img_n * s->img_x * s->img_y); - - if (req_comp && req_comp != s->img_n) { - out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - return out; -} - -static int stbi__pnm_isspace(char c) -{ - return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; -} - -static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) -{ - for (;;) { - while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) - *c = (char) stbi__get8(s); - - if (stbi__at_eof(s) || *c != '#') - break; - - while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) - *c = (char) stbi__get8(s); - } -} - -static int stbi__pnm_isdigit(char c) -{ - return c >= '0' && c <= '9'; -} - -static int stbi__pnm_getinteger(stbi__context *s, char *c) -{ - int value = 0; - - while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { - value = value*10 + (*c - '0'); - *c = (char) stbi__get8(s); - } - - return value; -} - -static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) -{ - int maxv, dummy; - char c, p, t; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - stbi__rewind(s); - - // Get identifier - p = (char) stbi__get8(s); - t = (char) stbi__get8(s); - if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind(s); - return 0; - } - - *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm - - c = (char) stbi__get8(s); - stbi__pnm_skip_whitespace(s, &c); - - *x = stbi__pnm_getinteger(s, &c); // read width - stbi__pnm_skip_whitespace(s, &c); - - *y = stbi__pnm_getinteger(s, &c); // read height - stbi__pnm_skip_whitespace(s, &c); - - maxv = stbi__pnm_getinteger(s, &c); // read max value - - if (maxv > 255) - return stbi__err("max value > 255", "PPM image not 8-bit"); - else - return 1; -} -#endif - -static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) -{ - #ifndef STBI_NO_JPEG - if (stbi__jpeg_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PNG - if (stbi__png_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_GIF - if (stbi__gif_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_BMP - if (stbi__bmp_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PSD - if (stbi__psd_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PIC - if (stbi__pic_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PNM - if (stbi__pnm_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_HDR - if (stbi__hdr_info(s, x, y, comp)) return 1; - #endif - - // test tga last because it's a crappy test! - #ifndef STBI_NO_TGA - if (stbi__tga_info(s, x, y, comp)) - return 1; - #endif - return stbi__err("unknown image type", "Image not of any known type, or corrupt"); -} - -static int stbi__is_16_main(stbi__context *s) -{ - #ifndef STBI_NO_PNG - if (stbi__png_is16(s)) return 1; - #endif - - #ifndef STBI_NO_PSD - if (stbi__psd_is16(s)) return 1; - #endif - - return 0; -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result; - if (!f) return stbi__err("can't fopen", "Unable to open file"); - result = stbi_info_from_file(f, x, y, comp); - fclose(f); - return result; -} - -STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) -{ - int r; - stbi__context s; - long pos = ftell(f); - stbi__start_file(&s, f); - r = stbi__info_main(&s,x,y,comp); - fseek(f,pos,SEEK_SET); - return r; -} - -STBIDEF int stbi_is_16_bit(char const *filename) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result; - if (!f) return stbi__err("can't fopen", "Unable to open file"); - result = stbi_is_16_bit_from_file(f); - fclose(f); - return result; -} - -STBIDEF int stbi_is_16_bit_from_file(FILE *f) -{ - int r; - stbi__context s; - long pos = ftell(f); - stbi__start_file(&s, f); - r = stbi__is_16_main(&s); - fseek(f,pos,SEEK_SET); - return r; -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__info_main(&s,x,y,comp); -} - -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); - return stbi__info_main(&s,x,y,comp); -} - -STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__is_16_main(&s); -} - -STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); - return stbi__is_16_main(&s); -} - -#endif // STB_IMAGE_IMPLEMENTATION - -/* - revision history: - 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs - 2.19 (2018-02-11) fix warning - 2.18 (2018-01-30) fix warnings - 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug - 1-bit BMP - *_is_16_bit api - avoid warnings - 2.16 (2017-07-23) all functions have 16-bit variants; - STBI_NO_STDIO works again; - compilation fixes; - fix rounding in unpremultiply; - optimize vertical flip; - disable raw_len validation; - documentation fixes - 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; - warning fixes; disable run-time SSE detection on gcc; - uniform handling of optional "return" values; - thread-safe initialization of zlib tables - 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs - 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now - 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes - 2.11 (2016-04-02) allocate large structures on the stack - remove white matting for transparent PSD - fix reported channel count for PNG & BMP - re-enable SSE2 in non-gcc 64-bit - support RGB-formatted JPEG - read 16-bit PNGs (only as 8-bit) - 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED - 2.09 (2016-01-16) allow comments in PNM files - 16-bit-per-pixel TGA (not bit-per-component) - info() for TGA could break due to .hdr handling - info() for BMP to shares code instead of sloppy parse - can use STBI_REALLOC_SIZED if allocator doesn't support realloc - code cleanup - 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA - 2.07 (2015-09-13) fix compiler warnings - partial animated GIF support - limited 16-bpc PSD support - #ifdef unused functions - bug with < 92 byte PIC,PNM,HDR,TGA - 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value - 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning - 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit - 2.03 (2015-04-12) extra corruption checking (mmozeiko) - stbi_set_flip_vertically_on_load (nguillemot) - fix NEON support; fix mingw support - 2.02 (2015-01-19) fix incorrect assert, fix warning - 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 - 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG - 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) - progressive JPEG (stb) - PGM/PPM support (Ken Miller) - STBI_MALLOC,STBI_REALLOC,STBI_FREE - GIF bugfix -- seemingly never worked - STBI_NO_*, STBI_ONLY_* - 1.48 (2014-12-14) fix incorrectly-named assert() - 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) - optimize PNG (ryg) - fix bug in interlaced PNG with user-specified channel count (stb) - 1.46 (2014-08-26) - fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG - 1.45 (2014-08-16) - fix MSVC-ARM internal compiler error by wrapping malloc - 1.44 (2014-08-07) - various warning fixes from Ronny Chevalier - 1.43 (2014-07-15) - fix MSVC-only compiler problem in code changed in 1.42 - 1.42 (2014-07-09) - don't define _CRT_SECURE_NO_WARNINGS (affects user code) - fixes to stbi__cleanup_jpeg path - added STBI_ASSERT to avoid requiring assert.h - 1.41 (2014-06-25) - fix search&replace from 1.36 that messed up comments/error messages - 1.40 (2014-06-22) - fix gcc struct-initialization warning - 1.39 (2014-06-15) - fix to TGA optimization when req_comp != number of components in TGA; - fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) - add support for BMP version 5 (more ignored fields) - 1.38 (2014-06-06) - suppress MSVC warnings on integer casts truncating values - fix accidental rename of 'skip' field of I/O - 1.37 (2014-06-04) - remove duplicate typedef - 1.36 (2014-06-03) - convert to header file single-file library - if de-iphone isn't set, load iphone images color-swapped instead of returning NULL - 1.35 (2014-05-27) - various warnings - fix broken STBI_SIMD path - fix bug where stbi_load_from_file no longer left file pointer in correct place - fix broken non-easy path for 32-bit BMP (possibly never used) - TGA optimization by Arseny Kapoulkine - 1.34 (unknown) - use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case - 1.33 (2011-07-14) - make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements - 1.32 (2011-07-13) - support for "info" function for all supported filetypes (SpartanJ) - 1.31 (2011-06-20) - a few more leak fixes, bug in PNG handling (SpartanJ) - 1.30 (2011-06-11) - added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) - removed deprecated format-specific test/load functions - removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway - error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) - fix inefficiency in decoding 32-bit BMP (David Woo) - 1.29 (2010-08-16) - various warning fixes from Aurelien Pocheville - 1.28 (2010-08-01) - fix bug in GIF palette transparency (SpartanJ) - 1.27 (2010-08-01) - cast-to-stbi_uc to fix warnings - 1.26 (2010-07-24) - fix bug in file buffering for PNG reported by SpartanJ - 1.25 (2010-07-17) - refix trans_data warning (Won Chun) - 1.24 (2010-07-12) - perf improvements reading from files on platforms with lock-heavy fgetc() - minor perf improvements for jpeg - deprecated type-specific functions so we'll get feedback if they're needed - attempt to fix trans_data warning (Won Chun) - 1.23 fixed bug in iPhone support - 1.22 (2010-07-10) - removed image *writing* support - stbi_info support from Jetro Lauha - GIF support from Jean-Marc Lienher - iPhone PNG-extensions from James Brown - warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) - 1.21 fix use of 'stbi_uc' in header (reported by jon blow) - 1.20 added support for Softimage PIC, by Tom Seddon - 1.19 bug in interlaced PNG corruption check (found by ryg) - 1.18 (2008-08-02) - fix a threading bug (local mutable static) - 1.17 support interlaced PNG - 1.16 major bugfix - stbi__convert_format converted one too many pixels - 1.15 initialize some fields for thread safety - 1.14 fix threadsafe conversion bug - header-file-only version (#define STBI_HEADER_FILE_ONLY before including) - 1.13 threadsafe - 1.12 const qualifiers in the API - 1.11 Support installable IDCT, colorspace conversion routines - 1.10 Fixes for 64-bit (don't use "unsigned long") - optimized upsampling by Fabian "ryg" Giesen - 1.09 Fix format-conversion for PSD code (bad global variables!) - 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz - 1.07 attempt to fix C++ warning/errors again - 1.06 attempt to fix C++ warning/errors again - 1.05 fix TGA loading to return correct *comp and use good luminance calc - 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free - 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR - 1.02 support for (subset of) HDR files, float interface for preferred access to them - 1.01 fix bug: possible bug in handling right-side up bmps... not sure - fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all - 1.00 interface to zlib that skips zlib header - 0.99 correct handling of alpha in palette - 0.98 TGA loader by lonesock; dynamically add loaders (untested) - 0.97 jpeg errors on too large a file; also catch another malloc failure - 0.96 fix detection of invalid v value - particleman@mollyrocket forum - 0.95 during header scan, seek to markers in case of padding - 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same - 0.93 handle jpegtran output; verbose errors - 0.92 read 4,8,16,24,32-bit BMP files of several formats - 0.91 output 24-bit Windows 3.0 BMP files - 0.90 fix a few more warnings; bump version number to approach 1.0 - 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd - 0.60 fix compiling as c++ - 0.59 fix warnings: merge Dave Moore's -Wall fixes - 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian - 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available - 0.56 fix bug: zlib uncompressed mode len vs. nlen - 0.55 fix bug: restart_interval not initialized to 0 - 0.54 allow NULL for 'int *comp' - 0.53 fix bug in png 3->4; speedup png decoding - 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments - 0.51 obey req_comp requests, 1-component jpegs return as 1-component, - on 'test' only check type, not whether we support this variant - 0.50 (2006-11-19) - first released version -*/ - - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/thirdparty/stb/stb_image_write.h b/thirdparty/stb/stb_image_write.h deleted file mode 100644 index cffd473c1f..0000000000 --- a/thirdparty/stb/stb_image_write.h +++ /dev/null @@ -1,1666 +0,0 @@ -/* stb_image_write - v1.14 - public domain - http://nothings.org/stb - writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 - no warranty implied; use at your own risk - - Before #including, - - #define STB_IMAGE_WRITE_IMPLEMENTATION - - in the file that you want to have the implementation. - - Will probably not work correctly with strict-aliasing optimizations. - -ABOUT: - - This header file is a library for writing images to C stdio or a callback. - - The PNG output is not optimal; it is 20-50% larger than the file - written by a decent optimizing implementation; though providing a custom - zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. - This library is designed for source code compactness and simplicity, - not optimal image file size or run-time performance. - -BUILDING: - - You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. - You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace - malloc,realloc,free. - You can #define STBIW_MEMMOVE() to replace memmove() - You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function - for PNG compression (instead of the builtin one), it must have the following signature: - unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); - The returned data will be freed with STBIW_FREE() (free() by default), - so it must be heap allocated with STBIW_MALLOC() (malloc() by default), - -UNICODE: - - If compiling for Windows and you wish to use Unicode filenames, compile - with - #define STBIW_WINDOWS_UTF8 - and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert - Windows wchar_t filenames to utf8. - -USAGE: - - There are five functions, one for each image file format: - - int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); - int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); - int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); - int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); - int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); - - void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically - - There are also five equivalent functions that use an arbitrary write function. You are - expected to open/close your file-equivalent before and after calling these: - - int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); - int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); - int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); - int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); - int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); - - where the callback is: - void stbi_write_func(void *context, void *data, int size); - - You can configure it with these global variables: - int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE - int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression - int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode - - - You can define STBI_WRITE_NO_STDIO to disable the file variant of these - functions, so the library will not use stdio.h at all. However, this will - also disable HDR writing, because it requires stdio for formatted output. - - Each function returns 0 on failure and non-0 on success. - - The functions create an image file defined by the parameters. The image - is a rectangle of pixels stored from left-to-right, top-to-bottom. - Each pixel contains 'comp' channels of data stored interleaved with 8-bits - per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is - monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. - The *data pointer points to the first byte of the top-left-most pixel. - For PNG, "stride_in_bytes" is the distance in bytes from the first byte of - a row of pixels to the first byte of the next row of pixels. - - PNG creates output files with the same number of components as the input. - The BMP format expands Y to RGB in the file format and does not - output alpha. - - PNG supports writing rectangles of data even when the bytes storing rows of - data are not consecutive in memory (e.g. sub-rectangles of a larger image), - by supplying the stride between the beginning of adjacent rows. The other - formats do not. (Thus you cannot write a native-format BMP through the BMP - writer, both because it is in BGR order and because it may have padding - at the end of the line.) - - PNG allows you to set the deflate compression level by setting the global - variable 'stbi_write_png_compression_level' (it defaults to 8). - - HDR expects linear float data. Since the format is always 32-bit rgb(e) - data, alpha (if provided) is discarded, and for monochrome data it is - replicated across all three channels. - - TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed - data, set the global variable 'stbi_write_tga_with_rle' to 0. - - JPEG does ignore alpha channels in input data; quality is between 1 and 100. - Higher quality looks better but results in a bigger image. - JPEG baseline (no JPEG progressive). - -CREDITS: - - - Sean Barrett - PNG/BMP/TGA - Baldur Karlsson - HDR - Jean-Sebastien Guay - TGA monochrome - Tim Kelsey - misc enhancements - Alan Hickman - TGA RLE - Emmanuel Julien - initial file IO callback implementation - Jon Olick - original jo_jpeg.cpp code - Daniel Gibson - integrate JPEG, allow external zlib - Aarni Koskela - allow choosing PNG filter - - bugfixes: - github:Chribba - Guillaume Chereau - github:jry2 - github:romigrou - Sergio Gonzalez - Jonas Karlsson - Filip Wasil - Thatcher Ulrich - github:poppolopoppo - Patrick Boettcher - github:xeekworx - Cap Petschulat - Simon Rodriguez - Ivan Tikhonov - github:ignotion - Adam Schackart - -LICENSE - - See end of file for license information. - -*/ - -#ifndef INCLUDE_STB_IMAGE_WRITE_H -#define INCLUDE_STB_IMAGE_WRITE_H - -#include - -// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' -#ifndef STBIWDEF -#ifdef STB_IMAGE_WRITE_STATIC -#define STBIWDEF static -#else -#ifdef __cplusplus -#define STBIWDEF extern "C" -#else -#define STBIWDEF extern -#endif -#endif -#endif - -#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations -extern int stbi_write_tga_with_rle; -extern int stbi_write_png_compression_level; -extern int stbi_write_force_png_filter; -#endif - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); -STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); -STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); - -#ifdef STBI_WINDOWS_UTF8 -STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); -#endif -#endif - -typedef void stbi_write_func(void *context, void *data, int size); - -STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); -STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); -STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); - -STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); - -#endif//INCLUDE_STB_IMAGE_WRITE_H - -#ifdef STB_IMAGE_WRITE_IMPLEMENTATION - -#ifdef _WIN32 - #ifndef _CRT_SECURE_NO_WARNINGS - #define _CRT_SECURE_NO_WARNINGS - #endif - #ifndef _CRT_NONSTDC_NO_DEPRECATE - #define _CRT_NONSTDC_NO_DEPRECATE - #endif -#endif - -#ifndef STBI_WRITE_NO_STDIO -#include -#endif // STBI_WRITE_NO_STDIO - -#include -#include -#include -#include - -#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) -// ok -#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) -// ok -#else -#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." -#endif - -#ifndef STBIW_MALLOC -#define STBIW_MALLOC(sz) malloc(sz) -#define STBIW_REALLOC(p,newsz) realloc(p,newsz) -#define STBIW_FREE(p) free(p) -#endif - -#ifndef STBIW_REALLOC_SIZED -#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) -#endif - - -#ifndef STBIW_MEMMOVE -#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) -#endif - - -#ifndef STBIW_ASSERT -#include -#define STBIW_ASSERT(x) assert(x) -#endif - -#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) - -#ifdef STB_IMAGE_WRITE_STATIC -static int stbi_write_png_compression_level = 8; -static int stbi_write_tga_with_rle = 1; -static int stbi_write_force_png_filter = -1; -#else -int stbi_write_png_compression_level = 8; -int stbi_write_tga_with_rle = 1; -int stbi_write_force_png_filter = -1; -#endif - -static int stbi__flip_vertically_on_write = 0; - -STBIWDEF void stbi_flip_vertically_on_write(int flag) -{ - stbi__flip_vertically_on_write = flag; -} - -typedef struct -{ - stbi_write_func *func; - void *context; -} stbi__write_context; - -// initialize a callback-based context -static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) -{ - s->func = c; - s->context = context; -} - -#ifndef STBI_WRITE_NO_STDIO - -static void stbi__stdio_write(void *context, void *data, int size) -{ - fwrite(data,1,size,(FILE*) context); -} - -#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) -#ifdef __cplusplus -#define STBIW_EXTERN extern "C" -#else -#define STBIW_EXTERN extern -#endif -STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); -STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); - -STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) -{ - return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); -} -#endif - -static FILE *stbiw__fopen(char const *filename, char const *mode) -{ - FILE *f; -#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) - wchar_t wMode[64]; - wchar_t wFilename[1024]; - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) - return 0; - - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) - return 0; - -#if _MSC_VER >= 1400 - if (0 != _wfopen_s(&f, wFilename, wMode)) - f = 0; -#else - f = _wfopen(wFilename, wMode); -#endif - -#elif defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != fopen_s(&f, filename, mode)) - f=0; -#else - f = fopen(filename, mode); -#endif - return f; -} - -static int stbi__start_write_file(stbi__write_context *s, const char *filename) -{ - FILE *f = stbiw__fopen(filename, "wb"); - stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); - return f != NULL; -} - -static void stbi__end_write_file(stbi__write_context *s) -{ - fclose((FILE *)s->context); -} - -#endif // !STBI_WRITE_NO_STDIO - -typedef unsigned int stbiw_uint32; -typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; - -static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) -{ - while (*fmt) { - switch (*fmt++) { - case ' ': break; - case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); - s->func(s->context,&x,1); - break; } - case '2': { int x = va_arg(v,int); - unsigned char b[2]; - b[0] = STBIW_UCHAR(x); - b[1] = STBIW_UCHAR(x>>8); - s->func(s->context,b,2); - break; } - case '4': { stbiw_uint32 x = va_arg(v,int); - unsigned char b[4]; - b[0]=STBIW_UCHAR(x); - b[1]=STBIW_UCHAR(x>>8); - b[2]=STBIW_UCHAR(x>>16); - b[3]=STBIW_UCHAR(x>>24); - s->func(s->context,b,4); - break; } - default: - STBIW_ASSERT(0); - return; - } - } -} - -static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) -{ - va_list v; - va_start(v, fmt); - stbiw__writefv(s, fmt, v); - va_end(v); -} - -static void stbiw__putc(stbi__write_context *s, unsigned char c) -{ - s->func(s->context, &c, 1); -} - -static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) -{ - unsigned char arr[3]; - arr[0] = a; arr[1] = b; arr[2] = c; - s->func(s->context, arr, 3); -} - -static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) -{ - unsigned char bg[3] = { 255, 0, 255}, px[3]; - int k; - - if (write_alpha < 0) - s->func(s->context, &d[comp - 1], 1); - - switch (comp) { - case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case - case 1: - if (expand_mono) - stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp - else - s->func(s->context, d, 1); // monochrome TGA - break; - case 4: - if (!write_alpha) { - // composite against pink background - for (k = 0; k < 3; ++k) - px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; - stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); - break; - } - /* FALLTHROUGH */ - case 3: - stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); - break; - } - if (write_alpha > 0) - s->func(s->context, &d[comp - 1], 1); -} - -static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) -{ - stbiw_uint32 zero = 0; - int i,j, j_end; - - if (y <= 0) - return; - - if (stbi__flip_vertically_on_write) - vdir *= -1; - - if (vdir < 0) { - j_end = -1; j = y-1; - } else { - j_end = y; j = 0; - } - - for (; j != j_end; j += vdir) { - for (i=0; i < x; ++i) { - unsigned char *d = (unsigned char *) data + (j*x+i)*comp; - stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); - } - s->func(s->context, &zero, scanline_pad); - } -} - -static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) -{ - if (y < 0 || x < 0) { - return 0; - } else { - va_list v; - va_start(v, fmt); - stbiw__writefv(s, fmt, v); - va_end(v); - stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); - return 1; - } -} - -static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) -{ - int pad = (-x*3) & 3; - return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, - "11 4 22 4" "4 44 22 444444", - 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header - 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header -} - -STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) -{ - stbi__write_context s; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_bmp_core(&s, x, y, comp, data); -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) -{ - stbi__write_context s; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_bmp_core(&s, x, y, comp, data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif //!STBI_WRITE_NO_STDIO - -static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) -{ - int has_alpha = (comp == 2 || comp == 4); - int colorbytes = has_alpha ? comp-1 : comp; - int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 - - if (y < 0 || x < 0) - return 0; - - if (!stbi_write_tga_with_rle) { - return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, - "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); - } else { - int i,j,k; - int jend, jdir; - - stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); - - if (stbi__flip_vertically_on_write) { - j = 0; - jend = y; - jdir = 1; - } else { - j = y-1; - jend = -1; - jdir = -1; - } - for (; j != jend; j += jdir) { - unsigned char *row = (unsigned char *) data + j * x * comp; - int len; - - for (i = 0; i < x; i += len) { - unsigned char *begin = row + i * comp; - int diff = 1; - len = 1; - - if (i < x - 1) { - ++len; - diff = memcmp(begin, row + (i + 1) * comp, comp); - if (diff) { - const unsigned char *prev = begin; - for (k = i + 2; k < x && len < 128; ++k) { - if (memcmp(prev, row + k * comp, comp)) { - prev += comp; - ++len; - } else { - --len; - break; - } - } - } else { - for (k = i + 2; k < x && len < 128; ++k) { - if (!memcmp(begin, row + k * comp, comp)) { - ++len; - } else { - break; - } - } - } - } - - if (diff) { - unsigned char header = STBIW_UCHAR(len - 1); - s->func(s->context, &header, 1); - for (k = 0; k < len; ++k) { - stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); - } - } else { - unsigned char header = STBIW_UCHAR(len - 129); - s->func(s->context, &header, 1); - stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); - } - } - } - } - return 1; -} - -STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) -{ - stbi__write_context s; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_tga_core(&s, x, y, comp, (void *) data); -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) -{ - stbi__write_context s; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif - -// ************************************************************************************************* -// Radiance RGBE HDR writer -// by Baldur Karlsson - -#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) - -static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) -{ - int exponent; - float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); - - if (maxcomp < 1e-32f) { - rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; - } else { - float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; - - rgbe[0] = (unsigned char)(linear[0] * normalize); - rgbe[1] = (unsigned char)(linear[1] * normalize); - rgbe[2] = (unsigned char)(linear[2] * normalize); - rgbe[3] = (unsigned char)(exponent + 128); - } -} - -static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) -{ - unsigned char lengthbyte = STBIW_UCHAR(length+128); - STBIW_ASSERT(length+128 <= 255); - s->func(s->context, &lengthbyte, 1); - s->func(s->context, &databyte, 1); -} - -static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) -{ - unsigned char lengthbyte = STBIW_UCHAR(length); - STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code - s->func(s->context, &lengthbyte, 1); - s->func(s->context, data, length); -} - -static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) -{ - unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; - unsigned char rgbe[4]; - float linear[3]; - int x; - - scanlineheader[2] = (width&0xff00)>>8; - scanlineheader[3] = (width&0x00ff); - - /* skip RLE for images too small or large */ - if (width < 8 || width >= 32768) { - for (x=0; x < width; x++) { - switch (ncomp) { - case 4: /* fallthrough */ - case 3: linear[2] = scanline[x*ncomp + 2]; - linear[1] = scanline[x*ncomp + 1]; - linear[0] = scanline[x*ncomp + 0]; - break; - default: - linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; - break; - } - stbiw__linear_to_rgbe(rgbe, linear); - s->func(s->context, rgbe, 4); - } - } else { - int c,r; - /* encode into scratch buffer */ - for (x=0; x < width; x++) { - switch(ncomp) { - case 4: /* fallthrough */ - case 3: linear[2] = scanline[x*ncomp + 2]; - linear[1] = scanline[x*ncomp + 1]; - linear[0] = scanline[x*ncomp + 0]; - break; - default: - linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; - break; - } - stbiw__linear_to_rgbe(rgbe, linear); - scratch[x + width*0] = rgbe[0]; - scratch[x + width*1] = rgbe[1]; - scratch[x + width*2] = rgbe[2]; - scratch[x + width*3] = rgbe[3]; - } - - s->func(s->context, scanlineheader, 4); - - /* RLE each component separately */ - for (c=0; c < 4; c++) { - unsigned char *comp = &scratch[width*c]; - - x = 0; - while (x < width) { - // find first run - r = x; - while (r+2 < width) { - if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) - break; - ++r; - } - if (r+2 >= width) - r = width; - // dump up to first run - while (x < r) { - int len = r-x; - if (len > 128) len = 128; - stbiw__write_dump_data(s, len, &comp[x]); - x += len; - } - // if there's a run, output it - if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd - // find next byte after run - while (r < width && comp[r] == comp[x]) - ++r; - // output run up to r - while (x < r) { - int len = r-x; - if (len > 127) len = 127; - stbiw__write_run_data(s, len, comp[x]); - x += len; - } - } - } - } - } -} - -static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) -{ - if (y <= 0 || x <= 0 || data == NULL) - return 0; - else { - // Each component is stored separately. Allocate scratch space for full output scanline. - unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); - int i, len; - char buffer[128]; - char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; - s->func(s->context, header, sizeof(header)-1); - -#ifdef __STDC_WANT_SECURE_LIB__ - len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); -#else - len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); -#endif - s->func(s->context, buffer, len); - - for(i=0; i < y; i++) - stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); - STBIW_FREE(scratch); - return 1; - } -} - -STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) -{ - stbi__write_context s; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_hdr_core(&s, x, y, comp, (float *) data); -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) -{ - stbi__write_context s; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif // STBI_WRITE_NO_STDIO - - -////////////////////////////////////////////////////////////////////////////// -// -// PNG writer -// - -#ifndef STBIW_ZLIB_COMPRESS -// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() -#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) -#define stbiw__sbm(a) stbiw__sbraw(a)[0] -#define stbiw__sbn(a) stbiw__sbraw(a)[1] - -#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) -#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) -#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) - -#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) -#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) -#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) - -static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) -{ - int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; - void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); - STBIW_ASSERT(p); - if (p) { - if (!*arr) ((int *) p)[1] = 0; - *arr = (void *) ((int *) p + 2); - stbiw__sbm(*arr) = m; - } - return *arr; -} - -static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) -{ - while (*bitcount >= 8) { - stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); - *bitbuffer >>= 8; - *bitcount -= 8; - } - return data; -} - -static int stbiw__zlib_bitrev(int code, int codebits) -{ - int res=0; - while (codebits--) { - res = (res << 1) | (code & 1); - code >>= 1; - } - return res; -} - -static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) -{ - int i; - for (i=0; i < limit && i < 258; ++i) - if (a[i] != b[i]) break; - return i; -} - -static unsigned int stbiw__zhash(unsigned char *data) -{ - stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); - hash ^= hash << 3; - hash += hash >> 5; - hash ^= hash << 4; - hash += hash >> 17; - hash ^= hash << 25; - hash += hash >> 6; - return hash; -} - -#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) -#define stbiw__zlib_add(code,codebits) \ - (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) -#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) -// default huffman tables -#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) -#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) -#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) -#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) -#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) -#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) - -#define stbiw__ZHASH 16384 - -#endif // STBIW_ZLIB_COMPRESS - -STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) -{ -#ifdef STBIW_ZLIB_COMPRESS - // user provided a zlib compress implementation, use that - return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); -#else // use builtin - static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; - static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; - static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; - static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; - unsigned int bitbuf=0; - int i,j, bitcount=0; - unsigned char *out = NULL; - unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); - if (hash_table == NULL) - return NULL; - if (quality < 5) quality = 5; - - stbiw__sbpush(out, 0x78); // DEFLATE 32K window - stbiw__sbpush(out, 0x5e); // FLEVEL = 1 - stbiw__zlib_add(1,1); // BFINAL = 1 - stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman - - for (i=0; i < stbiw__ZHASH; ++i) - hash_table[i] = NULL; - - i=0; - while (i < data_len-3) { - // hash next 3 bytes of data to be compressed - int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; - unsigned char *bestloc = 0; - unsigned char **hlist = hash_table[h]; - int n = stbiw__sbcount(hlist); - for (j=0; j < n; ++j) { - if (hlist[j]-data > i-32768) { // if entry lies within window - int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); - if (d >= best) { best=d; bestloc=hlist[j]; } - } - } - // when hash table entry is too long, delete half the entries - if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { - STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); - stbiw__sbn(hash_table[h]) = quality; - } - stbiw__sbpush(hash_table[h],data+i); - - if (bestloc) { - // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal - h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); - hlist = hash_table[h]; - n = stbiw__sbcount(hlist); - for (j=0; j < n; ++j) { - if (hlist[j]-data > i-32767) { - int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); - if (e > best) { // if next match is better, bail on current match - bestloc = NULL; - break; - } - } - } - } - - if (bestloc) { - int d = (int) (data+i - bestloc); // distance back - STBIW_ASSERT(d <= 32767 && best <= 258); - for (j=0; best > lengthc[j+1]-1; ++j); - stbiw__zlib_huff(j+257); - if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); - for (j=0; d > distc[j+1]-1; ++j); - stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); - if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); - i += best; - } else { - stbiw__zlib_huffb(data[i]); - ++i; - } - } - // write out final bytes - for (;i < data_len; ++i) - stbiw__zlib_huffb(data[i]); - stbiw__zlib_huff(256); // end of block - // pad with 0 bits to byte boundary - while (bitcount) - stbiw__zlib_add(0,1); - - for (i=0; i < stbiw__ZHASH; ++i) - (void) stbiw__sbfree(hash_table[i]); - STBIW_FREE(hash_table); - - { - // compute adler32 on input - unsigned int s1=1, s2=0; - int blocklen = (int) (data_len % 5552); - j=0; - while (j < data_len) { - for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } - s1 %= 65521; s2 %= 65521; - j += blocklen; - blocklen = 5552; - } - stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); - stbiw__sbpush(out, STBIW_UCHAR(s2)); - stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); - stbiw__sbpush(out, STBIW_UCHAR(s1)); - } - *out_len = stbiw__sbn(out); - // make returned pointer freeable - STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); - return (unsigned char *) stbiw__sbraw(out); -#endif // STBIW_ZLIB_COMPRESS -} - -static unsigned int stbiw__crc32(unsigned char *buffer, int len) -{ -#ifdef STBIW_CRC32 - return STBIW_CRC32(buffer, len); -#else - static unsigned int crc_table[256] = - { - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, - 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, - 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, - 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, - 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, - 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, - 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, - 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, - 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, - 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, - 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, - 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, - 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, - 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, - 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, - 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, - 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, - 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, - 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, - 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, - 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, - 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, - 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, - 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, - 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, - 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, - 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, - 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, - 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, - 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, - 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, - 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D - }; - - unsigned int crc = ~0u; - int i; - for (i=0; i < len; ++i) - crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; - return ~crc; -#endif -} - -#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) -#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); -#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) - -static void stbiw__wpcrc(unsigned char **data, int len) -{ - unsigned int crc = stbiw__crc32(*data - len - 4, len+4); - stbiw__wp32(*data, crc); -} - -static unsigned char stbiw__paeth(int a, int b, int c) -{ - int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); - if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); - if (pb <= pc) return STBIW_UCHAR(b); - return STBIW_UCHAR(c); -} - -// @OPTIMIZE: provide an option that always forces left-predict or paeth predict -static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) -{ - static int mapping[] = { 0,1,2,3,4 }; - static int firstmap[] = { 0,1,0,5,6 }; - int *mymap = (y != 0) ? mapping : firstmap; - int i; - int type = mymap[filter_type]; - unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); - int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; - - if (type==0) { - memcpy(line_buffer, z, width*n); - return; - } - - // first loop isn't optimized since it's just one pixel - for (i = 0; i < n; ++i) { - switch (type) { - case 1: line_buffer[i] = z[i]; break; - case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; - case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; - case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; - case 5: line_buffer[i] = z[i]; break; - case 6: line_buffer[i] = z[i]; break; - } - } - switch (type) { - case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; - case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; - case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; - case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; - case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; - case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; - } -} - -STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) -{ - int force_filter = stbi_write_force_png_filter; - int ctype[5] = { -1, 0, 4, 2, 6 }; - unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; - unsigned char *out,*o, *filt, *zlib; - signed char *line_buffer; - int j,zlen; - - if (stride_bytes == 0) - stride_bytes = x * n; - - if (force_filter >= 5) { - force_filter = -1; - } - - filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; - line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } - for (j=0; j < y; ++j) { - int filter_type; - if (force_filter > -1) { - filter_type = force_filter; - stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); - } else { // Estimate the best filter by running through all of them: - int best_filter = 0, best_filter_val = 0x7fffffff, est, i; - for (filter_type = 0; filter_type < 5; filter_type++) { - stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); - - // Estimate the entropy of the line using this filter; the less, the better. - est = 0; - for (i = 0; i < x*n; ++i) { - est += abs((signed char) line_buffer[i]); - } - if (est < best_filter_val) { - best_filter_val = est; - best_filter = filter_type; - } - } - if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it - stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); - filter_type = best_filter; - } - } - // when we get here, filter_type contains the filter type, and line_buffer contains the data - filt[j*(x*n+1)] = (unsigned char) filter_type; - STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); - } - STBIW_FREE(line_buffer); - zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); - STBIW_FREE(filt); - if (!zlib) return 0; - - // each tag requires 12 bytes of overhead - out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); - if (!out) return 0; - *out_len = 8 + 12+13 + 12+zlen + 12; - - o=out; - STBIW_MEMMOVE(o,sig,8); o+= 8; - stbiw__wp32(o, 13); // header length - stbiw__wptag(o, "IHDR"); - stbiw__wp32(o, x); - stbiw__wp32(o, y); - *o++ = 8; - *o++ = STBIW_UCHAR(ctype[n]); - *o++ = 0; - *o++ = 0; - *o++ = 0; - stbiw__wpcrc(&o,13); - - stbiw__wp32(o, zlen); - stbiw__wptag(o, "IDAT"); - STBIW_MEMMOVE(o, zlib, zlen); - o += zlen; - STBIW_FREE(zlib); - stbiw__wpcrc(&o, zlen); - - stbiw__wp32(o,0); - stbiw__wptag(o, "IEND"); - stbiw__wpcrc(&o,0); - - STBIW_ASSERT(o == out + *out_len); - - return out; -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) -{ - FILE *f; - int len; - unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); - if (png == NULL) return 0; - - f = stbiw__fopen(filename, "wb"); - if (!f) { STBIW_FREE(png); return 0; } - fwrite(png, 1, len, f); - fclose(f); - STBIW_FREE(png); - return 1; -} -#endif - -STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) -{ - int len; - unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); - if (png == NULL) return 0; - func(context, png, len); - STBIW_FREE(png); - return 1; -} - - -/* *************************************************************************** - * - * JPEG writer - * - * This is based on Jon Olick's jo_jpeg.cpp: - * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html - */ - -static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, - 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; - -static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { - int bitBuf = *bitBufP, bitCnt = *bitCntP; - bitCnt += bs[1]; - bitBuf |= bs[0] << (24 - bitCnt); - while(bitCnt >= 8) { - unsigned char c = (bitBuf >> 16) & 255; - stbiw__putc(s, c); - if(c == 255) { - stbiw__putc(s, 0); - } - bitBuf <<= 8; - bitCnt -= 8; - } - *bitBufP = bitBuf; - *bitCntP = bitCnt; -} - -static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { - float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; - float z1, z2, z3, z4, z5, z11, z13; - - float tmp0 = d0 + d7; - float tmp7 = d0 - d7; - float tmp1 = d1 + d6; - float tmp6 = d1 - d6; - float tmp2 = d2 + d5; - float tmp5 = d2 - d5; - float tmp3 = d3 + d4; - float tmp4 = d3 - d4; - - // Even part - float tmp10 = tmp0 + tmp3; // phase 2 - float tmp13 = tmp0 - tmp3; - float tmp11 = tmp1 + tmp2; - float tmp12 = tmp1 - tmp2; - - d0 = tmp10 + tmp11; // phase 3 - d4 = tmp10 - tmp11; - - z1 = (tmp12 + tmp13) * 0.707106781f; // c4 - d2 = tmp13 + z1; // phase 5 - d6 = tmp13 - z1; - - // Odd part - tmp10 = tmp4 + tmp5; // phase 2 - tmp11 = tmp5 + tmp6; - tmp12 = tmp6 + tmp7; - - // The rotator is modified from fig 4-8 to avoid extra negations. - z5 = (tmp10 - tmp12) * 0.382683433f; // c6 - z2 = tmp10 * 0.541196100f + z5; // c2-c6 - z4 = tmp12 * 1.306562965f + z5; // c2+c6 - z3 = tmp11 * 0.707106781f; // c4 - - z11 = tmp7 + z3; // phase 5 - z13 = tmp7 - z3; - - *d5p = z13 + z2; // phase 6 - *d3p = z13 - z2; - *d1p = z11 + z4; - *d7p = z11 - z4; - - *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; -} - -static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { - int tmp1 = val < 0 ? -val : val; - val = val < 0 ? val-1 : val; - bits[1] = 1; - while(tmp1 >>= 1) { - ++bits[1]; - } - bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { - } - // end0pos = first element in reverse order !=0 - if(end0pos == 0) { - stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); - return DU[0]; - } - for(i = 1; i <= end0pos; ++i) { - int startpos = i; - int nrzeroes; - unsigned short bits[2]; - for (; DU[i]==0 && i<=end0pos; ++i) { - } - nrzeroes = i-startpos; - if ( nrzeroes >= 16 ) { - int lng = nrzeroes>>4; - int nrmarker; - for (nrmarker=1; nrmarker <= lng; ++nrmarker) - stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); - nrzeroes &= 15; - } - stbiw__jpg_calcBits(DU[i], bits); - stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); - stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); - } - if(end0pos != 63) { - stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); - } - return DU[0]; -} - -static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { - // Constants that don't pollute global namespace - static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; - static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; - static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; - static const unsigned char std_ac_luminance_values[] = { - 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, - 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, - 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, - 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, - 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, - 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, - 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa - }; - static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; - static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; - static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; - static const unsigned char std_ac_chrominance_values[] = { - 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, - 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, - 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, - 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, - 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, - 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, - 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa - }; - // Huffman tables - static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; - static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; - static const unsigned short YAC_HT[256][2] = { - {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} - }; - static const unsigned short UVAC_HT[256][2] = { - {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} - }; - static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, - 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; - static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, - 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; - static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, - 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; - - int row, col, i, k, subsample; - float fdtbl_Y[64], fdtbl_UV[64]; - unsigned char YTable[64], UVTable[64]; - - if(!data || !width || !height || comp > 4 || comp < 1) { - return 0; - } - - quality = quality ? quality : 90; - subsample = quality <= 90 ? 1 : 0; - quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; - quality = quality < 50 ? 5000 / quality : 200 - quality * 2; - - for(i = 0; i < 64; ++i) { - int uvti, yti = (YQT[i]*quality+50)/100; - YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); - uvti = (UVQT[i]*quality+50)/100; - UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); - } - - for(row = 0, k = 0; row < 8; ++row) { - for(col = 0; col < 8; ++col, ++k) { - fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); - fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); - } - } - - // Write Headers - { - static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; - static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; - const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), - 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; - s->func(s->context, (void*)head0, sizeof(head0)); - s->func(s->context, (void*)YTable, sizeof(YTable)); - stbiw__putc(s, 1); - s->func(s->context, UVTable, sizeof(UVTable)); - s->func(s->context, (void*)head1, sizeof(head1)); - s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); - s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); - stbiw__putc(s, 0x10); // HTYACinfo - s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); - s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); - stbiw__putc(s, 1); // HTUDCinfo - s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); - s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); - stbiw__putc(s, 0x11); // HTUACinfo - s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); - s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); - s->func(s->context, (void*)head2, sizeof(head2)); - } - - // Encode 8x8 macroblocks - { - static const unsigned short fillBits[] = {0x7F, 7}; - int DCY=0, DCU=0, DCV=0; - int bitBuf=0, bitCnt=0; - // comp == 2 is grey+alpha (alpha is ignored) - int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; - const unsigned char *dataR = (const unsigned char *)data; - const unsigned char *dataG = dataR + ofsG; - const unsigned char *dataB = dataR + ofsB; - int x, y, pos; - if(subsample) { - for(y = 0; y < height; y += 16) { - for(x = 0; x < width; x += 16) { - float Y[256], U[256], V[256]; - for(row = y, pos = 0; row < y+16; ++row) { - // row >= height => use last input row - int clamped_row = (row < height) ? row : height - 1; - int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; - for(col = x; col < x+16; ++col, ++pos) { - // if col >= width => use pixel from last input column - int p = base_p + ((col < width) ? col : (width-1))*comp; - float r = dataR[p], g = dataG[p], b = dataB[p]; - Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; - U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; - V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; - } - } - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); - - // subsample U,V - { - float subU[64], subV[64]; - int yy, xx; - for(yy = 0, pos = 0; yy < 8; ++yy) { - for(xx = 0; xx < 8; ++xx, ++pos) { - int j = yy*32+xx*2; - subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; - subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; - } - } - DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); - DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); - } - } - } - } else { - for(y = 0; y < height; y += 8) { - for(x = 0; x < width; x += 8) { - float Y[64], U[64], V[64]; - for(row = y, pos = 0; row < y+8; ++row) { - // row >= height => use last input row - int clamped_row = (row < height) ? row : height - 1; - int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; - for(col = x; col < x+8; ++col, ++pos) { - // if col >= width => use pixel from last input column - int p = base_p + ((col < width) ? col : (width-1))*comp; - float r = dataR[p], g = dataG[p], b = dataB[p]; - Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; - U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; - V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; - } - } - - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); - DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); - } - } - } - - // Do the bit alignment of the EOI marker - stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); - } - - // EOI - stbiw__putc(s, 0xFF); - stbiw__putc(s, 0xD9); - - return 1; -} - -STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) -{ - stbi__write_context s; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); -} - - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) -{ - stbi__write_context s; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif - -#endif // STB_IMAGE_WRITE_IMPLEMENTATION - -/* Revision history - 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels - 1.13 - 1.12 - 1.11 (2019-08-11) - - 1.10 (2019-02-07) - support utf8 filenames in Windows; fix warnings and platform ifdefs - 1.09 (2018-02-11) - fix typo in zlib quality API, improve STB_I_W_STATIC in C++ - 1.08 (2018-01-29) - add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter - 1.07 (2017-07-24) - doc fix - 1.06 (2017-07-23) - writing JPEG (using Jon Olick's code) - 1.05 ??? - 1.04 (2017-03-03) - monochrome BMP expansion - 1.03 ??? - 1.02 (2016-04-02) - avoid allocating large structures on the stack - 1.01 (2016-01-16) - STBIW_REALLOC_SIZED: support allocators with no realloc support - avoid race-condition in crc initialization - minor compile issues - 1.00 (2015-09-14) - installable file IO function - 0.99 (2015-09-13) - warning fixes; TGA rle support - 0.98 (2015-04-08) - added STBIW_MALLOC, STBIW_ASSERT etc - 0.97 (2015-01-18) - fixed HDR asserts, rewrote HDR rle logic - 0.96 (2015-01-17) - add HDR output - fix monochrome BMP - 0.95 (2014-08-17) - add monochrome TGA output - 0.94 (2014-05-31) - rename private functions to avoid conflicts with stb_image.h - 0.93 (2014-05-27) - warning fixes - 0.92 (2010-08-01) - casts to unsigned char to fix warnings - 0.91 (2010-07-17) - first public release - 0.90 first internal release -*/ - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/wrappers/android/.idea/codeStyles/Project.xml b/wrappers/android/.idea/codeStyles/Project.xml index b5f2664b65..c4a490d90b 100644 --- a/wrappers/android/.idea/codeStyles/Project.xml +++ b/wrappers/android/.idea/codeStyles/Project.xml @@ -1,27 +1,18 @@ - - + + diff --git a/wrappers/android/.idea/jarRepositories.xml b/wrappers/android/.idea/jarRepositories.xml index 17c8136147..52a77b6c6e 100644 --- a/wrappers/android/.idea/jarRepositories.xml +++ b/wrappers/android/.idea/jarRepositories.xml @@ -21,5 +21,10 @@