Introduce an object-oriented C++ API based on a new C ABI

The current C API prevents adding new features and the introduction of an object oriented C++ interface. Every change results directly in a ABI break. To prevent this a new C API is introduced that is easier to extend and to maintain. On top of this API are 2 C++ classes: jpeg_encoder and jpeg_decoder to encode and decode JPEG-LS files.

This commit comes also with the following changes:
- Updated .clang-format
- Switch to Visual Studio 2019 for the CI build pipeline
- Support for reading and writing SPIFF header
- Incomplete support for JFIF header has been removed
- Extended unit test coverage
This commit is contained in:
Victor Derks 2019-11-08 21:56:38 +01:00
parent dcb9de7ffd
commit e90209e8f2
81 changed files with 6071 additions and 1457 deletions

View File

@ -1,7 +1,7 @@
BasedOnStyle: LLVM
Language: Cpp
# Use features of C++11, C++14 and C++17 (e.g. A<A<int>> instead of A<A<int> >)
# Use features of C++11, C++14 and C++17 (e.g. A<A<int>> instead of A<A<int> >)
Standard: Cpp11
UseTab: Never
@ -9,14 +9,30 @@ IndentWidth: 4
ColumnLimit: 0
PointerAlignment: Left
BreakBeforeBraces: Allman
BreakBeforeBraces: Custom
BraceWrapping:
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
AccessModifierOffset: -4
AlignTrailingComments: true
AllowShortFunctionsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AlignTrailingComments: true
AllowShortFunctionsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
AlwaysBreakTemplateDeclarations: true
BreakConstructorInitializers: AfterColon
CompactNamespaces: true
ConstructorInitializerAllOnOneLineOrOnePerLine: true
SpaceAfterTemplateKeyword: false
SpaceBeforeCpp11BracedList: false
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 2
MaxEmptyLinesToKeep: 2
NamespaceIndentation: None

View File

@ -8,6 +8,7 @@ trim_trailing_whitespace = true
[*.{h,cpp}]
indent_size = 4
insert_final_newline = true
[CMakeLists.txt]
indent_size = 2

1
.gitignore vendored
View File

@ -10,6 +10,7 @@ build/
[Dd]ebug/
[Rr]elease/
[Cc]hecked/
TestResults/
*.opensdf
*.sdf

View File

@ -1,22 +1,16 @@
dist: bionic
language: cpp
compiler:
- gcc
- clang
install:
- if [ "$CXX" = "g++" ]; then export CXX="g++-5" CC="gcc-5"; fi
- if [ "$CXX" = "clang++" ]; then export CXX="clang++-3.7" CC="clang-3.7"; fi
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.7
packages:
- gcc-5
- g++-5
- clang-3.7
sudo: false

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.16
# Visual Studio Version 16
VisualStudioVersion = 16.0.29006.145
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CharLSTest", "test\CharLSTest.vcxproj", "{7185AD7F-57BA-42C7-A715-239CEA8ADC31}"
EndProject
@ -15,7 +15,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CharLS", "src\CharLS.vcxpro
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5F4C6204-36F8-4D63-BF33-ECFCB5D1D114}"
ProjectSection(SolutionItems) = preProject
.clang-format = .clang-format
.editorconfig = .editorconfig
cpp.hint = cpp.hint
default.ruleset.md = default.ruleset.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CharLSNetTest", "dotnet\test\CharLSNetTest.csproj", "{002B897F-9D96-4A99-853F-53806C39559D}"

View File

@ -3,11 +3,19 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyCertMsc32C/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyCertMsc51Cpp/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyClangDiagnosticMissingBraces/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyCppcoreguidelinesAvoidMagicNumbers/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyCppcoreguidelinesMacroUsage/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyCppcoreguidelinesProTypeMemberInit/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyCppcoreguidelinesSpecialMemberFunctions/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyHicppMemberInit/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyHicppSpecialMemberFunctions/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyModernizeLoopConvert/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyModernizeUseDefaultMemberInit/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyModernizeUseNullptr/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyPerformanceNoexceptMoveConstructor/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyReadabilityMagicNumbers/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClangTidyReadabilityStaticAccessedThroughInstance/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConst/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAuto/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Classes_0020and_0020structs/@EntryIndexedValue">&lt;NamingElement Priority="1"&gt;&lt;Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="__interface" /&gt;&lt;type Name="class" /&gt;&lt;type Name="struct" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="False" Prefix="" Suffix="" Style="aa_bb" /&gt;&lt;/NamingElement&gt;</s:String>
@ -30,14 +38,19 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=bugprone/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=CHARLS/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=charlstest/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=cmyk/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=cpixel/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=decodetest/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dicom/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dtor/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Endian/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=errc/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=facto/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=forceinline/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Golomb/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=grayscale/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=HRES/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=jbig/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=jfif/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=jpegls/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=lossless/@EntryIndexedValue">True</s:Boolean>
@ -45,14 +58,20 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=maxval/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mrfx/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=NODISCARD/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=opto/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ptype/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=qbpp/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=RGBRGBRGB/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=RRRGGGBBB/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Runmode/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Undefine/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=UNSUPPRESS/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Vaan/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=VRES/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Xdensity/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=xfrm/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Xthumbnail/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ycbcr/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ycck/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ydensity/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ythumbnail/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -15,7 +15,7 @@
<!-- The C runtime is provided by the OS on the Windows platform (Universal C Runtime (CRT)),
it is necesarry to explicitly define which version to use.
At the momement there is no way to specify to use just the latest version. -->
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<!-- Use the latest released C# language version -->
<LangVersion>latest</LangVersion>
@ -92,7 +92,12 @@
</ClCompile>
</ItemDefinitionGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Checked'">
<LinkIncremental>true</LinkIncremental>
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
</Project>

View File

@ -1,5 +1,5 @@
version: 2.00.{build}
os: Visual Studio 2017
os: Visual Studio 2019
configuration:
- Debug
- Release
@ -7,7 +7,7 @@ configuration:
platform:
- x86
- x64
before_build:
before_build:
- msbuild /t:restore
build:
project: CharLS.sln

View File

@ -3,11 +3,14 @@
//
// This file is used solely to aid Visual Studio Intellisense while editing.
// It is not used as part of any build. See Microsoft docs for more info about this file.
//
//
#define CHARLS_API_IMPORT_EXPORT
#define CHARLS_API_CALLING_CONVENTION
#define CHARLS_ENUM_DEPRECATED
#define CHARLS_NO_DISCARD [[nodiscard]]
#define CHARLS_DEPRECATED [[deprecated]]
#define CHARLS_FINAL final
#define CHARLS_NOEXCEPT noexcept
#define FORCE_INLINE
#define MSVC_WARNING_SUPPRESS(x)
#define MSVC_WARNING_UNSUPPRESS()

View File

@ -2,10 +2,8 @@
<RuleSet Name="Default Rules for CharLS code" Description="These rules are for the VS static analyzer." ToolsVersion="15.0">
<IncludeAll Action="Error" />
<Rules AnalyzerId="Microsoft.Analyzers.NativeCodeAnalysis" RuleNamespace="Microsoft.Rules.Native">
<Rule Id="C26050" Action="None" />
<Rule Id="C26426" Action="None" />
<Rule Id="C26429" Action="None" />
<Rule Id="C26440" Action="None" />
<Rule Id="C26446" Action="None" />
<Rule Id="C26472" Action="None" />
<Rule Id="C26481" Action="None" />
@ -17,6 +15,5 @@
<Rule Id="C26490" Action="None" />
<Rule Id="C26492" Action="None" />
<Rule Id="C26494" Action="None" />
<Rule Id="C26814" Action="None" />
</Rules>
</RuleSet>

View File

