initial commit

This commit is contained in:
Rich Geldreich
2021-12-18 01:04:05 -05:00
parent 657ef8c372
commit 986d233032
15 changed files with 22016 additions and 0 deletions

72
CMakeLists.txt Normal file
View File

@@ -0,0 +1,72 @@
project(fpng_test)
cmake_minimum_required(VERSION 3.0)
option(BUILD_X64 "build 64-bit" TRUE)
message("Initial BUILD_X64=${BUILD_X64}")
if( NOT CMAKE_BUILD_TYPE )
set( CMAKE_BUILD_TYPE Release )
endif()
message( ${PROJECT_NAME} " build type: " ${CMAKE_BUILD_TYPE} )
if (BUILD_X64)
message("Building 64-bit")
else()
message("Building 32-bit")
endif()
if (NOT MSVC)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS -std=c++11)
set(GCC_COMPILE_FLAGS "-fvisibility=hidden -fPIC -fno-strict-aliasing -D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64 -Wall -Wextra")
if (NOT BUILD_X64)
set(GCC_COMPILE_FLAGS "${GCC_COMPILE_FLAGS} -m32")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCC_LINK_FLAGS} -Wl,-rpath .")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${GCC_COMPILE_FLAGS}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${GCC_COMPILE_FLAGS}")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${GCC_COMPILE_FLAGS} -D_DEBUG")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COMPILE_FLAGS}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${GCC_COMPILE_FLAGS}")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${GCC_COMPILE_FLAGS} -D_DEBUG")
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
endif()
set(FPNG_SRC_LIST ${COMMON_SRC_LIST}
fpng.cpp
Crc32.cpp
fpng_test.cpp
lodepng.cpp
)
if (APPLE)
set(BIN_DIRECTORY "bin_osx")
else()
set(BIN_DIRECTORY "bin")
endif()
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${BIN_DIRECTORY})
add_executable(fpng_test ${FPNG_SRC_LIST})
if (NOT MSVC)
target_link_libraries(fpng_test m pthread)
endif()
install(TARGETS fpng_test DESTINATION bin)

1245
Crc32.cpp Normal file

File diff suppressed because it is too large Load Diff

69
Crc32.h Normal file
View File

@@ -0,0 +1,69 @@
// //////////////////////////////////////////////////////////
// Crc32.h
// Copyright (c) 2011-2019 Stephan Brumme. All rights reserved.
// Slicing-by-16 contributed by Bulat Ziganshin
// Tableless bytewise CRC contributed by Hagai Gold
// see http://create.stephan-brumme.com/disclaimer.html
//
// if running on an embedded system, you might consider shrinking the
// big Crc32Lookup table by undefining these lines:
//#define CRC32_USE_LOOKUP_TABLE_BYTE
//#define CRC32_USE_LOOKUP_TABLE_SLICING_BY_4
#define CRC32_USE_LOOKUP_TABLE_SLICING_BY_8
//#define CRC32_USE_LOOKUP_TABLE_SLICING_BY_16
// - crc32_bitwise doesn't need it at all
// - crc32_halfbyte has its own small lookup table
// - crc32_1byte_tableless and crc32_1byte_tableless2 don't need it at all
// - crc32_1byte needs only Crc32Lookup[0]
// - crc32_4bytes needs only Crc32Lookup[0..3]
// - crc32_8bytes needs only Crc32Lookup[0..7]
// - crc32_4x8bytes needs only Crc32Lookup[0..7]
// - crc32_16bytes needs all of Crc32Lookup
// using the aforementioned #defines the table is automatically fitted to your needs
// uint8_t, uint32_t, int32_t
#include <stdint.h>
// size_t
#include <cstddef>
// crc32_fast selects the fastest algorithm depending on flags (CRC32_USE_LOOKUP_...)
/// compute CRC32 using the fastest algorithm for large datasets on modern CPUs
uint32_t crc32_fast (const void* data, size_t length, uint32_t previousCrc32 = 0);
/// merge two CRC32 such that result = crc32(dataB, lengthB, crc32(dataA, lengthA))
uint32_t crc32_combine (uint32_t crcA, uint32_t crcB, size_t lengthB);
/// compute CRC32 (bitwise algorithm)
uint32_t crc32_bitwise (const void* data, size_t length, uint32_t previousCrc32 = 0);
/// compute CRC32 (half-byte algoritm)
uint32_t crc32_halfbyte(const void* data, size_t length, uint32_t previousCrc32 = 0);
#ifdef CRC32_USE_LOOKUP_TABLE_BYTE
/// compute CRC32 (standard algorithm)
uint32_t crc32_1byte (const void* data, size_t length, uint32_t previousCrc32 = 0);
#endif
/// compute CRC32 (byte algorithm) without lookup tables
uint32_t crc32_1byte_tableless (const void* data, size_t length, uint32_t previousCrc32 = 0);
/// compute CRC32 (byte algorithm) without lookup tables
uint32_t crc32_1byte_tableless2(const void* data, size_t length, uint32_t previousCrc32 = 0);
#ifdef CRC32_USE_LOOKUP_TABLE_SLICING_BY_4
/// compute CRC32 (Slicing-by-4 algorithm)
uint32_t crc32_4bytes (const void* data, size_t length, uint32_t previousCrc32 = 0);
#endif
#ifdef CRC32_USE_LOOKUP_TABLE_SLICING_BY_8
/// compute CRC32 (Slicing-by-8 algorithm)
uint32_t crc32_8bytes (const void* data, size_t length, uint32_t previousCrc32 = 0);
/// compute CRC32 (Slicing-by-8 algorithm), unroll inner loop 4 times
uint32_t crc32_4x8bytes(const void* data, size_t length, uint32_t previousCrc32 = 0);
#endif
#ifdef CRC32_USE_LOOKUP_TABLE_SLICING_BY_16
/// compute CRC32 (Slicing-by-16 algorithm)
uint32_t crc32_16bytes (const void* data, size_t length, uint32_t previousCrc32 = 0);
/// compute CRC32 (Slicing-by-16 algorithm, prefetch upcoming data blocks)
uint32_t crc32_16bytes_prefetch(const void* data, size_t length, uint32_t previousCrc32 = 0, size_t prefetchAhead = 256);
#endif