@ -23,7 +23,7 @@ There are 2 methods to prevent double include:
1) #pragma once. Supported by all main compilers MSVC, GCC, clang and many other compilers, but in essence a compiler extension.
2) #ifdef CHARLS_<FILENAME> \ #define CHARLS_<FILENAME> \ #endif construction.
2) #ifdef CHARLS_\<FILENAME> \ #define CHARLS_\<FILENAME> \ #endif construction.
* Given the industry acceptance, use method 1 (easier and less manual code).
@ -54,12 +54,17 @@ catch (const std::system_error& e)
}
```
## Types
The type charls_jpegls_encoder is placed in the charls namespace as the type needs also to be defined as a C type.
It makes it also easier to keep the header only type charls::jpegls_encoder and the implementation class charls_jpegls_encoder separate.
## Jpeg-LS Design decisions
### Width and Height
The Jpeg-LS standards support a height and weight up to 2^32-1. This means that at least an 32-bit unsigned integer is
needed to support the complete range. Using unsigned integers has however the drawback that the interoperability with other languages is poort:
needed to support the complete range. Using unsigned integers has however the drawback that the interoperability with other languages is poor:
C# : supports unsigned 32 bit integers (but .NET marks them as not CLS compliant)
VB.NET : supports unsigned 32 bit integers (but .NET marks them as not CLS compliant)
@ -67,22 +72,39 @@ Java : by default integers are signed
Javascript: only signed integers
Python: only signed integers
Given the practical applications that 2^31 * 2^31 (max signed integer) will be sufficient for the coming 10 years, the API should use signed integers.
References:
8K Images = (7680×4320)
Having unsigned integers in the C and C++ application and signed integers in wrapping libaries should
not be a practical problem. Most real world images can be expressed in signed integers, 8K Images = (7680×4320).
### ABI
A C style interface is used to ensure that the ABI is stable. However just a C style API is not enough.
As CharLS can be distributes as a dynamic link library also the filename needs to be managed.
- It should be possible for applications to use v1 and v2 DLL at the same time.
- It should be possible to load the correct CPU architecture from the same directory.
* It should be possible for applications to use v1 and v2 DLL at the same time.
* It should be possible to load the correct CPU architecture from the same directory.
Design (Windows):
- filename = charls-<ABI version>-<CPU architecture>.dll
* filename = charls-\<ABI version>-\<CPU architecture>.dll
#### Null pointer checking
Passing a NULL pointer as parameter into the C ABI can be handled in 2 ways:
* There can be an explicit check and an error return value.
* The pointer can be deferenced directly and passing NULL will just crash the process.
Passing a NULL pointer is a defect of the calling application, it is however helpfull to
not generate an access violation inside the library module. If the library is build without
symbol info, it is difficult for the user to detect this mistake. Returning a "bad parameter"
error is in this case more helpfull.
Note: NULL is the only special value that can be checked, but also the common mistake.
### Supported C++ language
CharLS currently targets C++14 on the main branch. This will be done until December 2020 (3 years after the release of C++17)
CharLS currently targets C++14 on the main branch. This will be done until December 2022 (5 years after the release of C++17)
#### Features currently not available (C++17)
@ -100,10 +122,29 @@ CharLS currently targets C++14 on the main branch. This will be done until Decem
The following features are available in C++20 (usable after 2023), or in dual language support mode.
* endian
* <span>
* \<span>
* modules
### Supported C# language
CharLS currently targets C# 7.3 on the main branch. This will be done until C# 8.0 becomes available.
Client code in C# 7.3 calling the CharLS assembly will be supported up to 3 years after the release of C# 8.0.
Client code in C# 7.3 calling the CharLS assembly will be supported up to 3 years after the release of C# 8.0.
### Portable Anymap Format
The de facto standard used by the JPEG standard to deliver test files is the Portable Anymap Format.
This format has been made populair by the netpbm project. It is an extreme simple format and only
designed to make it easy to exchange images on many platforms.
It consists of the following variants
* P5 = Portable Graymap (0 ... 16 bits monochrome), extension = .pgm
* P6 = Portable PixMap (0 .. 16 bits RGB), extension = .ppm
* P7 = Portable Arbitrary Map (0..16 bits, N channels), extension = .pam
### External components \ Package Manager
One of the missing features of C++ is a standard Package Manager. The following packages would be usefull to use:
* Cross-platform unit test library (for example Catch2)
* Library to read Anymap files (for example Netpbm)
* Library to parse command line parameters (for example Clara, CLI11)

View File

@ -43,7 +43,27 @@
#endif
#if defined(__cplusplus) && __cplusplus >= 201703
#define CHARLS_NO_DISCARD [[nodiscard]]
#else
#define CHARLS_NO_DISCARD
#endif
#ifdef __cplusplus
#define CHARLS_FINAL final
#define CHARLS_NOEXCEPT noexcept
#ifdef CHARLS_NO_DEPRECATED_WARNINGS
#define CHARLS_DEPRECATED
#else
#define CHARLS_DEPRECATED [[deprecated]]
#endif
#else
#define CHARLS_FINAL
#define CHARLS_NOEXCEPT
#endif

View File

@ -4,22 +4,292 @@
#include "jpegls_error.h"
struct charls_jpegls_decoder;
struct charls_jpegls_encoder;
#ifdef __cplusplus
#include <memory>
extern "C" {
#else
#include <stdbool.h>
#include <stddef.h>
typedef struct charls_jpegls_decoder charls_jpegls_decoder;
typedef struct charls_jpegls_encoder charls_jpegls_encoder;
#endif
// The following functions define the public C API of the CharLS library.
// The C++ API is defined after the C API.
/// <summary>
/// Creates a JPEG-LS decoder instance, when finished with the instance destroy it with the function charls_jpegls_decoder_destroy.
/// </summary>
/// <returns>A reference to a new created decoder instance, or a null pointer when the creation fails.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_decoder* CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_create(void) CHARLS_NOEXCEPT;
/// <summary>
/// Destroys a JPEG-LS decoder instance created with charls_jpegls_decoder_create and releases all internal resources attached to it.
/// </summary>
/// <param name="decoder">Instance to destroy. If a null pointer is passed as argument, no action occurs.</param>
CHARLS_API_IMPORT_EXPORT void CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_destroy(const charls_jpegls_decoder* decoder) CHARLS_NOEXCEPT;
/// <summary>
/// Set the reference to a source buffer that contains the encoded JPEG-LS byte stream data.
/// This buffer needs to remain valid until the buffer is fully decoded.
/// </summary>
/// <param name="decoder">Reference to the decoder instance.</param>
/// <param name="source_buffer">Reference to the start of the source buffer.</param>
/// <param name="source_size_bytes">Size of the source buffer in bytes.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_set_source_buffer(charls_jpegls_decoder* decoder, const void* source_buffer, size_t source_size_bytes) CHARLS_NOEXCEPT;
/// <summary>
/// Tries to read the SPIFF header from the source buffer.
/// If a SPIFF header exists its content will be put into the spiff_header parameter and header_found will be set to 1.
/// Call charls_jpegls_decoder_read_header to read the normal JPEG header afterwards.
/// </summary>
/// <param name="decoder">Reference to the decoder instance.</param>
/// <param name="spiff_header">Output argument, will hold the SPIFF header when one could be found.</param>
/// <param name="header_found">Output argument, will hold 1 if a SPIFF header could be found, otherwise 0.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_read_spiff_header(charls_jpegls_decoder* decoder, charls_spiff_header* spiff_header, int32_t* header_found) CHARLS_NOEXCEPT;
/// <summary>
/// Reads the JPEG-LS header from the JPEG byte stream. After this function is called frame info can be retrieved.
/// </summary>
/// <param name="decoder">Reference to the decoder instance.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_read_header(charls_jpegls_decoder* decoder) CHARLS_NOEXCEPT;
/// <summary>
/// Returns information about the frame stored in the JPEG-LS byte stream.
/// Function can be called after charls_jpegls_decoder_read_header.
/// </summary>
/// <param name="decoder">Reference to the decoder instance.</param>
/// <param name="frame_info">Output argument, will hold the frame info when the function returns.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_get_frame_info(const charls_jpegls_decoder* decoder, charls_frame_info* frame_info) CHARLS_NOEXCEPT;
/// <summary>
/// Returns the NEAR parameter that was used to encode the scan. A value of 0 means lossless.
/// </summary>
/// <param name="decoder">Reference to the decoder instance.</param>
/// <param name="component">The component index for which the NEAR parameter should be retrieved.</param>
/// <param name="near_lossless">Reference that will hold the value of the NEAR parameter.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_get_near_lossless(const charls_jpegls_decoder* decoder, int32_t component, int32_t* near_lossless) CHARLS_NOEXCEPT;
/// <summary>
/// Returns the interleave mode that was used to encode the scan(s).
/// </summary>
/// <param name="decoder">Reference to the decoder instance.</param>
/// <param name="interleave_mode">Reference that will hold the value of the interleave mode.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_get_interleave_mode(const charls_jpegls_decoder* decoder, charls_interleave_mode* interleave_mode) CHARLS_NOEXCEPT;
/// <summary>
/// Returns the preset coding parameters used to encode the first scan.
/// </summary>
/// <param name="decoder">Reference to the decoder instance.</param>
/// <param name="reserved">Reserved. Should be set to 0.</param>
/// <param name="preset_coding_parameters">Reference that will hold the values preset coding parameters.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_get_preset_coding_parameters(const charls_jpegls_decoder* decoder, int32_t reserved, charls_jpegls_pc_parameters* preset_coding_parameters) CHARLS_NOEXCEPT;
/// <summary>
/// Returns the size required for the destination buffer in bytes to hold the decoded pixel data.
/// Function can be called after charls_jpegls_decoder_read_header.
/// </summary>
/// <param name="decoder">Reference to the decoder instance.</param>
/// <param name="destination_size_bytes">Output argument, will hold the required size when the function returns.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_get_destination_size(const charls_jpegls_decoder* decoder, size_t* destination_size_bytes) CHARLS_NOEXCEPT;
/// <summary>
/// Will decode the JPEG-LS byte stream from the source buffer into the destination buffer.
/// </summary>
/// <param name="decoder">Reference to the decoder instance.</param>
/// <param name="destination_buffer">Byte array that holds the encoded bytes when the function returns.</param>
/// <param name="destination_size_bytes">Length of the array in bytes. If the array is too small the function will return an error.</param>
/// <param name="stride">Number of bytes to the next line in the buffer, when zero, decoder will compute it.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_decode_to_buffer(const charls_jpegls_decoder* decoder, void* destination_buffer, size_t destination_size_bytes, uint32_t stride) CHARLS_NOEXCEPT;
/// <summary>
/// Creates a JPEG-LS encoder instance, when finished with the instance destroy it with the function charls_jpegls_encoder_destroy.
/// </summary>
/// <returns>A reference to a new created encoder instance, or a null pointer when the creation fails.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_encoder* CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_create(void) CHARLS_NOEXCEPT;
/// <summary>
/// Destroys a JPEG-LS encoder instance created with charls_jpegls_encoder_create and releases all internal resources attached to it.
/// </summary>
/// <param name="encoder">Instance to destroy. If a null pointer is passed as argument, no action occurs.</param>
CHARLS_API_IMPORT_EXPORT void CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_destroy(const charls_jpegls_encoder* encoder) CHARLS_NOEXCEPT;
/// <summary>
/// Configures the frame that needs to be encoded. This information will be written to the Start of Frame segment.
/// </summary>
/// <param name="encoder">Reference to the encoder instance.</param>
/// <param name="frame_info">Information about the frame that needs to be encoded.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_set_frame_info(charls_jpegls_encoder* encoder, const charls_frame_info* frame_info) CHARLS_NOEXCEPT;
/// <summary>
/// Configures the NEAR parameter the encoder should use. A value of 0 means lossless, this is also the default.
/// </summary>
/// <param name="encoder">Reference to the encoder instance.</param>
/// <param name="near_lossless">Value of the NEAR parameter.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_set_near_lossless(charls_jpegls_encoder* encoder, int32_t near_lossless) CHARLS_NOEXCEPT;
/// <summary>
/// Configures the interleave mode the encoder should use. The default is none.
/// The encoder expects the input buffer in the same format as the interleave mode.
/// </summary>
/// <param name="encoder">Reference to the encoder instance.</param>
/// <param name="interleave_mode">Value of the interleave mode.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_set_interleave_mode(charls_jpegls_encoder* encoder, charls_interleave_mode interleave_mode) CHARLS_NOEXCEPT;
/// <summary>
/// Configures the preset coding parameters the encoder should use.
/// If not set the encoder will use the default preset coding parameters as defined by the JPEG-LS standard.
/// Only when the coding parameters are different then the default parameters, they will be written to the
/// JPEG-LS stream during the encode phase.
/// </summary>
/// <param name="encoder">Reference to the encoder instance.</param>
/// <param name="preset_coding_parameters">Reference to the preset coding parameters.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_set_preset_coding_parameters(charls_jpegls_encoder* encoder, const charls_jpegls_pc_parameters* preset_coding_parameters) CHARLS_NOEXCEPT;
/// <summary>
/// Configures the HP color transformation the encoder should use.
/// If not set the encoder will use no color transformation.
/// Color transformations are a HP extension and not defined by the JPEG-LS standard and can only be set for 3 component encodings.
/// </summary>
/// <param name="encoder">Reference to the encoder instance.</param>
/// <param name="color_transformation">The color transformation parameters.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_set_color_transformation(charls_jpegls_encoder* encoder, charls_color_transformation color_transformation) CHARLS_NOEXCEPT;
/// <summary>
/// Returns the size in bytes, that the encoder expects are needed to hold the encoded image.
/// </summary>
/// <param name="encoder">Reference to the encoder instance.</param>
/// <param name="size_in_bytes">Reference to the size that will be set when the functions returns.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_get_estimated_destination_size(const charls_jpegls_encoder* encoder, size_t* size_in_bytes) CHARLS_NOEXCEPT;
/// <summary>
/// Set the reference to the destination buffer that will contain the encoded JPEG-LS byte stream data after encoding.
/// This buffer needs to remain valid during the encoding process.
/// </summary>
/// <param name="encoder">Reference to the encoder instance.</param>
/// <param name="destination_buffer">Reference to the start of the destination buffer.</param>
/// <param name="destination_size">Size of the destination buffer in bytes.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_set_destination_buffer(charls_jpegls_encoder* encoder, void* destination_buffer, size_t destination_size) CHARLS_NOEXCEPT;
/// <summary>
/// Writes a standard SPIFF header to the destination. The additional values are computed from the current encoder settings.
/// </summary>
/// <param name="encoder">Reference to the encoder instance.</param>
/// <param name="color_space">The color space of the image.</param>
/// <param name="resolution_units">The resolution units of the next 2 parameters.</param>
/// <param name="vertical_resolution">The vertical resolution.</param>
/// <param name="horizontal_resolution">The horizontal resolution.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_write_standard_spiff_header(charls_jpegls_encoder* encoder,
charls_spiff_color_space color_space,
charls_spiff_resolution_units resolution_units,
uint32_t vertical_resolution,
uint32_t horizontal_resolution) CHARLS_NOEXCEPT;
/// <summary>
/// Writes a SPIFF header to the destination.
/// </summary>
/// <param name="encoder">Reference to the encoder instance.</param>
/// <param name="spiff_header">Reference to a SPIFF header that will be written to the destination.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_write_spiff_header(charls_jpegls_encoder* encoder, const charls_spiff_header* spiff_header) CHARLS_NOEXCEPT;
/// <summary>
/// Writes a SPIFF directory entry to the destination.
/// </summary>
/// <param name="encoder">Reference to the encoder instance.</param>
/// <param name="entry_tag">The entry tag of the directory entry.</param>
/// <param name="entry_data">The entry data of the directory entry.</param>
/// <param name="entry_data_size">The size in bytes of the directory entry [0-65528].</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_write_spiff_entry(charls_jpegls_encoder* encoder, uint32_t entry_tag, const void* entry_data, size_t entry_data_size) CHARLS_NOEXCEPT;
/// <summary>
/// Encodes the passed buffer with the source image data to the destination.
/// </summary>
/// <param name="encoder">Reference to the encoder instance.</param>
/// <param name="source_buffer">Byte array that holds the image data that needs to be encoded.</param>
/// <param name="source_size">Length of the array in bytes.</param>
/// <param name="stride">
/// The number of bytes from one row of pixels in memory to the next row of pixels in memory.
/// Stride is sometimes called pitch. If padding bytes are present, the stride is wider than the width of the image.
/// </param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_encode_from_buffer(charls_jpegls_encoder* encoder, const void* source_buffer, size_t source_size, uint32_t stride) CHARLS_NOEXCEPT;
/// <summary>
/// Returns the size in bytes, that are written to the destination.
/// </summary>
/// <param name="encoder">Reference to the encoder instance.</param>
/// <param name="bytes_written">Reference to the size that will be set when the functions returns.</param>
/// <returns>The result of the operation: success or a failure code.</returns>
CHARLS_API_IMPORT_EXPORT charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_get_bytes_written(const charls_jpegls_encoder* encoder, size_t* bytes_written) CHARLS_NOEXCEPT;
// Note: The 4 methods below are considered obsolete and will be removed in the next major update.
/// <summary>
/// Encodes a byte array with pixel data to a JPEG-LS encoded (compressed) byte array.
/// </summary>
/// <remarks>This method is considered obsolete and will be removed in the next major update.</remarks>
/// <param name="destination">Byte array that holds the encoded bytes when the function returns.</param>
/// <param name="destinationLength">Length of the array in bytes. If the array is too small the function will return an error.</param>
/// <param name="bytesWritten">This parameter will hold the number of bytes written to the destination byte array. Cannot be NULL.</param>
/// <param name="source">Byte array that holds the pixels that should be encoded.</param>
/// <param name="sourceLength">Length of the array in bytes.</param>
/// <param name="params">Parameter object that describes the pixel data and how to encode it.</param>
/// <param name="reserved">Reserved, pass NULL pointer (will be removed in next ABI update).</param>
/// <param name="errorMessage">Character array of at least 256 characters or NULL. Hold the error message when a failure occurs, empty otherwise.</param>
CHARLS_API_IMPORT_EXPORT CharlsApiResultType CHARLS_API_CALLING_CONVENTION JpegLsEncode(
void* destination,
size_t destinationLength,
@ -27,38 +297,41 @@ CHARLS_API_IMPORT_EXPORT CharlsApiResultType CHARLS_API_CALLING_CONVENTION JpegL
const void* source,
size_t sourceLength,
const struct JlsParameters* params,
const void* reserved);
char* errorMessage);
/// <summary>
/// Retrieves the JPEG-LS header. This info can be used to pre-allocate the uncompressed output buffer.
/// </summary>
/// <remarks>This method will be removed in the next major update.</remarks>
/// <param name="source">Byte array that holds the JPEG-LS encoded data of which the header should be extracted.</param>
/// <param name="sourceLength">Length of the array in bytes.</param>
/// <param name="params">Parameter object that describes how the pixel data is encoded.</param>
/// <param name="reserved">Reserved, pass NULL pointer (will be removed in next ABI update).</param>
/// <param name="errorMessage">Character array of at least 256 characters or NULL. Hold the error message when a failure occurs, empty otherwise.</param>
CHARLS_API_IMPORT_EXPORT CharlsApiResultType CHARLS_API_CALLING_CONVENTION JpegLsReadHeader(
const void* source,
size_t sourceLength,
struct JlsParameters* params,
const void* reserved);
char* errorMessage);
/// <summary>
/// Encodes a JPEG-LS encoded byte array to uncompressed pixel data byte array.
/// </summary>
/// <remarks>This method will be removed in the next major update.</remarks>
/// <param name="destination">Byte array that holds the uncompressed pixel data bytes when the function returns.</param>
/// <param name="destinationLength">Length of the array in bytes. If the array is too small the function will return an error.</param>
/// <param name="source">Byte array that holds the JPEG-LS encoded data that should be decoded.</param>
/// <param name="sourceLength">Length of the array in bytes.</param>
/// <param name="params">Parameter object that describes the pixel data and how to decode it.</param>
/// <param name="reserved">Reserved, pass NULL pointer (will be removed in next ABI update).</param>
/// <param name="errorMessage">Character array of at least 256 characters or NULL. Hold the error message when a failure occurs, empty otherwise.</param>
CHARLS_API_IMPORT_EXPORT CharlsApiResultType CHARLS_API_CALLING_CONVENTION JpegLsDecode(
void* destination,
size_t destinationLength,
const void* source,
size_t sourceLength,
const struct JlsParameters* params,
const void* reserved);
char* errorMessage);
/// <remarks>This method will be removed in the next major update.</remarks>
CHARLS_API_IMPORT_EXPORT CharlsApiResultType CHARLS_API_CALLING_CONVENTION JpegLsDecodeRect(
void* destination,
size_t destinationLength,
@ -66,13 +339,394 @@ CHARLS_API_IMPORT_EXPORT CharlsApiResultType CHARLS_API_CALLING_CONVENTION JpegL
size_t sourceLength,
struct JlsRect roi,
const struct JlsParameters* params,
const void* reserved);
char* errorMessage);
#ifdef __cplusplus
}
CHARLS_API_IMPORT_EXPORT CharlsApiResultType JpegLsEncodeStream(ByteStreamInfo destination, size_t& bytesWritten, ByteStreamInfo source, const JlsParameters& params);
CHARLS_API_IMPORT_EXPORT CharlsApiResultType JpegLsDecodeStream(ByteStreamInfo destination, ByteStreamInfo source, const JlsParameters* params);
CHARLS_API_IMPORT_EXPORT CharlsApiResultType JpegLsReadHeaderStream(ByteStreamInfo source, JlsParameters* params);
} // extern "C"
namespace charls {
// WARNING: THESE CLASSES ARE NOT FINAL AND THEIR DESIGN AND API MAY CHANGE
/// <summary>
/// JPEG-LS decoder class that encapsulates the C ABI interface calls and provide a native C++ interface.
/// </summary>
class jpegls_decoder final
{
public:
jpegls_decoder() = default;
~jpegls_decoder() = default;
jpegls_decoder(const jpegls_decoder&) = delete;
jpegls_decoder(jpegls_decoder&&) noexcept = default;
jpegls_decoder& operator=(const jpegls_decoder&) = delete;
jpegls_decoder& operator=(jpegls_decoder&&) noexcept = default;
/// <summary>
/// Set the reference to a source buffer that contains the encoded JPEG-LS byte stream data.
/// This buffer needs to remain valid until the stream is fully decoded.
/// </summary>
/// <param name="source_buffer">Reference to the start of the source buffer.</param>
/// <param name="source_size_bytes">Size of the source buffer in bytes.</param>
jpegls_decoder& source(const void* source_buffer, const size_t source_size_bytes)
{
check_jpegls_errc(charls_jpegls_decoder_set_source_buffer(decoder_.get(), source_buffer, source_size_bytes));
return *this;
}
/// <summary>
/// Set the reference to a source container that contains the encoded JPEG-LS byte stream data.
/// This container needs to remain valid until the stream is fully decoded.
/// </summary>
/// <param name="source_container">A STL like container that provides the functions data() and size() and the type value_type.</param>
template<typename Container, typename ValueType = typename Container::value_type>
jpegls_decoder& source(const Container& source_container)
{
return source(source_container.data(), source_container.size() * sizeof(ValueType));
}
/// <summary>
/// Tries to read the SPIFF header from the JPEG-LS stream.
/// If a SPIFF header exists its will be returned otherwise the struct will be filled with default values.
/// The header_found parameter will be set to true if the spiff header could be read.
/// Call read_header to read the normal JPEG header afterwards.
/// </summary>
/// <param name="header_found">Output argument, will hold true if a SPIFF header could be found, otherwise false.</param>
/// <returns>The SPIFF header.</returns>
CHARLS_NO_DISCARD spiff_header read_spiff_header(bool& header_found) const
{
spiff_header header{};
int32_t found;
check_jpegls_errc(charls_jpegls_decoder_read_spiff_header(decoder_.get(), &header, &found));
header_found = static_cast<bool>(found);
return header;
}
/// <summary>
/// Reads the JPEG-LS header from the beginning of the JPEG-LS byte stream or after the SPIFF header.
/// After this function is called frame info and other info can be retrieved.
/// </summary>
jpegls_decoder& read_header()
{
check_jpegls_errc(charls_jpegls_decoder_read_header(decoder_.get()));
return *this;
}
/// <summary>
/// Returns information about the frame stored in the JPEG-LS byte stream.
/// Function can be called after read_header.
/// </summary>
/// <returns>The frame info that describes the image stored in the JPEG-LS byte stream.</returns>
CHARLS_NO_DISCARD charls::frame_info frame_info() const
{
charls::frame_info frame_info;
check_jpegls_errc(charls_jpegls_decoder_get_frame_info(decoder_.get(), &frame_info));
return frame_info;
}
/// <summary>
/// Returns the NEAR parameter that was used to encode the scan. A value of 0 means lossless.
/// </summary>
/// <param name="component">The component index for which the NEAR parameter should be retrieved.</param>
/// <returns>The value of the NEAR parameter.</returns>
CHARLS_NO_DISCARD int32_t near_lossless(int32_t component = 0) const
{
int32_t near_lossless;
check_jpegls_errc(charls_jpegls_decoder_get_near_lossless(decoder_.get(), component, &near_lossless));
return near_lossless;
}
/// <summary>
/// Returns the interleave mode that was used to encode the scan(s).
/// </summary>
/// <returns>The value of the interleave mode.</returns>
CHARLS_NO_DISCARD charls::interleave_mode interleave_mode() const
{
charls::interleave_mode interleave_mode;
check_jpegls_errc(charls_jpegls_decoder_get_interleave_mode(decoder_.get(), &interleave_mode));
return interleave_mode;
}
/// <summary>
/// Returns the preset coding parameters used to encode the first scan.
/// </summary>
/// <returns>The values of the JPEG-LS preset coding parameters.</returns>
CHARLS_NO_DISCARD jpegls_pc_parameters preset_coding_parameters() const
{
jpegls_pc_parameters preset_coding_parameters;
check_jpegls_errc(charls_jpegls_decoder_get_preset_coding_parameters(decoder_.get(), 0, &preset_coding_parameters));
return preset_coding_parameters;
}
/// <summary>
/// Returns the size required for the destination buffer in bytes to hold the decoded pixel data.
/// Function can be read_header.
/// </summary>
/// <returns>The required size in bytes of the destination buffer.</returns>
CHARLS_NO_DISCARD size_t destination_size() const
{
size_t size_in_bytes;
check_jpegls_errc(charls_jpegls_decoder_get_destination_size(decoder_.get(), &size_in_bytes));
return size_in_bytes;
}
/// <summary>
/// Will decode the JPEG-LS byte stream set with source into the destination buffer.
/// </summary>
/// <param name="destination_buffer">Byte array that holds the encoded bytes when the function returns.</param>
/// <param name="destination_size_bytes">Length of the array in bytes. If the array is too small the function will return an error.</param>
/// <param name="stride">Number of bytes to the next line in the buffer, when zero, decoder will compute it.</param>
void decode(void* destination_buffer, const size_t destination_size_bytes, const uint32_t stride = 0) const
{
check_jpegls_errc(charls_jpegls_decoder_decode_to_buffer(decoder_.get(), destination_buffer, destination_size_bytes, stride));
}
/// <summary>
/// Will decode the JPEG-LS byte stream set with source into the destination container.
/// </summary>
/// <param name="destination_container">A STL like container that provides the functions data() and size() and the type value_type.</param>
/// <param name="stride">Number of bytes to the next line in the buffer, when zero, decoder will compute it.</param>
template<typename Container, typename ValueType = typename Container::value_type>
void decode(Container& destination_container, const uint32_t stride = 0)
{
decode(destination_container.data(), destination_container.size() * sizeof(ValueType), stride);
}
private:
CHARLS_NO_DISCARD static charls_jpegls_decoder* create_decoder()
{
charls_jpegls_decoder* decoder = charls_jpegls_decoder_create();
if (!decoder)
throw std::bad_alloc();
return decoder;
}
static void destroy_decoder(const charls_jpegls_decoder* decoder) noexcept
{
charls_jpegls_decoder_destroy(decoder);
}
std::unique_ptr<charls_jpegls_decoder, void (*)(const charls_jpegls_decoder*)> decoder_{create_decoder(), destroy_decoder};
};
/// <summary>
/// JPEG-LS encoder class that encapsulates the C ABI interface calls and provide a native C++ interface.
/// </summary>
class jpegls_encoder final
{
public:
jpegls_encoder() = default;
~jpegls_encoder() = default;
jpegls_encoder(const jpegls_encoder&) = delete;
jpegls_encoder(jpegls_encoder&&) noexcept = default;
jpegls_encoder& operator=(const jpegls_encoder&) = delete;
jpegls_encoder& operator=(jpegls_encoder&&) noexcept = default;
/// <summary>
/// Configures the frame that needs to be encoded.
/// This information will be written to the Start of Frame (SOF) segment during the encode phase.
/// </summary>
/// <param name="frame_info">Information about the frame that needs to be encoded.</param>
jpegls_encoder& frame_info(const frame_info& frame_info)
{
check_jpegls_errc(charls_jpegls_encoder_set_frame_info(encoder_.get(), &frame_info));
return *this;
}
/// <summary>
/// Configures the NEAR parameter the encoder should use. A value of 0 means lossless, this is also the default.
/// </summary>
/// <param name="near_lossless">Value of the NEAR parameter.</param>
jpegls_encoder& near_lossless(const int32_t near_lossless)
{
check_jpegls_errc(charls_jpegls_encoder_set_near_lossless(encoder_.get(), near_lossless));
return *this;
}
/// <summary>
/// Configures the interleave mode the encoder should use. The default is none.
/// The encoder expects the input buffer in the same format as the interleave mode.
/// </summary>
/// <param name="interleave_mode">Value of the interleave mode.</param>
jpegls_encoder& interleave_mode(const interleave_mode interleave_mode)
{
check_jpegls_errc(charls_jpegls_encoder_set_interleave_mode(encoder_.get(), interleave_mode));
return *this;
}
/// <summary>
/// Configures the preset coding parameters the encoder should use.
/// If not set the encoder will use the default preset coding parameters as defined by the JPEG-LS standard.
/// Only when the coding parameters are different then the default parameters, they will be written to the
/// JPEG-LS stream during the encode phase.
/// </summary>
/// <param name="preset_coding_parameters">Reference to the preset coding parameters.</param>
jpegls_encoder& preset_coding_parameters(const jpegls_pc_parameters& preset_coding_parameters)
{
check_jpegls_errc(charls_jpegls_encoder_set_preset_coding_parameters(encoder_.get(), &preset_coding_parameters));
return *this;
}
/// <summary>
/// Configures the HP color transformation the encoder should use.
/// If not set the encoder will use no color transformation.
/// Color transformations are a HP extension and not defined by the JPEG-LS standard and can only be set for 3 component encodings.
/// </summary>
/// <param name="color_transformation">The color transformation parameters.</param>
jpegls_encoder& color_transformation(const color_transformation color_transformation)
{
check_jpegls_errc(charls_jpegls_encoder_set_color_transformation(encoder_.get(), color_transformation));
return *this;
}
/// <summary>
/// Returns the size in bytes, that the encoder expects are needed to hold the encoded image.
/// </summary>
/// <returns>The estimated size in bytes needed to hold the encoded image.</returns>
CHARLS_NO_DISCARD size_t estimated_destination_size() const
{
size_t size_in_bytes;
check_jpegls_errc(charls_jpegls_encoder_get_estimated_destination_size(encoder_.get(), &size_in_bytes));
return size_in_bytes;
}
/// <summary>
/// Set the reference to the destination buffer that will contain the encoded JPEG-LS byte stream data after encoding.
/// This buffer needs to remain valid during the encoding process.
/// </summary>
/// <param name="destination_buffer">Reference to the start of the destination buffer.</param>
/// <param name="destination_size_bytes">Size of the destination buffer in bytes.</param>
jpegls_encoder& destination(void* destination_buffer, const size_t destination_size_bytes)
{
check_jpegls_errc(charls_jpegls_encoder_set_destination_buffer(encoder_.get(), destination_buffer, destination_size_bytes));
return *this;
}
/// <summary>
/// Set the the container that will contain the encoded JPEG-LS byte stream data after encoding.
/// This container needs to remain valid during the encoding process.
/// </summary>
/// <param name="destination_container">The STL like container, that supports the functions data() and size() and the typedef value_type.</param>
template<typename Container, typename ValueType = typename Container::value_type>
jpegls_encoder& destination(Container& destination_container)
{
return destination(destination_container.data(), destination_container.size() * sizeof(ValueType));
}
/// <summary>
/// Writes a standard SPIFF header to the destination. The additional values are computed from the current encoder settings.
/// </summary>
/// <param name="color_space">The color space of the image.</param>
/// <param name="resolution_units">The resolution units of the next 2 parameters.</param>
/// <param name="vertical_resolution">The vertical resolution.</param>
/// <param name="horizontal_resolution">The horizontal resolution.</param>
jpegls_encoder& write_standard_spiff_header(const spiff_color_space color_space,
const spiff_resolution_units resolution_units = spiff_resolution_units::aspect_ratio,
const uint32_t vertical_resolution = 1,
const uint32_t horizontal_resolution = 1)
{
check_jpegls_errc(charls_jpegls_encoder_write_standard_spiff_header(encoder_.get(), color_space, resolution_units, vertical_resolution, horizontal_resolution));
return *this;
}
/// <summary>
/// Writes a SPIFF header to the destination.
/// </summary>
/// <param name="header">Reference to a SPIFF header that will be written to the destination.</param>
jpegls_encoder& write_spiff_header(const spiff_header& header)
{
check_jpegls_errc(charls_jpegls_encoder_write_spiff_header(encoder_.get(), &header));
return *this;
}
/// <summary>
/// Writes a SPIFF directory entry to the destination.
/// </summary>
/// <param name="entry_tag">The entry tag of the directory entry.</param>
/// <param name="entry_data">The entry data of the directory entry.</param>
/// <param name="entry_data_size">The size in bytes of the directory entry [0-65528].</param>
template<typename IntDerivedType>
jpegls_encoder& write_spiff_entry(const IntDerivedType entry_tag, const void* entry_data, const size_t entry_data_size)
{
check_jpegls_errc(charls_jpegls_encoder_write_spiff_entry(encoder_.get(), static_cast<uint32_t>(entry_tag), entry_data, entry_data_size));
return *this;
}
/// <summary>
/// Encodes the passed buffer with the source image data to the destination.
/// </summary>
/// <param name="source_buffer">Byte array that holds the image data that needs to be encoded.</param>
/// <param name="source_size_bytes">Length of the array in bytes.</param>
/// <param name="stride">
/// The number of bytes from one row of pixels in memory to the next row of pixels in memory.
/// Stride is sometimes called pitch. If padding bytes are present, the stride is wider than the width of the image.
/// </param>
/// <returns>The number of bytes written to the destination.</returns>
size_t encode(const void* source_buffer, const size_t source_size_bytes, const uint32_t stride = 0) const
{
check_jpegls_errc(charls_jpegls_encoder_encode_from_buffer(encoder_.get(), source_buffer, source_size_bytes, stride));
return bytes_written();
}
/// <summary>
/// Encodes the passed STL like container with the source image data to the destination.
/// </summary>
/// <param name="source_container">Container that holds the image data that needs to be encoded.</param>
/// <param name="stride">
/// The number of bytes from one row of pixels in memory to the next row of pixels in memory.
/// Stride is sometimes called pitch. If padding bytes are present, the stride is wider than the width of the image.
/// </param>
/// <returns>The number of bytes written to the destination.</returns>
template<typename Container, typename ValueType = typename Container::value_type>
size_t encode(const Container& source_container, const uint32_t stride = 0) const
{
return encode(source_container.data(), source_container.size() * sizeof(ValueType), stride);
}
/// <summary>
/// Returns the size in bytes, that are written to the destination.
/// </summary>
/// <returns>The bytes written.</returns>
CHARLS_NO_DISCARD size_t bytes_written() const
{
size_t bytes_written;
check_jpegls_errc(charls_jpegls_encoder_get_bytes_written(encoder_.get(), &bytes_written));
return bytes_written;
}
private:
CHARLS_NO_DISCARD static charls_jpegls_encoder* create_encoder()
{
charls_jpegls_encoder* encoder = charls_jpegls_encoder_create();
if (!encoder)
throw std::bad_alloc();
return encoder;
}
static void destroy_encoder(const charls_jpegls_encoder* encoder) noexcept
{
charls_jpegls_encoder_destroy(encoder);
}
std::unique_ptr<charls_jpegls_encoder, void (*)(const charls_jpegls_encoder*)> encoder_{create_encoder(), destroy_encoder};
};
} // namespace charls
#endif
// Undefine CHARLS macros to prevent global namespace pollution
#if !defined(CHARLS_LIBRARY_BUILD)
#undef CHARLS_API_IMPORT_EXPORT
#undef CHARLS_NO_DISCARD
#undef CHARLS_ENUM_DEPRECATED
#undef CHARLS_FINAL
#undef CHARLS_NOEXCEPT
#endif

View File

@ -0,0 +1,35 @@
// Copyright (c) Team CharLS. All rights reserved. See the accompanying "LICENSE.md" for licensed use.
#pragma once
#include "public_types.h"
#include <iostream>
//
// ByteStreamInfo & FromByteArray helper function
//
// ByteStreamInfo describes the stream: either set rawStream to a valid stream, or rawData/count, not both.
// it's possible to decode to memory streams, but using rawData will always be faster.
struct ByteStreamInfo final
{
std::basic_streambuf<char>* rawStream;
uint8_t* rawData;
std::size_t count;
};
inline ByteStreamInfo FromByteArray(void* bytes, std::size_t count) noexcept
{
return {nullptr, static_cast<uint8_t*>(bytes), count};
}
inline ByteStreamInfo FromByteArrayConst(const void* bytes, std::size_t count) noexcept
{
return FromByteArray(const_cast<void*>(bytes), count);
}
CHARLS_API_IMPORT_EXPORT charls::jpegls_errc JpegLsEncodeStream(ByteStreamInfo destination, size_t& bytesWritten, ByteStreamInfo source, const JlsParameters& params);
CHARLS_API_IMPORT_EXPORT charls::jpegls_errc JpegLsDecodeStream(ByteStreamInfo destination, ByteStreamInfo source, const JlsParameters* params);
CHARLS_API_IMPORT_EXPORT charls::jpegls_errc JpegLsReadHeaderStream(ByteStreamInfo source, JlsParameters* params);

View File

@ -1,78 +0,0 @@
#pragma once
#include "charls.h"
#include <vector>
#include <cstddef>
#include <filesystem>
// WARNING: THESE CLASSES ARE NOT FINAL AND THEIR DESIGN AND API MAY CHANGE
#if __cplusplus >= 201703L
namespace charls {
struct metadata_info_t
{
int32_t width;
int32_t height;
int32_t bits_per_sample;
int32_t component_count;
};
class jpegls_decoder final
{
public:
void read_header(const void* source, const size_t source_size_bytes)
{
std::error_code ec;
read_header(source, source_size_bytes, ec);
if (ec)
throw jpegls_error(ec);
}
void read_header(const void* source, const size_t source_size_bytes, std::error_code& error) noexcept
{
error = JpegLsReadHeader(source, source_size_bytes, &params_, nullptr);
if (error)
return;
source_ = source;
source_size_bytes_ = source_size_bytes;
metadata_ = { params_.width, params_.height, params_.bitsPerSample, params_.components };
}
const metadata_info_t& metadata_info() const noexcept
{
return metadata_;
}
void decode(void* destination, const size_t destination_size_bytes) const
{
std::error_code error;
decode(destination, destination_size_bytes, error);
if (error)
throw jpegls_error(error);
}
void decode(void* destination, const size_t destination_size_bytes, std::error_code& error) const noexcept
{
error = JpegLsDecode(destination, destination_size_bytes, source_, source_size_bytes_, &params_, nullptr);
}
size_t required_size() const noexcept
{
return static_cast<size_t>(params_.width) * params_.height * params_.components * (params_.bitsPerSample <= 8 ? 1 : 2);
}
private:
const void* source_{};
size_t source_size_bytes_{};
JlsParameters params_{};
metadata_info_t metadata_{};
};
}
#endif // __cplusplus

View File

@ -1,93 +0,0 @@
#pragma once
#include "charls.h"
#include <vector>
#include <cstddef>
// WARNING: THESE CLASSES ARE NOT FINAL AND THEIR DESIGN AND API MAY CHANGE
#if __cplusplus >= 201703L
namespace charls {
struct metadata
{
int32_t width;
int32_t height;
int32_t bits_per_sample;
int32_t component_count;
};
class jpegls_encoder final
{
public:
void source(const void* source, size_t source_size_bytes, const metadata& metadata) noexcept
{
source_ = source,
source_size_bytes_ = source_size_bytes;
metadata_ = metadata;
}
void interleave_mode(InterleaveMode interleave_mode) noexcept
{
interleave_mode_ = interleave_mode;
}
void allowed_lossy_error(int value) noexcept
{
allowed_lossy_error_ = value;
}
std::vector<std::byte> encode()
{
// Assume that compressed pixels are smaller or equal to uncompressed pixels and reserve some room for JPEG header.
const size_t encoded_buffer_size = source_size_bytes_ + 1024;
std::vector<std::byte> buffer(encoded_buffer_size);
buffer.resize(encode(buffer.data(), buffer.size()));
return buffer;
}
size_t encode(void* destination, const size_t destination_size_bytes)
{
std::error_code error;
const size_t final_size = encode(destination, destination_size_bytes, error);
if (error)
throw jpegls_error(error);
return final_size;
}
size_t encode(void* destination, const size_t destination_size_bytes, std::error_code& error) noexcept
{
size_t bytes_written;
JlsParameters parameters
{
metadata_.width,
metadata_.height,
metadata_.bits_per_sample,
0,
metadata_.component_count,
allowed_lossy_error_,
interleave_mode_
};
error = JpegLsEncode(destination, destination_size_bytes, &bytes_written,
source_, source_size_bytes_, &parameters, nullptr);
return bytes_written;
}
private:
InterleaveMode interleave_mode_{InterleaveMode::None};
int allowed_lossy_error_{};
const void* source_{};
size_t source_size_bytes_{};
metadata metadata_{};
};
} // namespace charls
#endif // __cplusplus

View File

@ -3,25 +3,25 @@
#pragma once
#include "public_types.h"
#include "api_abi.h"
#ifdef __cplusplus
#include <system_error>
extern "C" {
#endif
CHARLS_API_IMPORT_EXPORT const void* CHARLS_API_CALLING_CONVENTION charls_jpegls_category(void);
CHARLS_API_IMPORT_EXPORT const void* CHARLS_API_CALLING_CONVENTION charls_get_jpegls_category(void);
CHARLS_API_IMPORT_EXPORT const char* CHARLS_API_CALLING_CONVENTION charls_get_error_message(int32_t error_value);
#ifdef __cplusplus
}
#include <system_error>
namespace charls {
CHARLS_NO_DISCARD inline const std::error_category& jpegls_category() noexcept
{
return *static_cast<const std::error_category*>(charls_jpegls_category());
return *static_cast<const std::error_category*>(charls_get_jpegls_category());
}
CHARLS_NO_DISCARD inline std::error_code make_error_code(jpegls_errc error_value) noexcept
@ -33,7 +33,7 @@ CHARLS_NO_DISCARD inline std::error_code make_error_code(jpegls_errc error_value
/// <summary>
/// Exception that will be thrown when a called charls method cannot succeed and is allowed to throw.
/// </summary>
class jpegls_error : public std::system_error
class jpegls_error final : public std::system_error
{
public:
explicit jpegls_error(std::error_code ec)
@ -47,6 +47,12 @@ public:
}
};
inline void check_jpegls_errc(const std::error_code ec)
{
if (ec)
throw jpegls_error(ec);
}
} // namespace charls
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,6 @@
// Copyright (c) Team CharLS. All rights reserved. See the accompanying "LICENSE.md" for licensed use.
#include <charls/charls.h>
#include <charls/jpegls_error.h>
#include <stdlib.h>
#include <stdint.h>
@ -93,34 +92,76 @@ static void *bmp_read_pixel_data(FILE *fp, uint32_t offset, const bmp_dib_header
return buffer;
}
static void* handle_encoder_failure(charls_jpegls_errc error, const char *step, charls_jpegls_encoder* encoder, void* buffer)
{
printf("Failed to %s: %i, %s\n", step, error, charls_get_error_message(error));
charls_jpegls_encoder_destroy(encoder);
free(buffer);
return NULL;
}
static void* encode_bmp_to_jpegls(const void* pixel_data, size_t pixel_data_size, const bmp_dib_header_t* header, int allowed_lossy_error, size_t* bytes_written)
{
assert(header->depth == 24); // This function only supports 24-bit BMP pixel data.
assert(header->compress_type == 0); // Data needs to be stored by pixel as RGB.
struct JlsParameters params =
charls_jpegls_encoder* encoder = charls_jpegls_encoder_create();
if (!encoder)
{
.interleaveMode = CHARLS_IM_SAMPLE,
.bitsPerSample = 8,
.components = 3
};
params.allowedLossyError = allowed_lossy_error;
params.width = header->width;
params.height = header->height;
// Assume that compressed pixels are smaller or equal to uncompressed pixels and reserve some room for JPEG header.
const size_t encoded_buffer_size = pixel_data_size + 1024;
void *encoded_buffer = malloc(encoded_buffer_size);
const int error_value = JpegLsEncode(encoded_buffer, encoded_buffer_size, bytes_written, pixel_data, pixel_data_size, &params, NULL);
if (error_value)
{
printf("Failed to encode pixel data: %i, %s\n", error_value, charls_get_error_message(error_value));
free(encoded_buffer);
encoded_buffer = NULL;
printf("Failed to create JPEG-LS encoder\n");
return NULL;
}
charls_frame_info frame_info = { .bits_per_sample = 8, .component_count = 3 };
frame_info.width = header->width;
frame_info.height = header->height;
charls_jpegls_errc error = charls_jpegls_encoder_set_frame_info(encoder, &frame_info);
if (error)
{
return handle_encoder_failure(error, "set frame_info", encoder, NULL);
}
error = charls_jpegls_encoder_set_near_lossless(encoder, allowed_lossy_error);
if (error)
{
return handle_encoder_failure(error, "set near lossless", encoder, NULL);
}
error = charls_jpegls_encoder_set_interleave_mode(encoder, CHARLS_INTERLEAVE_MODE_SAMPLE);
if (error)
{
return handle_encoder_failure(error, "set near interleave mode", encoder, NULL);
}
size_t encoded_buffer_size;
error = charls_jpegls_encoder_get_estimated_destination_size(encoder, &encoded_buffer_size);
if (error)
{
return handle_encoder_failure(error, "get estimated destination size", encoder, NULL);
}
void* encoded_buffer = malloc(encoded_buffer_size);
error = charls_jpegls_encoder_set_destination_buffer(encoder, encoded_buffer, encoded_buffer_size);
if (error)
{
return handle_encoder_failure(error, "set destination buffer", encoder, encoded_buffer);
}
error = charls_jpegls_encoder_encode_from_buffer(encoder, pixel_data, pixel_data_size, 0);
if (error)
{
return handle_encoder_failure(error, "encode", encoder, encoded_buffer);
}
error = charls_jpegls_encoder_get_bytes_written(encoder, bytes_written);
if (error)
{
return handle_encoder_failure(error, "get bytes written", encoder, encoded_buffer);
}
charls_jpegls_encoder_destroy(encoder);
return encoded_buffer;
}

View File

@ -2,9 +2,9 @@
#pragma once
#include <vector>
#include <fstream>
#include <cstddef>
#include <fstream>
#include <vector>
class bmp_image final
{
@ -57,7 +57,7 @@ public:
bmp_header header;
bmp_dib_header dib_header;
std::vector<std::byte> pixel_data;
std::vector<uint8_t> pixel_data;
private:
static bmp_header read_bmp_header(std::istream& input)

View File

@ -112,7 +112,6 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -126,7 +125,6 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -140,7 +138,6 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -154,7 +151,6 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -170,7 +166,6 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -188,7 +183,6 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>

View File

@ -4,32 +4,40 @@
#include "bmp_image.h"
#include <charls/jpegls_encoder.h>
#include <charls/charls.h>
#include <cassert>
#include <charconv>
#include <iostream>
#include <vector>
using std::cerr;
using std::exception;
using std::ofstream;
using std::ios;
using std::ofstream;
using std::vector;
using std::byte;
using charls::jpegls_encoder;
using charls::metadata;
using namespace charls;
namespace {
vector<byte> encode_bmp_image_to_jpegls(const bmp_image& image, int allowed_lossy_error)
vector<uint8_t> encode_bmp_image_to_jpegls(const bmp_image& image, int near_lossless)
{
assert(image.dib_header.depth == 24); // This function only supports 24-bit BMP pixel data.
assert(image.dib_header.compress_type == 0); // Data needs to be stored by pixel as RGB.
jpegls_encoder encoder;
encoder.source(image.pixel_data.data(), image.pixel_data.size(),
metadata{static_cast<int32_t>(image.dib_header.width), static_cast<int32_t>(image.dib_header.height), 8, 3});
encoder.allowed_lossy_error(allowed_lossy_error);
encoder.frame_info({image.dib_header.width, image.dib_header.height, 8, 3})
.near_lossless(near_lossless);
return encoder.encode();
vector<uint8_t> buffer(encoder.estimated_destination_size());
encoder.destination(buffer);
encoder.write_standard_spiff_header(spiff_color_space::rgb);
const size_t encoded_size = encoder.encode(image.pixel_data);
buffer.resize(encoded_size);
return buffer;
}
void save_buffer_to_file(const void* buffer, size_t buffer_size, const char* filename)
@ -42,38 +50,33 @@ void save_buffer_to_file(const void* buffer, size_t buffer_size, const char* fil
output.write(static_cast<const char*>(buffer), buffer_size);
}
int from_chars(const char* argument) noexcept
} // namespace
int main(const int argc, char const* const argv[])
{
int value;
std::from_chars(argument, argument + strlen(argument), value);
return value;
}
ios::sync_with_stdio(false);
} // nameless namespace.
int main(const int argc, char const * const argv[])
{
try
{
if (argc < 3)
{
cerr << "Usage: <input_file_name> <output_file_name> [allowed_lossy_error, default=0 (lossless)]\n";
cerr << "Usage: <input_file_name> <output_file_name> [near-lossless-value, default=0 (lossless)]\n";
return EXIT_FAILURE;
}
int allowed_lossy_error = 0;
int near_lossless{};
if (argc > 3)
{
allowed_lossy_error = from_chars(argv[3]);
if (allowed_lossy_error < 0 || allowed_lossy_error > 255)
near_lossless = strtol(argv[3], nullptr, 10);
if (near_lossless < 0 || near_lossless > 255)
{
cerr << "allowed_lossy_error needs to be in the range [0,255]\n";
cerr << "near-lossless-value needs to be in the range [0,255]\n";
return EXIT_FAILURE;
}
}
auto encoded_buffer = encode_bmp_image_to_jpegls(bmp_image{argv[1]}, allowed_lossy_error);
auto encoded_buffer = encode_bmp_image_to_jpegls(bmp_image{argv[1]}, near_lossless);
save_buffer_to_file(encoded_buffer.data(), encoded_buffer.size(), argv[2]);
return EXIT_SUCCESS;
@ -88,4 +91,4 @@ int main(const int argc, char const * const argv[])
}
return EXIT_FAILURE;
}
}

View File

@ -2,9 +2,8 @@
#pragma once
#include <charls/jpegls_encoder.h>
#include <charls/charls.h>
#include <cassert>
#include <iostream>
#include <vector>
#include <fstream>
#include <charconv>

View File

@ -27,9 +27,12 @@ target_sources(charls
PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/include/charls/api_abi.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/charls/charls.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/charls/charls_legacy.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/charls/jpegls_error.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/charls/public_types.h"
PRIVATE
"${CMAKE_CURRENT_LIST_DIR}/charls_jpegls_decoder.cpp"
"${CMAKE_CURRENT_LIST_DIR}/charls_jpegls_encoder.cpp"
"${CMAKE_CURRENT_LIST_DIR}/color_transform.h"
"${CMAKE_CURRENT_LIST_DIR}/constants.h"
"${CMAKE_CURRENT_LIST_DIR}/context.h"

View File

@ -108,7 +108,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>CHARLS_LIBRARY_BUILD;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BufferSecurityCheck>true</BufferSecurityCheck>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>
@ -130,7 +129,6 @@
<PreprocessorDefinitions>CHARLS_LIBRARY_BUILD;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<SmallerTypeCheck>false</SmallerTypeCheck>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BufferSecurityCheck>true</BufferSecurityCheck>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>
@ -156,7 +154,6 @@
<PreprocessorDefinitions>CHARLS_LIBRARY_BUILD;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>true</StringPooling>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>
</PrecompiledHeader>
@ -178,7 +175,6 @@
<PreprocessorDefinitions>CHARLS_LIBRARY_BUILD;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<SmallerTypeCheck>false</SmallerTypeCheck>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BufferSecurityCheck>true</BufferSecurityCheck>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>
@ -199,7 +195,6 @@
<PreprocessorDefinitions>CHARLS_LIBRARY_BUILD;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<SmallerTypeCheck>false</SmallerTypeCheck>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BufferSecurityCheck>true</BufferSecurityCheck>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>
@ -225,7 +220,6 @@
<PreprocessorDefinitions>CHARLS_LIBRARY_BUILD;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>true</StringPooling>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<BufferSecurityCheck>false</BufferSecurityCheck>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>
@ -245,8 +239,10 @@
<None Include="JpegStreamWriter.cd" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="charls_jpegls_decoder.cpp" />
<ClCompile Include="interface.cpp" />
<ClCompile Include="jpegls.cpp" />
<ClCompile Include="charls_jpegls_encoder.cpp" />
<ClCompile Include="jpegls_error.cpp" />
<ClCompile Include="jpeg_stream_reader.cpp" />
<ClCompile Include="jpeg_stream_writer.cpp" />
@ -254,8 +250,7 @@
<ItemGroup>
<ClInclude Include="..\include\charls\api_abi.h" />
<ClInclude Include="..\include\charls\charls.h" />
<ClInclude Include="..\include\charls\jpegls_decoder.h" />
<ClInclude Include="..\include\charls\jpegls_encoder.h" />
<ClInclude Include="..\include\charls\charls_legacy.h" />
<ClInclude Include="..\include\charls\jpegls_error.h" />
<ClInclude Include="..\include\charls\public_types.h" />
<ClInclude Include="color_transform.h" />

View File

@ -16,6 +16,12 @@
<ClCompile Include="jpeg_stream_writer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="charls_jpegls_encoder.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="charls_jpegls_decoder.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="context.h">
@ -84,10 +90,7 @@
<ClInclude Include="jpegls_preset_parameters_type.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\charls\jpegls_decoder.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\charls\jpegls_encoder.h">
<ClInclude Include="..\include\charls\charls_legacy.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>

View File

@ -6,6 +6,4 @@ EXPORTS
JpegLsReadHeader
JpegLsEncodeStream
JpegLsDecodeStream
JpegLsReadHeaderStream
charls_jpegls_category
charls_get_error_message
JpegLsReadHeaderStream

View File

@ -0,0 +1,388 @@
// Copyright (c) Team CharLS. All rights reserved. See the accompanying "LICENSE.md" for licensed use.
#include <charls/charls.h>
#include "jpeg_stream_reader.h"
#include "util.h"
#include <cassert>
#include <memory>
#include <new>
using std::unique_ptr;
using namespace charls;
struct charls_jpegls_decoder final
{
void source(const void* source_buffer, size_t source_size_bytes)
{
if (state_ != state::initial)
throw jpegls_error{jpegls_errc::invalid_operation};
source_buffer_ = source_buffer;
size_ = source_size_bytes;
ByteStreamInfo source{FromByteArrayConst(source_buffer_, size_)};
reader_ = std::make_unique<JpegStreamReader>(source);
state_ = state::source_set;
}
bool read_header(spiff_header* spiff_header)
{
if (state_ != state::source_set)
throw jpegls_error{jpegls_errc::invalid_operation};
bool spiff_header_found{};
reader_->ReadHeader(spiff_header, &spiff_header_found);
state_ = state::spiff_header_read;
return spiff_header_found;
}
void read_header()
{
if (state_ >= state::header_read || state_ == state::initial)
throw jpegls_error{jpegls_errc::invalid_operation};
reader_->ReadHeader();
reader_->ReadStartOfScan(true);
state_ = state::header_read;
}
charls::frame_info frame_info() const
{
if (state_ < state::header_read)
throw jpegls_error{jpegls_errc::invalid_operation};
const auto& metadata = reader_->GetMetadata();
return {static_cast<uint32_t>(metadata.width), static_cast<uint32_t>(metadata.height), metadata.bitsPerSample, metadata.components};
}
int32_t near_lossless(int32_t /*component*/) const
{
if (state_ < state::header_read)
throw jpegls_error{jpegls_errc::invalid_operation};
// Note: The JPEG-LS standard allows to define different NEAR parameter for every scan.
const auto& metadata = reader_->GetMetadata();
return metadata.allowedLossyError;
}
charls::interleave_mode interleave_mode() const
{
if (state_ < state::header_read)
throw jpegls_error{jpegls_errc::invalid_operation};
// Note: The JPEG-LS standard allows to define different interleave modes for every scan.
// CharLS doesn't support mixed interleave modes, first scan determines the mode.
const auto& metadata = reader_->GetMetadata();
return metadata.interleaveMode;
}
jpegls_pc_parameters preset_coding_parameters() const
{
if (state_ < state::header_read)
throw jpegls_error{jpegls_errc::invalid_operation};
const auto& preset_parameters = reader_->GetCustomPreset();
jpegls_pc_parameters preset_coding_parameters;
preset_coding_parameters.maximum_sample_value = preset_parameters.MaximumSampleValue;
preset_coding_parameters.reset_value = preset_parameters.ResetValue;
preset_coding_parameters.threshold1 = preset_parameters.Threshold1;
preset_coding_parameters.threshold2 = preset_parameters.Threshold2;
preset_coding_parameters.threshold3 = preset_parameters.Threshold3;
return preset_coding_parameters;
}
size_t destination_size() const
{
const charls::frame_info info{frame_info()};
return static_cast<size_t>(info.width) * info.height * info.component_count * (info.bits_per_sample <= 8 ? 1 : 2);
}
void decode(void* destination_buffer, size_t destination_size_bytes, uint32_t stride) const
{
if (state_ != state::header_read)
throw jpegls_error{jpegls_errc::invalid_operation};
if (stride != 0)
{
reader_->GetMetadata().stride = static_cast<int32_t>(stride);
}
const ByteStreamInfo destination = FromByteArray(destination_buffer, destination_size_bytes);
reader_->Read(destination);
}
void output_bgr(char value) const noexcept
{
reader_->SetOutputBgr(value);
}
const JlsParameters& metadata() const noexcept
{
return reader_->GetMetadata();
}
void region(const JlsRect& rect) const noexcept
{
reader_->SetRect(rect);
}
private:
enum class state
{
initial,
source_set,
spiff_header_read,
header_read,
completed,
};
state state_{};
unique_ptr<JpegStreamReader> reader_;
const void* source_buffer_{};
size_t size_{};
};
extern "C" {
#if defined _MSC_VER && _MSC_VER < 1917
#pragma warning(disable : 26447) // Function is declared 'noexcept' but calls function '' which may throw exceptions (f.6). [false warning in VS 2017]
#endif
charls_jpegls_decoder* CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_create() noexcept
{
MSVC_WARNING_SUPPRESS(26402 26409) // don't use new and delete + scoped object and move
return new (std::nothrow) charls_jpegls_decoder;
MSVC_WARNING_UNSUPPRESS()
}
void CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_destroy(const charls_jpegls_decoder* decoder) noexcept
{
MSVC_WARNING_SUPPRESS(26401 26409) // don't use new and delete + non-owner.
delete decoder;
MSVC_WARNING_UNSUPPRESS()
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_set_source_buffer(charls_jpegls_decoder* decoder, const void* source_buffer, size_t source_size_bytes) noexcept
try
{
if (!decoder || !source_buffer)
return jpegls_errc::invalid_argument;
decoder->source(source_buffer, source_size_bytes);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_read_spiff_header(charls_jpegls_decoder* const decoder, charls_spiff_header* spiff_header, int32_t* header_found) noexcept
try
{
if (!decoder || !spiff_header || !header_found)
return jpegls_errc::invalid_argument;
*header_found = decoder->read_header(spiff_header);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_read_header(charls_jpegls_decoder* const decoder) noexcept
try
{
if (!decoder)
return jpegls_errc::invalid_argument;
decoder->read_header();
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_get_frame_info(const charls_jpegls_decoder* const decoder, charls_frame_info* frame_info) noexcept
try
{
if (!decoder || !frame_info)
return jpegls_errc::invalid_argument;
*frame_info = decoder->frame_info();
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_get_near_lossless(const charls_jpegls_decoder* decoder, int32_t component, int32_t* near_lossless) noexcept
try
{
if (!decoder || !near_lossless)
return jpegls_errc::invalid_argument;
*near_lossless = decoder->near_lossless(component);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_get_interleave_mode(const charls_jpegls_decoder* decoder, charls_interleave_mode* interleave_mode) noexcept
try
{
if (!decoder || !interleave_mode)
return jpegls_errc::invalid_argument;
*interleave_mode = decoder->interleave_mode();
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
charls_jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_get_preset_coding_parameters(const charls_jpegls_decoder* decoder, int32_t /*reserved*/, charls_jpegls_pc_parameters* preset_coding_parameters) noexcept
try
{
if (!decoder || !preset_coding_parameters)
return jpegls_errc::invalid_argument;
*preset_coding_parameters = decoder->preset_coding_parameters();
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_get_destination_size(const struct charls_jpegls_decoder* decoder, size_t* destination_size_bytes) noexcept
try
{
if (!decoder || !destination_size_bytes)
return jpegls_errc::invalid_argument;
*destination_size_bytes = decoder->destination_size();
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_decoder_decode_to_buffer(const charls_jpegls_decoder* decoder, void* destination_buffer, size_t destination_size_bytes, uint32_t stride) noexcept
try
{
if (!decoder || !destination_buffer)
return jpegls_errc::invalid_argument;
decoder->decode(destination_buffer, destination_size_bytes, stride);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
JpegLsReadHeader(const void* source, size_t sourceLength, JlsParameters* params, char* errorMessage)
try
{
if (!source || !params)
return jpegls_errc::invalid_argument;
charls_jpegls_decoder decoder;
decoder.source(source, sourceLength);
decoder.read_header();
*params = decoder.metadata();
clear_error_message(errorMessage);
return jpegls_errc::success;
}
catch (...)
{
return set_error_message(to_jpegls_errc(), errorMessage);
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
JpegLsDecode(void* destination, size_t destinationLength, const void* source, size_t sourceLength,
const struct JlsParameters* params, char* errorMessage)
try
{
if (!destination || !source)
return jpegls_errc::invalid_argument;
charls_jpegls_decoder decoder;
decoder.source(source, sourceLength);
decoder.read_header();
int32_t stride{};
if (params)
{
decoder.output_bgr(params->outputBgr);
stride = params->stride;
}
decoder.decode(destination, destinationLength, static_cast<uint32_t>(stride));
clear_error_message(errorMessage);
return jpegls_errc::success;
}
catch (...)
{
return set_error_message(to_jpegls_errc(), errorMessage);
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
JpegLsDecodeRect(void* destination, size_t destinationLength, const void* source, size_t sourceLength,
JlsRect roi, const JlsParameters* params, char* errorMessage)
try
{
if (!destination || !source)
return jpegls_errc::invalid_argument;
charls_jpegls_decoder decoder;
decoder.source(source, sourceLength);
decoder.read_header();
int32_t stride{};
if (params)
{
decoder.output_bgr(params->outputBgr);
stride = params->stride;
}
decoder.region(roi);
decoder.decode(destination, destinationLength, static_cast<uint32_t>(stride));
clear_error_message(errorMessage);
return jpegls_errc::success;
}
catch (...)
{
return set_error_message(to_jpegls_errc(), errorMessage);
}
}

View File

@ -0,0 +1,476 @@
// Copyright (c) Team CharLS. All rights reserved. See the accompanying "LICENSE.md" for licensed use.
#include <charls/charls.h>
#include "encoder_strategy.h"
#include "jls_codec_factory.h"
#include "jpeg_stream_writer.h"
#include "jpegls_preset_coding_parameters.h"
#include "util.h"
#include <cassert>
#include <new>
using namespace charls;
using std::unique_ptr;
struct charls_jpegls_encoder final
{
charls_jpegls_encoder() = default;
void destination(void* destination, const size_t size)
{
if (state_ != state::initial)
throw jpegls_error{jpegls_errc::invalid_operation};
writer_.UpdateDestination(destination, size);
state_ = state::destination_set;
}
void frame_info(const charls_frame_info& frame_info)
{
if (frame_info.width < 1 || frame_info.width > maximum_width)
throw jpegls_error{jpegls_errc::invalid_argument_width};
if (frame_info.height < 1 || frame_info.height > maximum_height)
throw jpegls_error{jpegls_errc::invalid_argument_height};
if (frame_info.bits_per_sample < MinimumBitsPerSample || frame_info.bits_per_sample > MaximumBitsPerSample)
throw jpegls_error{jpegls_errc::invalid_argument_bits_per_sample};
if (frame_info.component_count < 1 || frame_info.component_count > MaximumComponentCount)
throw jpegls_error{jpegls_errc::invalid_argument_component_count};
frame_info_ = frame_info;
}
void interleave_mode(const charls::interleave_mode interleave_mode)
{
if (interleave_mode < charls::interleave_mode::none || interleave_mode > charls::interleave_mode::sample)
throw jpegls_error{jpegls_errc::invalid_argument_interleave_mode};
interleave_mode_ = interleave_mode;
}
void near_lossless(const int32_t near_lossless)
{
if (near_lossless < 0 || near_lossless > maximum_near_lossless)
throw jpegls_error{jpegls_errc::invalid_argument_near_lossless};
near_lossless_ = near_lossless;
}
void preset_coding_parameters(const jpegls_pc_parameters& preset_coding_parameters)
{
if (!is_valid(preset_coding_parameters, UINT16_MAX, near_lossless_))
throw jpegls_error{jpegls_errc::invalid_argument_pc_parameters};
custom_preset_coding_parameters_.MaximumSampleValue = preset_coding_parameters.maximum_sample_value;
custom_preset_coding_parameters_.ResetValue = preset_coding_parameters.reset_value;
custom_preset_coding_parameters_.Threshold1 = preset_coding_parameters.threshold1;
custom_preset_coding_parameters_.Threshold2 = preset_coding_parameters.threshold2;
custom_preset_coding_parameters_.Threshold3 = preset_coding_parameters.threshold3;
}
void color_transformation(const color_transformation color_transformation)
{
if (color_transformation < charls::color_transformation::none || color_transformation > charls::color_transformation::hp3)
throw jpegls_error{jpegls_errc::invalid_argument_color_transformation};
color_transformation_ = color_transformation;
}
size_t estimated_destination_size() const
{
if (!is_frame_info_configured())
throw jpegls_error{jpegls_errc::invalid_operation};
return static_cast<size_t>(frame_info_.height) * frame_info_.height * frame_info_.component_count * (frame_info_.bits_per_sample < 9 ? 1 : 2) + 1024;
}
void write_spiff_header(const spiff_header& spiff_header)
{
if (spiff_header.height == 0)
throw jpegls_error{jpegls_errc::invalid_argument_height};
if (spiff_header.width == 0)
throw jpegls_error{jpegls_errc::invalid_argument_width};
if (state_ != state::destination_set)
throw jpegls_error{jpegls_errc::invalid_operation};
writer_.WriteSpiffHeaderSegment(spiff_header);
state_ = state::spiff_header;
}
void write_standard_spiff_header(spiff_color_space color_space, const spiff_resolution_units resolution_units, const uint32_t vertical_resolution, const uint32_t horizontal_resolution)
{
if (!is_frame_info_configured())
throw jpegls_error{jpegls_errc::invalid_operation};
write_spiff_header({spiff_profile_id::none,
frame_info_.component_count,
frame_info_.height,
frame_info_.width,
color_space,
frame_info_.bits_per_sample,
spiff_compression_type::jpeg_ls,
resolution_units,
vertical_resolution,
horizontal_resolution});
}
void write_spiff_entry(const uint32_t entry_tag, const void* entry_data, const size_t entry_data_size)
{
if (entry_tag == spiff_end_of_directory_entry_type)
throw jpegls_error{jpegls_errc::invalid_argument}; // TODO: create specific code.
if (entry_data_size > 65528)
throw jpegls_error{jpegls_errc::invalid_argument_spiff_entry_size};
if (state_ != state::spiff_header)
throw jpegls_error{jpegls_errc::invalid_operation};
writer_.WriteSpiffDirectoryEntry(entry_tag, entry_data, entry_data_size);
}
void encode(const void* source, const size_t source_size, uint32_t stride)
{
if (!is_frame_info_configured() || state_ == state::initial)
throw jpegls_error{jpegls_errc::invalid_operation};
////if (!is_valid(preset_coding_parameters, UINT16_MAX, 0))
//// throw jpegls_error{jpegls_errc::invalid_argument_pc_parameters};
if (stride == 0)
{
stride = frame_info_.width * ((frame_info_.bits_per_sample + 7) / 8);
if (interleave_mode_ != interleave_mode::none)
{
stride *= frame_info_.component_count;
}
}
if (state_ == state::spiff_header)
{
writer_.WriteSpiffEndOfDirectoryEntry();
}
else
{
writer_.WriteStartOfImage();
}
writer_.WriteStartOfFrameSegment(frame_info_.width, frame_info_.height, frame_info_.bits_per_sample, frame_info_.component_count);
if (color_transformation_ != color_transformation::none)
{
writer_.WriteColorTransformSegment(color_transformation_);
}
if (!IsDefault(custom_preset_coding_parameters_))
{
writer_.WriteJpegLSPresetParametersSegment(custom_preset_coding_parameters_);
}
else if (frame_info_.bits_per_sample > 12)
{
const JpegLSPresetCodingParameters preset = ComputeDefault((1 << frame_info_.bits_per_sample) - 1, near_lossless_);
writer_.WriteJpegLSPresetParametersSegment(preset);
}
ByteStreamInfo sourceInfo = FromByteArrayConst(source, source_size);
if (interleave_mode_ == interleave_mode::none)
{
const int32_t byteCountComponent = frame_info_.width * frame_info_.height * ((frame_info_.bits_per_sample + 7) / 8);
for (int32_t component = 0; component < frame_info_.component_count; ++component)
{
writer_.WriteStartOfScanSegment(1, near_lossless_, interleave_mode_);
encode_scan(sourceInfo, stride, 1);
// Synchronize the source stream (EncodeScan works on a local copy)
SkipBytes(sourceInfo, byteCountComponent);
}
}
else
{
writer_.WriteStartOfScanSegment(frame_info_.component_count, near_lossless_, interleave_mode_);
encode_scan(sourceInfo, stride, frame_info_.component_count);
}
writer_.WriteEndOfImage();
}
size_t bytes_written() const noexcept
{
return writer_.GetBytesWritten();
}
private:
enum class state
{
initial,
destination_set,
spiff_header,
completed,
};
bool is_frame_info_configured() const noexcept
{
return frame_info_.width != 0;
}
void encode_scan(ByteStreamInfo source, const uint32_t stride, const int32_t component_count)
{
JlsParameters info{};
info.components = component_count;
info.bitsPerSample = frame_info_.bits_per_sample;
info.height = frame_info_.height;
info.width = frame_info_.width;
info.stride = stride;
info.interleaveMode = interleave_mode_;
info.allowedLossyError = near_lossless_;
auto codec = JlsCodecFactory<EncoderStrategy>().CreateCodec(info, custom_preset_coding_parameters_);
unique_ptr<ProcessLine> processLine(codec->CreateProcess(source));
ByteStreamInfo destination{writer_.OutputStream()};
const size_t bytesWritten = codec->EncodeScan(move(processLine), destination);
// Synchronize the destination encapsulated in the writer (EncodeScan works on a local copy)
writer_.Seek(bytesWritten);
}
charls_frame_info frame_info_{};
int32_t near_lossless_{};
charls::interleave_mode interleave_mode_{};
charls::color_transformation color_transformation_{};
state state_{};
JpegStreamWriter writer_;
JpegLSPresetCodingParameters custom_preset_coding_parameters_{};
};
extern "C" {
#if defined _MSC_VER && _MSC_VER < 1917
#pragma warning(disable : 26447) // Function is declared 'noexcept' but calls function '' which may throw exceptions (f.6). [false warning in VS 2017]
#endif
charls_jpegls_encoder* CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_create() noexcept
{
MSVC_WARNING_SUPPRESS(26402 26409) // don't use new and delete + scoped object and move
return new (std::nothrow) charls_jpegls_encoder;
MSVC_WARNING_UNSUPPRESS()
}
void CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_destroy(const charls_jpegls_encoder* encoder) noexcept
{
MSVC_WARNING_SUPPRESS(26401 26409) // don't use new and delete + non-owner.
delete encoder;
MSVC_WARNING_UNSUPPRESS()
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_set_destination_buffer(charls_jpegls_encoder* encoder, void* destination_buffer, size_t destination_size) noexcept
try
{
if (!encoder || !destination_buffer)
return jpegls_errc::invalid_argument;
encoder->destination(destination_buffer, destination_size);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_set_frame_info(charls_jpegls_encoder* encoder, const charls_frame_info* frame_info) noexcept
try
{
if (!encoder || !frame_info)
return jpegls_errc::invalid_argument;
encoder->frame_info(*frame_info);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_set_near_lossless(charls_jpegls_encoder* encoder, int32_t near_lossless) noexcept
try
{
if (!encoder)
return jpegls_errc::invalid_argument;
encoder->near_lossless(near_lossless);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_set_interleave_mode(charls_jpegls_encoder* encoder, charls_interleave_mode interleave_mode) noexcept
try
{
if (!encoder)
return jpegls_errc::invalid_argument;
encoder->interleave_mode(interleave_mode);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_set_preset_coding_parameters(charls_jpegls_encoder* encoder, const charls_jpegls_pc_parameters* preset_coding_parameters) noexcept
try
{
if (!encoder || !preset_coding_parameters)
return jpegls_errc::invalid_argument;
encoder->preset_coding_parameters(*preset_coding_parameters);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_set_color_transformation(charls_jpegls_encoder* encoder, charls_color_transformation color_transformation) noexcept
try
{
if (!encoder)
return jpegls_errc::invalid_argument;
encoder->color_transformation(color_transformation);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_get_estimated_destination_size(const charls_jpegls_encoder* encoder, size_t* size_in_bytes) noexcept
try
{
if (!encoder || !size_in_bytes)
return jpegls_errc::invalid_argument;
*size_in_bytes = encoder->estimated_destination_size();
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_get_bytes_written(const charls_jpegls_encoder* encoder, size_t* bytes_written) noexcept
{
if (!encoder || !bytes_written)
return jpegls_errc::invalid_argument;
*bytes_written = encoder->bytes_written();
return jpegls_errc::success;
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_encode_from_buffer(charls_jpegls_encoder* encoder, const void* source_buffer, const size_t source_size, const uint32_t stride) noexcept
try
{
if (!encoder || !source_buffer)
return jpegls_errc::invalid_argument;
encoder->encode(source_buffer, source_size, stride);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_write_spiff_header(charls_jpegls_encoder* encoder, const charls_spiff_header* spiff_header) noexcept
try
{
if (!encoder || !spiff_header)
return jpegls_errc::invalid_argument;
encoder->write_spiff_header(*spiff_header);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_write_standard_spiff_header(charls_jpegls_encoder* encoder, const charls_spiff_color_space color_space,
const charls_spiff_resolution_units resolution_units, const uint32_t vertical_resolution, const uint32_t horizontal_resolution) noexcept
try
{
if (!encoder)
return jpegls_errc::invalid_argument;
encoder->write_standard_spiff_header(color_space, resolution_units, vertical_resolution, horizontal_resolution);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
charls_jpegls_encoder_write_spiff_entry(charls_jpegls_encoder* encoder, const uint32_t entry_tag, const void* entry_data, const size_t entry_data_size) noexcept
try
{
if (!encoder || (!entry_data && entry_data_size != 0))
return jpegls_errc::invalid_argument;
encoder->write_spiff_entry(entry_tag, entry_data, entry_data_size);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
JpegLsEncode(void* destination, size_t destinationLength, size_t* bytesWritten, const void* source, size_t sourceLength, const struct JlsParameters* params, char* errorMessage)
try
{
if (!destination || !bytesWritten || !source || !params || params->jfif.version)
return jpegls_errc::invalid_argument;
charls_jpegls_encoder encoder;
encoder.destination(destination, destinationLength);
encoder.frame_info({static_cast<uint32_t>(params->height), static_cast<uint32_t>(params->width), params->bitsPerSample, params->components});
encoder.near_lossless(params->allowedLossyError);
encoder.interleave_mode(params->interleaveMode);
encoder.color_transformation(params->colorTransformation);
const auto& pc = params->custom;
encoder.preset_coding_parameters({pc.MaximumSampleValue, pc.Threshold1, pc.Threshold2, pc.Threshold3, pc.ResetValue});
encoder.encode(source, sourceLength, params->stride);
*bytesWritten = encoder.bytes_written();
clear_error_message(errorMessage);
return jpegls_errc::success;
}
catch (...)
{
return set_error_message(to_jpegls_errc(), errorMessage);
}
}

View File

@ -4,8 +4,7 @@
#include "util.h"
namespace charls
{
namespace charls {
// This file defines simple classes that define (lossless) color transforms.
// They are invoked in process_line.h to convert between decoded values and the internal line buffers.
@ -147,9 +146,9 @@ struct TransformShifted final
struct Inverse final
{
explicit Inverse(const TransformShifted& transform) noexcept
: shift_{transform.shift_},
inverseTransform_{transform.colorTransform_}
explicit Inverse(const TransformShifted& transform) noexcept :
shift_{transform.shift_},
inverseTransform_{transform.colorTransform_}
{
}
@ -170,8 +169,8 @@ struct TransformShifted final
typename Transform::Inverse inverseTransform_;
};
explicit TransformShifted(int shift) noexcept
: shift_{shift}
explicit TransformShifted(int shift) noexcept :
shift_{shift}
{
}

View File

@ -4,8 +4,8 @@
#include <algorithm>
namespace charls
{
namespace charls {
// Default threshold values for JPEG-LS statistical modeling as defined in ISO/IEC 14495-1, Table C.3
// for the case MAXVAL = 255 and NEAR = 0.
// Can be overridden at compression time, however this is rarely done.
@ -15,13 +15,22 @@ constexpr int DefaultThreshold3 = 21; // BASIC_T3
constexpr int DefaultResetValue = 64; // Default RESET value as defined in ISO/IEC 14495-1, table C.2
constexpr int maximum_width = 65535;
constexpr int maximum_height = 65535;
constexpr int MaximumComponentCount = 255;
constexpr int MinimumBitsPerSample = 2;
constexpr int MaximumBitsPerSample = 16;
constexpr int maximum_near_lossless = 255;
constexpr int MaximumNearLossless(const int maximumSampleValue) noexcept
{
return std::min(255, maximumSampleValue / 2); // As defined by ISO/IEC 14495-1, C.2.3
}
// ISO/IEC 14495-1, section 4.8.1 defines the SPIFF version numbers to be used for the SPIFF header in combination with JPEG-LS.
constexpr uint8_t spiff_major_revision_number = 2;
constexpr uint8_t spiff_minor_revision_number = 0;
constexpr uint8_t spiff_end_of_directory_entry_type = 1;
} // namespace charls

View File

@ -2,10 +2,12 @@
#pragma once
#include "util.h"
#include <cassert>
#include <cstdint>
namespace charls
{
namespace charls {
// Purpose: a JPEG-LS context with it's current statistics.
struct JlsContext final
@ -18,11 +20,10 @@ struct JlsContext final
JlsContext() = default;
explicit JlsContext(int32_t a) noexcept :
A(a)
A{a}
{
}
FORCE_INLINE int32_t GetErrorCorrection(int32_t k) const noexcept
{
if (k != 0)
@ -31,7 +32,6 @@ struct JlsContext final
return BitWiseSign(2 * B + N - 1);
}
FORCE_INLINE void UpdateVariables(int32_t errorValue, int32_t NEAR, int32_t NRESET) noexcept
{
ASSERT(N != 0);
@ -64,7 +64,7 @@ struct JlsContext final
}
C = C - (C > -128);
}
else if (b > 0)
else if (b > 0)
{
b = b - n;
if (b > 0)
@ -78,7 +78,6 @@ struct JlsContext final
ASSERT(N != 0);
}
FORCE_INLINE int32_t GetGolomb() const noexcept
{
const int32_t nTest = N;
@ -91,7 +90,7 @@ struct JlsContext final
if (nTest << 4 >= aTest) return 4;
int32_t k = 5;
for(; nTest << k < aTest; ++k)
for (; nTest << k < aTest; ++k)
{
ASSERT(k <= 32);
}

View File

@ -2,10 +2,12 @@
#pragma once
#include "util.h"
#include <cassert>
#include <cstdint>
namespace charls
{
namespace charls {
// Implements statistical modeling for the run mode context.
// Computes model dependent parameters like the Golomb code lengths
@ -20,15 +22,14 @@ struct CContextRunMode final
CContextRunMode() = default;
CContextRunMode(int32_t a, int32_t nRItype, int32_t nReset) noexcept
: A{a},
nRItype_{nRItype},
nReset_{static_cast<uint8_t>(nReset)},
N{1}
CContextRunMode(int32_t a, int32_t nRItype, int32_t nReset) noexcept :
A{a},
nRItype_{nRItype},
nReset_{static_cast<uint8_t>(nReset)},
N{1}
{
}
FORCE_INLINE int32_t GetGolomb() const noexcept
{
const int32_t TEMP = A + (N >> 1) * nRItype_;
@ -42,7 +43,6 @@ struct CContextRunMode final
return k;
}
void UpdateVariables(int32_t errorValue, int32_t EMErrval) noexcept
{
if (errorValue < 0)
@ -59,7 +59,6 @@ struct CContextRunMode final
N = N + 1;
}
FORCE_INLINE int32_t ComputeErrVal(int32_t temp, int32_t k) const noexcept
{
const bool map = temp & 1;
@ -75,7 +74,6 @@ struct CContextRunMode final
return errorValueAbs;
}
bool ComputeMap(int32_t errorValue, int32_t k) const noexcept
{
if (k == 0 && errorValue > 0 && 2 * Nn < N)
@ -90,7 +88,6 @@ struct CContextRunMode final
return false;
}
FORCE_INLINE bool ComputeMapNegativeE(int32_t k) const noexcept
{
return k != 0 || 2 * Nn >= N;

View File

@ -62,22 +62,23 @@ public:
if (!byteStream_ || byteStream_->sgetc() == std::char_traits<char>::eof())
return;
const std::size_t count = endPosition_ - position_;
const auto count = endPosition_ - position_;
if (count > 64)
return;
for (std::size_t i = 0; i < count; ++i)
for (std::size_t i = 0; i < static_cast < std::size_t>(count); ++i)
{
buffer_[i] = position_[i];
}
const std::size_t offset = buffer_.data() - position_;
const auto offset = buffer_.data() - position_;
position_ += offset;
endPosition_ += offset;
nextFFPosition_ += offset;
const std::streamsize readBytes = byteStream_->sgetn(reinterpret_cast<char*>(endPosition_), buffer_.size() - count);
const std::streamsize readBytes = byteStream_->sgetn(reinterpret_cast<char*>(endPosition_),
static_cast<std::streamsize>(buffer_.size()) - count);
endPosition_ += readBytes;
}

View File

@ -2,13 +2,13 @@
#pragma once
#include "util.h"
#include "constants.h"
#include "util.h"
#include <algorithm>
#include <cstdlib>
#include <cmath>
#include <cassert>
#include <cmath>
#include <cstdlib>
// Default traits that support all JPEG LS parameters: custom limit, near, maxval (not power of 2)
@ -17,8 +17,7 @@
// This is to allow the traits class to replace the default implementation here with optimized specific implementations.
// This is done for lossless coding/decoding: see losslesstraits.h
namespace charls
{
namespace charls {
template<typename sample, typename pixel>
struct DefaultTraits final
@ -34,14 +33,14 @@ struct DefaultTraits final
const int32_t LIMIT;
const int32_t RESET;
DefaultTraits(int32_t max, int32_t near, int32_t reset = DefaultResetValue) noexcept
: MAXVAL{max},
RANGE{(max + 2 * near) / (2 * near + 1) + 1},
NEAR{near},
qbpp{log_2(RANGE)},
bpp{log_2(max)},
LIMIT{2 * (bpp + std::max(8, bpp))},
RESET{reset}
DefaultTraits(int32_t max, int32_t near, int32_t reset = DefaultResetValue) noexcept :
MAXVAL{max},
RANGE{(max + 2 * near) / (2 * near + 1) + 1},
NEAR{near},
qbpp{log_2(RANGE)},
bpp{log_2(max)},
LIMIT{2 * (bpp + std::max(8, bpp))},
RESET{reset}
{
}
@ -57,7 +56,7 @@ struct DefaultTraits final
}
DefaultTraits() = delete;
DefaultTraits(DefaultTraits&&) = default;
DefaultTraits(DefaultTraits&&) noexcept = default;
~DefaultTraits() = default;
DefaultTraits& operator=(const DefaultTraits&) = delete;
DefaultTraits& operator=(DefaultTraits&&) = delete;
@ -97,7 +96,7 @@ struct DefaultTraits final
if ((Pxc & MAXVAL) == Pxc)
return Pxc;
return (~(Pxc >> (int32_t_bit_count-1))) & MAXVAL;
return (~(Pxc >> (int32_t_bit_count - 1))) & MAXVAL;
}
/// <summary>
@ -124,9 +123,9 @@ private:
int32_t Quantize(int32_t errorValue) const noexcept
{
if (errorValue > 0)
return (errorValue + NEAR) / (2 * NEAR + 1);
return (errorValue + NEAR) / (2 * NEAR + 1);
return - (NEAR - errorValue) / (2 * NEAR + 1);
return -(NEAR - errorValue) / (2 * NEAR + 1);
}
FORCE_INLINE int32_t DeQuantize(int32_t ErrorValue) const noexcept

View File

@ -2,25 +2,17 @@
#pragma once
#include "process_line.h"
#include "decoder_strategy.h"
#include "process_line.h"
namespace charls
{
namespace charls {
// Purpose: Implements encoding to stream of bits. In encoding mode JpegLsCodec inherits from EncoderStrategy
class EncoderStrategy
{
public:
explicit EncoderStrategy(const JlsParameters& params) :
params_(params),
bitBuffer_(0),
freeBitCount_(sizeof(bitBuffer_) * 8),
compressedLength_(0),
position_(nullptr),
isFFWritten_(false),
bytesWritten_(0),
compressedStream_(nullptr)
params_{params}
{
}
@ -47,7 +39,6 @@ public:
}
protected:
void Init(ByteStreamInfo& compressedStream)
{
freeBitCount_ = sizeof(bitBuffer_) * 8;
@ -70,9 +61,9 @@ protected:
void AppendToBitStream(int32_t bits, int32_t bitCount)
{
ASSERT(bitCount < 32 && bitCount >= 0);
ASSERT((!decoder_) || (bitCount == 0 && bits == 0) ||( decoder_->ReadLongValue(bitCount) == bits));
ASSERT((!decoder_) || (bitCount == 0 && bits == 0) || (decoder_->ReadLongValue(bitCount) == bits));
#ifndef NDEBUG
const int mask = (1u << (bitCount)) - 1;
const int mask = (1U << (bitCount)) - 1;
ASSERT((bits | mask) == mask); // Not used bits must be set to zero.
#endif
@ -177,22 +168,21 @@ protected:
}
std::unique_ptr<DecoderStrategy> decoder_;
JlsParameters params_;
std::unique_ptr<ProcessLine> processLine_;
private:
unsigned int bitBuffer_;
int32_t freeBitCount_;
std::size_t compressedLength_;
unsigned int bitBuffer_{};
int32_t freeBitCount_{sizeof bitBuffer_ * 8};
std::size_t compressedLength_{};
// encoding
uint8_t* position_;
bool isFFWritten_;
std::size_t bytesWritten_;
uint8_t* position_{};
bool isFFWritten_{};
std::size_t bytesWritten_{};
std::vector<uint8_t> buffer_;
std::basic_streambuf<char>* compressedStream_;
std::basic_streambuf<char>* compressedStream_{};
};
} // namespace charls

View File

@ -1,8 +1,5 @@
// Copyright (c) Team CharLS. All rights reserved. See the accompanying "LICENSE.md" for licensed use.
#include <charls/charls.h>
#include <charls/jpegls_error.h>
#include "jpeg_stream_reader.h"
#include "jpeg_stream_writer.h"
#include "jpegls_preset_coding_parameters.h"
@ -18,12 +15,12 @@ namespace {
void VerifyInput(const ByteStreamInfo& destination, const JlsParameters& parameters)
{
if (!destination.rawStream && !destination.rawData)
throw jpegls_error{jpegls_errc::invalid_argument_destination};
throw jpegls_error{jpegls_errc::invalid_operation};
if (parameters.bitsPerSample < MinimumBitsPerSample || parameters.bitsPerSample > MaximumBitsPerSample)
throw jpegls_error{jpegls_errc::invalid_argument_bits_per_sample};
if (!(parameters.interleaveMode == InterleaveMode::None || parameters.interleaveMode == InterleaveMode::Sample || parameters.interleaveMode == InterleaveMode::Line))
if (!(parameters.interleaveMode == interleave_mode::none || parameters.interleaveMode == interleave_mode::sample || parameters.interleaveMode == interleave_mode::line))
throw jpegls_error{jpegls_errc::invalid_argument_interleave_mode};
if (parameters.components < 1 || parameters.components > MaximumComponentCount)
@ -39,34 +36,12 @@ void VerifyInput(const ByteStreamInfo& destination, const JlsParameters& paramet
case 4:
break;
default:
if (parameters.interleaveMode != InterleaveMode::None)
if (parameters.interleaveMode != interleave_mode::none)
throw jpegls_error{jpegls_errc::invalid_argument_interleave_mode};
break;
}
}
jpegls_errc to_jpegls_errc() noexcept
{
try
{
// re-trow the exception.
throw;
}
catch (const jpegls_error& error)
{
return static_cast<jpegls_errc>(error.code().value());
}
catch (const std::bad_alloc&)
{
return jpegls_errc::not_enough_memory;
}
catch (...)
{
return jpegls_errc::unexpected_failure;
}
}
void EncodeScan(const JlsParameters& params, int componentCount, ByteStreamInfo source, JpegStreamWriter& writer)
{
JlsParameters info{params};
@ -101,7 +76,7 @@ jpegls_errc JpegLsEncodeStream(ByteStreamInfo destination, size_t& bytesWritten,
if (info.stride == 0)
{
info.stride = info.width * ((info.bitsPerSample + 7) / 8);
if (info.interleaveMode != InterleaveMode::None)
if (info.interleaveMode != interleave_mode::none)
{
info.stride *= info.components;
}
@ -111,14 +86,9 @@ jpegls_errc JpegLsEncodeStream(ByteStreamInfo destination, size_t& bytesWritten,
writer.WriteStartOfImage();
if (info.jfif.version != 0)
{
writer.WriteJpegFileInterchangeFormatSegment(info.jfif);
}
writer.WriteStartOfFrameSegment(info.width, info.height, info.bitsPerSample, info.components);
if (info.colorTransformation != ColorTransformation::None)
if (info.colorTransformation != color_transformation::none)
{
writer.WriteColorTransformSegment(info.colorTransformation);
}
@ -133,7 +103,7 @@ jpegls_errc JpegLsEncodeStream(ByteStreamInfo destination, size_t& bytesWritten,
writer.WriteJpegLSPresetParametersSegment(preset);
}
if (info.interleaveMode == InterleaveMode::None)
if (info.interleaveMode == interleave_mode::none)
{
const int32_t byteCountComponent = info.width * info.height * ((info.bitsPerSample + 7) / 8);
for (int32_t component = 0; component < info.components; ++component)
@ -170,6 +140,9 @@ jpegls_errc JpegLsDecodeStream(ByteStreamInfo destination, ByteStreamInfo source
{
JpegStreamReader reader{source};
reader.ReadHeader();
reader.ReadStartOfScan(true);
if (params)
{
reader.SetInfo(*params);
@ -202,63 +175,3 @@ jpegls_errc JpegLsReadHeaderStream(ByteStreamInfo source, JlsParameters* params)
return to_jpegls_errc();
}
}
extern "C" {
jpegls_errc CHARLS_API_CALLING_CONVENTION
JpegLsEncode(void* destination, size_t destinationLength, size_t* bytesWritten, const void* source, size_t sourceLength, const struct JlsParameters* params, const void* /*reserved*/)
{
if (!destination || !bytesWritten || !source || !params)
return jpegls_errc::invalid_argument;
const ByteStreamInfo sourceInfo{FromByteArrayConst(source, sourceLength)};
const ByteStreamInfo destinationInfo{FromByteArray(destination, destinationLength)};
return JpegLsEncodeStream(destinationInfo, *bytesWritten, sourceInfo, *params);
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
JpegLsReadHeader(const void* source, size_t sourceLength, JlsParameters* params, const void* /*reserved*/)
{
return JpegLsReadHeaderStream(FromByteArrayConst(source, sourceLength), params);
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
JpegLsDecode(void* destination, size_t destinationLength, const void* source, size_t sourceLength, const struct JlsParameters* params, const void* /*reserved*/)
{
const ByteStreamInfo compressedStream{FromByteArrayConst(source, sourceLength)};
const ByteStreamInfo rawStreamInfo{FromByteArray(destination, destinationLength)};
return JpegLsDecodeStream(rawStreamInfo, compressedStream, params);
}
jpegls_errc CHARLS_API_CALLING_CONVENTION
JpegLsDecodeRect(void* destination, size_t destinationLength, const void* source, size_t sourceLength,
JlsRect roi, const JlsParameters* params, const void* /*reserved*/)
{
try
{
const ByteStreamInfo sourceInfo{FromByteArrayConst(source, sourceLength)};
JpegStreamReader reader{sourceInfo};
const ByteStreamInfo destinationInfo{FromByteArray(destination, destinationLength)};
if (params)
{
reader.SetInfo(*params);
}
reader.SetRect(roi);
reader.Read(destinationInfo);
return jpegls_errc::success;
}
catch (...)
{
return to_jpegls_errc();
}
}
}

View File

@ -7,8 +7,7 @@
struct JlsParameters;
struct JpegLSPresetCodingParameters;
namespace charls
{
namespace charls {
template<typename Strategy>
class JlsCodecFactory final

View File

@ -4,8 +4,7 @@
#include <cstdint>
namespace charls
{
namespace charls {
// JPEG Marker codes have the pattern 0xFFaa in a JPEG byte stream.
// The valid 'aa' options are defined by several ISO/IEC, ITU standards:

View File

@ -14,15 +14,12 @@
#include <iomanip>
#include <memory>
using std::vector;
using std::find;
using std::vector;
using std::unique_ptr;
using namespace charls;
namespace
{
// JFIF\0
uint8_t jfifID[] = {'J', 'F', 'I', 'F', '\0'};
namespace {
void CheckParameterCoherent(const JlsParameters& params)
{
@ -32,7 +29,7 @@ void CheckParameterCoherent(const JlsParameters& params)
case 3:
break;
default:
if (params.interleaveMode != InterleaveMode::None)
if (params.interleaveMode != interleave_mode::none)
throw jpegls_error{jpegls_errc::parameter_value_not_supported};
break;
@ -41,17 +38,18 @@ void CheckParameterCoherent(const JlsParameters& params)
} // namespace
namespace charls
{
JpegStreamReader::JpegStreamReader(ByteStreamInfo byteStreamInfo) noexcept
: byteStream_{byteStreamInfo}
namespace charls {
JpegStreamReader::JpegStreamReader(ByteStreamInfo byteStreamInfo) noexcept :
byteStream_{byteStreamInfo}
{
}
void JpegStreamReader::Read(ByteStreamInfo rawPixels)
{
ReadHeader();
ASSERT(state_ == state::bit_stream_section);
CheckParameterCoherent(params_);
if (rect_.Width <= 0)
@ -66,20 +64,23 @@ void JpegStreamReader::Read(ByteStreamInfo rawPixels)
throw jpegls_error{jpegls_errc::destination_buffer_too_small};
int componentIndex{};
while (componentIndex < params_.components)
{
ReadStartOfScan(componentIndex == 0);
if (state_ == state::scan_section)
{
ReadStartOfScan(componentIndex == 0);
}
std::unique_ptr<DecoderStrategy> codec = JlsCodecFactory<DecoderStrategy>().CreateCodec(params_, params_.custom);
std::unique_ptr<ProcessLine> processLine(codec->CreateProcess(rawPixels));
unique_ptr<DecoderStrategy> codec = JlsCodecFactory<DecoderStrategy>().CreateCodec(params_, params_.custom);
unique_ptr<ProcessLine> processLine(codec->CreateProcess(rawPixels));
codec->DecodeScan(move(processLine), rect_, byteStream_);
SkipBytes(rawPixels, static_cast<size_t>(bytesPerPlane));
state_ = state::scan_section;
if (params_.interleaveMode != InterleaveMode::None)
if (params_.interleaveMode != interleave_mode::none)
return;
componentIndex += 1;
componentIndex++;
}
}
@ -93,10 +94,17 @@ void JpegStreamReader::ReadNBytes(std::vector<char>& destination, int byteCount)
}
void JpegStreamReader::ReadHeader()
void JpegStreamReader::ReadHeader(spiff_header* header, bool* spiff_header_found)
{
if (ReadNextMarkerCode() != JpegMarkerCode::StartOfImage)
throw jpegls_error{jpegls_errc::start_of_image_marker_not_found};
ASSERT(state_ != state::scan_section);
if (state_ == state::before_start_of_image)
{
if (ReadNextMarkerCode() != JpegMarkerCode::StartOfImage)
throw jpegls_error{jpegls_errc::start_of_image_marker_not_found};
state_ = state::header_section;
}
for (;;)
{
@ -104,10 +112,24 @@ void JpegStreamReader::ReadHeader()
ValidateMarkerCode(markerCode);
if (markerCode == JpegMarkerCode::StartOfScan)
{
state_ = state::scan_section;
return;
}
const int32_t segmentSize = ReadSegmentSize();
const int bytesRead = ReadMarkerSegment(markerCode, segmentSize - 2) + 2;
int bytesRead;
switch (state_)
{
case state::spiff_header_section:
bytesRead = ReadSpiffDirectoryEntry(markerCode, segmentSize - 2) + 2;
break;
default:
bytesRead = ReadMarkerSegment(markerCode, segmentSize - 2, header, spiff_header_found) + 2;
break;
}
const int paddingToRead = segmentSize - bytesRead;
if (paddingToRead < 0)
throw jpegls_error{jpegls_errc::invalid_marker_segment_size};
@ -116,6 +138,12 @@ void JpegStreamReader::ReadHeader()
{
ReadByte();
}
if (state_ == state::header_section && spiff_header_found && *spiff_header_found)
{
state_ = state::spiff_header_section;
return;
}
}
}
@ -136,7 +164,7 @@ JpegMarkerCode JpegStreamReader::ReadNextMarkerCode()
}
void JpegStreamReader::ValidateMarkerCode(JpegMarkerCode markerCode) const
void JpegStreamReader::ValidateMarkerCode(JpegMarkerCode markerCode)
{
// ISO/IEC 14495-1, C.1.1. defines the following markers as valid for a JPEG-LS byte stream:
// SOF55, LSE, SOI, EOI, SOS, DNL, DRI, RSTm, APPn and COM.
@ -190,7 +218,7 @@ void JpegStreamReader::ValidateMarkerCode(JpegMarkerCode markerCode) const
}
int JpegStreamReader::ReadMarkerSegment(JpegMarkerCode markerCode, int32_t segmentSize)
int JpegStreamReader::ReadMarkerSegment(JpegMarkerCode markerCode, int32_t segmentSize, spiff_header* header, bool* spiff_header_found)
{
switch (markerCode)
{
@ -221,7 +249,7 @@ int JpegStreamReader::ReadMarkerSegment(JpegMarkerCode markerCode, int32_t segme
return 0;
case JpegMarkerCode::ApplicationData8:
return TryReadHPColorTransformSegment(segmentSize);
return TryReadApplicationData8Segment(segmentSize, header, spiff_header_found);
// Other tags not supported (among which DNL DRI)
default:
@ -230,6 +258,22 @@ int JpegStreamReader::ReadMarkerSegment(JpegMarkerCode markerCode, int32_t segme
}
}
int JpegStreamReader::ReadSpiffDirectoryEntry(JpegMarkerCode markerCode, int32_t segmentSize)
{
if (markerCode != JpegMarkerCode::ApplicationData8)
throw jpegls_error{jpegls_errc::missing_end_of_spiff_directory};
if (segmentSize < 4)
throw jpegls_error{jpegls_errc::invalid_marker_segment_size};
const uint32_t spiffDirectoryType = ReadUInt32();
if (spiffDirectoryType == spiff_end_of_directory_entry_type)
{
state_ = state::image_section;
}
return 4;
}
int JpegStreamReader::ReadStartOfFrameSegment(int32_t segmentSize)
{
@ -300,6 +344,9 @@ int JpegStreamReader::ReadPresetParametersSegment(int32_t segmentSize)
params_.custom.Threshold2 = ReadUInt16();
params_.custom.Threshold3 = ReadUInt16();
params_.custom.ResetValue = ReadUInt16();
// TODO: perform more extensive validation (see C.2.4.1.1 )
return CodingParameterSegmentSize;
}
@ -349,9 +396,9 @@ void JpegStreamReader::ReadStartOfScan(bool firstComponent)
ReadByte(); // Read Mapping table selector
}
params_.allowedLossyError = ReadByte(); // Read NEAR parameter
params_.interleaveMode = static_cast<InterleaveMode>(ReadByte()); // Read ILV parameter
if (!(params_.interleaveMode == InterleaveMode::None || params_.interleaveMode == InterleaveMode::Line || params_.interleaveMode == InterleaveMode::Sample))
params_.allowedLossyError = ReadByte(); // Read NEAR parameter
params_.interleaveMode = static_cast<interleave_mode>(ReadByte()); // Read ILV parameter
if (!(params_.interleaveMode == interleave_mode::none || params_.interleaveMode == interleave_mode::line || params_.interleaveMode == interleave_mode::sample))
throw jpegls_error{jpegls_errc::invalid_parameter_interleave_mode};
if ((ReadByte() & 0xF) != 0) // Read Ah (no meaning) and Al (point transform).
@ -360,35 +407,11 @@ void JpegStreamReader::ReadStartOfScan(bool firstComponent)
if (params_.stride == 0)
{
const int width = rect_.Width != 0 ? rect_.Width : params_.width;
const int components = params_.interleaveMode == InterleaveMode::None ? 1 : params_.components;
const int components = params_.interleaveMode == interleave_mode::none ? 1 : params_.components;
params_.stride = components * width * ((params_.bitsPerSample + 7) / 8);
}
}
void JpegStreamReader::ReadJfif()
{
for (int i = 0; i < static_cast<int>(sizeof(jfifID)); i++)
{
if (jfifID[i] != ReadByte())
return;
}
params_.jfif.version = ReadUInt16();
// DPI or DPcm
params_.jfif.units = ReadByte();
params_.jfif.Xdensity = ReadUInt16();
params_.jfif.Ydensity = ReadUInt16();
// thumbnail
params_.jfif.Xthumbnail = ReadByte();
params_.jfif.Ythumbnail = ReadByte();
if (params_.jfif.Xthumbnail > 0 && params_.jfif.thumbnail)
{
std::vector<char> tempBuffer(static_cast<char*>(params_.jfif.thumbnail),
static_cast<char*>(params_.jfif.thumbnail) + static_cast<size_t>(3) * params_.jfif.Xthumbnail * params_.jfif.Ythumbnail);
ReadNBytes(tempBuffer, 3 * params_.jfif.Xthumbnail * params_.jfif.Ythumbnail);
}
state_ = state::bit_stream_section;
}
@ -418,6 +441,14 @@ int JpegStreamReader::ReadUInt16()
return i + ReadByte();
}
uint32_t JpegStreamReader::ReadUInt32()
{
uint32_t value = ReadUInt16();
value = value << 16;
value += ReadUInt16();
return value;
}
int32_t JpegStreamReader::ReadSegmentSize()
{
@ -428,12 +459,26 @@ int32_t JpegStreamReader::ReadSegmentSize()
return segmentSize;
}
int JpegStreamReader::TryReadHPColorTransformSegment(int32_t segmentSize)
int JpegStreamReader::TryReadApplicationData8Segment(int32_t segmentSize, spiff_header* header, bool* spiff_header_found)
{
if (segmentSize < 5)
return 0;
if (spiff_header_found)
{
ASSERT(header);
*spiff_header_found = false;
}
if (segmentSize == 5)
return TryReadHPColorTransformSegment();
if (header && spiff_header_found && segmentSize >= 30)
return TryReadSpiffHeaderSegment(header, *spiff_header_found);
return 0;
}
int JpegStreamReader::TryReadHPColorTransformSegment()
{
vector<char> sourceTag;
ReadNBytes(sourceTag, 4);
if (strncmp(sourceTag.data(), "mrfx", 4) != 0) // mrfx = xfrm (in big endian) = colorXFoRM
@ -442,11 +487,11 @@ int JpegStreamReader::TryReadHPColorTransformSegment(int32_t segmentSize)
const auto colorTransformation = ReadByte();
switch (colorTransformation)
{
case static_cast<uint8_t>(ColorTransformation::None):
case static_cast<uint8_t>(ColorTransformation::HP1):
case static_cast<uint8_t>(ColorTransformation::HP2):
case static_cast<uint8_t>(ColorTransformation::HP3):
params_.colorTransformation = static_cast<ColorTransformation>(colorTransformation);
case static_cast<uint8_t>(color_transformation::none):
case static_cast<uint8_t>(color_transformation::hp1):
case static_cast<uint8_t>(color_transformation::hp2):
case static_cast<uint8_t>(color_transformation::hp3):
params_.colorTransformation = static_cast<color_transformation>(colorTransformation);
return 5;
case 4: // RgbAsYuvLossy (The standard lossy RGB to YCbCr transform used in JPEG.)
@ -458,6 +503,46 @@ int JpegStreamReader::TryReadHPColorTransformSegment(int32_t segmentSize)
}
}
template<typename EnumType, EnumType Low, EnumType High>
EnumType enum_cast(uint8_t value)
{
if (value < static_cast<uint8_t>(Low))
throw jpegls_error{jpegls_errc::invalid_encoded_data};
if (value > static_cast<uint8_t>(High))
throw jpegls_error{jpegls_errc::invalid_encoded_data};
return static_cast<EnumType>(value);
}
int JpegStreamReader::TryReadSpiffHeaderSegment(spiff_header* header, bool& spiff_header_found)
{
vector<char> sourceTag;
ReadNBytes(sourceTag, 6);
if (strncmp(sourceTag.data(), "SPIFF", 6) != 0)
return 6;
const auto high_version = ReadByte();
if (high_version > spiff_major_revision_number)
return 7; // Treat unknown versions as if the SPIFF header doesn't exists.
SkipByte(); // low version
header->profile_id = static_cast<spiff_profile_id>(ReadByte());
header->component_count = ReadByte();
header->height = ReadUInt32();
header->width = ReadUInt32();
header->color_space = static_cast<spiff_color_space>(ReadByte());
header->bits_per_sample = ReadByte();
header->compression_type = static_cast<spiff_compression_type>(ReadByte());
header->resolution_units = static_cast<spiff_resolution_units>(ReadByte());
header->vertical_resolution = ReadUInt32();
header->horizontal_resolution = ReadUInt32();
spiff_header_found = true;
return 30;
}
void JpegStreamReader::AddComponent(uint8_t componentId)
{

View File

@ -2,13 +2,13 @@
#pragma once
#include <charls/charls_legacy.h>
#include <charls/public_types.h>
#include <cstdint>
#include <vector>
namespace charls
{
namespace charls {
enum class JpegMarkerCode : uint8_t;
@ -18,7 +18,7 @@ class JpegStreamReader final
public:
explicit JpegStreamReader(ByteStreamInfo byteStreamInfo) noexcept;
const JlsParameters& GetMetadata() const noexcept
JlsParameters& GetMetadata() noexcept
{
return params_;
}
@ -29,13 +29,18 @@ public:
}
void Read(ByteStreamInfo rawPixels);
void ReadHeader();
void ReadHeader(spiff_header* header = nullptr, bool* spiff_header_found = nullptr);
void SetInfo(const JlsParameters& params) noexcept
{
params_ = params;
}
void SetOutputBgr(char value) noexcept
{
params_.outputBgr = value;
}
void SetRect(const JlsRect& rect) noexcept
{
rect_ = rect;
@ -47,23 +52,39 @@ public:
private:
void SkipByte();
int ReadUInt16();
uint32_t ReadUInt32();
int32_t ReadSegmentSize();
void ReadNBytes(std::vector<char>& destination, int byteCount);
JpegMarkerCode ReadNextMarkerCode();
void ValidateMarkerCode(JpegMarkerCode markerCode) const;
static void ValidateMarkerCode(JpegMarkerCode markerCode);
int ReadMarkerSegment(JpegMarkerCode markerCode, int32_t segmentSize);
int ReadMarkerSegment(JpegMarkerCode markerCode, int32_t segmentSize, spiff_header* header, bool* spiff_header_found);
int ReadSpiffDirectoryEntry(JpegMarkerCode markerCode, int32_t segmentSize);
int ReadStartOfFrameSegment(int32_t segmentSize);
static int ReadComment() noexcept;
int ReadPresetParametersSegment(int32_t segmentSize);
void ReadJfif();
int TryReadHPColorTransformSegment(int32_t segmentSize);
int TryReadApplicationData8Segment(int32_t segmentSize, spiff_header* header, bool* spiff_header_found);
int TryReadSpiffHeaderSegment(spiff_header* header, bool& spiff_header_found);
int TryReadHPColorTransformSegment();
void AddComponent(uint8_t componentId);
enum class state
{
before_start_of_image,
header_section,
spiff_header_section,
image_section,
frame_section,
scan_section,
bit_stream_section
};
ByteStreamInfo byteStream_;
JlsParameters params_{};
JlsRect rect_{};
std::vector<uint8_t> componentIds_;
state state_{};
};
} // namespace charls

View File

@ -15,16 +15,10 @@
using std::array;
using std::vector;
namespace charls
{
JpegStreamWriter::JpegStreamWriter() noexcept
: destination_{}
{
}
namespace charls {
JpegStreamWriter::JpegStreamWriter(const ByteStreamInfo& destination) noexcept
: destination_{destination}
JpegStreamWriter::JpegStreamWriter(const ByteStreamInfo& destination) noexcept :
destination_{destination}
{
}
@ -41,34 +35,47 @@ void JpegStreamWriter::WriteEndOfImage()
}
void JpegStreamWriter::WriteJpegFileInterchangeFormatSegment(const JfifParameters& params)
void JpegStreamWriter::WriteSpiffHeaderSegment(const spiff_header& header)
{
ASSERT(params.units == 0 || params.units == 1 || params.units == 2);
ASSERT(params.Xdensity > 0);
ASSERT(params.Ydensity > 0);
ASSERT(params.Xthumbnail >= 0 && params.Xthumbnail < 256);
ASSERT(params.Ythumbnail >= 0 && params.Ythumbnail < 256);
ASSERT(header.height > 0);
ASSERT(header.width > 0);
// Create a JPEG APP0 segment in the JPEG File Interchange Format (JFIF), v1.02
vector<uint8_t> segment{'J', 'F', 'I', 'F', '\0'};
push_back(segment, static_cast<uint16_t>(params.version));
segment.push_back(static_cast<uint8_t>(params.units));
push_back(segment, static_cast<uint16_t>(params.Xdensity));
push_back(segment, static_cast<uint16_t>(params.Ydensity));
// Create a JPEG APP8 segment in Still Picture Interchange File Format (SPIFF), v2.0
vector<uint8_t> segment{'S', 'P', 'I', 'F', 'F', '\0'};
segment.push_back(spiff_major_revision_number);
segment.push_back(spiff_minor_revision_number);
segment.push_back(static_cast<uint8_t>(header.profile_id));
segment.push_back(static_cast<uint8_t>(header.component_count));
push_back(segment, header.height);
push_back(segment, header.width);
segment.push_back(static_cast<uint8_t>(header.color_space));
segment.push_back(static_cast<uint8_t>(header.bits_per_sample));
segment.push_back(static_cast<uint8_t>(header.compression_type));
segment.push_back(static_cast<uint8_t>(header.resolution_units));
push_back(segment, header.vertical_resolution);
push_back(segment, header.horizontal_resolution);
// thumbnail
segment.push_back(static_cast<uint8_t>(params.Xthumbnail));
segment.push_back(static_cast<uint8_t>(params.Ythumbnail));
if (params.Xthumbnail > 0)
{
if (params.thumbnail)
throw jpegls_error{jpegls_errc::invalid_argument_thumbnail};
WriteSegment(JpegMarkerCode::ApplicationData8, segment.data(), segment.size());
}
segment.insert(segment.end(), static_cast<uint8_t*>(params.thumbnail),
static_cast<uint8_t*>(params.thumbnail) + static_cast<size_t>(3) * params.Xthumbnail * params.Ythumbnail);
}
WriteSegment(JpegMarkerCode::ApplicationData0, segment.data(), segment.size());
void JpegStreamWriter::WriteSpiffDirectoryEntry(uint32_t entry_tag, const void* entry_data, size_t entry_data_size)
{
WriteMarker(JpegMarkerCode::ApplicationData8);
WriteUInt16(static_cast<uint16_t>(sizeof(uint16_t) + sizeof(uint32_t) + entry_data_size));
WriteUInt32(entry_tag);
WriteBytes(entry_data, entry_data_size);
}
void JpegStreamWriter::WriteSpiffEndOfDirectoryEntry()
{
// Note: ISO/IEC 10918-3, Annex F.2.2.3 documents that the EOD entry segment should have a length of 8
// but only 6 data bytes. This approach allows to wrap existing bit streams\encoders with a SPIFF header.
// In this implementation the SOI marker is added as data bytes to simplify the design.
array<uint8_t, 6> segment{0, 0, 0, spiff_end_of_directory_entry_type, 0xFF, static_cast<uint8_t>(JpegMarkerCode::StartOfImage)};
WriteSegment(JpegMarkerCode::ApplicationData8, segment.data(), segment.size());
}
@ -102,7 +109,7 @@ void JpegStreamWriter::WriteStartOfFrameSegment(int width, int height, int bitsP
}
void JpegStreamWriter::WriteColorTransformSegment(ColorTransformation transformation)
void JpegStreamWriter::WriteColorTransformSegment(color_transformation transformation)
{
array<uint8_t, 5> segment{'m', 'r', 'f', 'x', static_cast<uint8_t>(transformation)};
WriteSegment(JpegMarkerCode::ApplicationData8, segment.data(), segment.size());
@ -125,13 +132,13 @@ void JpegStreamWriter::WriteJpegLSPresetParametersSegment(const JpegLSPresetCodi
}
void JpegStreamWriter::WriteStartOfScanSegment(int componentCount, int allowedLossyError, InterleaveMode interleaveMode)
void JpegStreamWriter::WriteStartOfScanSegment(int componentCount, int allowedLossyError, interleave_mode interleaveMode)
{
ASSERT(componentCount > 0 && componentCount <= UINT8_MAX);
ASSERT(allowedLossyError >= 0 && allowedLossyError <= UINT8_MAX);
ASSERT(interleaveMode == InterleaveMode::None ||
interleaveMode == InterleaveMode::Line ||
interleaveMode == InterleaveMode::Sample);
ASSERT(interleaveMode == interleave_mode::none ||
interleaveMode == interleave_mode::line ||
interleaveMode == interleave_mode::sample);
// Create a Scan Header as defined in T.87, C.2.3 and T.81, B.2.3
vector<uint8_t> segment;

View File

@ -3,6 +3,7 @@
#pragma once
#include <charls/jpegls_error.h>
#include <charls/charls_legacy.h>
#include "jpeg_marker_code.h"
@ -16,22 +17,31 @@ enum class JpegMarkerCode : uint8_t;
class JpegStreamWriter final
{
public:
JpegStreamWriter() noexcept;
JpegStreamWriter() = default;
explicit JpegStreamWriter(const ByteStreamInfo& destination) noexcept;
void WriteStartOfImage();
/// <summary>
/// Write a JPEG File Interchange (APP1 + jfif) segment.
/// Write a JPEG SPIFF (APP8 + spiff) segment.
/// This segment is documented in ISO/IEC 10918-3, Annex F.
/// </summary>
/// <param name="params">Parameters to write into the JFIF segment.</param>
void WriteJpegFileInterchangeFormatSegment(const JfifParameters& params);
/// <param name="header">Header info to write into the SPIFF segment.</param>
void WriteSpiffHeaderSegment(const spiff_header& header);
void WriteSpiffDirectoryEntry(uint32_t entry_tag, const void* entry_data, size_t entry_data_size);
/// <summary>
/// Write a JPEG SPIFF end of directory (APP8) segment.
/// This segment is documented in ISO/IEC 10918-3, Annex F.
/// </summary>
void WriteSpiffEndOfDirectoryEntry();
/// <summary>
/// Writes a HP color transformation (APP8) segment.
/// </summary>
/// <param name="transformation">Color transformation to put into the segment.</param>
void WriteColorTransformSegment(ColorTransformation transformation);
void WriteColorTransformSegment(color_transformation transformation);
/// <summary>
/// Writes a JPEG-LS preset parameters (LSE) segment.
@ -54,7 +64,7 @@ public:
/// <param name="componentCount">The number of components in the scan segment. Can only be > 1 when the components are interleaved.</param>
/// <param name="allowedLossyError">The allowed lossy error. 0 means lossless.</param>
/// <param name="interleaveMode">The interleave mode of the components.</param>
void WriteStartOfScanSegment(int componentCount, int allowedLossyError, InterleaveMode interleaveMode);
void WriteStartOfScanSegment(int componentCount, int allowedLossyError, interleave_mode interleaveMode);
void WriteEndOfImage();
@ -84,6 +94,12 @@ public:
byteOffset_ += byteCount;
}
void UpdateDestination(void* destination_buffer, size_t destination_size) noexcept
{
destination_.rawData = static_cast<uint8_t*>(destination_buffer);
destination_.count = destination_size;
}
private:
uint8_t* GetPos() const noexcept
{
@ -96,7 +112,7 @@ private:
{
if (destination_.rawStream)
{
destination_.rawStream->sputc(value);
destination_.rawStream->sputc(static_cast<char>(value));
}
else
{
@ -131,13 +147,21 @@ private:
WriteByte(static_cast<uint8_t>(value % 0x100));
}
void WriteUInt32(uint32_t value)
{
WriteByte(static_cast<uint8_t>(value >> 24));
WriteByte(static_cast<uint8_t>(value >> 16));
WriteByte(static_cast<uint8_t>(value >> 8));
WriteByte(static_cast<uint8_t>(value));
}
void WriteMarker(JpegMarkerCode markerCode)
{
WriteByte(JpegMarkerStartByte);
WriteByte(static_cast<uint8_t>(markerCode));
}
ByteStreamInfo destination_;
ByteStreamInfo destination_{};
std::size_t byteOffset_{};
int8_t componentId_{1};
};

View File

@ -1,13 +1,13 @@
// Copyright (c) Team CharLS. All rights reserved. See the accompanying "LICENSE.md" for licensed use.
#include "decoder_strategy.h"
#include "encoder_strategy.h"
#include "lookup_table.h"
#include "lossless_traits.h"
#include "default_traits.h"
#include "encoder_strategy.h"
#include "jls_codec_factory.h"
#include "jpeg_stream_reader.h"
#include "jpegls_preset_coding_parameters.h"
#include "lookup_table.h"
#include "lossless_traits.h"
#include "util.h"
#include <vector>
@ -23,8 +23,7 @@ using std::make_unique;
using std::unique_ptr;
using std::vector;
namespace
{
namespace {
signed char QuantizeGradientOrg(const JpegLSPresetCodingParameters& preset, int32_t NEAR, int32_t Di) noexcept
{
@ -32,12 +31,12 @@ signed char QuantizeGradientOrg(const JpegLSPresetCodingParameters& preset, int3
if (Di <= -preset.Threshold2) return -3;
if (Di <= -preset.Threshold1) return -2;
if (Di < -NEAR) return -1;
if (Di <= NEAR) return 0;
if (Di <= NEAR) return 0;
if (Di < preset.Threshold1) return 1;
if (Di < preset.Threshold2) return 2;
if (Di < preset.Threshold3) return 3;
return 4;
return 4;
}
@ -50,7 +49,7 @@ vector<signed char> CreateQLutLossless(int32_t bitCount)
for (int32_t diff = -range; diff < range; diff++)
{
lut[static_cast<size_t>(range) + diff] = QuantizeGradientOrg(preset, 0,diff);
lut[static_cast<size_t>(range) + diff] = QuantizeGradientOrg(preset, 0, diff);
}
return lut;
}
@ -64,17 +63,16 @@ unique_ptr<Strategy> create_codec(const Traits& traits, const JlsParameters& par
} // namespace
namespace charls
{
namespace charls {
// Lookup tables to replace code with lookup tables.
// To avoid threading issues, all tables are created when the program is loaded.
// Lookup table: decode symbols that are smaller or equal to 8 bit (16 tables for each value of k)
CTable decodingTables[16] = { InitTable(0), InitTable(1), InitTable(2), InitTable(3),
InitTable(4), InitTable(5), InitTable(6), InitTable(7),
InitTable(8), InitTable(9), InitTable(10), InitTable(11),
InitTable(12), InitTable(13), InitTable(14),InitTable(15) };
CTable decodingTables[16] = {InitTable(0), InitTable(1), InitTable(2), InitTable(3),
InitTable(4), InitTable(5), InitTable(6), InitTable(7),
InitTable(8), InitTable(9), InitTable(10), InitTable(11),
InitTable(12), InitTable(13), InitTable(14), InitTable(15)};
// Lookup tables: sample differences to bin indexes.
vector<signed char> rgquant8Ll = CreateQLutLossless(8);
@ -116,7 +114,7 @@ unique_ptr<Strategy> JlsCodecFactory<Strategy>::CreateCodec(const JlsParameters&
template<typename Strategy>
unique_ptr<Strategy> JlsCodecFactory<Strategy>::CreateOptimizedCodec(const JlsParameters& params)
{
if (params.interleaveMode == InterleaveMode::Sample && params.components != 3 && params.components != 4)
if (params.interleaveMode == interleave_mode::sample && params.components != 3 && params.components != 4)
return nullptr;
#ifndef DISABLE_SPECIALIZATIONS
@ -124,22 +122,25 @@ unique_ptr<Strategy> JlsCodecFactory<Strategy>::CreateOptimizedCodec(const JlsPa
// optimized lossless versions common formats
if (params.allowedLossyError == 0)
{
if (params.interleaveMode == InterleaveMode::Sample)
if (params.interleaveMode == interleave_mode::sample)
{
if (params.components == 3 && params.bitsPerSample == 8)
return create_codec<Strategy>(LosslessTraits<Triplet<uint8_t>, 8>(), params);
else if (params.components == 4 && params.bitsPerSample == 8)
if (params.components == 4 && params.bitsPerSample == 8)
return create_codec<Strategy>(LosslessTraits<Quad<uint8_t>, 8>(), params);
}
else
{
switch (params.bitsPerSample)
{
case 8: return create_codec<Strategy>(LosslessTraits<uint8_t, 8>(), params);
case 12: return create_codec<Strategy>(LosslessTraits<uint16_t, 12>(), params);
case 16: return create_codec<Strategy>(LosslessTraits<uint16_t, 16>(), params);
default:
break;
case 8:
return create_codec<Strategy>(LosslessTraits<uint8_t, 8>(), params);
case 12:
return create_codec<Strategy>(LosslessTraits<uint16_t, 12>(), params);
case 16:
return create_codec<Strategy>(LosslessTraits<uint16_t, 16>(), params);
default:
break;
}
}
}
@ -150,24 +151,24 @@ unique_ptr<Strategy> JlsCodecFactory<Strategy>::CreateOptimizedCodec(const JlsPa
if (params.bitsPerSample <= 8)
{
if (params.interleaveMode == InterleaveMode::Sample)
if (params.interleaveMode == interleave_mode::sample)
{
if (params.components == 3)
return create_codec<Strategy>(DefaultTraits<uint8_t, Triplet<uint8_t> >(maxval, params.allowedLossyError), params);
else if (params.components == 4)
return create_codec<Strategy>(DefaultTraits<uint8_t, Quad<uint8_t> >(maxval, params.allowedLossyError), params);
return create_codec<Strategy>(DefaultTraits<uint8_t, Triplet<uint8_t>>(maxval, params.allowedLossyError), params);
if (params.components == 4)
return create_codec<Strategy>(DefaultTraits<uint8_t, Quad<uint8_t>>(maxval, params.allowedLossyError), params);
}
return create_codec<Strategy>(DefaultTraits<uint8_t, uint8_t>((1u << params.bitsPerSample) - 1, params.allowedLossyError), params);
}
if (params.bitsPerSample <= 16)
{
if (params.interleaveMode == InterleaveMode::Sample)
if (params.interleaveMode == interleave_mode::sample)
{
if (params.components == 3)
return create_codec<Strategy>(DefaultTraits<uint16_t, Triplet<uint16_t> >(maxval, params.allowedLossyError), params);
else if (params.components == 4)
return create_codec<Strategy>(DefaultTraits<uint16_t, Quad<uint16_t> >(maxval, params.allowedLossyError), params);
return create_codec<Strategy>(DefaultTraits<uint16_t, Triplet<uint16_t>>(maxval, params.allowedLossyError), params);
if (params.components == 4)
return create_codec<Strategy>(DefaultTraits<uint16_t, Quad<uint16_t>>(maxval, params.allowedLossyError), params);
}
return create_codec<Strategy>(DefaultTraits<uint16_t, uint16_t>(maxval, params.allowedLossyError), params);

View File

@ -2,9 +2,12 @@
#include <charls/jpegls_error.h>
using std::string;
using std::error_category;
namespace charls {
class jpegls_category_t : public std::error_category
class jpegls_category final : public error_category
{
public:
const char* name() const noexcept override
@ -12,7 +15,7 @@ public:
return "charls::jpegls";
}
std::string message(int error_value) const override
string message(int error_value) const override
{
return charls_get_error_message(error_value);
}
@ -22,9 +25,9 @@ public:
using namespace charls;
const void* CHARLS_API_CALLING_CONVENTION charls_jpegls_category()
const void* CHARLS_API_CALLING_CONVENTION charls_get_jpegls_category()
{
static jpegls_category_t instance;
static class jpegls_category instance;
return &instance;
}
@ -33,7 +36,7 @@ const char* CHARLS_API_CALLING_CONVENTION charls_get_error_message(int32_t error
switch (static_cast<jpegls_errc>(error_value))
{
case jpegls_errc::success:
return "";
return "Success";
case jpegls_errc::invalid_argument:
return "Invalid argument";
@ -53,14 +56,17 @@ const char* CHARLS_API_CALLING_CONVENTION charls_get_error_message(int32_t error
case jpegls_errc::invalid_argument_interleave_mode:
return "The interleave mode is not None, Sample, Line) or invalid in combination with component count";
case jpegls_errc::invalid_argument_destination:
return "The destination buffer or stream is not set";
case jpegls_errc::invalid_argument_near_lossless:
return "The near lossless argument is outside the range [0, 255]";
case jpegls_errc::invalid_argument_source:
return "The source buffer or stream is not set";
case jpegls_errc::invalid_argument_spiff_entry_size:
return "The argument for the entry size parameter is outside the range [0, 65528]";
case jpegls_errc::invalid_argument_thumbnail:
return "The arguments for the thumbnail and the dimensions don't match";
case jpegls_errc::invalid_argument_color_transformation:
return "The argument for the color component is not (None, Hp1, Hp2, Hp3) or invalid in combination with component count";
case jpegls_errc::invalid_argument_pc_parameters:
return "The argument for the JPEG-LS preset coding parameters is not valid";
case jpegls_errc::start_of_image_marker_not_found:
return "Invalid JPEG-LS stream, first JPEG marker is not a Start Of Image (SOI) marker";
@ -89,6 +95,9 @@ const char* CHARLS_API_CALLING_CONVENTION charls_get_error_message(int32_t error
case jpegls_errc::jpegls_preset_extended_parameter_type_not_supported:
return "Unsupported JPEG-LS stream, JPEG-LS preset parameters segment contains an JPEG-LS Extended (ISO/IEC 14495-2) type";
case jpegls_errc::missing_end_of_spiff_directory:
return "Invalid JPEG-LS stream, SPIFF header without End Of Directory (EOD) entry";
case jpegls_errc::invalid_parameter_bits_per_sample:
return "Invalid JPEG-LS stream, The bit per sample (sample precision) parameter is not in the range [2, 16]";
@ -107,6 +116,9 @@ const char* CHARLS_API_CALLING_CONVENTION charls_get_error_message(int32_t error
case jpegls_errc::too_much_encoded_data:
return "Invalid JPEG-LS stream, the decoding process is ready but the source buffer still contains encoded data";
case jpegls_errc::invalid_operation:
return "Method call is invalid for the current state";
case jpegls_errc::bit_depth_for_transform_not_supported:
return "The bit depth for the transformation is not supported";
@ -141,5 +153,5 @@ const char* CHARLS_API_CALLING_CONVENTION charls_get_error_message(int32_t error
return "Invalid JPEG-LS stream, interleave mode is outside the range [0, 2] or conflicts with component count";
}
return nullptr;
return "Unknown";
}

View File

@ -10,8 +10,7 @@
#include <algorithm>
#include <cassert>
namespace charls
{
namespace charls {
/// <summary>Clamping function as defined by ISO/IEC 14495-1, Figure C.3</summary>
inline int32_t clamp(const int32_t i, const int32_t j, const int32_t maximumSampleValue) noexcept
@ -25,7 +24,7 @@ inline int32_t clamp(const int32_t i, const int32_t j, const int32_t maximumSamp
/// <summary>Default coding threshold values as defined by ISO/IEC 14495-1, C.2.4.1.1.1</summary>
inline JpegLSPresetCodingParameters ComputeDefault(const int32_t maximumSampleValue, const int32_t allowedLossyError) noexcept
{
ASSERT(maximumSampleValue >= 3 && maximumSampleValue <= UINT16_MAX);
ASSERT(maximumSampleValue <= UINT16_MAX);
ASSERT(allowedLossyError >= 0 && allowedLossyError <= MaximumNearLossless(maximumSampleValue));
if (maximumSampleValue >= 128)
@ -74,4 +73,85 @@ inline bool IsDefault(const JpegLSPresetCodingParameters& custom) noexcept
return true;
}
/// <summary>Default coding threshold values as defined by ISO/IEC 14495-1, C.2.4.1.1.1</summary>
inline jpegls_pc_parameters compute_default(const int32_t maximum_sample_value, const int32_t near_lossless) noexcept
{
ASSERT(maximum_sample_value <= UINT16_MAX);
ASSERT(near_lossless >= 0 && near_lossless <= MaximumNearLossless(maximum_sample_value));
if (maximum_sample_value >= 128)
{
const int32_t factor = (std::min(maximum_sample_value, 4095) + 128) / 256;
const int threshold1 = clamp(factor * (DefaultThreshold1 - 2) + 2 + 3 * near_lossless, near_lossless + 1, maximum_sample_value);
const int threshold2 = clamp(factor * (DefaultThreshold2 - 3) + 3 + 5 * near_lossless, threshold1, maximum_sample_value); //-V537
return {
maximum_sample_value,
threshold1,
threshold2,
clamp(factor * (DefaultThreshold3 - 4) + 4 + 7 * near_lossless, threshold2, maximum_sample_value),
DefaultResetValue};
}
const int32_t factor = 256 / (maximum_sample_value + 1);
const int threshold1 = clamp(std::max(2, DefaultThreshold1 / factor + 3 * near_lossless), near_lossless + 1, maximum_sample_value);
const int threshold2 = clamp(std::max(3, DefaultThreshold2 / factor + 5 * near_lossless), threshold1, maximum_sample_value);
return {
maximum_sample_value,
threshold1,
threshold2,
clamp(std::max(4, DefaultThreshold3 / factor + 7 * near_lossless), threshold2, maximum_sample_value),
DefaultResetValue};
}
inline bool is_default(const jpegls_pc_parameters& preset_coding_parameters) noexcept
{
if (preset_coding_parameters.maximum_sample_value != 0)
return false;
if (preset_coding_parameters.threshold1 != 0)
return false;
if (preset_coding_parameters.threshold2 != 0)
return false;
if (preset_coding_parameters.threshold3 != 0)
return false;
if (preset_coding_parameters.reset_value != 0)
return false;
return true;
}
inline bool is_valid(const jpegls_pc_parameters& pc_parameters, const int32_t maximum_component_value, const int32_t near_lossless) noexcept
{
ASSERT(maximum_component_value <= UINT16_MAX);
// ISO/IEC 14495-1, C.2.4.1.1, Table C.1 defines the valid JPEG-LS preset coding parameters values.
if (pc_parameters.maximum_sample_value != 0 && (pc_parameters.maximum_sample_value < 1 || pc_parameters.maximum_sample_value > maximum_component_value))
return false;
const int32_t maximum_sample_value = pc_parameters.maximum_sample_value != 0 ? pc_parameters.maximum_sample_value : maximum_component_value;
if (pc_parameters.threshold1 != 0 && (pc_parameters.threshold1 < near_lossless + 1 || pc_parameters.threshold1 > maximum_sample_value))
return false;
const jpegls_pc_parameters default_parameters{compute_default(maximum_sample_value, near_lossless)};
const int32_t threshold1 = pc_parameters.threshold1 != 0 ? pc_parameters.threshold1 : default_parameters.threshold1;
if (pc_parameters.threshold2 != 0 && (pc_parameters.threshold2 < threshold1 || pc_parameters.threshold2 > maximum_sample_value))
return false;
const int32_t threshold2 = pc_parameters.threshold2 != 0 ? pc_parameters.threshold2 : default_parameters.threshold2;
if (pc_parameters.threshold3 != 0 && (pc_parameters.threshold3 < threshold2 || pc_parameters.threshold3 > maximum_sample_value))
return false;
if (pc_parameters.reset_value != 0 && (pc_parameters.reset_value < 3 || pc_parameters.reset_value > std::max(255, maximum_sample_value)))
return false;
return true;
}
} // namespace charls

View File

@ -2,12 +2,11 @@
#pragma once
#include <cstring>
#include <array>
#include <cassert>
#include <cstring>
namespace charls
{
namespace charls {
// Tables for fast decoding of short Golomb Codes.
struct Code final
@ -50,7 +49,7 @@ public:
const int32_t length = c.GetLength();
ASSERT(static_cast<size_t>(length) <= byte_bit_count);
for (int32_t i = 0; i < 1 << (byte_bit_count - length); ++i)
for (size_t i = 0; i < static_cast<size_t>(1U) << (byte_bit_count - length); ++i)
{
ASSERT(types_[(static_cast<size_t>(value) << (byte_bit_count - length)) + i].GetLength() == 0);
types_[(static_cast<size_t>(value) << (byte_bit_count - length)) + i] = c;

View File

@ -5,8 +5,7 @@
#include "constants.h"
#include <cstdint>
namespace charls
{
namespace charls {
// Optimized trait classes for lossless compression of 8 bit color and 8/16 bit monochrome images.
// This class assumes MaximumSampleValue correspond to a whole number of bits, and no custom ResetValue is set when encoding.
@ -18,11 +17,11 @@ struct LosslessTraitsImpl
enum
{
NEAR = 0,
bpp = bitsPerPixel,
qbpp = bitsPerPixel,
NEAR = 0,
bpp = bitsPerPixel,
qbpp = bitsPerPixel,
RANGE = (1 << bpp),
MAXVAL= (1 << bpp) - 1,
MAXVAL = (1 << bpp) - 1,
LIMIT = 2 * (bitsPerPixel + std::max(8, bitsPerPixel)),
RESET = DefaultResetValue
};
@ -39,9 +38,10 @@ struct LosslessTraitsImpl
// The following optimization is implementation-dependent (works on x86 and ARM, see charlstest).
#if defined(__clang__)
__attribute__((no_sanitize("shift")))
__attribute__((no_sanitize("shift")))
#endif
FORCE_INLINE constexpr static int32_t ModuloRange(int32_t errorValue) noexcept
FORCE_INLINE constexpr static int32_t
ModuloRange(int32_t errorValue) noexcept
{
return static_cast<int32_t>(errorValue << (int32_t_bit_count - bpp)) >> (int32_t_bit_count - bpp);
}
@ -56,7 +56,7 @@ struct LosslessTraitsImpl
if ((Pxc & MAXVAL) == Pxc)
return Pxc;
return (~(Pxc >> (int32_t_bit_count-1))) & MAXVAL;
return (~(Pxc >> (int32_t_bit_count - 1))) & MAXVAL;
}
};

View File

@ -3,13 +3,14 @@
#pragma once
#include <charls/jpegls_error.h>
#include <charls/charls_legacy.h>
#include "util.h"
#include <vector>
#include <sstream>
#include <cstring>
#include <algorithm>
#include <cstring>
#include <sstream>
#include <vector>
//
@ -21,8 +22,7 @@
// This mechanism could be used to encode/decode images as they are received.
//
namespace charls
{
namespace charls {
class ProcessLine
{
@ -73,20 +73,20 @@ private:
inline void ByteSwap(void* data, int count)
{
if (static_cast<unsigned int>(count) & 1u)
if (static_cast<unsigned int>(count) & 1U)
throw jpegls_error{jpegls_errc::invalid_encoded_data};
const auto data32 = static_cast<unsigned int*>(data);
for(auto i = 0; i < count / 4; i++)
for (auto i = 0; i < count / 4; i++)
{
const auto value = data32[i];
data32[i] = ((value >> 8u) & 0x00FF00FFu) | ((value & 0x00FF00FFu) << 8u);
data32[i] = ((value >> 8U) & 0x00FF00FFU) | ((value & 0x00FF00FFU) << 8U);
}
const auto data8 = static_cast<unsigned char*>(data);
if ((count % 4) != 0)
{
std::swap(data8[count-2], data8[count-1]);
std::swap(data8[count - 2], data8[count - 1]);
}
}
@ -94,15 +94,15 @@ class PostProcessSingleStream final : public ProcessLine
{
public:
PostProcessSingleStream(std::basic_streambuf<char>* rawData, const JlsParameters& params, size_t bytesPerPixel) noexcept :
rawData_(rawData),
bytesPerPixel_(bytesPerPixel),
bytesPerLine_(params.stride)
rawData_{rawData},
bytesPerPixel_{bytesPerPixel},
bytesPerLine_{static_cast<size_t>(params.stride)}
{
}
void NewLineRequested(void* destination, int pixelCount, int /*destStride*/) override
{
auto bytesToRead = static_cast<std::streamsize>(pixelCount) * bytesPerPixel_;
auto bytesToRead = static_cast<std::streamsize>(static_cast<std::streamsize>(pixelCount) * bytesPerPixel_);
while (bytesToRead != 0)
{
const auto bytesRead = rawData_->sgetn(static_cast<char*>(destination), bytesToRead);
@ -126,7 +126,7 @@ public:
void NewLineDecoded(const void* source, int pixelCount, int /*sourceStride*/) override
{
const auto bytesToWrite = pixelCount * bytesPerPixel_;
const auto bytesWritten = static_cast<size_t>(rawData_->sputn(static_cast<const char*>(source), bytesToWrite));
const auto bytesWritten = static_cast<size_t>(rawData_->sputn(static_cast<const char*>(source), static_cast<std::streamsize>(bytesToWrite)));
if (bytesWritten != bytesToWrite)
throw jpegls_error{jpegls_errc::destination_buffer_too_small};
}
@ -146,7 +146,7 @@ void TransformLineToQuad(const T* ptypeInput, int32_t pixelStrideIn, Quad<T>* by
for (auto x = 0; x < cpixel; ++x)
{
const Quad<T> pixel(transform(ptypeInput[x], ptypeInput[x + pixelStrideIn], ptypeInput[x + 2*pixelStrideIn]), ptypeInput[x + 3 * pixelStrideIn]);
const Quad<T> pixel(transform(ptypeInput[x], ptypeInput[x + pixelStrideIn], ptypeInput[x + 2 * pixelStrideIn]), ptypeInput[x + 3 * pixelStrideIn]);
ptypeBuffer[x] = pixel;
}
}
@ -210,7 +210,7 @@ void TransformLineToTriplet(const T* ptypeInput, int32_t pixelStrideIn, Triplet<
for (auto x = 0; x < cpixel; ++x)
{
ptypeBuffer[x] = transform(ptypeInput[x], ptypeInput[x + pixelStrideIn], ptypeInput[x + 2*pixelStrideIn]);
ptypeBuffer[x] = transform(ptypeInput[x], ptypeInput[x + pixelStrideIn], ptypeInput[x + 2 * pixelStrideIn]);
}
}
@ -228,7 +228,7 @@ void TransformTripletToLine(const Triplet<T>* byteInput, int32_t pixelStrideIn,
ptypeBuffer[x] = colorTransformed.v1;
ptypeBuffer[x + pixelStride] = colorTransformed.v2;
ptypeBuffer[x + 2 *pixelStride] = colorTransformed.v3;
ptypeBuffer[x + 2 * pixelStride] = colorTransformed.v3;
}
}
@ -238,12 +238,12 @@ class ProcessTransformed final : public ProcessLine
{
public:
ProcessTransformed(ByteStreamInfo rawStream, const JlsParameters& info, TRANSFORM transform) :
params_(info),
params_{info},
tempLine_(static_cast<size_t>(info.width) * info.components),
buffer_(static_cast<size_t>(info.width) * info.components * sizeof(size_type)),
transform_(transform),
inverseTransform_(transform),
rawPixels_(rawStream)
transform_{transform},
inverseTransform_{transform},
rawPixels_{rawStream}
{
}
@ -284,7 +284,7 @@ public:
if (params_.components == 3)
{
if (params_.interleaveMode == InterleaveMode::Sample)
if (params_.interleaveMode == interleave_mode::sample)
{
TransformLine(static_cast<Triplet<size_type>*>(dest), static_cast<const Triplet<size_type>*>(source), pixelCount, transform_);
}
@ -295,11 +295,11 @@ public:
}
else if (params_.components == 4)
{
if (params_.interleaveMode == InterleaveMode::Sample)
if (params_.interleaveMode == interleave_mode::sample)
{
TransformLine(static_cast<Quad<size_type>*>(dest), static_cast<const Quad<size_type>*>(source), pixelCount, transform_);
}
else if (params_.interleaveMode == InterleaveMode::Line)
else if (params_.interleaveMode == interleave_mode::line)
{
TransformQuadToLine(static_cast<const Quad<size_type>*>(source), pixelCount, static_cast<size_type*>(dest), destStride, transform_);
}
@ -310,7 +310,7 @@ public:
{
if (params_.components == 3)
{
if (params_.interleaveMode == InterleaveMode::Sample)
if (params_.interleaveMode == interleave_mode::sample)
{
TransformLine(static_cast<Triplet<size_type>*>(rawData), static_cast<const Triplet<size_type>*>(pSrc), pixelCount, inverseTransform_);
}
@ -321,11 +321,11 @@ public:
}
else if (params_.components == 4)
{
if (params_.interleaveMode == InterleaveMode::Sample)
if (params_.interleaveMode == interleave_mode::sample)
{
TransformLine(static_cast<Quad<size_type>*>(rawData), static_cast<const Quad<size_type>*>(pSrc), pixelCount, inverseTransform_);
}
else if (params_.interleaveMode == InterleaveMode::Line)
else if (params_.interleaveMode == interleave_mode::line)
{
TransformLineToQuad(static_cast<const size_type*>(pSrc), byteStride, static_cast<Quad<size_type>*>(rawData), pixelCount, inverseTransform_);
}

View File

@ -2,19 +2,18 @@
#pragma once
#include "lookup_table.h"
#include "context_run_mode.h"
#include "context.h"
#include "color_transform.h"
#include "context.h"
#include "context_run_mode.h"
#include "lookup_table.h"
#include "process_line.h"
#include <sstream>
#include <array>
#include <sstream>
// This file contains the code for handling a "scan". Usually an image is encoded as a single scan.
namespace charls
{
namespace charls {
extern CTable decodingTables[16];
extern std::vector<signed char> rgquant8Ll;
@ -85,7 +84,7 @@ constexpr int32_t UnMapErrVal(int32_t mappedError) noexcept
constexpr int32_t GetMappedErrVal(int32_t errorValue) noexcept
{
const int32_t mappedError = (errorValue >> (int32_t_bit_count-2)) ^ (2 * errorValue);
const int32_t mappedError = (errorValue >> (int32_t_bit_count - 2)) ^ (2 * errorValue);
return mappedError;
}
@ -107,7 +106,7 @@ public:
traits{std::move(inTraits)},
width_{params.width}
{
if (Info().interleaveMode == InterleaveMode::None)
if (Info().interleaveMode == interleave_mode::none)
{
Info().components = 1;
}
@ -127,7 +126,7 @@ public:
bool IsInterleaved() noexcept
{
if (Info().interleaveMode == InterleaveMode::None)
if (Info().interleaveMode == interleave_mode::none)
return false;
if (Info().components == 1)
@ -225,7 +224,7 @@ protected:
// Encode/decode a single sample. Performance wise the #1 important functions
template<typename Traits, typename Strategy>
typename Traits::SAMPLE JlsCodec<Traits,Strategy>::DoRegular(int32_t Qs, int32_t, int32_t pred, DecoderStrategy*)
typename Traits::SAMPLE JlsCodec<Traits, Strategy>::DoRegular(int32_t Qs, int32_t, int32_t pred, DecoderStrategy*)
{
const int32_t sign = BitWiseSign(Qs);
JlsContext& ctx = contexts_[ApplySign(Qs, sign)];
@ -257,7 +256,7 @@ typename Traits::SAMPLE JlsCodec<Traits,Strategy>::DoRegular(int32_t Qs, int32_t
template<typename Traits, typename Strategy>
typename Traits::SAMPLE JlsCodec<Traits,Strategy>::DoRegular(int32_t Qs, int32_t x, int32_t pred, EncoderStrategy*)
typename Traits::SAMPLE JlsCodec<Traits, Strategy>::DoRegular(int32_t Qs, int32_t x, int32_t pred, EncoderStrategy*)
{
const int32_t sign = BitWiseSign(Qs);
JlsContext& ctx = contexts_[ApplySign(Qs, sign)];
@ -284,7 +283,7 @@ inline std::pair<int32_t, int32_t> CreateEncodedValue(int32_t k, int32_t mappedE
inline CTable InitTable(int32_t k) noexcept
{
CTable table;
for (short nerr = 0; ; nerr++)
for (short nerr = 0;; nerr++)
{
// Q is not used when k != 0
const int32_t merrval = GetMappedErrVal(nerr);
@ -296,7 +295,7 @@ inline CTable InitTable(int32_t k) noexcept
table.AddEntry(static_cast<uint8_t>(pairCode.second), code);
}
for (short nerr = -1; ; nerr--)
for (short nerr = -1;; nerr--)
{
// Q is not used when k != 0
const int32_t merrval = GetMappedErrVal(nerr);
@ -361,7 +360,8 @@ FORCE_INLINE void JlsCodec<Traits, Strategy>::EncodeMappedValue(int32_t k, int32
// C4127 = conditional expression is constant (caused by some template methods that are not fully specialized) [VS2017]
// 6326 = Potential comparison of a constant with another constant. (false warning, triggered by template construction in Checked build)
MSVC_WARNING_SUPPRESS(4127 6326)
// 26814 = The const variable 'RANGE' can be computed at compile-time. [incorrect warning, VS 16.3.0 P3]
MSVC_WARNING_SUPPRESS(4127 6326 26814)
// Sets up a lookup table to "Quantize" sample difference.
@ -411,28 +411,28 @@ void JlsCodec<Traits, Strategy>::InitQuantizationLUT()
MSVC_WARNING_UNSUPPRESS()
template<typename Traits, typename Strategy>
signed char JlsCodec<Traits,Strategy>::QuantizeGradientOrg(int32_t Di) const noexcept
signed char JlsCodec<Traits, Strategy>::QuantizeGradientOrg(int32_t Di) const noexcept
{
if (Di <= -T3) return -4;
if (Di <= -T2) return -3;
if (Di <= -T1) return -2;
if (Di < -traits.NEAR) return -1;
if (Di <= traits.NEAR) return 0;
if (Di < T1) return 1;
if (Di < T2) return 2;
if (Di < T3) return 3;
if (Di <= -T3) return -4;
if (Di <= -T2) return -3;
if (Di <= -T1) return -2;
if (Di < -traits.NEAR) return -1;
if (Di <= traits.NEAR) return 0;
if (Di < T1) return 1;
if (Di < T2) return 2;
if (Di < T3) return 3;
return 4;
return 4;
}
// RI = Run interruption: functions that handle the sample terminating a run.
template<typename Traits, typename Strategy>
int32_t JlsCodec<Traits,Strategy>::DecodeRIError(CContextRunMode& ctx)
int32_t JlsCodec<Traits, Strategy>::DecodeRIError(CContextRunMode& ctx)
{
const int32_t k = ctx.GetGolomb();
const int32_t EMErrval = DecodeValue(k, traits.LIMIT - J[RUNindex_]-1, traits.qbpp);
const int32_t EMErrval = DecodeValue(k, traits.LIMIT - J[RUNindex_] - 1, traits.qbpp);
const int32_t errorValue = ctx.ComputeErrVal(EMErrval + ctx.nRItype_, k);
ctx.UpdateVariables(errorValue, EMErrval);
return errorValue;
@ -440,33 +440,33 @@ int32_t JlsCodec<Traits,Strategy>::DecodeRIError(CContextRunMode& ctx)
template<typename Traits, typename Strategy>
void JlsCodec<Traits,Strategy>::EncodeRIError(CContextRunMode& ctx, int32_t errorValue)
void JlsCodec<Traits, Strategy>::EncodeRIError(CContextRunMode& ctx, int32_t errorValue)
{
const int32_t k = ctx.GetGolomb();
const bool map = ctx.ComputeMap(errorValue, k);
const int32_t EMErrval = 2 * std::abs(errorValue) - ctx.nRItype_ - static_cast<int32_t>(map);
ASSERT(errorValue == ctx.ComputeErrVal(EMErrval + ctx.nRItype_, k));
EncodeMappedValue(k, EMErrval, traits.LIMIT-J[RUNindex_]-1);
EncodeMappedValue(k, EMErrval, traits.LIMIT - J[RUNindex_] - 1);
ctx.UpdateVariables(errorValue, EMErrval);
}
template<typename Traits, typename Strategy>
Triplet<typename Traits::SAMPLE> JlsCodec<Traits,Strategy>::DecodeRIPixel(Triplet<SAMPLE> Ra, Triplet<SAMPLE> Rb)
Triplet<typename Traits::SAMPLE> JlsCodec<Traits, Strategy>::DecodeRIPixel(Triplet<SAMPLE> Ra, Triplet<SAMPLE> Rb)
{
const int32_t errorValue1 = DecodeRIError(contextRunmode_[0]);
const int32_t errorValue2 = DecodeRIError(contextRunmode_[0]);
const int32_t errorValue3 = DecodeRIError(contextRunmode_[0]);
return Triplet<SAMPLE>(traits.ComputeReconstructedSample(Rb.v1, errorValue1 * Sign(Rb.v1 - Ra.v1)),
traits.ComputeReconstructedSample(Rb.v2, errorValue2 * Sign(Rb.v2 - Ra.v2)),
traits.ComputeReconstructedSample(Rb.v3, errorValue3 * Sign(Rb.v3 - Ra.v3)));
return Triplet<SAMPLE>(traits.ComputeReconstructedSample(Rb.v1, errorValue1 * Sign(Rb.v1 - Ra.v1)),
traits.ComputeReconstructedSample(Rb.v2, errorValue2 * Sign(Rb.v2 - Ra.v2)),
traits.ComputeReconstructedSample(Rb.v3, errorValue3 * Sign(Rb.v3 - Ra.v3)));
}
template<typename Traits, typename Strategy>
Triplet<typename Traits::SAMPLE> JlsCodec<Traits,Strategy>::EncodeRIPixel(Triplet<SAMPLE> x, Triplet<SAMPLE> Ra, Triplet<SAMPLE> Rb)
Triplet<typename Traits::SAMPLE> JlsCodec<Traits, Strategy>::EncodeRIPixel(Triplet<SAMPLE> x, Triplet<SAMPLE> Ra, Triplet<SAMPLE> Rb)
{
const int32_t errorValue1 = traits.ComputeErrVal(Sign(Rb.v1 - Ra.v1) * (x.v1 - Rb.v1));
EncodeRIError(contextRunmode_[0], errorValue1);
@ -477,9 +477,9 @@ Triplet<typename Traits::SAMPLE> JlsCodec<Traits,Strategy>::EncodeRIPixel(Triple
const int32_t errorValue3 = traits.ComputeErrVal(Sign(Rb.v3 - Ra.v3) * (x.v3 - Rb.v3));
EncodeRIError(contextRunmode_[0], errorValue3);
return Triplet<SAMPLE>(traits.ComputeReconstructedSample(Rb.v1, errorValue1 * Sign(Rb.v1 - Ra.v1)),
traits.ComputeReconstructedSample(Rb.v2, errorValue2 * Sign(Rb.v2 - Ra.v2)),
traits.ComputeReconstructedSample(Rb.v3, errorValue3 * Sign(Rb.v3 - Ra.v3)));
return Triplet<SAMPLE>(traits.ComputeReconstructedSample(Rb.v1, errorValue1 * Sign(Rb.v1 - Ra.v1)),
traits.ComputeReconstructedSample(Rb.v2, errorValue2 * Sign(Rb.v2 - Ra.v2)),
traits.ComputeReconstructedSample(Rb.v3, errorValue3 * Sign(Rb.v3 - Ra.v3)));
}
template<typename Traits, typename Strategy>
@ -493,7 +493,7 @@ Quad<typename Traits::SAMPLE> JlsCodec<Traits, Strategy>::DecodeRIPixel(Quad<SAM
return Quad<SAMPLE>(Triplet<SAMPLE>(traits.ComputeReconstructedSample(Rb.v1, errorValue1 * Sign(Rb.v1 - Ra.v1)),
traits.ComputeReconstructedSample(Rb.v2, errorValue2 * Sign(Rb.v2 - Ra.v2)),
traits.ComputeReconstructedSample(Rb.v3, errorValue3 * Sign(Rb.v3 - Ra.v3))),
traits.ComputeReconstructedSample(Rb.v4, errorValue4 * Sign(Rb.v4 - Ra.v4)));
traits.ComputeReconstructedSample(Rb.v4, errorValue4 * Sign(Rb.v4 - Ra.v4)));
}
@ -515,12 +515,12 @@ Quad<typename Traits::SAMPLE> JlsCodec<Traits, Strategy>::EncodeRIPixel(Quad<SAM
return Quad<SAMPLE>(Triplet<SAMPLE>(traits.ComputeReconstructedSample(Rb.v1, errorValue1 * Sign(Rb.v1 - Ra.v1)),
traits.ComputeReconstructedSample(Rb.v2, errorValue2 * Sign(Rb.v2 - Ra.v2)),
traits.ComputeReconstructedSample(Rb.v3, errorValue3 * Sign(Rb.v3 - Ra.v3))),
traits.ComputeReconstructedSample(Rb.v4, errorValue4 * Sign(Rb.v4 - Ra.v4)));
traits.ComputeReconstructedSample(Rb.v4, errorValue4 * Sign(Rb.v4 - Ra.v4)));
}
template<typename Traits, typename Strategy>
typename Traits::SAMPLE JlsCodec<Traits,Strategy>::DecodeRIPixel(int32_t Ra, int32_t Rb)
typename Traits::SAMPLE JlsCodec<Traits, Strategy>::DecodeRIPixel(int32_t Ra, int32_t Rb)
{
if (std::abs(Ra - Rb) <= traits.NEAR)
{
@ -534,7 +534,7 @@ typename Traits::SAMPLE JlsCodec<Traits,Strategy>::DecodeRIPixel(int32_t Ra, int
template<typename Traits, typename Strategy>
typename Traits::SAMPLE JlsCodec<Traits,Strategy>::EncodeRIPixel(int32_t x, int32_t Ra, int32_t Rb)
typename Traits::SAMPLE JlsCodec<Traits, Strategy>::EncodeRIPixel(int32_t x, int32_t Ra, int32_t Rb)
{
if (std::abs(Ra - Rb) <= traits.NEAR)
{
@ -622,7 +622,7 @@ int32_t JlsCodec<Traits, Strategy>::DoRunMode(int32_t index, EncoderStrategy*)
int32_t runLength = 0;
while (traits.IsNear(ptypeCurX[runLength],Ra))
while (traits.IsNear(ptypeCurX[runLength], Ra))
{
ptypeCurX[runLength] = Ra;
runLength++;
@ -645,7 +645,7 @@ int32_t JlsCodec<Traits, Strategy>::DoRunMode(int32_t index, EncoderStrategy*)
template<typename Traits, typename Strategy>
int32_t JlsCodec<Traits, Strategy>::DoRunMode(int32_t startIndex, DecoderStrategy*)
{
const PIXEL Ra = currentLine_[startIndex-1];
const PIXEL Ra = currentLine_[startIndex - 1];
const int32_t runLength = DecodeRunPixels(Ra, currentLine_ + startIndex, width_ - startIndex);
const int32_t endIndex = startIndex + runLength;
@ -666,12 +666,12 @@ template<typename Traits, typename Strategy>
void JlsCodec<Traits, Strategy>::DoLine(SAMPLE*)
{
int32_t index = 0;
int32_t Rb = previousLine_[index-1];
int32_t Rb = previousLine_[index - 1];
int32_t Rd = previousLine_[index];
while (index < width_)
{
const int32_t Ra = currentLine_[index -1];
const int32_t Ra = currentLine_[index - 1];
const int32_t Rc = Rb;
Rb = Rd;
Rd = previousLine_[index + 1];
@ -698,7 +698,7 @@ template<typename Traits, typename Strategy>
void JlsCodec<Traits, Strategy>::DoLine(Triplet<SAMPLE>*)
{
int32_t index = 0;
while(index < width_)
while (index < width_)
{
const Triplet<SAMPLE> Ra = currentLine_[index - 1];
const Triplet<SAMPLE> Rc = previousLine_[index - 1];
@ -770,7 +770,7 @@ template<typename Traits, typename Strategy>
void JlsCodec<Traits, Strategy>::DoScan()
{
const int32_t pixelStride = width_ + 4;
const int components = Info().interleaveMode == InterleaveMode::Line ? Info().components : 1;
const int components = Info().interleaveMode == interleave_mode::line ? Info().components : 1;
std::vector<PIXEL> vectmp(static_cast<size_t>(2) * components * pixelStride);
std::vector<int32_t> rgRUNindex(components);
@ -816,23 +816,24 @@ std::unique_ptr<ProcessLine> JlsCodec<Traits, Strategy>::CreateProcess(ByteStrea
{
if (!IsInterleaved())
{
return info.rawData ?
std::unique_ptr<ProcessLine>(std::make_unique<PostProcessSingleComponent>(info.rawData, Info(), sizeof(typename Traits::PIXEL))) :
std::unique_ptr<ProcessLine>(std::make_unique<PostProcessSingleStream>(info.rawStream, Info(), sizeof(typename Traits::PIXEL)));
return info.rawData ? std::unique_ptr<ProcessLine>(std::make_unique<PostProcessSingleComponent>(info.rawData, Info(), sizeof(typename Traits::PIXEL))) : std::unique_ptr<ProcessLine>(std::make_unique<PostProcessSingleStream>(info.rawStream, Info(), sizeof(typename Traits::PIXEL)));
}
if (Info().colorTransformation == ColorTransformation::None)
if (Info().colorTransformation == color_transformation::none)
return std::make_unique<ProcessTransformed<TransformNone<typename Traits::SAMPLE>>>(info, Info(), TransformNone<SAMPLE>());
if (Info().bitsPerSample == sizeof(SAMPLE) * 8)
{
switch (Info().colorTransformation)
{
case ColorTransformation::HP1: return std::make_unique<ProcessTransformed<TransformHp1<SAMPLE>>>(info, Info(), TransformHp1<SAMPLE>());
case ColorTransformation::HP2: return std::make_unique<ProcessTransformed<TransformHp2<SAMPLE>>>(info, Info(), TransformHp2<SAMPLE>());
case ColorTransformation::HP3: return std::make_unique<ProcessTransformed<TransformHp3<SAMPLE>>>(info, Info(), TransformHp3<SAMPLE>());
default:
throw jpegls_error{jpegls_errc::color_transform_not_supported};
case color_transformation::hp1:
return std::make_unique<ProcessTransformed<TransformHp1<SAMPLE>>>(info, Info(), TransformHp1<SAMPLE>());
case color_transformation::hp2:
return std::make_unique<ProcessTransformed<TransformHp2<SAMPLE>>>(info, Info(), TransformHp2<SAMPLE>());
case color_transformation::hp3:
return std::make_unique<ProcessTransformed<TransformHp3<SAMPLE>>>(info, Info(), TransformHp3<SAMPLE>());
default:
throw jpegls_error{jpegls_errc::color_transform_not_supported};
}
}
@ -841,11 +842,14 @@ std::unique_ptr<ProcessLine> JlsCodec<Traits, Strategy>::CreateProcess(ByteStrea
const int shift = 16 - Info().bitsPerSample;
switch (Info().colorTransformation)
{
case ColorTransformation::HP1: return std::make_unique<ProcessTransformed<TransformShifted<TransformHp1<uint16_t>>>>(info, Info(), TransformShifted<TransformHp1<uint16_t>>(shift));
case ColorTransformation::HP2: return std::make_unique<ProcessTransformed<TransformShifted<TransformHp2<uint16_t>>>>(info, Info(), TransformShifted<TransformHp2<uint16_t>>(shift));
case ColorTransformation::HP3: return std::make_unique<ProcessTransformed<TransformShifted<TransformHp3<uint16_t>>>>(info, Info(), TransformShifted<TransformHp3<uint16_t>>(shift));
default:
throw jpegls_error{jpegls_errc::color_transform_not_supported};
case color_transformation::hp1:
return std::make_unique<ProcessTransformed<TransformShifted<TransformHp1<uint16_t>>>>(info, Info(), TransformShifted<TransformHp1<uint16_t>>(shift));
case color_transformation::hp2:
return std::make_unique<ProcessTransformed<TransformShifted<TransformHp2<uint16_t>>>>(info, Info(), TransformShifted<TransformHp2<uint16_t>>(shift));
case color_transformation::hp3:
return std::make_unique<ProcessTransformed<TransformShifted<TransformHp3<uint16_t>>>>(info, Info(), TransformShifted<TransformHp3<uint16_t>>(shift));
default:
throw jpegls_error{jpegls_errc::color_transform_not_supported};
}
}

View File

@ -2,7 +2,11 @@
#pragma once
#include <charls/public_types.h>
#include <charls/charls_legacy.h>
#include <charls/jpegls_error.h>
#include <cassert>
#include <cstring>
#include <vector>
// Use an uppercase alias for assert to make it clear that it is a pre-processor macro.
@ -13,35 +17,86 @@
// Note: usage of FORCE_INLINE may be reduced in the future as the latest generation of C++ compilers
// can handle optimization by themselves.
#ifndef FORCE_INLINE
# ifdef _MSC_VER
# ifdef NDEBUG
# define FORCE_INLINE __forceinline
# else
# define FORCE_INLINE
# endif
# else
# define FORCE_INLINE
# endif
#ifdef _MSC_VER
#ifdef NDEBUG
#define FORCE_INLINE __forceinline
#else
#define FORCE_INLINE
#endif
#else
#define FORCE_INLINE
#endif
#endif
#ifdef _MSC_VER
#define MSVC_WARNING_SUPPRESS(x) __pragma(warning(push)) __pragma(warning(disable : x)) // NOLINT(misc-macro-parentheses, bugprone-macro-parentheses)
#define MSVC_WARNING_SUPPRESS(x) __pragma(warning(push)) \
__pragma(warning(disable \
: x)) // NOLINT(misc-macro-parentheses, bugprone-macro-parentheses)
#define MSVC_WARNING_UNSUPPRESS() __pragma(warning(pop))
#else
#define MSVC_WARNING_SUPPRESS(x)
#define MSVC_WARNING_UNSUPPRESS()
#endif
namespace charls
namespace charls {
inline jpegls_errc to_jpegls_errc() noexcept
{
try
{
// re-trow the exception.
throw;
}
catch (const jpegls_error& error)
{
return static_cast<jpegls_errc>(error.code().value());
}
catch (const std::bad_alloc&)
{
return jpegls_errc::not_enough_memory;
}
catch (...)
{
return jpegls_errc::unexpected_failure;
}
}
inline void clear_error_message(char* errorMessage) noexcept
{
if (errorMessage)
{
errorMessage[0] = 0;
}
}
inline jpegls_errc set_error_message(const jpegls_errc error, char* error_message) noexcept
{
if (error_message)
{
ASSERT(strlen(charls_get_error_message(static_cast<int32_t>(error))) < ErrorMessageSize);
strcpy(error_message, charls_get_error_message(static_cast<int32_t>(error)));
}
return error;
}
constexpr size_t int32_t_bit_count = sizeof(int32_t) * 8;
inline void push_back(std::vector<uint8_t>& values, uint16_t value)
{
values.push_back(uint8_t(value / 0x100));
values.push_back(uint8_t(value % 0x100));
values.push_back(static_cast<uint8_t>(value >> 8));
values.push_back(static_cast<uint8_t>(value));
}
inline void push_back(std::vector<uint8_t>& values, uint32_t value)
{
values.push_back(static_cast<uint8_t>(value >> 24));
values.push_back(static_cast<uint8_t>(value >> 16));
values.push_back(static_cast<uint8_t>(value >> 8));
values.push_back(static_cast<uint8_t>(value));
}
@ -75,13 +130,15 @@ struct Triplet
v1(0),
v2(0),
v3(0)
{}
{
}
Triplet(int32_t x1, int32_t x2, int32_t x3) noexcept :
v1(static_cast<T>(x1)),
v2(static_cast<T>(x2)),
v3(static_cast<T>(x3))
{}
{
}
union
{
@ -151,7 +208,8 @@ struct FromBigEndian<4> final
{
FORCE_INLINE static unsigned int Read(const uint8_t* buffer) noexcept
{
return (buffer[0] << 24u) + (buffer[1] << 16u) + (buffer[2] << 8u) + (buffer[3] << 0u);
return (static_cast<uint32_t>(buffer[0]) << 24U) + (static_cast<uint32_t>(buffer[1]) << 16U) +
(static_cast<uint32_t>(buffer[2]) << 8U) + (static_cast<uint32_t>(buffer[3]) << 0U);
}
};
@ -161,10 +219,10 @@ struct FromBigEndian<8> final
{
FORCE_INLINE static uint64_t Read(const uint8_t* buffer) noexcept
{
return (static_cast<uint64_t>(buffer[0]) << 56u) + (static_cast<uint64_t>(buffer[1]) << 48u) +
(static_cast<uint64_t>(buffer[2]) << 40u) + (static_cast<uint64_t>(buffer[3]) << 32u) +
(static_cast<uint64_t>(buffer[4]) << 24u) + (static_cast<uint64_t>(buffer[5]) << 16u) +
(static_cast<uint64_t>(buffer[6]) << 8u) + (static_cast<uint64_t>(buffer[7]) << 0u);
return (static_cast<uint64_t>(buffer[0]) << 56U) + (static_cast<uint64_t>(buffer[1]) << 48U) +
(static_cast<uint64_t>(buffer[2]) << 40U) + (static_cast<uint64_t>(buffer[3]) << 32U) +
(static_cast<uint64_t>(buffer[4]) << 24U) + (static_cast<uint64_t>(buffer[5]) << 16U) +
(static_cast<uint64_t>(buffer[6]) << 8U) + (static_cast<uint64_t>(buffer[7]) << 0U);
}
};

View File

@ -107,7 +107,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
</ClCompile>
@ -124,7 +123,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
<EnablePREfast>true</EnablePREfast>
@ -142,7 +140,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
@ -158,7 +155,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
@ -176,7 +172,6 @@
<OmitFramePointers>true</OmitFramePointers>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>true</StringPooling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
@ -197,7 +192,6 @@
<OmitFramePointers>true</OmitFramePointers>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>true</StringPooling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
@ -444,4 +438,4 @@
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
</Project>

View File

@ -54,72 +54,28 @@
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="0015.raw">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="lena8b.jls">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="conformance\T8C2E3.JLS">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="alphatest.raw">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="MR2_UNC">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="lena8b.raw" />
<CustomBuild Include="conformance\T8C0E0.JLS">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="conformance\T8C1E0.JLS">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="conformance\T8C0E3.JLS">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="conformance\T8C2E0.JLS">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="conformance\T8C1E3.JLS">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="conformance\T8NDE0.JLS">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="conformance\T8NDE3.JLS">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="conformance\T16E0.JLS">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="conformance\T16E3.JLS">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="conformance\TEST16.PGM">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="conformance\TEST8BS2.PGM">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="jlsimage\banny_HP1.jls">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="jlsimage\banny_HP2.jls">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="jlsimage\banny_HP3.jls">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="jlsimage\banny_normal.jls">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="jlsimage\banny.ppm">
<Filter>Data Files</Filter>
</CustomBuild>
<CustomBuild Include="conformance\TEST8.PPM">
<Filter>Data Files</Filter>
</CustomBuild>
<CopyFileToFolders Include="0015.raw" />
<CopyFileToFolders Include="conformance\TEST8.PPM" />
<CopyFileToFolders Include="lena8b.jls" />
<CopyFileToFolders Include="alphatest.raw" />
<CopyFileToFolders Include="conformance\T16E0.JLS" />
<CopyFileToFolders Include="conformance\T8C2E3.JLS" />
<CopyFileToFolders Include="conformance\T8C0E0.JLS" />
<CopyFileToFolders Include="conformance\T8C1E0.JLS" />
<CopyFileToFolders Include="conformance\T8C0E3.JLS" />
<CopyFileToFolders Include="conformance\T8C2E0.JLS" />
<CopyFileToFolders Include="conformance\T16E3.JLS" />
<CopyFileToFolders Include="conformance\T8C1E3.JLS" />
<CopyFileToFolders Include="conformance\T8NDE0.JLS" />
<CopyFileToFolders Include="conformance\T8NDE3.JLS" />
<CopyFileToFolders Include="conformance\TEST16.PGM" />
<CopyFileToFolders Include="MR2_UNC" />
<CopyFileToFolders Include="lena8b.raw" />
<CopyFileToFolders Include="conformance\TEST8BS2.PGM" />
<CopyFileToFolders Include="jlsimage\banny_HP1.jls" />
<CopyFileToFolders Include="jlsimage\banny_HP2.jls" />
<CopyFileToFolders Include="jlsimage\banny_HP3.jls" />
<CopyFileToFolders Include="jlsimage\banny_normal.jls" />
<CopyFileToFolders Include="jlsimage\banny.ppm" />
</ItemGroup>
</Project>

View File

@ -107,7 +107,7 @@ void DecompressFile(const char* strNameEncoded, const char* strNameRaw, int offs
FixEndian(&rawBuffer, false);
}
if (params.interleaveMode == InterleaveMode::None && params.components == 3)
if (params.interleaveMode == interleave_mode::none && params.components == 3)
{
Triplet2Planar(rawBuffer, Size(params.width, params.height));
}

View File

@ -26,7 +26,7 @@ bool ContainsString(const uint8_t* container, const uint8_t* bytesToFind, size_t
return true;
}
int FindString(vector<uint8_t>& container, const uint8_t* bytesToFind, size_t bytesLength)
int FindString(vector<uint8_t>& container, const uint8_t* bytesToFind, size_t bytesLength) noexcept
{
for (size_t i = 0; i < container.size() - bytesLength; ++i)
{

View File

@ -34,19 +34,13 @@ using std::fstream;
using std::string;
using std::iter_swap;
using std::getline;
using charls::DefaultTraits;
using charls::LosslessTraits;
using charls::jpegls_errc;
using charls::TransformRgbToBgr;
using charls::InterleaveMode;
using charls::log_2;
using namespace charls;
namespace
{
const ios_base::openmode mode_input = ios_base::in | ios::binary;
const ios_base::openmode mode_output = ios_base::out | ios::binary;
constexpr ios_base::openmode mode_input = ios_base::in | ios::binary;
constexpr ios_base::openmode mode_output = ios_base::out | ios::binary;
vector<uint8_t> ScanFile(const char* strNameEncoded, JlsParameters* params)
@ -174,7 +168,7 @@ void TestNoiseImage()
void TestNoiseImageWithCustomReset()
{
const Size size{512, 512};
const int bitDepth = 16;
constexpr int bitDepth = 16;
const vector<uint8_t> noiseBytes = MakeSomeNoise16bit(size.cx * size.cy, bitDepth, 21344);
JlsParameters params{};
@ -323,7 +317,7 @@ void TestDecodeRect()
}
void TestEncodeFromStream(const char* file, int offset, int width, int height, int bpp, int componentCount, InterleaveMode ilv, size_t expectedLength)
void TestEncodeFromStream(const char* file, int offset, int width, int height, int bpp, int componentCount, interleave_mode ilv, size_t expectedLength)
{
basic_filebuf<char> myFile; // On the stack
myFile.open(file, mode_input);
@ -429,7 +423,7 @@ bool EncodePnm(istream& pnmFile, const ostream& jlsFileStream)
params.width = readValues[1];
params.height = readValues[2];
params.bitsPerSample = log_2(readValues[3] + 1);
params.interleaveMode = params.components == 3 ? InterleaveMode::Line : InterleaveMode::None;
params.interleaveMode = params.components == 3 ? interleave_mode::line : interleave_mode::none;
const int bytesPerSample = (params.bitsPerSample + 7) / 8;
vector<uint8_t> inputBuffer(static_cast<size_t>(params.width) * params.height * bytesPerSample * params.components);
@ -578,10 +572,10 @@ void TestEncodeFromStream()
{
////TestDecodeFromStream("test/user_supplied/output.jls");
TestEncodeFromStream("test/0015.raw", 0, 1024, 1024, 8, 1, InterleaveMode::None, 0x3D3ee);
TestEncodeFromStream("test/0015.raw", 0, 1024, 1024, 8, 1, interleave_mode::none, 0x3D3ee);
////TestEncodeFromStream("test/MR2_UNC", 1728, 1024, 1024, 16, 1,0, 0x926e1);
TestEncodeFromStream("test/conformance/TEST8.PPM", 15, 256, 256, 8, 3, InterleaveMode::Sample, 99734);
TestEncodeFromStream("test/conformance/TEST8.PPM", 15, 256, 256, 8, 3, InterleaveMode::Line, 100615);
TestEncodeFromStream("test/conformance/TEST8.PPM", 15, 256, 256, 8, 3, interleave_mode::sample, 99734);
TestEncodeFromStream("test/conformance/TEST8.PPM", 15, 256, 256, 8, 3, interleave_mode::line, 100615);
}

View File

@ -6,6 +6,7 @@
#include <vector>
#include <ratio>
#include <chrono>
#include <iostream>
using std::vector;
using std::cout;

View File

@ -1,10 +1,14 @@
#pragma once
#include "../src/util.h"
#include <vector>
#include <string>
#include <sstream>
#include <fstream>
#include <ios>
namespace charls_test {
// Purpose: this class can read an image stored in the Portable Anymap Format (PNM).
// The 2 binary formats P5 and P6 are supported:
@ -22,7 +26,7 @@ public:
std::vector<int> header_info = read_header(pnm_file);
if (header_info.size() != 4)
throw std::istream::failure("Incorrect PNM header");
throw std::ios_base::failure("Incorrect PNM header");
component_count_ = header_info[0] == 6 ? 3 : 1;
width_ = header_info[1];
@ -54,7 +58,7 @@ public:
return bits_per_sample_;
}
const std::vector<uint8_t>& image_data() const noexcept
std::vector<uint8_t>& image_data() noexcept
{
return input_buffer_;
}
@ -95,3 +99,5 @@ private:
int bits_per_sample_;
std::vector<uint8_t> input_buffer_;
};
} // namespace charls_test

View File

@ -20,9 +20,8 @@ using std::vector;
using std::chrono::duration;
using std::chrono::steady_clock;
using std::milli;
using charls::InterleaveMode;
using charls::ColorTransformation;
using namespace charls;
using namespace charls_test;
namespace
@ -32,7 +31,7 @@ MSVC_WARNING_SUPPRESS(26497) // cannot be marked constexpr, check must be execut
bool IsMachineLittleEndian() noexcept
{
const int a = 0xFF000001;
constexpr int a = 0xFF000001;
const auto* chars = reinterpret_cast<const char*>(&a);
return chars[0] == 0x01;
}
@ -43,7 +42,7 @@ MSVC_WARNING_UNSUPPRESS()
} // namespace
void FixEndian(vector<uint8_t>* buffer, bool littleEndianData)
void FixEndian(vector<uint8_t>* buffer, bool littleEndianData) noexcept
{
if (littleEndianData == IsMachineLittleEndian())
return;
@ -122,20 +121,20 @@ void TestRoundTrip(const char* strName, const vector<uint8_t>& originalBuffer, J
if (params.components == 4)
{
params.interleaveMode = InterleaveMode::Line;
params.interleaveMode = interleave_mode::line;
}
else if (params.components == 3)
{
params.interleaveMode = InterleaveMode::Line;
params.colorTransformation = ColorTransformation::HP1;
params.interleaveMode = interleave_mode::line;
params.colorTransformation = color_transformation::hp1;
}
size_t compressedLength = 0;
size_t encoded_actual_size{};
auto start = steady_clock::now();
for (int i = 0; i < loopCount; ++i)
{
const error_code error = JpegLsEncode(encodedBuffer.data(), encodedBuffer.size(), &compressedLength,
originalBuffer.data(), decodedBuffer.size(), &params, nullptr);
const error_code error = JpegLsEncode(encodedBuffer.data(), encodedBuffer.size(), &encoded_actual_size,
originalBuffer.data(), originalBuffer.size(), &params, nullptr);
Assert::IsTrue(!error);
}
@ -144,13 +143,13 @@ void TestRoundTrip(const char* strName, const vector<uint8_t>& originalBuffer, J
start = steady_clock::now();
for (int i = 0; i < loopCount; ++i)
{
const error_code error = JpegLsDecode(&decodedBuffer[0], decodedBuffer.size(), &encodedBuffer[0], compressedLength, nullptr, nullptr);
const error_code error = JpegLsDecode(decodedBuffer.data(), decodedBuffer.size(), encodedBuffer.data(), encoded_actual_size, nullptr, nullptr);
Assert::IsTrue(!error);
}
const auto totalDecodeDuration = steady_clock::now() - start;
const double bitsPerSample = 1.0 * compressedLength * 8 / (static_cast<double>(params.components) * params.height * params.width);
const double bitsPerSample = 1.0 * encoded_actual_size * 8 / (static_cast<double>(params.components) * params.height * params.width);
cout << "RoundTrip test for: " << strName << "\n\r";
const double encodeTime = duration<double, milli>(totalEncodeDuration).count() / loopCount;
const double decodeTime = duration<double, milli>(totalDecodeDuration).count() / loopCount;

View File

@ -2,11 +2,13 @@
#pragma once
#include <charls/charls_legacy.h>
#include <charls/charls.h>
#include <vector>
#include <exception>
struct Size
struct Size final
{
Size(size_t width, size_t height) noexcept
:
@ -18,7 +20,7 @@ struct Size
};
void FixEndian(std::vector<uint8_t>* buffer, bool littleEndianData);
void FixEndian(std::vector<uint8_t>* buffer, bool littleEndianData) noexcept;
std::vector<uint8_t> ReadFile(const char* filename, long offset = 0, size_t bytes = 0);
void TestFile(const char* filename, int offset, Size size2, int bitsPerSample, int componentCount, bool littleEndianFile = false, int loopCount = 1);
void TestRoundTrip(const char* strName, const std::vector<uint8_t>& decodedBuffer, Size size, int bitsPerSample, int componentCount, int loopCount = 1);
@ -31,7 +33,7 @@ public:
explicit UnitTestException() = default;
};
class Assert
class Assert final
{
public:
static void IsTrue(bool condition)

View File

@ -109,13 +109,13 @@
<PrecompiledHeader>Use</PrecompiledHeader>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>$(VCToolsInstallDir)..\..\..\Auxiliary\VS\UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>CHARLS_LIBRARY_BUILD;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(VCToolsInstallDir)..\..\..\Auxiliary\VS\UnitTest\lib;$(SolutionDir)intermediate\CharLS\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
<AdditionalDependencies>interface.obj;jpegls.obj;jpeg_stream_writer.obj;jpeg_stream_reader.obj;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>interface.obj;jpegls.obj;jpegls_error.obj;jpeg_stream_writer.obj;jpeg_stream_reader.obj;charls_jpegls_decoder.obj;charls_jpegls_encoder.obj;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@ -123,13 +123,13 @@
<PrecompiledHeader>Use</PrecompiledHeader>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>$(VCToolsInstallDir)..\..\..\Auxiliary\VS\UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>CHARLS_LIBRARY_BUILD;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(VCToolsInstallDir)..\..\..\Auxiliary\VS\UnitTest\lib;$(SolutionDir)intermediate\CharLS\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
<AdditionalDependencies>interface.obj;jpegls.obj;jpeg_stream_writer.obj;jpeg_stream_reader.obj;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>interface.obj;jpegls.obj;jpegls_error.obj;jpeg_stream_writer.obj;jpeg_stream_reader.obj;charls_jpegls_decoder.obj;charls_jpegls_encoder.obj;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Checked|Win32'">
@ -137,13 +137,13 @@
<PrecompiledHeader>Use</PrecompiledHeader>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>$(VCToolsInstallDir)..\..\..\Auxiliary\VS\UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>CHARLS_LIBRARY_BUILD;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(VCToolsInstallDir)..\..\..\Auxiliary\VS\UnitTest\lib;$(SolutionDir)intermediate\CharLS\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
<AdditionalDependencies>interface.obj;jpegls.obj;jpeg_stream_writer.obj;jpeg_stream_reader.obj;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>interface.obj;jpegls.obj;jpegls_error.obj;jpeg_stream_writer.obj;jpeg_stream_reader.obj;charls_jpegls_decoder.obj;charls_jpegls_encoder.obj;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Checked|x64'">
@ -151,13 +151,13 @@
<PrecompiledHeader>Use</PrecompiledHeader>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>$(VCToolsInstallDir)..\..\..\Auxiliary\VS\UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>CHARLS_LIBRARY_BUILD;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(VCToolsInstallDir)..\..\..\Auxiliary\VS\UnitTest\lib;$(SolutionDir)intermediate\CharLS\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
<AdditionalDependencies>interface.obj;jpegls.obj;jpeg_stream_writer.obj;jpeg_stream_reader.obj;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>interface.obj;jpegls.obj;jpegls_error.obj;jpeg_stream_writer.obj;jpeg_stream_reader.obj;charls_jpegls_decoder.obj;charls_jpegls_encoder.obj;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@ -167,7 +167,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>$(VCToolsInstallDir)..\..\..\Auxiliary\VS\UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>CHARLS_LIBRARY_BUILD;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -175,7 +175,7 @@
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalLibraryDirectories>$(VCToolsInstallDir)..\..\..\Auxiliary\VS\UnitTest\lib;$(SolutionDir)intermediate\CharLS\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
<AdditionalDependencies>interface.obj;jpegls.obj;jpeg_stream_writer.obj;jpeg_stream_reader.obj;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>interface.obj;jpegls.obj;jpegls_error.obj;jpeg_stream_writer.obj;jpeg_stream_reader.obj;charls_jpegls_decoder.obj;charls_jpegls_encoder.obj;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@ -185,7 +185,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>$(VCToolsInstallDir)..\..\..\Auxiliary\VS\UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>CHARLS_LIBRARY_BUILD;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -193,19 +193,27 @@
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalLibraryDirectories>$(VCToolsInstallDir)..\..\..\Auxiliary\VS\UnitTest\lib;$(SolutionDir)intermediate\CharLS\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
<AdditionalDependencies>interface.obj;jpegls.obj;jpeg_stream_writer.obj;jpeg_stream_reader.obj;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>interface.obj;jpegls.obj;jpegls_error.obj;jpeg_stream_writer.obj;jpeg_stream_reader.obj;charls_jpegls_decoder.obj;charls_jpegls_encoder.obj;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="encoder_strategy_tester.h" />
<ClInclude Include="jpeg_test_stream_writer.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="util.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="charls_jpegls_decoder_test.cpp" />
<ClCompile Include="charls_jpegls_encoder_test.cpp" />
<ClCompile Include="compliance_test.cpp" />
<ClCompile Include="decoder_strategy_test.cpp" />
<ClCompile Include="default_traits_test.cpp" />
<ClCompile Include="encoder_strategy_test.cpp" />
<ClCompile Include="interface_test.cpp" />
<ClCompile Include="jpegls_decoder_test.cpp" />
<ClCompile Include="jpegls_encoder_test.cpp" />
<ClCompile Include="jpegls_preset_coding_parameters_test.cpp" />
<ClCompile Include="jpeg_error_test.cpp" />
<ClCompile Include="jpeg_stream_reader_test.cpp" />
<ClCompile Include="color_transform_test.cpp" />
<ClCompile Include="pch.cpp">
@ -217,12 +225,80 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="jpeg_stream_writer_test.cpp" />
<ClCompile Include="util.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\src\CharLS.vcxproj">
<Project>{1e31f9f1-f175-4082-b3e2-b1f0eca3f44c}</Project>
<LinkLibraryDependencies>false</LinkLibraryDependencies>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<CopyFileToFolders Include="../test/conformance/T8C0E0.JLS">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8C1E0.JLS">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8C2E0.JLS">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8C0E3.JLS">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8C1E3.JLS">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8C2E3.JLS">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8NDE0.JLS">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8NDE3.JLS">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T16E0.JLS">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T16E3.JLS">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/lena8b.jls">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/TEST8.PPM">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/TEST8BS2.PGM">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/TEST16.PGM">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T16E3.pgm">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="lena8b.pgm">
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)DataFiles</DestinationFolders>
</CopyFileToFolders>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>

View File

@ -13,6 +13,9 @@
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Test Images">
<UniqueIdentifier>{1153eae4-e88f-4af5-8cad-d7545b2953c6}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
@ -24,6 +27,9 @@
<ClInclude Include="jpeg_test_stream_writer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="util.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
@ -47,8 +53,82 @@
<ClCompile Include="jpeg_stream_writer_test.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="jpegls_decoder_test.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="jpegls_encoder_test.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="jpegls_preset_coding_parameters_test.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="interface_test.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="util.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="compliance_test.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="charls_jpegls_encoder_test.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="charls_jpegls_decoder_test.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="jpeg_error_test.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<CopyFileToFolders Include="lena8b.pgm">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/lena8b.jls">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8C0E0.JLS">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8C0E3.JLS">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8C1E0.JLS">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8C1E3.JLS">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8C2E0.JLS">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8C2E3.JLS">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8NDE0.JLS">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T8NDE3.JLS">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T16E0.JLS">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T16E3.JLS">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/T16E3.pgm">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/TEST8.PPM">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/TEST8BS2.PGM">
<Filter>Test Images</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="../test/conformance/TEST16.PGM">
<Filter>Test Images</Filter>
</CopyFileToFolders>
</ItemGroup>
</Project>

View File

@ -0,0 +1,154 @@
// Copyright (c) Team CharLS. All rights reserved. See the accompanying "LICENSE.md" for licensed use.
#include "pch.h"
#include <charls/charls.h>
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
using Microsoft::VisualStudio::CppUnitTestFramework::TestClass;
using namespace charls;
namespace Microsoft {
namespace VisualStudio {
namespace CppUnitTestFramework {
template<>
std::wstring ToString<charls::jpegls_errc>(const jpegls_errc& t)
{
RETURN_WIDE_STRING(static_cast<int>(t));
}
}}} // namespace Microsoft::VisualStudio::CppUnitTestFramework
namespace CharLSUnitTest {
// clang-format off
TEST_CLASS(charls_jpegls_decoder_test)
{
public:
TEST_METHOD(destroy_nullptr)
{
charls_jpegls_decoder_destroy(nullptr);
// No explicit test possible, code should remain stable.
Assert::IsTrue(true);
}
TEST_METHOD(set_source_buffer_nullptr)
{
const uint8_t buffer[10]{};
auto error = charls_jpegls_decoder_set_source_buffer(nullptr, buffer, sizeof buffer);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
auto decoder = charls_jpegls_decoder_create();
error = charls_jpegls_decoder_set_source_buffer(decoder, nullptr, sizeof buffer);
charls_jpegls_decoder_destroy(decoder);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
}
TEST_METHOD(read_spiff_header_nullptr)
{
charls_spiff_header spiff_header;
int32_t header_found;
auto error = charls_jpegls_decoder_read_spiff_header(nullptr, &spiff_header, &header_found);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
auto decoder = charls_jpegls_decoder_create();
error = charls_jpegls_decoder_read_spiff_header(decoder, nullptr, &header_found);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
error = charls_jpegls_decoder_read_spiff_header(decoder, &spiff_header, nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
charls_jpegls_decoder_destroy(decoder);
}
TEST_METHOD(read_header_nullptr)
{
const auto error = charls_jpegls_decoder_read_header(nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
}
TEST_METHOD(get_frame_info_nullptr)
{
frame_info frame_info;
auto error = charls_jpegls_decoder_get_frame_info(nullptr, &frame_info);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
const auto* decoder = charls_jpegls_decoder_create();
error = charls_jpegls_decoder_get_frame_info(decoder, nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
charls_jpegls_decoder_destroy(decoder);
}
TEST_METHOD(get_near_lossless_nullptr)
{
int32_t near_lossless;
auto error = charls_jpegls_decoder_get_near_lossless(nullptr, 0, &near_lossless);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
const auto* decoder = charls_jpegls_decoder_create();
error = charls_jpegls_decoder_get_near_lossless(decoder, 0, nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
charls_jpegls_decoder_destroy(decoder);
}
TEST_METHOD(get_interleave_mode_nullptr)
{
interleave_mode interleave_mode;
auto error = charls_jpegls_decoder_get_interleave_mode(nullptr, &interleave_mode);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
const auto* decoder = charls_jpegls_decoder_create();
error = charls_jpegls_decoder_get_interleave_mode(decoder, nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
charls_jpegls_decoder_destroy(decoder);
}
TEST_METHOD(get_preset_coding_parameters_nullptr)
{
jpegls_pc_parameters preset_coding_parameters;
auto error = charls_jpegls_decoder_get_preset_coding_parameters(nullptr, 0, &preset_coding_parameters);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
const auto* decoder = charls_jpegls_decoder_create();
error = charls_jpegls_decoder_get_preset_coding_parameters(decoder, 0, nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
charls_jpegls_decoder_destroy(decoder);
}
TEST_METHOD(get_destination_size_nullptr)
{
size_t destination_size_bytes;
auto error = charls_jpegls_decoder_get_destination_size(nullptr, &destination_size_bytes);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
const auto* decoder = charls_jpegls_decoder_create();
error = charls_jpegls_decoder_get_destination_size(decoder, nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
charls_jpegls_decoder_destroy(decoder);
}
TEST_METHOD(decode_to_buffer_nullptr)
{
uint8_t buffer[5];
auto error = charls_jpegls_decoder_decode_to_buffer(nullptr, buffer, 5, 0);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
const auto* decoder = charls_jpegls_decoder_create();
error = charls_jpegls_decoder_decode_to_buffer(decoder, nullptr, 5, 0);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
charls_jpegls_decoder_destroy(decoder);
}
};
}

View File

@ -0,0 +1,159 @@
// Copyright (c) Team CharLS. All rights reserved. See the accompanying "LICENSE.md" for licensed use.
#include "pch.h"
#include <charls/charls.h>
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
namespace Microsoft {
namespace VisualStudio {
namespace CppUnitTestFramework {
template<>
std::wstring ToString<charls::jpegls_errc>(const charls::jpegls_errc& t)
{
RETURN_WIDE_STRING(static_cast<int>(t));
}
}}} // namespace Microsoft::VisualStudio::CppUnitTestFramework
namespace CharLSUnitTest {
// clang-format off
TEST_CLASS(charls_jpegls_encoder_test)
{
public:
TEST_METHOD(destroy_nullptr)
{
charls_jpegls_encoder_destroy(nullptr);
// No explicit test possible, code should remain stable.
Assert::IsTrue(true);
}
TEST_METHOD(set_destination_buffer_nullptr)
{
uint8_t buffer[10]{};
auto error = charls_jpegls_encoder_set_destination_buffer(nullptr, buffer, sizeof buffer);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
auto encoder = charls_jpegls_encoder_create();
error = charls_jpegls_encoder_set_destination_buffer(encoder, nullptr, sizeof buffer);
charls_jpegls_encoder_destroy(encoder);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
}
TEST_METHOD(set_frame_info_buffer_nullptr)
{
charls_frame_info frame_info{};
auto error = charls_jpegls_encoder_set_frame_info(nullptr, &frame_info);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
auto encoder = charls_jpegls_encoder_create();
error = charls_jpegls_encoder_set_frame_info(encoder, nullptr);
charls_jpegls_encoder_destroy(encoder);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
}
TEST_METHOD(set_near_lossless_nullptr)
{
const auto error = charls_jpegls_encoder_set_near_lossless(nullptr, 1);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
}
TEST_METHOD(set_interleave_mode_nullptr)
{
const auto error = charls_jpegls_encoder_set_interleave_mode(nullptr, charls_interleave_mode::line);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
}
TEST_METHOD(set_preset_coding_parameters_nullptr)
{
charls_jpegls_pc_parameters parameters{};
auto error = charls_jpegls_encoder_set_preset_coding_parameters(nullptr, &parameters);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
auto encoder = charls_jpegls_encoder_create();
error = charls_jpegls_encoder_set_preset_coding_parameters(encoder, nullptr);
charls_jpegls_encoder_destroy(encoder);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
}
TEST_METHOD(set_color_transformation_nullptr)
{
const auto error = charls_jpegls_encoder_set_color_transformation(nullptr, charls_color_transformation::hp1);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
}
TEST_METHOD(get_estimated_destination_size_nullptr)
{
size_t size_in_bytes{};
auto error = charls_jpegls_encoder_get_estimated_destination_size(nullptr, &size_in_bytes);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
charls_jpegls_encoder const * const encoder = charls_jpegls_encoder_create();
error = charls_jpegls_encoder_get_estimated_destination_size(encoder, nullptr);
charls_jpegls_encoder_destroy(encoder);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
}
TEST_METHOD(get_bytes_written_nullptr)
{
size_t bytes_written{};
auto error = charls_jpegls_encoder_get_bytes_written(nullptr, &bytes_written);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
charls_jpegls_encoder const * const encoder = charls_jpegls_encoder_create();
error = charls_jpegls_encoder_get_bytes_written(encoder, nullptr);
charls_jpegls_encoder_destroy(encoder);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
}
TEST_METHOD(encode_from_buffer_nullptr)
{
const uint8_t source_buffer[10]{};
auto error = charls_jpegls_encoder_encode_from_buffer(nullptr, source_buffer, sizeof source_buffer, 0);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
auto encoder = charls_jpegls_encoder_create();
error = charls_jpegls_encoder_encode_from_buffer(encoder, nullptr, sizeof source_buffer, 0);
charls_jpegls_encoder_destroy(encoder);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
}
TEST_METHOD(write_spiff_header_nullptr)
{
charls_spiff_header spiff_header{};
auto error = charls_jpegls_encoder_write_spiff_header(nullptr, &spiff_header);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
auto encoder = charls_jpegls_encoder_create();
error = charls_jpegls_encoder_write_spiff_header(encoder, nullptr);
charls_jpegls_encoder_destroy(encoder);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
}
TEST_METHOD(write_standard_spiff_header_nullptr)
{
const auto error = charls_jpegls_encoder_write_standard_spiff_header(nullptr, charls_spiff_color_space::cie_lab,
charls_spiff_resolution_units::dots_per_centimeter, 1, 1);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
}
TEST_METHOD(write_spiff_entry_nullptr)
{
const uint8_t entry_data[10]{};
auto error = charls_jpegls_encoder_write_spiff_entry(nullptr, 5, entry_data, sizeof(entry_data));
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
auto encoder = charls_jpegls_encoder_create();
error = charls_jpegls_encoder_write_spiff_entry(encoder, 5, nullptr, sizeof(entry_data));
charls_jpegls_encoder_destroy(encoder);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
}
};
}

View File

@ -6,90 +6,93 @@
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
namespace CharLSUnitTest
namespace CharLSUnitTest {
// clang-format off
TEST_CLASS(ColorTransformTest)
{
TEST_CLASS(ColorTransformTest)
public:
TEST_METHOD(TransformHp1RoundTrip)
{
public:
TEST_METHOD(TransformHp1RoundTrip)
// For the normal unit test keep the range small for a quick test.
// For a complete test which will take a while set the start and end to 0 and 255.
constexpr int startValue = 123;
constexpr int endValue = 124;
for (int red = startValue; red < endValue; ++red)
{
// For the normal unit test keep the range small for a quick test.
// For a complete test which will take a while set the start and end to 0 and 255.
const int startValue = 123;
const int endValue = 124;
for (int red = startValue; red < endValue; ++red)
for (int green = 0; green < 255; ++green)
{
for (int green = 0; green < 255; ++green)
for (int blue = 0; blue < 255; ++blue)
{
for (int blue = 0; blue < 255; ++blue)
{
const charls::TransformHp1<uint8_t> transform;
const auto sample = transform(red, green, blue);
const charls::TransformHp1<uint8_t>::Inverse inverse(transform);
const charls::TransformHp1<uint8_t> transform;
const auto sample = transform(red, green, blue);
const charls::TransformHp1<uint8_t>::Inverse inverse(transform);
const auto roundTrip = inverse(sample.v1, sample.v2, sample.v3);
const auto roundTrip = inverse(sample.v1, sample.v2, sample.v3);
Assert::AreEqual(static_cast<uint8_t>(red), roundTrip.R);
Assert::AreEqual(static_cast<uint8_t>(green), roundTrip.G);
Assert::AreEqual(static_cast<uint8_t>(blue), roundTrip.B);
}
Assert::AreEqual(static_cast<uint8_t>(red), roundTrip.R);
Assert::AreEqual(static_cast<uint8_t>(green), roundTrip.G);
Assert::AreEqual(static_cast<uint8_t>(blue), roundTrip.B);
}
}
}
}
TEST_METHOD(TransformHp2RoundTrip)
TEST_METHOD(TransformHp2RoundTrip)
{
// For the normal unit test keep the range small for a quick test.
// For a complete test which will take a while set the start and end to 0 and 255.
constexpr int startValue = 123;
constexpr int endValue = 124;
for (int red = startValue; red < endValue; ++red)
{
// For the normal unit test keep the range small for a quick test.
// For a complete test which will take a while set the start and end to 0 and 255.
const int startValue = 123;
const int endValue = 124;
for (int red = startValue; red < endValue; ++red)
for (int green = 0; green < 255; ++green)
{
for (int green = 0; green < 255; ++green)
for (int blue = 0; blue < 255; ++blue)
{
for (int blue = 0; blue < 255; ++blue)
{
const charls::TransformHp2<uint8_t> transform;
const auto sample = transform(red, green, blue);
const charls::TransformHp2<uint8_t>::Inverse inverse(transform);
const charls::TransformHp2<uint8_t> transform;
const auto sample = transform(red, green, blue);
const charls::TransformHp2<uint8_t>::Inverse inverse(transform);
const auto roundTrip = inverse(sample.v1, sample.v2, sample.v3);
const auto roundTrip = inverse(sample.v1, sample.v2, sample.v3);
Assert::AreEqual(static_cast<uint8_t>(red), roundTrip.R);
Assert::AreEqual(static_cast<uint8_t>(green), roundTrip.G);
Assert::AreEqual(static_cast<uint8_t>(blue), roundTrip.B);
}
Assert::AreEqual(static_cast<uint8_t>(red), roundTrip.R);
Assert::AreEqual(static_cast<uint8_t>(green), roundTrip.G);
Assert::AreEqual(static_cast<uint8_t>(blue), roundTrip.B);
}
}
}
}
TEST_METHOD(TransformHp3RoundTrip)
TEST_METHOD(TransformHp3RoundTrip)
{
// For the normal unit test keep the range small for a quick test.
// For a complete test which will take a while set the start and end to 0 and 255.
constexpr uint8_t startValue = 123;
constexpr uint8_t endValue = 124;
charls::TransformHp3<uint8_t> transformation;
for (int red = startValue; red < endValue; ++red)
{
// For the normal unit test keep the range small for a quick test.
// For a complete test which will take a while set the start and end to 0 and 255.
const uint8_t startValue = 123;
const uint8_t endValue = 124;
charls::TransformHp3<uint8_t> transformation;
for (int red = startValue; red < endValue; ++red)
for (int green = 0; green < 255; ++green)
{
for (int green = 0; green < 255; ++green)
for (int blue = 0; blue < 255; ++blue)
{
for (int blue = 0; blue < 255; ++blue)
{
const auto sample = transformation(red, green, blue);
const charls::TransformHp3<uint8_t>::Inverse inverse(transformation);
const auto roundTrip = inverse(sample.v1, sample.v2, sample.v3);
const auto sample = transformation(red, green, blue);
const charls::TransformHp3<uint8_t>::Inverse inverse(transformation);
const auto roundTrip = inverse(sample.v1, sample.v2, sample.v3);
Assert::AreEqual(static_cast<uint8_t>(red), roundTrip.R);
Assert::AreEqual(static_cast<uint8_t>(green), roundTrip.G);
Assert::AreEqual(static_cast<uint8_t>(blue), roundTrip.B);
}
Assert::AreEqual(static_cast<uint8_t>(red), roundTrip.R);
Assert::AreEqual(static_cast<uint8_t>(green), roundTrip.G);
Assert::AreEqual(static_cast<uint8_t>(blue), roundTrip.B);
}
}
}
};
}
};
}

View File

@ -0,0 +1,218 @@
// Copyright (c) Team CharLS. All rights reserved. See the accompanying "LICENSE.md" for licensed use.
#include "pch.h"
#include "util.h"
#include <charls/charls.h>
#include "../test/portable_anymap_file.h"
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
using namespace charls;
using namespace charls_test;
using std::error_code;
using std::vector;
// clang-format off
namespace CharLSUnitTest {
TEST_CLASS(compliance_test)
{
public:
TEST_METHOD(decompress_color_8_bit_interleave_none_lossless)
{
// ISO 14495-1: official test image 1 (T87_test-1-2-3-4-5-6.zip)
decompress_file("DataFiles/T8C0E0.JLS", "DataFiles/TEST8.PPM");
}
TEST_METHOD(decompress_color_8_bit_interleave_line_lossless)
{
// ISO 14495-1: official test image 2 (T87_test-1-2-3-4-5-6.zip)
decompress_file("DataFiles/T8C1E0.JLS", "DataFiles/TEST8.PPM");
}
TEST_METHOD(decompress_color_8_bit_interleave_sample_lossless)
{
// ISO 14495-1: official test image 3 (T87_test-1-2-3-4-5-6.zip)
decompress_file("DataFiles/T8C2E0.JLS", "DataFiles/TEST8.PPM");
}
TEST_METHOD(decompress_color_8_bit_interleave_none_near_lossless_3)
{
// ISO 14495-1: official test image 4 (T87_test-1-2-3-4-5-6.zip)
decompress_file("DataFiles/T8C2E3.JLS", "DataFiles/TEST8.PPM");
}
TEST_METHOD(decompress_color_8_bit_interleave_line_near_lossless_3)
{
// ISO 14495-1: official test image 5 (T87_test-1-2-3-4-5-6.zip)
decompress_file("DataFiles/T8C1E3.JLS", "DataFiles/TEST8.PPM");
}
TEST_METHOD(decompress_color_8_bit_interleave_sample_near_lossless_3)
{
// ISO 14495-1: official test image 6 (T87_test-1-2-3-4-5-6.zip)
decompress_file("DataFiles/T8C2E3.JLS", "DataFiles/TEST8.PPM");
}
TEST_METHOD(decompress_color_8_bit_interleave_line_lossless_non_default)
{
// ISO 14495-1: official test image 9 (T87_test-1-2-3-4-5-6.zip)
// NON-DEFAULT parameters T1=T2=T3=9,RESET=31.
decompress_file("DataFiles/T8NDE0.JLS", "DataFiles/TEST8BS2.PGM");
}
TEST_METHOD(decompress_color_8_bit_interleave_line_near_lossless_3_non_default)
{
// ISO 14495-1: official test image 10 (T87_test-1-2-3-4-5-6.zip)
// NON-DEFAULT parameters T1=T2=T3=9,RESET=31.
decompress_file("DataFiles/T8NDE3.JLS", "DataFiles/TEST8BS2.PGM");
}
TEST_METHOD(decompress_monochrome_16_bit_lossless)
{
// ISO 14495-1: official test image 11 (T87_test-11-12.zip)
decompress_file("DataFiles/T16E0.JLS", "DataFiles/TEST16.PGM");
}
TEST_METHOD(decompress_monochrome_16_bit_near_lossless_3)
{
// ISO 14495-1: official test image 12 (T87_test-11-12.zip)
decompress_file("DataFiles/T16E0.JLS", "DataFiles/TEST16.pgm"); // TODO: fix incorrect test file.
}
TEST_METHOD(lena_monochrome_8_bit_lossless_ubc)
{
// Additional, Lena compressed with other codec (UBC?)
decompress_file("DataFiles/lena8b.jls", "DataFiles/lena8b.pgm");
}
private:
static void decompress_file(const char* encoded_filename, const char* raw_filename, bool check_encode = true)
{
vector<uint8_t> encoded_source = read_file(encoded_filename);
jpegls_decoder decoder;
decoder.source(encoded_source);
decoder.read_header();
portable_anymap_file reference_file = read_anymap_reference_file(raw_filename, decoder.interleave_mode(), decoder.frame_info());
test_compliance(encoded_source, reference_file.image_data(), check_encode);
test_compliance_legacy_api(encoded_source.data(), encoded_source.size(), reference_file.image_data().data(), reference_file.image_data().size(), check_encode);
}
static void test_compliance(const vector<uint8_t>& encoded_source, const vector<uint8_t>& uncompressed_source, bool checkEncode)
{
jpegls_decoder decoder;
decoder.source(encoded_source);
decoder.read_header();
if (checkEncode)
{
Assert::IsTrue(verify_encoded_bytes(uncompressed_source, encoded_source));
}
vector<uint8_t> destination(decoder.destination_size());
decoder.decode(destination);
if (decoder.near_lossless() == 0)
{
for (size_t i = 0; i < uncompressed_source.size(); ++i)
{
if (uncompressed_source[i] != destination[i]) // AreEqual is very slow, pre-test to speed up 50X
{
Assert::AreEqual(uncompressed_source[i], destination[i]);
}
}
}
}
static void test_compliance_legacy_api(const uint8_t* compressedBytes, size_t compressedLength, const uint8_t* uncompressedData, size_t uncompressedLength, bool checkEncode)
{
JlsParameters info{};
error_code error = JpegLsReadHeader(compressedBytes, compressedLength, &info, nullptr);
Assert::IsFalse(static_cast<bool>(error));
if (checkEncode)
{
Assert::IsTrue(verify_encoded_bytes_legacy_api(uncompressedData, uncompressedLength, compressedBytes, compressedLength));
}
vector<uint8_t> destination(static_cast<size_t>(info.height) *info.width * ((info.bitsPerSample + 7) / 8) * info.components);
error = JpegLsDecode(destination.data(), destination.size(), compressedBytes, compressedLength, nullptr, nullptr);
Assert::IsTrue(!error);
if (info.allowedLossyError == 0)
{
for (size_t i = 0; i < uncompressedLength; ++i)
{
if (uncompressedData[i] != destination[i]) // AreEqual is very slow, pre-test to speed up 50X
{
Assert::AreEqual(uncompressedData[i], destination[i]);
}
}
}
}
static bool verify_encoded_bytes_legacy_api(const void* uncompressedData, size_t uncompressedLength, const void* compressedData, size_t compressedLength)
{
JlsParameters info{};
error_code error = JpegLsReadHeader(compressedData, compressedLength, &info, nullptr);
if (error)
return false;
vector<uint8_t> ourEncodedBytes(compressedLength + 16);
size_t bytesWritten;
error = JpegLsEncode(ourEncodedBytes.data(), ourEncodedBytes.size(), &bytesWritten, uncompressedData, uncompressedLength, &info, nullptr);
if (error)
return false;
for (size_t i = 0; i < compressedLength; ++i)
{
if (static_cast<const uint8_t*>(compressedData)[i] != ourEncodedBytes[i])
{
return false;
}
}
return true;
}
static bool verify_encoded_bytes(const vector<uint8_t>& uncompressed_source, const vector<uint8_t>& encoded_source)
{
jpegls_decoder decoder;
decoder.source(encoded_source);
decoder.read_header();
jpegls_encoder encoder;
encoder.frame_info(decoder.frame_info())
.interleave_mode(decoder.interleave_mode())
.near_lossless(decoder.near_lossless())
.preset_coding_parameters(decoder.preset_coding_parameters());
vector<uint8_t> ourEncodedBytes(encoded_source.size() + 16);
encoder.destination(ourEncodedBytes);
const size_t bytes_written = encoder.encode(uncompressed_source);
if (bytes_written != encoded_source.size())
return false;
for (size_t i = 0; i < encoded_source.size(); ++i)
{
if (encoded_source[i] != ourEncodedBytes[i])
{
return false;
}
}
return true;
}
};
} // namespace CharLSUnitTest

View File

@ -6,8 +6,10 @@
#include "encoder_strategy_tester.h"
using std::unique_ptr;
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
using std::unique_ptr;
namespace {
class DecoderStrategyTester final : public charls::DecoderStrategy
{
@ -43,45 +45,49 @@ public:
}
};
} // namespace
namespace CharLSUnitTest
namespace CharLSUnitTest {
// clang-format off
TEST_CLASS(DecoderStrategyTest)
{
TEST_CLASS(DecoderStrategyTest)
public:
TEST_METHOD(DecodeEncodedFFPattern)
{
public:
TEST_METHOD(DecodeEncodedFFPattern)
const struct
{
const struct
{
int32_t val;
int bits;
} inData[5] = { { 0x00, 24 },{ 0xFF, 8 },{ 0xFFFF, 16 },{ 0xFFFF, 16 },{ 0x12345678, 31 } };
int32_t val;
int bits;
} inData[5] = { { 0x00, 24 },{ 0xFF, 8 },{ 0xFFFF, 16 },{ 0xFFFF, 16 },{ 0x12345678, 31 } };
uint8_t encBuf[100];
const JlsParameters params = { 0 };
uint8_t encBuf[100];
const JlsParameters params = { 0 };
EncoderStrategyTester encoder(params);
EncoderStrategyTester encoder(params);
ByteStreamInfo stream;
stream.rawStream = nullptr;
stream.rawData = encBuf;
stream.count = sizeof(encBuf);
encoder.InitForward(stream);
ByteStreamInfo stream;
stream.rawStream = nullptr;
stream.rawData = encBuf;
stream.count = sizeof(encBuf);
encoder.InitForward(stream);
for (int i = 0; i < sizeof(inData) / sizeof(inData[0]); i++)
{
encoder.AppendToBitStreamForward(inData[i].val, inData[i].bits);
}
encoder.EndScanForward();
// Note: Correct encoding is tested in EncoderStrategyTest::AppendToBitStreamFFPattern.
const auto length = encoder.GetLengthForward();
DecoderStrategyTester dec(params, encBuf, length);
for (auto i = 0; i < sizeof(inData) / sizeof(inData[0]); i++)
{
const auto actual = dec.Read(inData[i].bits);
Assert::AreEqual(inData[i].val, actual);
}
for (int i = 0; i < sizeof(inData) / sizeof(inData[0]); i++)
{
encoder.AppendToBitStreamForward(inData[i].val, inData[i].bits);
}
};
encoder.EndScanForward();
// Note: Correct encoding is tested in EncoderStrategyTest::AppendToBitStreamFFPattern.
const auto length = encoder.GetLengthForward();
DecoderStrategyTester dec(params, encBuf, length);
for (auto i = 0; i < sizeof(inData) / sizeof(inData[0]); i++)
{
const auto actual = dec.Read(inData[i].bits);
Assert::AreEqual(inData[i].val, actual);
}
}
};
}

View File

@ -6,22 +6,25 @@
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
namespace CharLSUnitTest
{
TEST_CLASS(DefaultTraitsTest)
{
public:
TEST_METHOD(Create)
{
const charls::DefaultTraits<uint8_t, uint8_t> traits((1 << 8) - 1, 0);
namespace CharLSUnitTest {
// clang-format off
TEST_CLASS(DefaultTraitsTest)
{
public:
TEST_METHOD(Create)
{
const charls::DefaultTraits<uint8_t, uint8_t> traits((1 << 8) - 1, 0);
Assert::AreEqual(255, traits.MAXVAL);
Assert::AreEqual(256, traits.RANGE);
Assert::AreEqual(0, traits.NEAR);
Assert::AreEqual(8, traits.qbpp);
Assert::AreEqual(8, traits.bpp);
Assert::AreEqual(32, traits.LIMIT);
Assert::AreEqual(64, traits.RESET);
}
};
Assert::AreEqual(255, traits.MAXVAL);
Assert::AreEqual(256, traits.RANGE);
Assert::AreEqual(0, traits.NEAR);
Assert::AreEqual(8, traits.qbpp);
Assert::AreEqual(8, traits.bpp);
Assert::AreEqual(32, traits.LIMIT);
Assert::AreEqual(64, traits.RESET);
}
};
}

View File

@ -6,73 +6,76 @@
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
namespace CharLSUnitTest
namespace CharLSUnitTest {
// clang-format off
TEST_CLASS(EncoderStrategyTest)
{
TEST_CLASS(EncoderStrategyTest)
public:
TEST_METHOD(AppendToBitStreamZeroLength)
{
public:
TEST_METHOD(AppendToBitStreamZeroLength)
{
JlsParameters params;
JlsParameters params;
EncoderStrategyTester strategy(params);
EncoderStrategyTester strategy(params);
uint8_t data[1024];
uint8_t data[1024];
ByteStreamInfo stream;
stream.rawStream = nullptr;
stream.rawData = data;
stream.count = sizeof(data);
strategy.InitForward(stream);
ByteStreamInfo stream;
stream.rawStream = nullptr;
stream.rawData = data;
stream.count = sizeof(data);
strategy.InitForward(stream);
strategy.AppendToBitStreamForward(0, 0);
strategy.FlushForward();
}
strategy.AppendToBitStreamForward(0, 0);
strategy.FlushForward();
}
TEST_METHOD(AppendToBitStreamFFPattern)
{
JlsParameters params;
TEST_METHOD(AppendToBitStreamFFPattern)
{
JlsParameters params;
EncoderStrategyTester strategy(params);
EncoderStrategyTester strategy(params);
uint8_t data[1024];
data[13] = 0x77; // marker byte to detect overruns.
uint8_t data[1024];
data[13] = 0x77; // marker byte to detect overruns.
ByteStreamInfo stream;
stream.rawStream = nullptr;
stream.rawData = data;
stream.count = sizeof(data);
strategy.InitForward(stream);
ByteStreamInfo stream;
stream.rawStream = nullptr;
stream.rawData = data;
stream.count = sizeof(data);
strategy.InitForward(stream);
// We want _isFFWritten == true.
strategy.AppendToBitStreamForward(0, 24);
strategy.AppendToBitStreamForward(0xff, 8);
// We want _isFFWritten == true.
strategy.AppendToBitStreamForward(0, 24);
strategy.AppendToBitStreamForward(0xff, 8);
// We need the buffer filled with set bits.
strategy.AppendToBitStreamForward(0xffff, 16);
strategy.AppendToBitStreamForward(0xffff, 16);
// We need the buffer filled with set bits.
strategy.AppendToBitStreamForward(0xffff, 16);
strategy.AppendToBitStreamForward(0xffff, 16);
// Buffer is full with FFs and _isFFWritten = true: Flush can only write 30 date bits.
strategy.AppendToBitStreamForward(0x3, 31);
// Buffer is full with FFs and _isFFWritten = true: Flush can only write 30 date bits.
strategy.AppendToBitStreamForward(0x3, 31);
strategy.FlushForward();
strategy.FlushForward();
// Verify output.
Assert::AreEqual(static_cast<size_t>(13), strategy.GetLengthForward());
Assert::AreEqual(static_cast<uint8_t>(0x00), data[0]);
Assert::AreEqual(static_cast<uint8_t>(0x00), data[1]);
Assert::AreEqual(static_cast<uint8_t>(0x00), data[2]);
Assert::AreEqual(static_cast<uint8_t>(0xFF), data[3]);
Assert::AreEqual(static_cast<uint8_t>(0x7F), data[4]); // extra 0 bit.
Assert::AreEqual(static_cast<uint8_t>(0xFF), data[5]);
Assert::AreEqual(static_cast<uint8_t>(0x7F), data[6]); // extra 0 bit.
Assert::AreEqual(static_cast<uint8_t>(0xFF), data[7]);
Assert::AreEqual(static_cast<uint8_t>(0x60), data[8]);
Assert::AreEqual(static_cast<uint8_t>(0x00), data[9]);
Assert::AreEqual(static_cast<uint8_t>(0x00), data[10]);
Assert::AreEqual(static_cast<uint8_t>(0x00), data[11]);
Assert::AreEqual(static_cast<uint8_t>(0xC0), data[12]);
Assert::AreEqual(static_cast<uint8_t>(0x77), data[13]);
}
};
// Verify output.
Assert::AreEqual(static_cast<size_t>(13), strategy.GetLengthForward());
Assert::AreEqual(static_cast<uint8_t>(0x00), data[0]);
Assert::AreEqual(static_cast<uint8_t>(0x00), data[1]);
Assert::AreEqual(static_cast<uint8_t>(0x00), data[2]);
Assert::AreEqual(static_cast<uint8_t>(0xFF), data[3]);
Assert::AreEqual(static_cast<uint8_t>(0x7F), data[4]); // extra 0 bit.
Assert::AreEqual(static_cast<uint8_t>(0xFF), data[5]);
Assert::AreEqual(static_cast<uint8_t>(0x7F), data[6]); // extra 0 bit.
Assert::AreEqual(static_cast<uint8_t>(0xFF), data[7]);
Assert::AreEqual(static_cast<uint8_t>(0x60), data[8]);
Assert::AreEqual(static_cast<uint8_t>(0x00), data[9]);
Assert::AreEqual(static_cast<uint8_t>(0x00), data[10]);
Assert::AreEqual(static_cast<uint8_t>(0x00), data[11]);
Assert::AreEqual(static_cast<uint8_t>(0xC0), data[12]);
Assert::AreEqual(static_cast<uint8_t>(0x77), data[13]);
}
};
}

View File

@ -4,11 +4,13 @@
#include "../src/encoder_strategy.h"
namespace CharLSUnitTest {
class EncoderStrategyTester final : charls::EncoderStrategy
{
public:
explicit EncoderStrategyTester(const JlsParameters& params) : EncoderStrategy(params)
explicit EncoderStrategyTester(const JlsParameters& params) :
EncoderStrategy(params)
{
}
@ -52,3 +54,4 @@ public:
}
};
} // namespace CharLSUnitTest

239
unittest/interface_test.cpp Normal file
View File

@ -0,0 +1,239 @@
// Copyright (c) Team CharLS. All rights reserved. See the accompanying "LICENSE.md" for licensed use.
#include "pch.h"
#include "util.h"
#include <charls/charls.h>
#include <charls/charls_legacy.h>
#include <array>
#include <vector>
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
using namespace charls;
using std::array;
using std::vector;
namespace Microsoft {
namespace VisualStudio {
namespace CppUnitTestFramework {
template<>
std::wstring ToString<charls::jpegls_errc>(const charls::jpegls_errc& t)
{
RETURN_WIDE_STRING(static_cast<int>(t));
}
}}} // namespace Microsoft::VisualStudio::CppUnitTestFramework
// clang-format off
namespace CharLSUnitTest {
TEST_CLASS(interface_test)
{
public:
TEST_METHOD(GetMetadataInfoFromNearLosslessEncodedColorImage)
{
vector<uint8_t> buffer{read_file("DataFiles/T8C0E3.JLS")};
JlsParameters params{};
const jpegls_errc result = JpegLsReadHeader(buffer.data(), buffer.size(), &params, nullptr);
Assert::AreEqual(jpegls_errc::success, result);
Assert::AreEqual(params.height, 256);
Assert::AreEqual(params.width, 256);
Assert::AreEqual(params.bitsPerSample, 8);
Assert::AreEqual(params.components, 3);
Assert::AreEqual(params.allowedLossyError, 3);
}
TEST_METHOD(JpegLsReadHeader_nullptr)
{
JlsParameters params{};
vector<uint8_t> buffer(100);
auto error = JpegLsReadHeader(nullptr, buffer.size(), &params, nullptr);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
error = JpegLsReadHeader(buffer.data(), buffer.size(), nullptr, nullptr);
Assert::AreEqual(charls::jpegls_errc::invalid_argument, error);
}
TEST_METHOD(JpegLsReadHeader_empty_source)
{
array<char, ErrorMessageSize> error_message;
JlsParameters params{};
array<uint8_t, 1> source;
const auto error = JpegLsReadHeader(source.data(), 0, &params, error_message.data());
Assert::AreEqual(charls::jpegls_errc::source_buffer_too_small, error);
Assert::IsTrue(strlen(error_message.data()) > 0);
}
TEST_METHOD(JpegLsEncode_nullptr)
{
JlsParameters params{};
size_t bytesWritten{};
vector<uint8_t> buffer(100);
auto error = JpegLsEncode(nullptr, 100, &bytesWritten, buffer.data(), buffer.size(), &params, nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
error = JpegLsEncode(buffer.data(), 100, nullptr, buffer.data(), buffer.size(), &params, nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
error = JpegLsEncode(buffer.data(), buffer.size(), &bytesWritten, nullptr, buffer.size(), &params, nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
error = JpegLsEncode(buffer.data(), buffer.size(), &bytesWritten, buffer.data(), buffer.size(), nullptr, nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
}
TEST_METHOD(JpegLsEncode_empty_destination)
{
array<char, ErrorMessageSize> error_message;
JlsParameters params{};
params.bitsPerSample = 8;
params.height = 10;
params.width = 10;
params.components = 1;
size_t bytesWritten{};
array<uint8_t, 1> destination;
vector<uint8_t> source(100);
const auto error = JpegLsEncode(destination.data(), 0, &bytesWritten, source.data(), source.size(), &params, error_message.data());
Assert::AreEqual(charls::jpegls_errc::destination_buffer_too_small, error);
Assert::IsTrue(strlen(error_message.data()) > 0);
}
TEST_METHOD(JpegLsDecode_nullptr)
{
JlsParameters params{};
vector<uint8_t> buffer(100);
auto error = JpegLsDecode(nullptr, 100, buffer.data(), buffer.size(), &params, nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
error = JpegLsDecode(buffer.data(), 100, nullptr, buffer.size(), &params, nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
}
TEST_METHOD(JpegLsDecode_empty_source)
{
array<char, ErrorMessageSize> error_message;
JlsParameters params{};
params.bitsPerSample = 8;
params.height = 10;
params.width = 10;
params.components = 1;
array<uint8_t, 1> source;
vector<uint8_t> destination(100);
const auto error = JpegLsDecode(destination.data(), destination.size(), source.data(), 0, &params, error_message.data());
Assert::AreEqual(jpegls_errc::source_buffer_too_small, error);
Assert::IsTrue(strlen(error_message.data()) > 0);
}
TEST_METHOD(JpegLsDecodeRect_lena)
{
JlsParameters params{};
vector<uint8_t> encoded_source = read_file("DataFiles/lena8b.jls");
auto error = JpegLsReadHeader(encoded_source.data(), encoded_source.size(), &params, nullptr);
Assert::AreEqual(jpegls_errc::success, error);
vector<uint8_t> decoded_destination(static_cast<size_t>(params.width) * params.height*params.components);
error = JpegLsDecode(decoded_destination.data(), decoded_destination.size(),
encoded_source.data(), encoded_source.size(), &params, nullptr);
Assert::IsFalse(static_cast<bool>(error));
const JlsRect rect = { 128, 128, 256, 1 };
vector<uint8_t> decoded_rect(static_cast<size_t>(rect.Width) * rect.Height);
decoded_rect.push_back(0x1f);
error = JpegLsDecodeRect(decoded_rect.data(), decoded_rect.size(),
encoded_source.data(), encoded_source.size(), rect, &params, nullptr);
Assert::IsFalse(static_cast<bool>(error));
Assert::IsTrue(memcmp(&decoded_destination[rect.X + static_cast<size_t>(rect.Y) * 512], decoded_rect.data(), static_cast<size_t>(rect.Width) * rect.Height) == 0);
Assert::IsTrue(decoded_rect[static_cast<size_t>(rect.Width) * rect.Height] == 0x1f);
}
TEST_METHOD(JpegLsDecodeRect_nullptr)
{
JlsParameters params{};
const JlsRect roi{};
vector<uint8_t> buffer(100);
auto error = JpegLsDecodeRect(nullptr, 100, buffer.data(), buffer.size(), roi, &params, nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
error = JpegLsDecodeRect(buffer.data(), 100, nullptr, buffer.size(), roi, &params, nullptr);
Assert::AreEqual(jpegls_errc::invalid_argument, error);
}
TEST_METHOD(JpegLsDecodeRect_empty_source)
{
array<char, ErrorMessageSize> error_message;
JlsParameters params{};
params.bitsPerSample = 8;
params.height = 10;
params.width = 10;
params.components = 1;
const JlsRect roi{};
array<uint8_t, 1> source;
vector<uint8_t> destination(100);
const auto error = JpegLsDecodeRect(destination.data(), destination.size(), source.data(), 0, roi, &params, error_message.data());
Assert::AreEqual(jpegls_errc::source_buffer_too_small, error);
Assert::IsTrue(strlen(error_message.data()) > 0);
}
TEST_METHOD(noise_image_with_custom_reset)
{
JlsParameters params{};
params.components = 1;
params.bitsPerSample = 16;
params.height = 512;
params.width = 512;
params.custom.MaximumSampleValue = (1 << params.bitsPerSample) - 1;
params.custom.ResetValue = 63;
const vector<uint8_t> noise_image = create_noise_image_16bit(static_cast<size_t>(params.height) * params.width, params.bitsPerSample, 21344);
test_round_trip_legacy(noise_image, params);
}
TEST_METHOD(JpegLsReadHeaderStream_valid_input)
{
vector<uint8_t> source{read_file("DataFiles/T8C0E3.JLS")};
JlsParameters params{};
const auto source_info = FromByteArrayConst(source.data(), source.size());
const jpegls_errc error = JpegLsReadHeaderStream(source_info, &params);
Assert::AreEqual(jpegls_errc::success, error);
Assert::AreEqual(params.height, 256);
Assert::AreEqual(params.width, 256);
Assert::AreEqual(params.bitsPerSample, 8);
Assert::AreEqual(params.components, 3);
Assert::AreEqual(params.allowedLossyError, 3);
}
TEST_METHOD(JpegLsDecodeStream_valid_input)
{
vector<uint8_t> source{read_file("DataFiles/T8C0E3.JLS")};
JlsParameters params{};
const auto source_info = FromByteArrayConst(source.data(), source.size());
jpegls_errc error = JpegLsReadHeaderStream(source_info, &params);
Assert::AreEqual(jpegls_errc::success, error);
const int bytesPerSample = params.bitsPerSample > 8 ? 2 : 1;
vector<uint8_t> destination(static_cast<size_t>(params.width) * params.height * bytesPerSample * params.components);
const auto destination_info = FromByteArray(destination.data(), destination.size());
error = JpegLsDecodeStream(destination_info, source_info, nullptr);
Assert::AreEqual(jpegls_errc::success, error);
}
};
} // namespace CharLSUnitTest

View File

@ -0,0 +1,44 @@
// Copyright (c) Team CharLS. All rights reserved. See the accompanying "LICENSE.md" for licensed use.
#include "pch.h"
#include <charls/jpegls_error.h>
#include <array>
#include <vector>
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
using Microsoft::VisualStudio::CppUnitTestFramework::TestClass;
using namespace charls;
using std::array;
using std::vector;
namespace CharLSUnitTest {
// clang-format off
TEST_CLASS(jpegls_error_test)
{
public:
TEST_METHOD(get_error_message_success)
{
auto result = charls_get_error_message(0);
Assert::IsNotNull(result);
Assert::IsTrue(strlen(result) > 0);
}
TEST_METHOD(get_error_message_unknown)
{
constexpr int32_t unknown_error_code{3000};
auto result = charls_get_error_message(unknown_error_code);
Assert::IsNotNull(result);
Assert::IsTrue(strlen(result) > 0);
}
TEST_METHOD(jpegls_category_name_is_not_an_empty_string)
{
Assert::IsTrue(strlen(jpegls_category().name()) > 0);
}
};
} // namespace CharLSUnitTest

View File

@ -2,29 +2,34 @@
#include "pch.h"
#include "util.h"
#include "../src/jpeg_stream_reader.h"
#include "../src/jpeg_stream_writer.h"
#include "jpeg_test_stream_writer.h"
#include <array>
#include <cstdint>
#include <vector>
using charls::jpegls_errc;
using charls::JpegStreamReader;
using namespace charls;
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
using std::array;
using std::system_error;
using std::vector;
namespace CharLSUnitTest
{
namespace CharLSUnitTest {
// clang-format off
TEST_CLASS(JpegStreamReaderTest)
{
public:
TEST_METHOD(ReadHeaderFromToSmallInputBuffer)
{
uint8_t buffer[1];
array<uint8_t, 1> buffer{};
const ByteStreamInfo byteStream = FromByteArray(buffer, 0);
const ByteStreamInfo byteStream = FromByteArray(buffer.data(), 0);
JpegStreamReader reader(byteStream);
try
@ -155,7 +160,7 @@ public:
const JpegLSPresetCodingParameters params{1, 2, 3, 4, 5};
writer.WriteJpegLSPresetParametersSegment(params);
writer.WriteStartOfFrameSegment(1, 1, 2, 1);
writer.WriteStartOfScanSegment(1, 0, charls::InterleaveMode::None);
writer.WriteStartOfScanSegment(1, 0, charls::interleave_mode::none);
const ByteStreamInfo destinationInfo = FromByteArray(source.data(), source.size());
JpegStreamReader reader(destinationInfo);
@ -442,10 +447,7 @@ public:
try
{
vector<uint8_t> outputBuffer;
const ByteStreamInfo destination = FromByteArray(outputBuffer.data(), outputBuffer.size());
reader.Read(destination);
reader.ReadHeader();
}
catch (const system_error& error)
{
@ -482,10 +484,7 @@ public:
try
{
vector<uint8_t> outputBuffer;
const ByteStreamInfo destination = FromByteArray(outputBuffer.data(), outputBuffer.size());
reader.Read(destination);
reader.ReadHeader();
}
catch (const system_error& error)
{
@ -534,14 +533,84 @@ public:
try
{
reader.ReadHeader();
Assert::Fail();
}
catch (const system_error& error)
{
Assert::AreEqual(static_cast<int>(jpegls_errc::duplicate_start_of_image_marker), error.code().value());
return;
}
}
Assert::Fail();
static void ReadSpiffHeader(uint8_t low_version)
{
vector<uint8_t> buffer = create_test_spiff_header(2, low_version);
const ByteStreamInfo byteStream = FromByteArray(buffer.data(), buffer.size());
JpegStreamReader reader(byteStream);
spiff_header spiff_header{};
bool spiff_header_found{};
reader.ReadHeader(&spiff_header, &spiff_header_found);
Assert::IsTrue(spiff_header_found);
Assert::AreEqual(static_cast<int32_t>(spiff_profile_id::none), static_cast<int32_t>(spiff_header.profile_id));
Assert::AreEqual(3, spiff_header.component_count);
Assert::AreEqual(800U, spiff_header.height);
Assert::AreEqual(600U, spiff_header.width);
Assert::AreEqual(static_cast<int32_t>(spiff_color_space::rgb), static_cast<int32_t>(spiff_header.color_space));
Assert::AreEqual(8, spiff_header.bits_per_sample);;
Assert::AreEqual(static_cast<int32_t>(spiff_compression_type::jpeg_ls), static_cast<int32_t>(spiff_header.compression_type));
Assert::AreEqual(static_cast<int32_t>(spiff_resolution_units::dots_per_inch), static_cast<int32_t>(spiff_header.resolution_units));
Assert::AreEqual(96U, spiff_header.vertical_resolution);
Assert::AreEqual(1024U, spiff_header.horizontal_resolution);
}
TEST_METHOD(ReadSpiffHeader)
{
ReadSpiffHeader(0);
}
TEST_METHOD(read_spiff_header_low_version_newer)
{
ReadSpiffHeader(1);
}
TEST_METHOD(read_spiff_header_high_version_to_new)
{
vector<uint8_t> buffer = create_test_spiff_header(3);
const ByteStreamInfo byteStream = FromByteArray(buffer.data(), buffer.size());
JpegStreamReader reader(byteStream);
spiff_header spiff_header{};
bool spiff_header_found{};
reader.ReadHeader(&spiff_header, &spiff_header_found);
Assert::IsFalse(spiff_header_found);
}
TEST_METHOD(read_spiff_header_without_end_of_directory)
{
vector<uint8_t> buffer = create_test_spiff_header(2, 0, false);
const ByteStreamInfo byteStream = FromByteArray(buffer.data(), buffer.size());
JpegStreamReader reader(byteStream);
spiff_header spiff_header{};
bool spiff_header_found{};
reader.ReadHeader(&spiff_header, &spiff_header_found);
Assert::IsTrue(spiff_header_found);
try
{
reader.ReadHeader();
Assert::Fail();
}
catch (const system_error& error)
{
Assert::AreEqual(static_cast<int>(jpegls_errc::missing_end_of_spiff_directory), error.code().value());
}
}
};
} // namespace CharLSUnitTest

View File

@ -2,248 +2,329 @@
#include "pch.h"
#include "../src/jpeg_stream_writer.h"
#include "../src/jpeg_marker_code.h"
#include "../src/jpeg_stream_writer.h"
#include <array>
using std::array;
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
using charls::JpegStreamWriter;
using charls::JpegMarkerCode;
using charls::ColorTransformation;
using charls::InterleaveMode;
using std::array;
using namespace charls;
namespace CharLSUnitTest
namespace CharLSUnitTest {
// clang-format off
TEST_CLASS(JpegStreamWriterTest)
{
TEST_CLASS(JpegStreamWriterTest)
public:
TEST_METHOD(LengthWillbeZeroAfterCreate)
{
public:
TEST_METHOD(LengthWillbeZeroAfterCreate)
{
const JpegStreamWriter writer;
Assert::AreEqual(static_cast<size_t>(0), writer.GetLength());
}
const JpegStreamWriter writer;
Assert::AreEqual(static_cast<size_t>(0), writer.GetLength());
}
TEST_METHOD(WriteStartOfImage)
{
array<uint8_t, 2> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
TEST_METHOD(WriteStartOfImage)
{
array<uint8_t, 2> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
JpegStreamWriter writer(info);
writer.WriteStartOfImage();
writer.WriteStartOfImage();
Assert::AreEqual(static_cast<size_t>(2), writer.GetBytesWritten());
Assert::AreEqual(static_cast<uint8_t>(0xFF), buffer[0]);
Assert::AreEqual(static_cast<uint8_t>(JpegMarkerCode::StartOfImage), buffer[1]);
}
Assert::AreEqual(static_cast<size_t>(2), writer.GetBytesWritten());
Assert::AreEqual(static_cast<uint8_t>(0xFF), buffer[0]);
Assert::AreEqual(static_cast<uint8_t>(JpegMarkerCode::StartOfImage), buffer[1]);
}
TEST_METHOD(WriteEndOfImage)
{
array<uint8_t, 2> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
TEST_METHOD(WriteEndOfImage)
{
array<uint8_t, 2> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
JpegStreamWriter writer(info);
writer.WriteEndOfImage();
writer.WriteEndOfImage();
Assert::AreEqual(static_cast<size_t>(2), writer.GetBytesWritten());
Assert::AreEqual(static_cast<uint8_t>(0xFF), buffer[0]);
Assert::AreEqual(static_cast<uint8_t>(JpegMarkerCode::EndOfImage), buffer[1]);
}
Assert::AreEqual(static_cast<size_t>(2), writer.GetBytesWritten());
Assert::AreEqual(static_cast<uint8_t>(0xFF), buffer[0]);
Assert::AreEqual(static_cast<uint8_t>(JpegMarkerCode::EndOfImage), buffer[1]);
}
TEST_METHOD(WriteJpegFileInterchangeFormatSegment)
{
array<uint8_t, 18> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
TEST_METHOD(WriteSpiffSegment)
{
array<uint8_t, 34> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
JpegStreamWriter writer(info);
JfifParameters params{};
params.version = (1 * 256) + 2;
params.units = 2;
params.Xdensity = 96;
params.Ydensity = 300;
params.Xthumbnail = 0;
params.Ythumbnail = 0;
spiff_header header{};
header.profile_id = spiff_profile_id::none;
header.component_count = 3;
header.height = 800;
header.width = 600;
header.color_space = spiff_color_space::rgb;
header.bits_per_sample = 8;
header.compression_type = spiff_compression_type::jpeg_ls;
header.resolution_units = spiff_resolution_units::dots_per_inch;
header.vertical_resolution = 96;
header.horizontal_resolution = 1024;
writer.WriteJpegFileInterchangeFormatSegment(params);
writer.WriteSpiffHeaderSegment(header);
Assert::AreEqual(static_cast<size_t>(18), writer.GetBytesWritten());
Assert::AreEqual(static_cast<size_t>(34), writer.GetBytesWritten());
Assert::AreEqual(static_cast<uint8_t>(0xFF), buffer[0]);
Assert::AreEqual(static_cast<uint8_t>(JpegMarkerCode::ApplicationData0), buffer[1]);
Assert::AreEqual(static_cast<uint8_t>(0xFF), buffer[0]);
Assert::AreEqual(static_cast<uint8_t>(JpegMarkerCode::ApplicationData8), buffer[1]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[2]);
Assert::AreEqual(static_cast<uint8_t>(16), buffer[3]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[2]);
Assert::AreEqual(static_cast<uint8_t>(32), buffer[3]);
// Verify JFIF identifier string.
Assert::AreEqual(static_cast<uint8_t>(0x4A), buffer[4]);
Assert::AreEqual(static_cast<uint8_t>(0x46), buffer[5]);
Assert::AreEqual(static_cast<uint8_t>(0x49), buffer[6]);
Assert::AreEqual(static_cast<uint8_t>(0x46), buffer[7]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[8]);
// Verify SPIFF identifier string.
Assert::AreEqual(static_cast<uint8_t>('S'), buffer[4]);
Assert::AreEqual(static_cast<uint8_t>('P'), buffer[5]);
Assert::AreEqual(static_cast<uint8_t>('I'), buffer[6]);
Assert::AreEqual(static_cast<uint8_t>('F'), buffer[7]);
Assert::AreEqual(static_cast<uint8_t>('F'), buffer[8]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[9]);
// Verify version
Assert::AreEqual(static_cast<uint8_t>(1), buffer[9]);
Assert::AreEqual(static_cast<uint8_t>(2), buffer[10]);
// Verify version
Assert::AreEqual(static_cast<uint8_t>(2), buffer[10]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[11]);
Assert::AreEqual(static_cast<uint8_t>(params.units), buffer[11]);
Assert::AreEqual(static_cast<uint8_t>(header.profile_id), buffer[12]);
Assert::AreEqual(static_cast<uint8_t>(header.component_count), buffer[13]);
// Xdensity
Assert::AreEqual(static_cast<uint8_t>(0), buffer[12]);
Assert::AreEqual(static_cast<uint8_t>(96), buffer[13]);
// Height
Assert::AreEqual(static_cast<uint8_t>(0), buffer[14]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[15]);
Assert::AreEqual(static_cast<uint8_t>(0x3), buffer[16]);
Assert::AreEqual(static_cast<uint8_t>(0x20), buffer[17]);
// Ydensity
Assert::AreEqual(static_cast<uint8_t>(1), buffer[14]);
Assert::AreEqual(static_cast<uint8_t>(44), buffer[15]);
}
// Width
Assert::AreEqual(static_cast<uint8_t>(0), buffer[18]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[19]);
Assert::AreEqual(static_cast<uint8_t>(0x2), buffer[20]);
Assert::AreEqual(static_cast<uint8_t>(0x58), buffer[21]);
TEST_METHOD(WriteStartOfFrameSegment)
{
const int32_t bitsPerSample = 8;
const int32_t componentCount = 3;
Assert::AreEqual(static_cast<uint8_t>(header.color_space), buffer[22]);
Assert::AreEqual(static_cast<uint8_t>(header.bits_per_sample), buffer[23]);
Assert::AreEqual(static_cast<uint8_t>(header.compression_type), buffer[24]);
Assert::AreEqual(static_cast<uint8_t>(header.resolution_units), buffer[25]);
array<uint8_t, 19> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
// vertical_resolution
Assert::AreEqual(static_cast<uint8_t>(0), buffer[26]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[27]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[28]);
Assert::AreEqual(static_cast<uint8_t>(96), buffer[29]);
writer.WriteStartOfFrameSegment(100, UINT16_MAX, bitsPerSample, componentCount);
// header.horizontal_resolution = 1024;
Assert::AreEqual(static_cast<uint8_t>(0), buffer[30]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[31]);
Assert::AreEqual(static_cast<uint8_t>(4), buffer[32]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[33]);
}
Assert::AreEqual(static_cast<size_t>(19), writer.GetBytesWritten());
TEST_METHOD(WriteSpiffEndOfDirectorySegment)
{
array<uint8_t, 10> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
Assert::AreEqual(static_cast<uint8_t>(0xFF), buffer[0]);
Assert::AreEqual(static_cast<uint8_t>(0xF7), buffer[1]); // JPEG_SOF_55
Assert::AreEqual(static_cast<uint8_t>(0), buffer[2]); // 6 + (3 * 3) + 2 (in little endian)
Assert::AreEqual(static_cast<uint8_t>(17), buffer[3]); // 6 + (3 * 3) + 2 (in little endian)
Assert::AreEqual(static_cast<uint8_t>(bitsPerSample), buffer[4]);
Assert::AreEqual(static_cast<uint8_t>(255), buffer[5]); // height (in little endian)
Assert::AreEqual(static_cast<uint8_t>(255), buffer[6]); // height (in little endian)
Assert::AreEqual(static_cast<uint8_t>(0), buffer[7]); // width (in little endian)
Assert::AreEqual(static_cast<uint8_t>(100), buffer[8]); // width (in little endian)
Assert::AreEqual(static_cast<uint8_t>(componentCount), buffer[9]);
JpegStreamWriter writer(info);
Assert::AreEqual(static_cast<uint8_t>(1), buffer[10]);
Assert::AreEqual(static_cast<uint8_t>(0x11), buffer[11]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[12]);
writer.WriteSpiffEndOfDirectoryEntry();
Assert::AreEqual(static_cast<uint8_t>(2), buffer[13]);
Assert::AreEqual(static_cast<uint8_t>(0x11), buffer[14]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[15]);
Assert::AreEqual(static_cast<size_t>(10), writer.GetBytesWritten());
Assert::AreEqual(static_cast<uint8_t>(3), buffer[16]);
Assert::AreEqual(static_cast<uint8_t>(0x11), buffer[17]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[18]);
}
// Verify Entry Magic Number (EMN)
Assert::AreEqual(static_cast<uint8_t>(0xFF), buffer[0]);
Assert::AreEqual(static_cast<uint8_t>(JpegMarkerCode::ApplicationData8), buffer[1]);
TEST_METHOD(WriteStartOfFrameMarkerSegmentWithLowBoundaryValues)
{
const int32_t bitsPerSample = 2;
const int32_t componentCount = 1;
// Verify EOD Entry Length (EOD = End Of Directory)
Assert::AreEqual(static_cast<uint8_t>(0), buffer[2]);
Assert::AreEqual(static_cast<uint8_t>(8), buffer[3]);
array<uint8_t, 13> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
// Verify EOD Tag
Assert::AreEqual(static_cast<uint8_t>(0), buffer[4]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[5]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[6]);
Assert::AreEqual(static_cast<uint8_t>(1), buffer[7]);
writer.WriteStartOfFrameSegment(0, 0, bitsPerSample, componentCount);
// Verify embedded SOI tag
Assert::AreEqual(static_cast<uint8_t>(0xFF), buffer[8]);
Assert::AreEqual(static_cast<uint8_t>(JpegMarkerCode::StartOfImage), buffer[9]);
}
Assert::AreEqual(buffer.size(), writer.GetBytesWritten());
Assert::AreEqual(static_cast<uint8_t>(bitsPerSample), buffer[4]);
Assert::AreEqual(static_cast<uint8_t>(componentCount), buffer[9]);
}
TEST_METHOD(WriteSpiffDirectoryEntry)
{
array<uint8_t, 10> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
TEST_METHOD(WriteStartOfFrameMarkerSegmentWithHighBoundaryValuesAndSerialize)
{
array<uint8_t, 775> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
JpegStreamWriter writer{info};
writer.WriteStartOfFrameSegment(UINT16_MAX, UINT16_MAX, 16, UINT8_MAX);
array<uint8_t, 2> data{0x77, 0x66};
Assert::AreEqual(buffer.size(), writer.GetBytesWritten());
Assert::AreEqual(static_cast<uint8_t>(16), buffer[4]);
Assert::AreEqual(static_cast<uint8_t>(UINT8_MAX), buffer[9]);
writer.WriteSpiffDirectoryEntry(2, data.data(), data.size());
Assert::AreEqual(static_cast<uint8_t>(UINT8_MAX), buffer[buffer.size() - 3]); // Last component index.
}
// Verify Entry Magic Number (EMN)
Assert::AreEqual(static_cast<uint8_t>(0xFF), buffer[0]);
Assert::AreEqual(static_cast<uint8_t>(JpegMarkerCode::ApplicationData8), buffer[1]);
TEST_METHOD(WriteColorTransformSegment)
{
const ColorTransformation transformation = ColorTransformation::HP1;
// Verify Entry Length
Assert::AreEqual(static_cast<uint8_t>(0), buffer[2]);
Assert::AreEqual(static_cast<uint8_t>(8), buffer[3]);
array<uint8_t, 9> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
// Verify Entry Tag
Assert::AreEqual(static_cast<uint8_t>(0), buffer[4]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[5]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[6]);
Assert::AreEqual(static_cast<uint8_t>(2), buffer[7]);
writer.WriteColorTransformSegment(transformation);
Assert::AreEqual(buffer.size(), writer.GetBytesWritten());
// Verify embedded data
Assert::AreEqual(data[0], buffer[8]);
Assert::AreEqual(data[1], buffer[9]);
}
// Verify mrfx identifier string.
Assert::AreEqual(static_cast<uint8_t>('m'), buffer[4]);
Assert::AreEqual(static_cast<uint8_t>('r'), buffer[5]);
Assert::AreEqual(static_cast<uint8_t>('f'), buffer[6]);
Assert::AreEqual(static_cast<uint8_t>('x'), buffer[7]);
TEST_METHOD(WriteStartOfFrameSegment)
{
constexpr int32_t bitsPerSample = 8;
constexpr int32_t componentCount = 3;
Assert::AreEqual(static_cast<uint8_t>(transformation), buffer[8]);
}
array<uint8_t, 19> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
TEST_METHOD(WriteJpegLSExtendedParametersMarkerAndSerialize)
{
JpegLSPresetCodingParameters params{};
writer.WriteStartOfFrameSegment(100, UINT16_MAX, bitsPerSample, componentCount);
params.MaximumSampleValue = 2;
params.Threshold1 = 1;
params.Threshold2 = 2;
params.Threshold3 = 3;
params.ResetValue = 7;
Assert::AreEqual(static_cast<size_t>(19), writer.GetBytesWritten());
array<uint8_t, 15> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
Assert::AreEqual(static_cast<uint8_t>(0xFF), buffer[0]);
Assert::AreEqual(static_cast<uint8_t>(0xF7), buffer[1]); // JPEG_SOF_55
Assert::AreEqual(static_cast<uint8_t>(0), buffer[2]); // 6 + (3 * 3) + 2 (in little endian)
Assert::AreEqual(static_cast<uint8_t>(17), buffer[3]); // 6 + (3 * 3) + 2 (in little endian)
Assert::AreEqual(static_cast<uint8_t>(bitsPerSample), buffer[4]);
Assert::AreEqual(static_cast<uint8_t>(255), buffer[5]); // height (in little endian)
Assert::AreEqual(static_cast<uint8_t>(255), buffer[6]); // height (in little endian)
Assert::AreEqual(static_cast<uint8_t>(0), buffer[7]); // width (in little endian)
Assert::AreEqual(static_cast<uint8_t>(100), buffer[8]); // width (in little endian)
Assert::AreEqual(static_cast<uint8_t>(componentCount), buffer[9]);
writer.WriteJpegLSPresetParametersSegment(params);
Assert::AreEqual(buffer.size(), writer.GetBytesWritten());
Assert::AreEqual(static_cast<uint8_t>(1), buffer[10]);
Assert::AreEqual(static_cast<uint8_t>(0x11), buffer[11]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[12]);
// Parameter ID.
Assert::AreEqual(static_cast<uint8_t>(0x1), buffer[4]);
Assert::AreEqual(static_cast<uint8_t>(2), buffer[13]);
Assert::AreEqual(static_cast<uint8_t>(0x11), buffer[14]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[15]);
// MaximumSampleValue
Assert::AreEqual(static_cast<uint8_t>(0), buffer[5]);
Assert::AreEqual(static_cast<uint8_t>(2), buffer[6]);
Assert::AreEqual(static_cast<uint8_t>(3), buffer[16]);
Assert::AreEqual(static_cast<uint8_t>(0x11), buffer[17]);
Assert::AreEqual(static_cast<uint8_t>(0), buffer[18]);
}
// Threshold1
Assert::AreEqual(static_cast<uint8_t>(0), buffer[7]);
Assert::AreEqual(static_cast<uint8_t>(1), buffer[8]);
TEST_METHOD(WriteStartOfFrameMarkerSegmentWithLowBoundaryValues)
{
constexpr int32_t bitsPerSample = 2;
constexpr int32_t componentCount = 1;
// Threshold2
Assert::AreEqual(static_cast<uint8_t>(0), buffer[9]);
Assert::AreEqual(static_cast<uint8_t>(2), buffer[10]);
array<uint8_t, 13> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
// Threshold3
Assert::AreEqual(static_cast<uint8_t>(0), buffer[11]);
Assert::AreEqual(static_cast<uint8_t>(3), buffer[12]);
writer.WriteStartOfFrameSegment(0, 0, bitsPerSample, componentCount);
// ResetValue
Assert::AreEqual(static_cast<uint8_t>(0), buffer[13]);
Assert::AreEqual(static_cast<uint8_t>(7), buffer[14]);
}
Assert::AreEqual(buffer.size(), writer.GetBytesWritten());
Assert::AreEqual(static_cast<uint8_t>(bitsPerSample), buffer[4]);
Assert::AreEqual(static_cast<uint8_t>(componentCount), buffer[9]);
}
TEST_METHOD(WriteStartOfScanMarker)
{
array<uint8_t, 10> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
TEST_METHOD(WriteStartOfFrameMarkerSegmentWithHighBoundaryValuesAndSerialize)
{
array<uint8_t, 775> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
writer.WriteStartOfScanSegment(1, 2, InterleaveMode::None);
writer.WriteStartOfFrameSegment(UINT16_MAX, UINT16_MAX, 16, UINT8_MAX);
Assert::AreEqual(buffer.size(), writer.GetBytesWritten());
Assert::AreEqual(static_cast<uint8_t>(16), buffer[4]);
Assert::AreEqual(static_cast<uint8_t>(UINT8_MAX), buffer[9]);
Assert::AreEqual(static_cast<uint8_t>(UINT8_MAX), buffer[buffer.size() - 3]); // Last component index.
}
TEST_METHOD(WriteColorTransformSegment)
{
const color_transformation transformation = color_transformation::hp1;
array<uint8_t, 9> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
writer.WriteColorTransformSegment(transformation);
Assert::AreEqual(buffer.size(), writer.GetBytesWritten());
// Verify mrfx identifier string.
Assert::AreEqual(static_cast<uint8_t>('m'), buffer[4]);
Assert::AreEqual(static_cast<uint8_t>('r'), buffer[5]);
Assert::AreEqual(static_cast<uint8_t>('f'), buffer[6]);
Assert::AreEqual(static_cast<uint8_t>('x'), buffer[7]);
Assert::AreEqual(static_cast<uint8_t>(transformation), buffer[8]);
}
TEST_METHOD(WriteJpegLSExtendedParametersMarkerAndSerialize)
{
const JpegLSPresetCodingParameters params{2, 1, 2, 3, 7};
array<uint8_t, 15> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
writer.WriteJpegLSPresetParametersSegment(params);
Assert::AreEqual(buffer.size(), writer.GetBytesWritten());
// Parameter ID.
Assert::AreEqual(static_cast<uint8_t>(0x1), buffer[4]);
// MaximumSampleValue
Assert::AreEqual(static_cast<uint8_t>(0), buffer[5]);
Assert::AreEqual(static_cast<uint8_t>(2), buffer[6]);
// Threshold1
Assert::AreEqual(static_cast<uint8_t>(0), buffer[7]);
Assert::AreEqual(static_cast<uint8_t>(1), buffer[8]);
// Threshold2
Assert::AreEqual(static_cast<uint8_t>(0), buffer[9]);
Assert::AreEqual(static_cast<uint8_t>(2), buffer[10]);
// Threshold3
Assert::AreEqual(static_cast<uint8_t>(0), buffer[11]);
Assert::AreEqual(static_cast<uint8_t>(3), buffer[12]);
// ResetValue
Assert::AreEqual(static_cast<uint8_t>(0), buffer[13]);
Assert::AreEqual(static_cast<uint8_t>(7), buffer[14]);
}
TEST_METHOD(WriteStartOfScanMarker)
{
array<uint8_t, 10> buffer{};
const ByteStreamInfo info = FromByteArray(buffer.data(), buffer.size());
JpegStreamWriter writer(info);
writer.WriteStartOfScanSegment(1, 2, interleave_mode::none);
Assert::AreEqual(buffer.size(), writer.GetBytesWritten());
Assert::AreEqual(static_cast<uint8_t>(1), buffer[4]); // component count.
Assert::AreEqual(static_cast<uint8_t>(1), buffer[5]); // component index.
Assert::AreEqual(static_cast<uint8_t>(0), buffer[6]); // table ID.
Assert::AreEqual(static_cast<uint8_t>(2), buffer[7]); // NEAR parameter.
Assert::AreEqual(static_cast<uint8_t>(0), buffer[8]); // ILV parameter.
Assert::AreEqual(static_cast<uint8_t>(0), buffer[9]); // transformation.
}
};
Assert::AreEqual(buffer.size(), writer.GetBytesWritten());
Assert::AreEqual(static_cast<uint8_t>(1), buffer[4]); // component count.
Assert::AreEqual(static_cast<uint8_t>(1), buffer[5]); // component index.
Assert::AreEqual(static_cast<uint8_t>(0), buffer[6]); // table ID.
Assert::AreEqual(static_cast<uint8_t>(2), buffer[7]); // NEAR parameter.
Assert::AreEqual(static_cast<uint8_t>(0), buffer[8]); // ILV parameter.
Assert::AreEqual(static_cast<uint8_t>(0), buffer[9]); // transformation.
}
};
}

View File

@ -5,6 +5,8 @@
#include "../src/jpeg_marker_code.h"
#include "../src/util.h"
namespace CharLSUnitTest {
class JpegTestStreamWriter final
{
public:
@ -78,3 +80,5 @@ public:
int componentIdOverride{};
std::vector<uint8_t> data_;
};
} // namespace CharLSUnitTest

View File

@ -0,0 +1,174 @@
// Copyright (c) Team CharLS. All rights reserved. See the accompanying "LICENSE.md" for licensed use.
#include "pch.h"
#include "util.h"
#include <charls/charls.h>
#include <vector>
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
using std::vector;
using namespace charls;
using namespace charls_test;
namespace CharLSUnitTest {
// clang-format off
TEST_CLASS(jpegls_decoder_test)
{
public:
TEST_METHOD(create_destroy)
{
// ReSharper disable once CppLocalVariableWithNonTrivialDtorIsNeverUsed
jpegls_decoder decoder;
}
TEST_METHOD(create_and_move)
{
jpegls_decoder decoder1;
jpegls_decoder decoder2(std::move(decoder1));
// ReSharper disable once CppLocalVariableWithNonTrivialDtorIsNeverUsed
jpegls_decoder decoder3 = std::move(decoder2);
}
TEST_METHOD(set_source_twice)
{
jpegls_decoder decoder;
vector<uint8_t> source(2000);
decoder.source(source);
assert_expect_exception(jpegls_errc::invalid_operation,
[&] { decoder.source(source); });
}
TEST_METHOD(read_spiff_header_without_source)
{
jpegls_decoder decoder;
assert_expect_exception(jpegls_errc::invalid_operation,
[&] {
bool header_found;
decoder.read_spiff_header(header_found);
});
}
TEST_METHOD(destination_size_without_reading_header)
{
jpegls_decoder decoder;
assert_expect_exception(jpegls_errc::invalid_operation,
[&] { static_cast<void>(decoder.destination_size()); });
}
TEST_METHOD(read_header_without_source)
{
jpegls_decoder decoder;
assert_expect_exception(jpegls_errc::invalid_operation,
[&] { decoder.read_header(); });
}
TEST_METHOD(frame_info_without_read_header)
{
jpegls_decoder decoder;
const vector<uint8_t> source(2000);
decoder.source(source);
assert_expect_exception(jpegls_errc::invalid_operation,
[&] { static_cast<void>(decoder.frame_info()); });
}
TEST_METHOD(interleave_mode_without_read_header)
{
jpegls_decoder decoder;
const vector<uint8_t> source(2000);
decoder.source(source);
assert_expect_exception(jpegls_errc::invalid_operation,
[&] { static_cast<void>(decoder.interleave_mode()); });
}
TEST_METHOD(near_lossless_without_read_header)
{
jpegls_decoder decoder;
const vector<uint8_t> source(2000);
decoder.source(source);
assert_expect_exception(jpegls_errc::invalid_operation,
[&] { static_cast<void>(decoder.near_lossless()); });
}
TEST_METHOD(preset_coding_parameters_without_read_header)
{
jpegls_decoder decoder;
const vector<uint8_t> source(2000);
decoder.source(source);
assert_expect_exception(jpegls_errc::invalid_operation,
[&] { static_cast<void>(decoder.preset_coding_parameters()); });
}
TEST_METHOD(decode_reference_file_from_buffer)
{
vector<uint8_t> buffer{read_file("DataFiles/T8C0E0.JLS")};
jpegls_decoder decoder;
decoder.source(buffer.data(), buffer.size())
.read_header();
vector<uint8_t> destination(decoder.destination_size());
decoder.decode(destination);
portable_anymap_file reference_file = read_anymap_reference_file("DataFiles/TEST8.PPM", decoder.interleave_mode(), decoder.frame_info());
const auto& reference_image_data = reference_file.image_data();
for (size_t i = 0; i < destination.size(); ++i)
{
Assert::AreEqual(reference_image_data[i], destination[i]);
}
}
TEST_METHOD(decode_without_reading_header)
{
jpegls_decoder decoder;
vector<uint8_t> buffer(1000);
assert_expect_exception(jpegls_errc::invalid_operation,
[&] { decoder.decode(buffer); });
}
TEST_METHOD(read_spiff_header)
{
jpegls_decoder decoder;
const vector<uint8_t> buffer = create_test_spiff_header();
decoder.source(buffer);
bool found;
const auto header = decoder.read_spiff_header(found);
Assert::IsTrue(found);
Assert::AreEqual(static_cast<int32_t>(spiff_profile_id::none), static_cast<int32_t>(header.profile_id));
Assert::AreEqual(3, header.component_count);
Assert::AreEqual(800U, header.height);
Assert::AreEqual(600U, header.width);
Assert::AreEqual(static_cast<int32_t>(spiff_color_space::rgb), static_cast<int32_t>(header.color_space));
Assert::AreEqual(8, header.bits_per_sample);
Assert::AreEqual(static_cast<int32_t>(spiff_compression_type::jpeg_ls), static_cast<int32_t>(header.compression_type));
Assert::AreEqual(static_cast<int32_t>(spiff_resolution_units::dots_per_inch), static_cast<int32_t>(header.resolution_units));
Assert::AreEqual(96U, header.vertical_resolution);
Assert::AreEqual(1024U, header.horizontal_resolution);
}
};
} // namespace CharLSUnitTest

View File

@ -0,0 +1,433 @@
// Copyright (c) Team CharLS. All rights reserved. See the accompanying "LICENSE.md" for licensed use.
#include "pch.h"
#include "util.h"
#include <charls/charls.h>
#include <vector>
#include <array>
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
using namespace charls;
using std::vector;
using std::array;
namespace CharLSUnitTest {
// clang-format off
TEST_CLASS(jpegls_encoder_test)
{
public:
TEST_METHOD(create_destroy)
{
// ReSharper disable once CppLocalVariableWithNonTrivialDtorIsNeverUsed
jpegls_encoder encoder;
}
TEST_METHOD(create_and_move)
{
jpegls_encoder encoder1;
jpegls_encoder encoder2(std::move(encoder1));
// ReSharper disable once CppLocalVariableWithNonTrivialDtorIsNeverUsed
jpegls_encoder encoder3 = std::move(encoder2);
}
TEST_METHOD(frame_info)
{
jpegls_encoder encoder;
encoder.frame_info({1, 1, 2, 1}); // minimum.
encoder.frame_info({UINT16_MAX, UINT16_MAX, 16, 255}); // maximum.
}
TEST_METHOD(frame_info_bad_width)
{
jpegls_encoder encoder;
assert_expect_exception(jpegls_errc::invalid_argument_width, [&] { encoder.frame_info({0, 1, 2, 1}); });
assert_expect_exception(jpegls_errc::invalid_argument_width, [&] { encoder.frame_info({UINT16_MAX + 1, 1, 2, 1}); });
}
TEST_METHOD(frame_info_bad_height)
{
jpegls_encoder encoder;
assert_expect_exception(jpegls_errc::invalid_argument_height, [&] { encoder.frame_info({1, 0, 2, 1}); });
assert_expect_exception(jpegls_errc::invalid_argument_height, [&] { encoder.frame_info({1, UINT16_MAX + 1, 2, 1}); });
}
TEST_METHOD(frame_info_bad_bits_per_sample)
{
jpegls_encoder encoder;
assert_expect_exception(jpegls_errc::invalid_argument_bits_per_sample, [&] { encoder.frame_info({1, 1, 1, 1}); });
assert_expect_exception(jpegls_errc::invalid_argument_bits_per_sample, [&] { encoder.frame_info({1, 1, 17, 1}); });
}
TEST_METHOD(frame_info_bad_component_count)
{
jpegls_encoder encoder;
assert_expect_exception(jpegls_errc::invalid_argument_component_count, [&] { encoder.frame_info({1, 1, 2, 0}); });
assert_expect_exception(jpegls_errc::invalid_argument_component_count, [&] { encoder.frame_info({1, 1, 2, 256}); });
}
TEST_METHOD(interleave_mode)
{
jpegls_encoder encoder;
encoder.interleave_mode(interleave_mode::none);
encoder.interleave_mode(interleave_mode::line);
encoder.interleave_mode(interleave_mode::sample);
}
TEST_METHOD(interleave_mode_bad)
{
jpegls_encoder encoder;
assert_expect_exception(jpegls_errc::invalid_argument_interleave_mode,
[&] { encoder.interleave_mode(static_cast<charls::interleave_mode>(-1)); });
assert_expect_exception(jpegls_errc::invalid_argument_interleave_mode,
[&] { encoder.interleave_mode(static_cast<charls::interleave_mode>(3)); });
}
TEST_METHOD(near_lossless)
{
jpegls_encoder encoder;
encoder.near_lossless(0); // set lowest value.
encoder.near_lossless(255); // set highest value.
}
TEST_METHOD(near_lossless_bad)
{
jpegls_encoder encoder;
assert_expect_exception(jpegls_errc::invalid_argument_near_lossless, [&] { encoder.near_lossless(-1); });
assert_expect_exception(jpegls_errc::invalid_argument_near_lossless, [&] { encoder.near_lossless(256); });
}
TEST_METHOD(estimated_destination_size_minimal_frame_info)
{
jpegls_encoder encoder;
encoder.frame_info({1, 1, 2, 1}); // minimum.
const auto size = encoder.estimated_destination_size();
Assert::IsTrue(size >= 1024);
}
TEST_METHOD(estimated_destination_size_16_bit)
{
jpegls_encoder encoder;
encoder.frame_info({100, 100, 16, 1}); // minimum.
const auto size = encoder.estimated_destination_size();
Assert::IsTrue(size >= 100 * 100 * 2);
}
TEST_METHOD(estimated_destination_size_too_soon)
{
jpegls_encoder encoder;
assert_expect_exception(jpegls_errc::invalid_operation, [&] { encoder.estimated_destination_size(); });
}
TEST_METHOD(destination)
{
jpegls_encoder encoder;
vector<uint8_t> destination(200);
encoder.destination(destination);
}
TEST_METHOD(destination_can_only_be_set_once)
{
jpegls_encoder encoder;
vector<uint8_t> destination(200);
encoder.destination(destination);
assert_expect_exception(jpegls_errc::invalid_operation, [&] {encoder.destination(destination); });
}
TEST_METHOD(write_standard_spiff_header)
{
jpegls_encoder encoder;
encoder.frame_info({1, 1, 2, 1});
vector<uint8_t> destination(encoder.estimated_destination_size());
encoder.destination(destination);
encoder.write_standard_spiff_header(spiff_color_space::cmyk);
Assert::AreEqual(static_cast<size_t>(34), encoder.bytes_written());
}
TEST_METHOD(write_standard_spiff_header_without_destination)
{
jpegls_encoder encoder;
encoder.frame_info({ 1, 1, 2, 1 });
assert_expect_exception(jpegls_errc::invalid_operation, [&] { encoder.write_standard_spiff_header(spiff_color_space::cmyk); });
}
TEST_METHOD(write_standard_spiff_header_without_frame_info)
{
jpegls_encoder encoder;
vector<uint8_t> destination(100);
encoder.destination(destination);
assert_expect_exception(jpegls_errc::invalid_operation, [&] { encoder.write_standard_spiff_header(spiff_color_space::cmyk); });
}
TEST_METHOD(write_standard_spiff_header_twice)
{
jpegls_encoder encoder;
encoder.frame_info({ 1, 1, 2, 1 });
vector<uint8_t> destination(encoder.estimated_destination_size());
encoder.destination(destination);
encoder.write_standard_spiff_header(spiff_color_space::cmyk);
assert_expect_exception(jpegls_errc::invalid_operation, [&] { encoder.write_standard_spiff_header(spiff_color_space::cmyk); });
}
TEST_METHOD(write_spiff_header)
{
jpegls_encoder encoder;
encoder.frame_info({1, 1, 2, 1});
vector<uint8_t> destination(encoder.estimated_destination_size());
encoder.destination(destination);
spiff_header spiff_header{};
spiff_header.width = 1;
spiff_header.height = 1;
encoder.write_spiff_header(spiff_header);
Assert::AreEqual(static_cast<size_t>(34), encoder.bytes_written());
}
TEST_METHOD(write_spiff_header_invalid_height)
{
jpegls_encoder encoder;
encoder.frame_info({1, 1, 2, 1});
vector<uint8_t> destination(encoder.estimated_destination_size());
encoder.destination(destination);
spiff_header spiff_header{};
spiff_header.width = 1;
assert_expect_exception(jpegls_errc::invalid_argument_height, [&] { encoder.write_spiff_header(spiff_header); });
Assert::AreEqual(static_cast<size_t>(0), encoder.bytes_written());
}
TEST_METHOD(write_spiff_header_invalid_width)
{
jpegls_encoder encoder;
encoder.frame_info({1, 1, 2, 1});
vector<uint8_t> destination(encoder.estimated_destination_size());
encoder.destination(destination);
spiff_header spiff_header{};
spiff_header.height = 1;
assert_expect_exception(jpegls_errc::invalid_argument_width, [&] { encoder.write_spiff_header(spiff_header); });
Assert::AreEqual(static_cast<size_t>(0), encoder.bytes_written());
}
TEST_METHOD(write_spiff_entry)
{
jpegls_encoder encoder;
encoder.frame_info({1, 1, 2, 1});
vector<uint8_t> destination(encoder.estimated_destination_size());
encoder.destination(destination);
encoder.write_standard_spiff_header(spiff_color_space::cmyk);
encoder.write_spiff_entry(spiff_entry_tag::image_title, "test", 4);
Assert::AreEqual(static_cast<size_t>(46), encoder.bytes_written());
}
TEST_METHOD(write_spiff_entry_twice)
{
jpegls_encoder encoder;
encoder.frame_info({1, 1, 2, 1});
vector<uint8_t> destination(encoder.estimated_destination_size());
encoder.destination(destination);
encoder.write_standard_spiff_header(spiff_color_space::cmyk);
encoder.write_spiff_entry(spiff_entry_tag::image_title, "test", 4);
encoder.write_spiff_entry(spiff_entry_tag::image_title, "test", 4);
Assert::AreEqual(static_cast<size_t>(58), encoder.bytes_written());
}
TEST_METHOD(write_empty_spiff_entry)
{
jpegls_encoder encoder;
encoder.frame_info({1, 1, 2, 1});
vector<uint8_t> destination(encoder.estimated_destination_size());
encoder.destination(destination);
encoder.write_standard_spiff_header(spiff_color_space::cmyk);
encoder.write_spiff_entry(spiff_entry_tag::image_title, nullptr, 0);
Assert::AreEqual(static_cast<size_t>(42), encoder.bytes_written());
}
TEST_METHOD(write_spiff_entry_with_invalid_tag)
{
jpegls_encoder encoder;
encoder.frame_info({1, 1, 2, 1});
vector<uint8_t> destination(encoder.estimated_destination_size());
encoder.destination(destination);
encoder.write_standard_spiff_header(spiff_color_space::cmyk);
assert_expect_exception(jpegls_errc::invalid_argument, [&] { encoder.write_spiff_entry(1, "test", 4); });
}
TEST_METHOD(write_spiff_entry_with_invalid_size)
{
jpegls_encoder encoder;
encoder.frame_info({1, 1, 2, 1});
vector<uint8_t> destination(encoder.estimated_destination_size());
encoder.destination(destination);
encoder.write_standard_spiff_header(spiff_color_space::cmyk);
assert_expect_exception(jpegls_errc::invalid_argument_spiff_entry_size,
[&] { encoder.write_spiff_entry(spiff_entry_tag::image_title, "test", 65528 + 1); });
}
TEST_METHOD(write_spiff_entry_without_spiff_header)
{
jpegls_encoder encoder;
encoder.frame_info({1, 1, 2, 1});
vector<uint8_t> destination(encoder.estimated_destination_size());
encoder.destination(destination);
assert_expect_exception(jpegls_errc::invalid_operation,
[&] { encoder.write_spiff_entry(spiff_entry_tag::image_title, "test", 65528); });
}
TEST_METHOD(set_preset_coding_parameters)
{
jpegls_encoder encoder;
charls_jpegls_pc_parameters pc_parameters{};
encoder.preset_coding_parameters(pc_parameters);
// No explicit test possible, code should remain stable.
Assert::IsTrue(true);
}
TEST_METHOD(set_preset_coding_parameters_bad_values)
{
jpegls_encoder encoder;
charls_jpegls_pc_parameters pc_parameters{1,1,1,1,1};
assert_expect_exception(jpegls_errc::invalid_argument_pc_parameters,
[&] { encoder.preset_coding_parameters(pc_parameters); });
}
TEST_METHOD(set_color_transformation_bad_value)
{
jpegls_encoder encoder;
assert_expect_exception(jpegls_errc::invalid_argument_color_transformation,
[&] { encoder.color_transformation(static_cast<color_transformation>(100)); });
}
TEST_METHOD(encode_without_destination)
{
jpegls_encoder encoder;
encoder.frame_info({1, 1, 2, 1});
vector<uint8_t> source(20);
assert_expect_exception(jpegls_errc::invalid_operation,[&] { encoder.encode(source); });
}
TEST_METHOD(encode_without_frame_info)
{
jpegls_encoder encoder;
vector<uint8_t> destination(20);
encoder.destination(destination);
vector<uint8_t> source(20);
assert_expect_exception(jpegls_errc::invalid_operation,[&] { encoder.encode(source); });
}
TEST_METHOD(encode_with_spiff_header)
{
const array<uint8_t, 5> source{0, 1, 2, 3, 4};
jpegls_encoder encoder;
encoder.frame_info({5, 1, 8, 1});
vector<uint8_t> destination(encoder.estimated_destination_size());
encoder.destination(destination);
encoder.write_standard_spiff_header(spiff_color_space::grayscale);
encoder.encode(source);
// TODO: add generic compare test function.
}
TEST_METHOD(encode_with_color_space)
{
const array<uint8_t, 6> source{0, 1, 2, 3, 4, 5};
jpegls_encoder encoder;
encoder.frame_info({2, 1, 8, 3});
vector<uint8_t> destination(encoder.estimated_destination_size());
encoder.destination(destination)
.color_transformation(color_transformation::hp1);
encoder.encode(source);
// TODO: add generic compare test function.
}
TEST_METHOD(encode_16bit)
{
const array<uint8_t, 6> source{0, 1, 2, 3, 4, 5};
jpegls_encoder encoder;
encoder.frame_info({3, 1, 16, 1});
vector<uint8_t> destination(encoder.estimated_destination_size());
encoder.destination(destination);
encoder.encode(source);
// TODO: add generic compare test function.
}
};
} // namespace CharLSUnitTest

View File

@ -7,8 +7,7 @@
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
using namespace charls;
namespace
{
namespace {
struct thresholds
{
@ -22,9 +21,7 @@ struct thresholds
// Threshold function of JPEG-LS reference implementation.
constexpr thresholds ComputeDefaultsUsingReferenceImplementation(const int32_t max_value, const uint16_t near) noexcept
{
thresholds result{};
result.MaxVal = max_value;
thresholds result{max_value, 0, 0, 0, 64};
if (result.MaxVal >= 128)
{
@ -61,12 +58,10 @@ constexpr thresholds ComputeDefaultsUsingReferenceImplementation(const int32_t m
if (result.T3 > result.MaxVal || result.T3 < result.T2)
result.T3 = result.T2;
}
result.Reset = 64;
return result;
}
} // namespace
// clang-format off
@ -158,6 +153,24 @@ public:
Assert::AreEqual(expected.T3, parameters.Threshold3);
Assert::AreEqual(expected.Reset, parameters.ResetValue);
}
TEST_METHOD(is_valid_default)
{
constexpr auto bits_per_sample = 16;
constexpr auto maximum_component_value = (1 << bits_per_sample) - 1;
const jpegls_pc_parameters pc_parameters{};
Assert::IsTrue(is_valid(pc_parameters, maximum_component_value, 0));
}
TEST_METHOD(is_valid_thresholds_zero)
{
constexpr auto bits_per_sample = 16;
constexpr auto maximum_component_value = (1 << bits_per_sample) - 1;
const jpegls_pc_parameters pc_parameters{maximum_component_value, 0, 0, 0, 63};
Assert::IsTrue(is_valid(pc_parameters, maximum_component_value, 0));
}
};
} // namespace CharLSUnitTest

343
unittest/lena8b.pgm Normal file

File diff suppressed because one or more lines are too long

198
unittest/util.cpp Normal file
View File

@ -0,0 +1,198 @@
// Copyright (c) Team CharLS. All rights reserved. See the accompanying "LICENSE.md" for licensed use.
#include "pch.h"
#include "util.h"
#include <charls/charls.h>
#include "../src/jpeg_stream_writer.h"
#include "../test/portable_anymap_file.h"
using Microsoft::VisualStudio::CppUnitTestFramework::Assert;
using std::ifstream;
using std::vector;
using namespace charls;
using namespace charls_test;
namespace {
MSVC_WARNING_SUPPRESS(26497) // cannot be marked constexpr: will give error C3615
bool IsMachineLittleEndian() noexcept
{
constexpr int a = 0xFF000001;
const auto* chars = reinterpret_cast<const char*>(&a);
return chars[0] == 0x01;
}
MSVC_WARNING_UNSUPPRESS()
void triplet_to_planar(vector<uint8_t>& buffer, uint32_t width, uint32_t height)
{
vector<uint8_t> workBuffer(buffer.size());
const size_t byteCount = static_cast<size_t>(width) * height;
for (size_t index = 0; index < byteCount; index++)
{
workBuffer[index] = buffer[index * 3 + 0];
workBuffer[index + 1 * byteCount] = buffer[index * 3 + 1];
workBuffer[index + 2 * byteCount] = buffer[index * 3 + 2];
}
swap(buffer, workBuffer);
}
} // namespace
vector<uint8_t> read_file(const char* filename)
{
ifstream input;
input.exceptions(input.eofbit | input.failbit | input.badbit);
input.open(filename, input.in | input.binary);
input.seekg(0, input.end);
const auto byteCountFile = static_cast<int>(input.tellg());
input.seekg(0, input.beg);
vector<uint8_t> buffer(byteCountFile);
input.read(reinterpret_cast<char*>(buffer.data()), buffer.size());
return buffer;
}
void fix_endian(vector<uint8_t>& buffer, bool little_endian_data) noexcept
{
if (little_endian_data == IsMachineLittleEndian())
return;
for (size_t i = 0; i < buffer.size() - 1; i += 2)
{
std::swap(buffer[i], buffer[i + 1]);
}
}
portable_anymap_file read_anymap_reference_file(const char* filename, interleave_mode interleave_mode, const frame_info& frame_info)
{
portable_anymap_file reference_file(filename);
if (frame_info.bits_per_sample > 8)
{
fix_endian(reference_file.image_data(), false);
}
if (interleave_mode == interleave_mode::none && frame_info.component_count == 3)
{
triplet_to_planar(reference_file.image_data(), frame_info.width, frame_info.height);
}
return reference_file;
}
vector<uint8_t> create_test_spiff_header(const uint8_t high_version, const uint8_t low_version, const bool end_of_directory)
{
vector<uint8_t> buffer;
buffer.push_back(0xFF);
buffer.push_back(0xD8); // SOI.
buffer.push_back(0xFF);
buffer.push_back(0xE8); // ApplicationData8
buffer.push_back(0);
buffer.push_back(32);
// SPIFF identifier string.
buffer.push_back('S');
buffer.push_back('P');
buffer.push_back('I');
buffer.push_back('F');
buffer.push_back('F');
buffer.push_back(0);
// Version
buffer.push_back(high_version);
buffer.push_back(low_version);
buffer.push_back(0); // profile id
buffer.push_back(3); // component count
// Height
buffer.push_back(0);
buffer.push_back(0);
buffer.push_back(0x3);
buffer.push_back(0x20);
// Width
buffer.push_back(0);
buffer.push_back(0);
buffer.push_back(0x2);
buffer.push_back(0x58);
buffer.push_back(10); // color space
buffer.push_back(8); // bits per sample
buffer.push_back(6); // compression type, 6 = JPEG-LS
buffer.push_back(1); // resolution units
// vertical_resolution
buffer.push_back(0);
buffer.push_back(0);
buffer.push_back(0);
buffer.push_back(96);
// header.horizontal_resolution = 1024;
buffer.push_back(0);
buffer.push_back(0);
buffer.push_back(4);
buffer.push_back(0);
const size_t spiff_header_size = buffer.size();
buffer.resize(buffer.size() + 100);
const ByteStreamInfo info = FromByteArray(buffer.data() + spiff_header_size, buffer.size() - spiff_header_size);
JpegStreamWriter writer(info);
if (end_of_directory)
{
writer.WriteSpiffEndOfDirectoryEntry();
}
writer.WriteStartOfFrameSegment(100, 100, 8, 1);
writer.WriteStartOfScanSegment(1, 0, interleave_mode::none);
return buffer;
}
vector<uint8_t> create_noise_image_16bit(const size_t pixel_count, const int bit_count, const uint32_t seed)
{
srand(seed);
vector<uint8_t> buffer(pixel_count * 2);
const auto mask = static_cast<uint16_t>((1 << bit_count) - 1);
for (size_t i = 0; i < pixel_count; i = i + 2)
{
const uint16_t value = static_cast<uint16_t>(rand()) & mask;
buffer[i] = static_cast<uint8_t>(value);
buffer[i] = static_cast<uint8_t>(value >> 8);
}
return buffer;
}
void test_round_trip_legacy(const vector<uint8_t>& source, const JlsParameters& params)
{
vector<uint8_t> encodedBuffer(params.height * params.width * params.components * params.bitsPerSample / 4);
vector<uint8_t> decodedBuffer(static_cast<size_t>(params.height) * params.width * ((params.bitsPerSample + 7) / 8) * params.components);
size_t compressedLength = 0;
auto error = JpegLsEncode(encodedBuffer.data(), encodedBuffer.size(), &compressedLength,
source.data(), source.size(), &params, nullptr);
Assert::IsTrue(error == jpegls_errc::success);
error = JpegLsDecode(decodedBuffer.data(), decodedBuffer.size(), encodedBuffer.data(), compressedLength, nullptr, nullptr);
Assert::IsTrue(error == jpegls_errc::success);
const uint8_t* byteOut = decodedBuffer.data();
for (size_t i = 0; i < decodedBuffer.size(); ++i)
{
if (source[i] != byteOut[i])
{
Assert::IsTrue(false);
break;
}
}
}

37
unittest/util.h Normal file
View File

@ -0,0 +1,37 @@
#pragma once
#include "../test/portable_anymap_file.h"
#include <vector>
std::vector<uint8_t> read_file(const char* filename);
void fix_endian(std::vector<uint8_t>& buffer, bool little_endian_data) noexcept;
charls_test::portable_anymap_file read_anymap_reference_file(const char* filename, charls::interleave_mode interleave_mode, const charls::frame_info& frame_info);
std::vector<uint8_t> create_test_spiff_header(uint8_t high_version = 2, uint8_t low_version = 0, bool end_of_directory = true);
std::vector<uint8_t> create_noise_image_16bit(size_t pixel_count, int bit_count, uint32_t seed);
void test_round_trip_legacy(const std::vector<uint8_t>& source, const JlsParameters& params);
namespace CharLSUnitTest {
template<typename Functor>
void assert_expect_exception(charls::jpegls_errc error_value, Functor functor)
{
try
{
functor();
}
catch (const charls::jpegls_error& error)
{
Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(error_value == error.code());
return;
}
catch (...)
{
throw;
}
Assert::Fail();
}
} // namespace CharLSUnitTest