1045
fpng.cpp Normal file

File diff suppressed because it is too large Load Diff

16
fpng.h Normal file
View File

@@ -0,0 +1,16 @@
// fpng.h - R. Geldreich, Jr. - public domain or zlib license (see end of fpng.cpp for Public Domain waiver/optional license)
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <vector>
namespace fpng
{
bool fpng_encode_image_to_memory(const void* pImage, int w, int h, int num_chans, bool flip, std::vector<uint8_t>& out_buf);
#ifndef FPNG_NO_STDIO
bool fpng_encode_image_to_file(const char* pFilename, const void* pImage, int w, int h, int num_chans, bool flip);
#endif
} // namespace fpng

31
fpng.sln Normal file
View File

@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31911.196
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fpng", "fpng.vcxproj", "{33211B65-F564-4E23-A640-FA7F02007CB3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{33211B65-F564-4E23-A640-FA7F02007CB3}.Debug|x64.ActiveCfg = Debug|x64
{33211B65-F564-4E23-A640-FA7F02007CB3}.Debug|x64.Build.0 = Debug|x64
{33211B65-F564-4E23-A640-FA7F02007CB3}.Debug|x86.ActiveCfg = Debug|Win32
{33211B65-F564-4E23-A640-FA7F02007CB3}.Debug|x86.Build.0 = Debug|Win32
{33211B65-F564-4E23-A640-FA7F02007CB3}.Release|x64.ActiveCfg = Release|x64
{33211B65-F564-4E23-A640-FA7F02007CB3}.Release|x64.Build.0 = Release|x64
{33211B65-F564-4E23-A640-FA7F02007CB3}.Release|x86.ActiveCfg = Release|Win32
{33211B65-F564-4E23-A640-FA7F02007CB3}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4225CF7B-633D-443D-A767-C66A2D22BCDA}
EndGlobalSection
EndGlobal

162
fpng.vcxproj Normal file
View File

@@ -0,0 +1,162 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{33211b65-f564-4e23-a640-fa7f02007cb3}</ProjectGuid>
<RootNamespace>fpng</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>bin</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>bin</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>.\bin</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>bin</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="Crc32.cpp" />
<ClCompile Include="fpng.cpp" />
<ClCompile Include="fpng_test.cpp" />
<ClCompile Include="lodepng.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Crc32.h" />
<ClInclude Include="fpng.h" />
<ClInclude Include="lodepng.h" />
<ClInclude Include="qoi.h" />
<ClInclude Include="stb_image.h" />
<ClInclude Include="stb_image_write.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

51
fpng.vcxproj.filters Normal file
View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<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>
</ItemGroup>
<ItemGroup>
<ClCompile Include="fpng.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Crc32.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="fpng_test.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="lodepng.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Crc32.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="fpng.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="qoi.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="stb_image.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="stb_image_write.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="lodepng.h">
<Filter>Source Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

532
fpng_test.cpp Normal file
View File

@@ -0,0 +1,532 @@
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#if defined(_WIN32)
// For QueryPerformanceCounter/QueryPerformanceFrequency
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include "fpng.h"
#include "lodepng.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
#define QOI_IMPLEMENTATION
#include "qoi.h"
typedef std::vector<uint8_t> uint8_vec;
typedef uint64_t timer_ticks;
template <typename S> static inline S maximum(S a, S b) { return (a > b) ? a : b; }
template <typename S> static inline S minimum(S a, S b) { return (a < b) ? a : b; }
class interval_timer
{
public:
interval_timer();
void start();
void stop();
double get_elapsed_secs() const;
inline double get_elapsed_ms() const { return 1000.0f * get_elapsed_secs(); }
static void init();
static inline timer_ticks get_ticks_per_sec() { return g_freq; }
static timer_ticks get_ticks();
static double ticks_to_secs(timer_ticks ticks);
static inline double ticks_to_ms(timer_ticks ticks) { return ticks_to_secs(ticks) * 1000.0f; }
private:
static timer_ticks g_init_ticks, g_freq;
static double g_timer_freq;
timer_ticks m_start_time, m_stop_time;
bool m_started, m_stopped;
};
timer_ticks interval_timer::g_init_ticks, interval_timer::g_freq;
double interval_timer::g_timer_freq;
#if defined(_WIN32)
inline void query_counter(timer_ticks* pTicks)
{
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(pTicks));
}
inline void query_counter_frequency(timer_ticks* pTicks)
{
QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(pTicks));
}
#elif defined(__APPLE__)
#include <sys/time.h>
inline void query_counter(timer_ticks* pTicks)
{
struct timeval cur_time;
gettimeofday(&cur_time, NULL);
*pTicks = static_cast<unsigned long long>(cur_time.tv_sec) * 1000000ULL + static_cast<unsigned long long>(cur_time.tv_usec);
}
inline void query_counter_frequency(timer_ticks* pTicks)
{
*pTicks = 1000000;
}
#elif defined(__GNUC__)
#include <sys/timex.h>
inline void query_counter(timer_ticks* pTicks)
{
struct timeval cur_time;
gettimeofday(&cur_time, NULL);
*pTicks = static_cast<unsigned long long>(cur_time.tv_sec) * 1000000ULL + static_cast<unsigned long long>(cur_time.tv_usec);
}
inline void query_counter_frequency(timer_ticks* pTicks)
{
*pTicks = 1000000;
}
#else
#error TODO
#endif
interval_timer::interval_timer() : m_start_time(0), m_stop_time(0), m_started(false), m_stopped(false)
{
if (!g_timer_freq)
init();
}
void interval_timer::start()
{
query_counter(&m_start_time);
m_started = true;
m_stopped = false;
}
void interval_timer::stop()
{
assert(m_started);
query_counter(&m_stop_time);
m_stopped = true;
}
double interval_timer::get_elapsed_secs() const
{
assert(m_started);
if (!m_started)
return 0;
timer_ticks stop_time = m_stop_time;
if (!m_stopped)
query_counter(&stop_time);
timer_ticks delta = stop_time - m_start_time;
return delta * g_timer_freq;
}
void interval_timer::init()
{
if (!g_timer_freq)
{
query_counter_frequency(&g_freq);
g_timer_freq = 1.0f / g_freq;
query_counter(&g_init_ticks);
}
}
timer_ticks interval_timer::get_ticks()
{
if (!g_timer_freq)
init();
timer_ticks ticks;
query_counter(&ticks);
return ticks - g_init_ticks;
}
double interval_timer::ticks_to_secs(timer_ticks ticks)
{
if (!g_timer_freq)
init();
return ticks * g_timer_freq;
}
bool read_file_to_vec(const char* pFilename, uint8_vec& data)
{
FILE* pFile = nullptr;
#ifdef _WIN32
fopen_s(&pFile, pFilename, "rb");
#else
pFile = fopen(pFilename, "rb");
#endif
if (!pFile)
return false;
fseek(pFile, 0, SEEK_END);
#ifdef _WIN32
int64_t filesize = _ftelli64(pFile);
#else
int64_t filesize = ftello(pFile);
#endif
if (filesize < 0)
{
fclose(pFile);
return false;
}
fseek(pFile, 0, SEEK_SET);
if (sizeof(size_t) == sizeof(uint32_t))
{
if (filesize > 0x70000000)
{
// File might be too big to load safely in one alloc
fclose(pFile);
return false;
}
}
data.resize((size_t)filesize);
if (filesize)
{
if (fread(&data[0], 1, (size_t)filesize, pFile) != (size_t)filesize)
{
fclose(pFile);
return false;
}
}
fclose(pFile);
return true;
}
bool write_data_to_file_internal(const char* pFilename, const void* pData, size_t len)
{
FILE* pFile = nullptr;
#ifdef _WIN32
fopen_s(&pFile, pFilename, "wb");
#else
pFile = fopen(pFilename, "wb");
#endif
if (!pFile)
return false;
if (len)
{
if (fwrite(pData, 1, len, pFile) != len)
{
fclose(pFile);
return false;
}
}
return fclose(pFile) != EOF;
}
bool write_data_to_file(const char* pFilename, const void* pData, size_t len)
{
#ifdef _WIN32
const uint32_t MAX_TRIES = 10;
#else
const uint32_t MAX_TRIES = 1;
#endif
for (uint32_t i = 0; i < MAX_TRIES; i++)
{
if (write_data_to_file_internal(pFilename, pData, len))
return true;
#ifdef _WIN32
Sleep(100);
#endif
}
return false;
}
struct color_rgba
{
uint8_t m_c[4];
};
static void write_func_stbi(void* context, void* data, int size)
{
if (!size)
return;
uint8_vec* pVec = (uint8_vec*)context;
size_t cur_s = pVec->size();
pVec->resize(cur_s + size);
memcpy(pVec->data() + cur_s, data, size);
}
int main(int arg_c, char **arg_v)
{
if (arg_c < 2)
{
printf("Usage: fpng_test filename\n");
return EXIT_FAILURE;
}
const char* pFilename = nullptr;
bool csv_flag = false;
for (int i = 1; i < arg_c; i++)
{
const char* pArg = arg_v[i];
if (pArg[0] == '-')
{
if (pArg[1] == 'c')
{
csv_flag = true;
}
else
{
fprintf(stderr, "Recognized option: %s\n", pArg);
return EXIT_FAILURE;
}
}
else
{
if (pFilename)
{
fprintf(stderr, "Too many filenames: %s\n", pArg);
return EXIT_FAILURE;
}
pFilename = pArg;
}
}
if (!csv_flag)
printf("Filename: %s\n", pFilename);
uint8_vec source_file_data;
if (!read_file_to_vec(pFilename, source_file_data))
{
fprintf(stderr, "Failed reading source file data \"%s\"\n", pFilename);
return EXIT_FAILURE;
}
unsigned int source_width = 0, source_height = 0;
unsigned char* pSource_image_buffer = nullptr;
unsigned error = lodepng_decode_memory(&pSource_image_buffer, &source_width, &source_height, source_file_data.data(), source_file_data.size(), LCT_RGBA, 8);
if (error != 0)
{
fprintf(stderr, "Failed unpacking source file \"%s\"\n", pFilename);
return EXIT_FAILURE;
}
const color_rgba* pSource_pixels32 = (const color_rgba*)pSource_image_buffer;
uint32_t total_source_pixels = source_width * source_height;
bool has_alpha = false;
for (uint32_t i = 0; i < total_source_pixels; i++)
{
if (pSource_pixels32[i].m_c[3] < 255)
{
has_alpha = true;
break;
}
}
const uint32_t source_chans = has_alpha ? 4 : 3;
if (!csv_flag)
printf("Dimensions: %ux%u, Has Alpha: %u, Total Pixels: %u, bytes: %u (%f MB)\n", source_width, source_height, has_alpha, total_source_pixels, total_source_pixels * source_chans, total_source_pixels * source_chans / (1024.0f * 1024.0f));
uint8_vec source_image_buffer24(total_source_pixels * 3);
for (uint32_t i = 0; i < total_source_pixels; i++)
{
source_image_buffer24[i * 3 + 0] = pSource_pixels32[i].m_c[0];
source_image_buffer24[i * 3 + 1] = pSource_pixels32[i].m_c[1];
source_image_buffer24[i * 3 + 2] = pSource_pixels32[i].m_c[2];
}
const uint8_t* pSource_pixels24 = source_image_buffer24.data();
const uint32_t NUM_TIMES_TO_ENCODE = 3;
interval_timer tm;
// FPNG
std::vector<uint8_t> fpng_file_buf;
double fpng_best_time = 1e+9f;
for (uint32_t i = 0; i < NUM_TIMES_TO_ENCODE; i++)
{
tm.start();
if (!fpng::fpng_encode_image_to_memory((source_chans == 3) ? (const void *)pSource_pixels24 : (const void*)pSource_pixels32, source_width, source_height, source_chans, false, fpng_file_buf))
{
fprintf(stderr, "fpng_encode_image_to_memory() failed!\n");
return EXIT_FAILURE;
}
fpng_best_time = minimum(fpng_best_time, tm.get_elapsed_secs());
}
if (!csv_flag)
printf("FPNG: %f secs, %u bytes, %f MB\n", fpng_best_time, (uint32_t)fpng_file_buf.size(), fpng_file_buf.size() / (1024.0f * 1024.0f));
if (!write_data_to_file("fpng.png", fpng_file_buf.data(), fpng_file_buf.size()))
{
fprintf(stderr, "Failed writing to file fpng.png\n");
return EXIT_FAILURE;
}
// Verify FPNG's output data using lodepng
{
unsigned int lodepng_decoded_w = 0, lodepng_decoded_h = 0;
unsigned char* lodepng_decoded_buffer = nullptr;
error = lodepng_decode_memory(&lodepng_decoded_buffer, &lodepng_decoded_w, &lodepng_decoded_h, (unsigned char*)fpng_file_buf.data(), fpng_file_buf.size(), LCT_RGBA, 8);
if (error != 0)
{
fprintf(stderr, "lodepng failed decompressing FPNG's output PNG file!\n");
return EXIT_FAILURE;
}
if (memcmp(lodepng_decoded_buffer, pSource_pixels32, total_source_pixels * 4) != 0)
{
fprintf(stderr, "FPNG decode verification failed (using lodepng)!\n");
return EXIT_FAILURE;
}
free(lodepng_decoded_buffer);
}
// Verify FPNG's output data using stb_image.h
{
int x, y, c;
void* p = stbi_load_from_memory(fpng_file_buf.data(), (int)fpng_file_buf.size(), &x, &y, &c, 4);
if (!p)
{
fprintf(stderr, "stb_image.h failed decompressing FPNG's output PNG file!\n");
return EXIT_FAILURE;
}
if (memcmp(p, pSource_pixels32, total_source_pixels * 4) != 0)
{
fprintf(stderr, "FPNG decode verification failed (using stb_image.h)!\n");
return EXIT_FAILURE;
}
free(p);
}
// lodepng
uint8_vec lodepng_file_buf;
double lodepng_best_time = 1e+9f;
for (uint32_t i = 0; i < NUM_TIMES_TO_ENCODE; i++)
{
tm.start();
lodepng_file_buf.resize(0);
error = lodepng::encode(lodepng_file_buf, pSource_image_buffer, source_width, source_height, LCT_RGBA, 8);
lodepng_best_time = minimum(lodepng_best_time, tm.get_elapsed_secs());
if (error != 0)
{
fprintf(stderr, "lodepng::encode() failed!\n");
return EXIT_FAILURE;
}
}
if (!csv_flag)
printf("lodepng: %f secs, %u bytes, %f MB\n", lodepng_best_time, (uint32_t)lodepng_file_buf.size(), (double)lodepng_file_buf.size() / (1024.0f * 1024.0f));
if (!write_data_to_file("lodepng.png", lodepng_file_buf.data(), lodepng_file_buf.size()))
{
fprintf(stderr, "Failed writing to file lodepng.png\n");
return EXIT_FAILURE;
}
// stb_image_write.h
uint8_vec stbi_file_buf;
stbi_file_buf.reserve(total_source_pixels * source_chans);
double stbi_best_time = 1e+9f;
for (uint32_t i = 0; i < NUM_TIMES_TO_ENCODE; i++)
{
stbi_file_buf.resize(0);
tm.start();
int res = stbi_write_png_to_func(write_func_stbi, &stbi_file_buf, source_width, source_height, source_chans, (source_chans == 3) ? (void *)pSource_pixels24 : (void*)pSource_pixels32, source_width * source_chans);
if (!res)
{
fprintf(stderr, "stbi_write_png_to_func() failed!\n");
return EXIT_FAILURE;
}
stbi_best_time = minimum(stbi_best_time, tm.get_elapsed_secs());
}
if (!csv_flag)
printf("stbi: %f secs, %u bytes, %f MB\n", stbi_best_time, (uint32_t)stbi_file_buf.size(), (double)stbi_file_buf.size() / (1024.0f * 1024.0f));
if (!write_data_to_file("stbi.png", stbi_file_buf.data(), stbi_file_buf.size()))
{
fprintf(stderr, "Failed writing to file stbi.png\n");
return EXIT_FAILURE;
}
// QOI
qoi_desc qdesc;
qdesc.channels = source_chans;
qdesc.width = source_width;
qdesc.height = source_height;
qdesc.colorspace = QOI_SRGB;
int qoi_len = 0;
void* pQOI_data = nullptr;
double qoi_best_time = 1e+9f;
for (uint32_t i = 0; i < NUM_TIMES_TO_ENCODE; i++)
{
if (pQOI_data)
free(pQOI_data);
tm.start();
pQOI_data = qoi_encode((source_chans == 4) ? (void*)pSource_pixels32 : (void*)pSource_pixels24, &qdesc, &qoi_len);
qoi_best_time = minimum(qoi_best_time, tm.get_elapsed_secs());
}
if (!write_data_to_file("qoi.qoi", pQOI_data, qoi_len))
{
fprintf(stderr, "Failed writing to file qoi.qoi\n");
return EXIT_FAILURE;
}
if (!csv_flag)
printf("qoi: %f secs, %i bytes, %f MB\n", qoi_best_time, qoi_len, (double)qoi_len / (1024.0f * 1024.0f));
// Validate QOI's output file
{
qoi_desc qddesc;
void* pQOI_decomp_data = qoi_decode(pQOI_data, qoi_len, &qddesc, 4);
if (memcmp(pQOI_decomp_data, pSource_pixels32, total_source_pixels * 4) != 0)
{
fprintf(stderr, "QOI verification failure!\n");
return EXIT_FAILURE;
}
free(pQOI_decomp_data);
pQOI_decomp_data = nullptr;
}
if (csv_flag)
{
const double MB = 1024.0f * 1024.0f;
printf("%s, %u, %u, %u, %f, %u, %f, %f, %u, %f, %f, %u, %f, %f, %u, %f\n",
pFilename, source_width, source_height, source_chans,
qoi_best_time, (uint32_t)qoi_len, (double)qoi_len / MB,
fpng_best_time, (uint32_t)fpng_file_buf.size(), (double)fpng_file_buf.size() / MB,
lodepng_best_time, (uint32_t)lodepng_file_buf.size(), (double)lodepng_file_buf.size() / MB,
stbi_best_time, (uint32_t)stbi_file_buf.size(), (double)stbi_file_buf.size() / MB
);
}
return EXIT_SUCCESS;
}

6497
lodepng.cpp Normal file

File diff suppressed because it is too large Load Diff

2019
lodepng.h Normal file

File diff suppressed because it is too large Load Diff

656
qoi.h Normal file
View File

@@ -0,0 +1,656 @@
/*
QOI - The "Quite OK Image" format for fast, lossless image compression
Dominic Szablewski - https://phoboslab.org
-- LICENSE: The MIT License(MIT)
Copyright(c) 2021 Dominic Szablewski
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files(the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-- About
QOI encodes and decodes images in a lossless format. An encoded QOI image is
usually around 10--30% larger than a decently optimized PNG image.
QOI outperforms simpler PNG encoders in compression ratio and performance. QOI
images are typically 20% smaller than PNGs written with stbi_image. Encoding is
25-50x faster and decoding is 3-4x faster than stbi_image or libpng.
-- Synopsis
// Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this
// library to create the implementation.
#define QOI_IMPLEMENTATION
#include "qoi.h"
// Encode and store an RGBA buffer to the file system. The qoi_desc describes
// the input pixel data.
qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){
.width = 1920,
.height = 1080,
.channels = 4,
.colorspace = QOI_SRGB
});
// Load and decode a QOI image from the file system into a 32bbp RGBA buffer.
// The qoi_desc struct will be filled with the width, height, number of channels
// and colorspace read from the file header.
qoi_desc desc;
void *rgba_pixels = qoi_read("image.qoi", &desc, 4);
-- Documentation
This library provides the following functions;
- qoi_read -- read and decode a QOI file
- qoi_decode -- decode the raw bytes of a QOI image from memory
- qoi_write -- encode and write a QOI file
- qoi_encode -- encode an rgba buffer into a QOI image in memory
See the function declaration below for the signature and more information.
If you don't want/need the qoi_read and qoi_write functions, you can define
QOI_NO_STDIO before including this library.
This library uses malloc() and free(). To supply your own malloc implementation
you can define QOI_MALLOC and QOI_FREE before including this library.
This library uses memset() to zero-initialize the index. To supply your own
implementation you can define QOI_ZEROARR before including this library.
-- Data Format
A QOI file has a 14 byte header, followed by any number of data "chunks" and 8
zero-bytes to mark the end of the data stream.
struct qoi_header_t {
char magic[4]; // magic bytes "qoif"
uint32_t width; // image width in pixels (BE)
uint32_t height; // image height in pixels (BE)
uint8_t channels; // 3 = RGB, 4 = RGBA
uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
};
The decoder and encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous
pixel value. Pixels are either encoded as
- a run of the previous pixel
- an index into an array of previously seen pixels
- a difference to the previous pixel value in r,g,b
- full r,g,b or r,g,b,a values
The color channels are assumed to not be premultiplied with the alpha channel
("un-premultiplied alpha").
A running array[64] (zero-initialized) of previously seen pixel values is
maintained by the encoder and decoder. Each pixel that is seen by the encoder
and decoder is put into this array at the position formed by a hash function of
the color value. In the encoder, if the pixel value at the index matches the
current pixel, this index position is written to the stream as QOI_OP_INDEX.
The hash function for the index is:
index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64
Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The
bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All
values encoded in these data bits have the most significant bit on the left.
The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the
presence of an 8-bit tag first.
The byte stream is padded with 8 zero-bytes at the end.
The possible chunks are:
.- QOI_OP_INDEX ----------.
| Byte[0] |
| 7 6 5 4 3 2 1 0 |
|-------+-----------------|
| 0 0 | index |
`-------------------------`
2-bit tag b00
6-bit index into the color index array: 0..63
.- QOI_OP_DIFF -----------.
| Byte[0] |
| 7 6 5 4 3 2 1 0 |
|-------+-----+-----+-----|
| 0 1 | dr | dg | db |
`-------------------------`
2-bit tag b01
2-bit red channel difference from the previous pixel between -2..1
2-bit green channel difference from the previous pixel between -2..1
2-bit blue channel difference from the previous pixel between -2..1
The difference to the current channel values are using a wraparound operation,
so "1 - 2" will result in 255, while "255 + 1" will result in 0.
Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as
0 (b00). 1 is stored as 3 (b11).
.- QOI_OP_LUMA -------------------------------------.
| Byte[0] | Byte[1] |
| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 |
|-------+-----------------+-------------+-----------|
| 1 0 | green diff | dr - dg | db - dg |
`---------------------------------------------------`
2-bit tag b10
6-bit green channel difference from the previous pixel -32..31
4-bit red channel difference minus green channel difference -8..7
4-bit blue channel difference minus green channel difference -8..7
The green channel is used to indicate the general direction of change and is
encoded in 6 bits. The red and green channels (dr and db) base their diffs off
of the green channel difference and are encoded in 4 bits. I.e.:
dr_dg = (last_px.r - cur_px.r) - (last_px.g - cur_px.g)
db_dg = (last_px.b - cur_px.b) - (last_px.g - cur_px.g)
The difference to the current channel values are using a wraparound operation,
so "10 - 13" will result in 253, while "250 + 7" will result in 1.
Values are stored as unsigned integers with a bias of 32 for the green channel
and a bias of 8 for the red and blue channel.
.- QOI_OP_RUN ------------.
| Byte[0] |
| 7 6 5 4 3 2 1 0 |
|-------+-----------------|
| 1 1 | run |
`-------------------------`
2-bit tag b11
6-bit run-length repeating the previous pixel: 1..62
The run-length is stored with a bias of 1. Note that the run-lengths 63 and 64
(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and
QOI_OP_RGBA tags.
.- QOI_OP_RGB ------------------------------------------.
| Byte[0] | Byte[1] | Byte[2] | Byte[3] |
| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |
|-------------------------+---------+---------+---------|
| 1 1 1 1 1 1 1 0 | red | green | blue |
`-------------------------------------------------------`
8-bit tag b11111110
8-bit red channel value
8-bit green channel value
8-bit blue channel value
.- QOI_OP_RGBA ---------------------------------------------------.
| Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] |
| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |
|-------------------------+---------+---------+---------+---------|
| 1 1 1 1 1 1 1 1 | red | green | blue | alpha |
`-----------------------------------------------------------------`
8-bit tag b11111111
8-bit red channel value
8-bit green channel value
8-bit blue channel value
8-bit alpha channel value
The byte stream is padded at the end with 8 zero bytes. Since the longest legal
chunk is 5 bytes (QOI_OP_RGBA), with this padding it is possible to check for an
overrun only once per decode loop iteration. These 0x00 bytes also mark the end
of the data stream, as an encoder should never produce 8 consecutive zero bytes
within the stream.
*/
/* -----------------------------------------------------------------------------
Header - Public functions */
#ifndef QOI_H
#define QOI_H
#ifdef __cplusplus
extern "C" {
#endif
/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions.
It describes either the input format (for qoi_write and qoi_encode), or is
filled with the description read from the file header (for qoi_read and
qoi_decode).
The colorspace in this qoi_desc is an enum where
0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel
1 = all channels are linear
You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely
informative. It will be saved to the file header, but does not affect
en-/decoding in any way. */
#define QOI_SRGB 0
#define QOI_LINEAR 1
typedef struct {
unsigned int width;
unsigned int height;
unsigned char channels;
unsigned char colorspace;
} qoi_desc;
#ifndef QOI_NO_STDIO
/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file
system. The qoi_desc struct must be filled with the image width, height,
number of channels (3 = RGB, 4 = RGBA) and the colorspace.
The function returns 0 on failure (invalid parameters, or fopen or malloc
failed) or the number of bytes written on success. */
int qoi_write(const char *filename, const void *data, const qoi_desc *desc);
/* Read and decode a QOI image from the file system. If channels is 0, the
number of channels from the file header is used. If channels is 3 or 4 the
output format will be forced into this number of channels.
The function either returns NULL on failure (invalid data, or malloc or fopen
failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
will be filled with the description from the file header.
The returned pixel data should be free()d after use. */
void *qoi_read(const char *filename, qoi_desc *desc, int channels);
#endif /* QOI_NO_STDIO */
/* Encode raw RGB or RGBA pixels into a QOI image in memory.
The function either returns NULL on failure (invalid parameters or malloc
failed) or a pointer to the encoded data on success. On success the out_len
is set to the size in bytes of the encoded data.
The returned qoi data should be free()d after use. */
void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len);
/* Decode a QOI image from memory.
The function either returns NULL on failure (invalid parameters or malloc
failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
is filled with the description from the file header.
The returned pixel data should be free()d after use. */
void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels);
#ifdef __cplusplus
}
#endif
#endif /* QOI_H */
/* -----------------------------------------------------------------------------
Implementation */
#ifdef QOI_IMPLEMENTATION
#include <stdlib.h>
#include <string.h>
#ifndef QOI_MALLOC
#define QOI_MALLOC(sz) malloc(sz)
#define QOI_FREE(p) free(p)
#endif
#ifndef QOI_ZEROARR
#define QOI_ZEROARR(a) memset((a),0,sizeof(a))
#endif
#define QOI_OP_INDEX 0x00 /* 00xxxxxx */
#define QOI_OP_DIFF 0x40 /* 01xxxxxx */
#define QOI_OP_LUMA 0x80 /* 10xxxxxx */
#define QOI_OP_RUN 0xc0 /* 11xxxxxx */
#define QOI_OP_RGB 0xfe /* 11111110 */
#define QOI_OP_RGBA 0xff /* 11111111 */
#define QOI_MASK_2 0xc0 /* 11000000 */
#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
#define QOI_MAGIC \
(((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
((unsigned int)'i') << 8 | ((unsigned int)'f'))
#define QOI_HEADER_SIZE 14
#define QOI_PADDING 8
typedef union {
struct { unsigned char r, g, b, a; } rgba;
unsigned int v;
} qoi_rgba_t;
void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {
bytes[(*p)++] = (0xff000000 & v) >> 24;
bytes[(*p)++] = (0x00ff0000 & v) >> 16;
bytes[(*p)++] = (0x0000ff00 & v) >> 8;
bytes[(*p)++] = (0x000000ff & v);
}
unsigned int qoi_read_32(const unsigned char *bytes, int *p) {
unsigned int a = bytes[(*p)++];
unsigned int b = bytes[(*p)++];
unsigned int c = bytes[(*p)++];
unsigned int d = bytes[(*p)++];
return a << 24 | b << 16 | c << 8 | d;
}
void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
int i, max_size, p, run;
int px_len, px_end, px_pos, channels;
unsigned char *bytes;
const unsigned char *pixels;
qoi_rgba_t index[64];
qoi_rgba_t px, px_prev;
if (
data == NULL || out_len == NULL || desc == NULL ||
desc->width == 0 || desc->height == 0 ||
desc->channels < 3 || desc->channels > 4 ||
desc->colorspace > 2
) {
return NULL;
}
max_size =
desc->width * desc->height * (desc->channels + 1) +
QOI_HEADER_SIZE + QOI_PADDING;
p = 0;
bytes = (unsigned char *) QOI_MALLOC(max_size);
if (!bytes) {
return NULL;
}
qoi_write_32(bytes, &p, QOI_MAGIC);
qoi_write_32(bytes, &p, desc->width);
qoi_write_32(bytes, &p, desc->height);
bytes[p++] = desc->channels;
bytes[p++] = desc->colorspace;
pixels = (const unsigned char *)data;
QOI_ZEROARR(index);
run = 0;
px_prev.rgba.r = 0;
px_prev.rgba.g = 0;
px_prev.rgba.b = 0;
px_prev.rgba.a = 255;
px = px_prev;
px_len = desc->width * desc->height * desc->channels;
px_end = px_len - desc->channels;
channels = desc->channels;
for (px_pos = 0; px_pos < px_len; px_pos += channels) {
if (channels == 4) {
px = *(qoi_rgba_t *)(pixels + px_pos);
}
else {
px.rgba.r = pixels[px_pos + 0];
px.rgba.g = pixels[px_pos + 1];
px.rgba.b = pixels[px_pos + 2];
}
if (px.v == px_prev.v) {
run++;
if (run == 62 || px_pos == px_end) {
bytes[p++] = QOI_OP_RUN | (run - 1);
run = 0;
}
}
else {
int index_pos;
if (run > 0) {
bytes[p++] = QOI_OP_RUN | (run - 1);
run = 0;
}
index_pos = QOI_COLOR_HASH(px) % 64;
if (index[index_pos].v == px.v) {
bytes[p++] = QOI_OP_INDEX | index_pos;
}
else {
index[index_pos] = px;
if (px.rgba.a == px_prev.rgba.a) {
signed char vr = px.rgba.r - px_prev.rgba.r;
signed char vg = px.rgba.g - px_prev.rgba.g;
signed char vb = px.rgba.b - px_prev.rgba.b;
signed char vg_r = vr - vg;
signed char vg_b = vb - vg;
if (
vr > -3 && vr < 2 &&
vg > -3 && vg < 2 &&
vb > -3 && vb < 2
) {
bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2);
}
else if (
vg_r > -9 && vg_r < 8 &&
vg > -33 && vg < 32 &&
vg_b > -9 && vg_b < 8
) {
bytes[p++] = QOI_OP_LUMA | (vg + 32);
bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8);
}
else {
bytes[p++] = QOI_OP_RGB;
bytes[p++] = px.rgba.r;
bytes[p++] = px.rgba.g;
bytes[p++] = px.rgba.b;
}
}
else {
bytes[p++] = QOI_OP_RGBA;
bytes[p++] = px.rgba.r;
bytes[p++] = px.rgba.g;
bytes[p++] = px.rgba.b;
bytes[p++] = px.rgba.a;
}
}
}
px_prev = px;
}
for (i = 0; i < QOI_PADDING; i++) {
bytes[p++] = 0;
}
*out_len = p;
return bytes;
}
void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {
const unsigned char *bytes;
unsigned int header_magic;
unsigned char *pixels;
qoi_rgba_t index[64];
qoi_rgba_t px;
int px_len, chunks_len, px_pos;
int p = 0, run = 0;
if (
data == NULL || desc == NULL ||
(channels != 0 && channels != 3 && channels != 4) ||
size < QOI_HEADER_SIZE + QOI_PADDING
) {
return NULL;
}
bytes = (const unsigned char *)data;
header_magic = qoi_read_32(bytes, &p);
desc->width = qoi_read_32(bytes, &p);
desc->height = qoi_read_32(bytes, &p);
desc->channels = bytes[p++];
desc->colorspace = bytes[p++];
if (
desc->width == 0 || desc->height == 0 ||
desc->channels < 3 || desc->channels > 4 ||
desc->colorspace > 2 ||
header_magic != QOI_MAGIC
) {
return NULL;
}
if (channels == 0) {
channels = desc->channels;
}
px_len = desc->width * desc->height * channels;
pixels = (unsigned char *) QOI_MALLOC(px_len);
if (!pixels) {
return NULL;
}
QOI_ZEROARR(index);
px.rgba.r = 0;
px.rgba.g = 0;
px.rgba.b = 0;
px.rgba.a = 255;
chunks_len = size - QOI_PADDING;
for (px_pos = 0; px_pos < px_len; px_pos += channels) {
if (run > 0) {
run--;
}
else if (p < chunks_len) {
int b1 = bytes[p++];
if (b1 == QOI_OP_RGB) {
px.rgba.r = bytes[p++];
px.rgba.g = bytes[p++];
px.rgba.b = bytes[p++];
}
else if (b1 == QOI_OP_RGBA) {
px.rgba.r = bytes[p++];
px.rgba.g = bytes[p++];
px.rgba.b = bytes[p++];
px.rgba.a = bytes[p++];
}
else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
px = index[b1];
}
else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
px.rgba.r += ((b1 >> 4) & 0x03) - 2;
px.rgba.g += ((b1 >> 2) & 0x03) - 2;
px.rgba.b += ( b1 & 0x03) - 2;
}
else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
int b2 = bytes[p++];
int vg = (b1 & 0x3f) - 32;
px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
px.rgba.g += vg;
px.rgba.b += vg - 8 + (b2 & 0x0f);
}
else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
run = (b1 & 0x3f);
}
index[QOI_COLOR_HASH(px) % 64] = px;
}
if (channels == 4) {
*(qoi_rgba_t*)(pixels + px_pos) = px;
}
else {
pixels[px_pos + 0] = px.rgba.r;
pixels[px_pos + 1] = px.rgba.g;
pixels[px_pos + 2] = px.rgba.b;
}
}
return pixels;
}
#ifndef QOI_NO_STDIO
#include <stdio.h>
int qoi_write(const char *filename, const void *data, const qoi_desc *desc) {
FILE *f = fopen(filename, "wb");
int size;
void *encoded;
if (!f) {
return 0;
}
encoded = qoi_encode(data, desc, &size);
if (!encoded) {
fclose(f);
return 0;
}
fwrite(encoded, 1, size, f);
fclose(f);
QOI_FREE(encoded);
return size;
}
void *qoi_read(const char *filename, qoi_desc *desc, int channels) {
FILE *f = fopen(filename, "rb");
int size, bytes_read;
void *pixels, *data;
if (!f) {
return NULL;
}
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
data = QOI_MALLOC(size);
if (!data) {
fclose(f);
return NULL;
}
bytes_read = fread(data, 1, size, f);
fclose(f);
pixels = qoi_decode(data, bytes_read, desc, channels);
QOI_FREE(data);
return pixels;
}
#endif /* QOI_NO_STDIO */
#endif /* QOI_IMPLEMENTATION */

BIN
qoi.qoi Normal file

Binary file not shown.

7897
stb_image.h Normal file

File diff suppressed because it is too large Load Diff

1724
stb_image_write.h Normal file

File diff suppressed because it is too large Load Diff