From 551dd495929b07a6a58bb73f70430546a2bf6fe9 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 10 Feb 2026 22:18:26 -0500 Subject: [PATCH 1/4] feat: add library API --- src/CMakeLists.txt | 14 +- src/ect_core.cpp | 466 +++++++++++++++++++++++++++++++++++++++++++++ src/libect.cpp | 74 +++++++ src/libect.h | 34 ++++ src/main.cpp | 441 ------------------------------------------ src/main.h | 6 + 6 files changed, 591 insertions(+), 444 deletions(-) create mode 100755 src/ect_core.cpp create mode 100644 src/libect.cpp create mode 100644 src/libect.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7ad3388..f4b878c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,8 +18,9 @@ if(EXISTS "${CMAKE_SOURCE_DIR}/../.git" AND NOT EXISTS "${CMAKE_SOURCE_DIR}/../s message (FATAL_ERROR "Submodules are not initialized. Run \n\tgit submodule update --init --recursive\n within the repository") endif() -add_executable(ect - main.cpp +add_library(ect_static STATIC + ect_core.cpp + libect.cpp gztools.cpp jpegtran.cpp LzFind.c @@ -33,7 +34,7 @@ add_executable(ect support.h mozjpeg/transupp.c) -add_executable(ect::ect ALIAS ect) +add_executable(ect main.cpp) if(MINGW) add_compile_options($<$:-mno-ms-bitfields>) @@ -174,3 +175,10 @@ if(ECT_MP3_SUPPORT) endif() install(TARGETS ect RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +target_link_libraries(ect ect_static) + +target_include_directories(ect_static + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} + PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/mozjpeg +) diff --git a/src/ect_core.cpp b/src/ect_core.cpp new file mode 100755 index 0000000..ef9bf8e --- /dev/null +++ b/src/ect_core.cpp @@ -0,0 +1,466 @@ +// main.cpp +// Efficient Compression Tool +// Created by Felix Hanau on 12/19/14. +// Copyright (c) 2014-2025 Felix Hanau. + +#include "main.h" +#include "support.h" +#include "gztools.h" +#include "miniz/miniz.h" +#include +#include + +#ifndef NOMULTI +#include +#endif + +#ifdef MP3_SUPPORTED +#include +#endif + +#ifdef _WIN32 +#include +#else +#include +#endif + +static std::atomic processedfiles; +static std::atomic bytes; +static std::atomic savings; + +void ECT_ReportSavings(){ + size_t localProcessedFiles = processedfiles.load(std::memory_order_seq_cst); + size_t localBytes = bytes.load(std::memory_order_seq_cst); + long long localSavings = savings.load(std::memory_order_seq_cst); + if (localProcessedFiles){ + printf("Processed %zu file%s\n", localProcessedFiles, localProcessedFiles > 1 ? "s":""); + if (localSavings < 0){ + printf("Result is bigger\n"); + return; + } + + int bk = 0; + int k = 0; + double smul = localSavings; + double bmul = localBytes; + while (smul > 1024) {smul /= 1024; k++;} + while (bmul > 1024) {bmul /= 1024; bk++;} + char *counter; + if (k == 1) {counter = (char *)"K";} + else if (k == 2) {counter = (char *)"M";} + else if (k == 3) {counter = (char *)"G";} + else {counter = (char *)"";} + char *counter2; + if (bk == 1){counter2 = (char *)"K";} + else if (bk == 2){counter2 = (char *)"M";} + else if (bk == 3){counter2 = (char *)"G";} + else {counter2 = (char *)"";} + printf("Saved "); + if (k == 0){printf("%0.0f", smul);} + else{printf("%0.2f", smul);} + printf("%sB out of ", counter); + if (bk == 0){printf("%0.0f", bmul);} + else{printf("%0.2f", bmul);} + printf("%sB (%0.4f%%)\n", counter2, (100.0 * localSavings)/localBytes);} + else {printf("No compatible files found\n");} +} + +int ECTGzip(const char * Infile, const unsigned Mode, unsigned char multithreading, long long fs, unsigned ZIP, int strict){ + if (!fs){ + printf("%s: Compression of empty files is currently not supported\n", Infile); + return 2; + } + char* gzip_name = 0; + int isGZ = IsGzip(Infile, &gzip_name); + if(isGZ == 2){ + if (gzip_name) { + free(gzip_name); + } + return 2; + } + if(isGZ == 3 && strict){ + if (gzip_name) { + free(gzip_name); + } + fprintf(stderr, "%s: File includes extra field or comment, can't be optimized in strict mode\n", Infile); + return 2; + } + + std::string out_str = ((std::string)Infile).append(ZIP ? ".zip" : isGZ ? ".tmp" : ".gz"); + const char* out_name = out_str.c_str(); + if (ZIP || !isGZ){ + if (exists(out_name)) { + fprintf(stderr, "%s: Compressed file already exists\n", Infile); + return 2; + } + if (ZopfliGzip(Infile, out_name, Mode, multithreading, ZIP, 0, Infile)) {return 2;} + return 1; + } + else { + if (exists(out_name) || ZopfliGzip(Infile, out_name, Mode, multithreading, ZIP, 1, gzip_name)) { + if (gzip_name) { + free(gzip_name); + } + return 2; + } + if (gzip_name) { + free(gzip_name); + } + if (filesize(out_name) < filesize(Infile)){ + RenameAndReplace(out_name, Infile); + } + else { + unlink(out_name); + } + return 0; + } +} + +unsigned char OptimizePNG(const char * Infile, const ECTOptions& Options){ + unsigned _mode = Options.Mode; + unsigned mode = (Options.Mode % 10000) > 9 ? 9 : (Options.Mode % 10000); + if (mode == 1 && Options.Reuse){ + mode++; + } + unsigned quiet = !Options.SavingsCounter; + + int x = 1; + long long size = filesize(Infile); + if(size < 0){ + printf("Can't read from %s\n", Infile); + return 1; + } + if(mode == 9 && !Options.Reuse && !Options.Allfilters){ + x = Zopflipng(Options.strip, Infile, Options.Strict, 3, 0, Options.DeflateMultithreading, quiet); + if(x < 0){ + return 1; + } + } + //Disabled as using this causes libpng warnings + //int filter = Optipng(Options.Mode, Infile, true, Options.Strict || Options.Mode > 1); + int filter = 0; + if (!Options.Allfilters){ + filter = Options.Reuse ? 6 : Optipng(mode, Infile, false, Options.Strict || mode > 1); + } + + if (filter == -1){ + return 1; + } + if(filter && !Options.Allfilters && Options.Allfilterscheap && !Options.Reuse){ + filter = 15; + } + if (mode != 1){ + if (Options.Allfilters){ + x = Zopflipng(Options.strip, Infile, Options.Strict, _mode, 6 + Options.palette_sort, Options.DeflateMultithreading, quiet); + if(x < 0){ + return 1; + } + Zopflipng(Options.strip, Infile, Options.Strict, _mode, Options.palette_sort, Options.DeflateMultithreading, quiet); + Zopflipng(Options.strip, Infile, Options.Strict, _mode, 5 + Options.palette_sort, Options.DeflateMultithreading, quiet); + Zopflipng(Options.strip, Infile, Options.Strict, _mode, 1 + Options.palette_sort, Options.DeflateMultithreading, quiet); + Zopflipng(Options.strip, Infile, Options.Strict, _mode, 2 + Options.palette_sort, Options.DeflateMultithreading, quiet); + Zopflipng(Options.strip, Infile, Options.Strict, _mode, 3 + Options.palette_sort, Options.DeflateMultithreading, quiet); + Zopflipng(Options.strip, Infile, Options.Strict, _mode, 4 + Options.palette_sort, Options.DeflateMultithreading, quiet); + Zopflipng(Options.strip, Infile, Options.Strict, _mode, 7 + Options.palette_sort, Options.DeflateMultithreading, quiet); + Zopflipng(Options.strip, Infile, Options.Strict, _mode, 8 + Options.palette_sort, Options.DeflateMultithreading, quiet); + Zopflipng(Options.strip, Infile, Options.Strict, _mode, 11 + Options.palette_sort, Options.DeflateMultithreading, quiet); + Zopflipng(Options.strip, Infile, Options.Strict, _mode, 12 + Options.palette_sort, Options.DeflateMultithreading, quiet); + Zopflipng(Options.strip, Infile, Options.Strict, _mode, 13 + Options.palette_sort, Options.DeflateMultithreading, quiet); + if (Options.Allfiltersbrute){ + Zopflipng(Options.strip, Infile, Options.Strict, _mode, 9 + Options.palette_sort, Options.DeflateMultithreading, quiet); + Zopflipng(Options.strip, Infile, Options.Strict, _mode, 10 + Options.palette_sort, Options.DeflateMultithreading, quiet); + Zopflipng(Options.strip, Infile, Options.Strict, _mode, 14 + Options.palette_sort, Options.DeflateMultithreading, quiet); + } + } + else if (mode == 9){ + Zopflipng(Options.strip, Infile, Options.Strict, _mode, filter + Options.palette_sort, Options.DeflateMultithreading, quiet); + } + else { + x = Zopflipng(Options.strip, Infile, Options.Strict, _mode, filter + Options.palette_sort, Options.DeflateMultithreading, quiet); + if(x < 0){ + return 1; + } + } + } + + if(Options.strip && x){ + Optipng(0, Infile, false, 0); + } + return 0; +} + +unsigned char OptimizeJPEG(const char * Infile, const ECTOptions& Options){ + size_t stsize = 0; + + int res = mozjpegtran(Options.Arithmetic, Options.Progressive && (Options.Mode > 1 || filesize(Infile) > 5000), Options.strip, Options.Autorotate, Infile, Infile, &stsize); + if (Options.Progressive && Options.Mode > 1 && res != 2){ + if(res == 1 || (Options.Mode == 2 && stsize < 6500) || (Options.Mode == 3 && stsize < 10000) || (Options.Mode == 4 && stsize < 15000) || (Options.Mode > 4 && stsize < 20000)){ + res = mozjpegtran(Options.Arithmetic, false, Options.strip, Options.Autorotate, Infile, Infile, &stsize); + } + } + return res == 2; +} + +#ifdef MP3_SUPPORTED +#error MP3 code may corrupt metadata and has been disabled. +void OptimizeMP3(const char * Infile, const ECTOptions& Options){ + ID3_Tag orig (Infile); + size_t start = orig.Size(); + ID3_Frame* picFrame = orig.Find(ID3FID_PICTURE); + if (picFrame) + { + ID3_Field* mime = picFrame->GetField(ID3FN_MIMETYPE); + if (mime){ + char mimetxt[20]; + mime->Get(mimetxt, 19); + ID3_Field* pic = picFrame->GetField(ID3FN_DATA); + bool ispng = memcmp(mimetxt, "image/png", 9) == 0 || memcmp(mimetxt, "PNG", 3) == 0; + if (pic && (memcmp(mimetxt, "image/jpeg", 10) == 0 || ispng)){ + pic->ToFile("out.jpg"); + if (ispng){ + OptimizePNG("out.jpg", Options); + } + else{ + OptimizeJPEG("out.jpg", Options); + } + pic->FromFile("out.jpg"); + unlink("out.jpg"); + orig.SetPadding(false); + //orig.SetCompression(true); + if (orig.Size() < start){ + orig.Update(); + } + } + } + } +} +#endif + +unsigned fileHandler(const char * Infile, const ECTOptions& Options, int internal){ + std::string Ext = Infile; + std::string x = Ext.substr(Ext.find_last_of(".") + 1); + time_t t = 0; + unsigned error = 0; + + if ((Options.PNG_ACTIVE && (x == "PNG" || x == "png")) || (Options.JPEG_ACTIVE && (x == "jpg" || x == "JPG" || x == "JPEG" || x == "jpeg")) || (Options.Gzip && !internal)){ + if(Options.keep){ + t = get_file_time(Infile); + } + long long size = filesize(Infile); + if (size < 0){ + printf("%s: bad file\n", Infile); + return 1; + } + int statcompressedfile = 0; + if (size < 1200000000) {//completely random value + if (Options.Gzip && !internal) { + statcompressedfile = ECTGzip(Infile, Options.Mode, Options.DeflateMultithreading, size, Options.Zip, Options.Strict); + if (statcompressedfile == 2){ + return 1; + } + } else if (x == "PNG" || x == "png") { + error = OptimizePNG(Infile, Options); + } else if (x == "jpg" || x == "JPG" || x == "JPEG" || x == "jpeg") { + error = OptimizeJPEG(Infile, Options); + } + if(Options.SavingsCounter && !internal){ + processedfiles.fetch_add(1); + bytes.fetch_add(size); + if (!statcompressedfile){ + savings.fetch_add(size - filesize(Infile)); + } + else if (statcompressedfile){ + savings.fetch_add((size - filesize(((std::string)Infile).append(Options.Zip ? ".zip" : ".gz").c_str()))); + } + } + } + else{printf("File too big\n");} + if(Options.keep && !statcompressedfile){ + set_file_time(Infile, t); + } + } +#ifdef MP3_SUPPORTED + else if(x == "mp3"){ + OptimizeMP3(Infile, Options); + } +#endif + return error; +} + +unsigned zipHandler(std::vector args, const char * argv[], int files, const ECTOptions& Options){ + std::string extension = ((std::string)argv[args[0]]).substr(((std::string)argv[args[0]]).find_last_of(".") + 1); + std::string zipfilename = argv[args[0]]; + size_t local_bytes = 0; + unsigned i = 0; + time_t t = -1; + if((extension=="zip" || extension=="ZIP" || IsZIP(argv[args[0]]) == 1) && !isDirectory(argv[args[0]])){ + i++; + if(exists(argv[args[0]])){ + local_bytes += filesize(zipfilename.c_str()); + if(Options.keep){ + t = get_file_time(argv[args[0]]); + } + } + } else { + //Construct name + if (!isDirectory(argv[args[0]]) +#ifdef FS_SUPPORTED + && std::filesystem::is_regular_file(argv[args[0]]) +#endif + ) { + // Cut off file extension, but handle file names beginning with a dot correctly + if(zipfilename.find_last_of(".") > zipfilename.find_last_of("/\\") + 1) { + zipfilename = zipfilename.substr(0, zipfilename.find_last_of(".")); + } + } else { + // Work around relative directory names ending in '.' or '..' + // TODO: Implement a proper file name parser and use the absolute path in all cases. +#ifndef _WIN32 + char abs_path[PATH_MAX]; + if (!realpath(argv[args[0]], abs_path)) { +#else + char abs_path[MAX_PATH]; + if (!GetFullPathNameA(argv[args[0]], MAX_PATH, abs_path, 0)) { +#endif + printf("Error: Could not find directory\n"); + return 1; + } + zipfilename = abs_path; + if(zipfilename.back() == '/' || zipfilename.back() == '\\') { + zipfilename.pop_back(); + } + } + zipfilename += ".zip"; + if(exists(zipfilename.c_str())){ + printf("Error: ZIP file for chosen file/folder already exists, but is not listed.\n"); + return 1; + } + } + + int error = 0; + for(; error == 0 && i < files; i++){ + if(isDirectory(argv[args[i]])){ +#ifdef FS_SUPPORTED + std::string fold = std::filesystem::canonical(argv[args[i]]).generic_string(); + int substr = std::filesystem::path(fold).has_parent_path() ? std::filesystem::path(fold).parent_path().generic_string().length() + 1 : 0; + + std::filesystem::recursive_directory_iterator a(fold), b; + std::vector paths(a, b); + for(unsigned j = 0; j < paths.size(); j++){ + std::string newfile = paths[j].generic_string(); + const char* name = newfile.erase(0, substr).c_str(); + std::string file_string = paths[j].generic_string(); + const char* file_path = file_string.c_str(); + + if(isDirectory(file_path)){ + //Only add dir if it is empty to minimize filesize + if (j + 1 < files) { + std::string next = paths[j + 1].generic_string(); + if (next.compare(0, file_string.size(), file_string) == 0) { + continue; + } + } + if (!mz_zip_add_mem_to_archive_file_in_place(zipfilename.c_str(), (((std::string)name) + "/").c_str(), 0, 0, 0, 0, file_path)) { + printf("can't add directory '%s'\n", file_path); + } + } + else{ + long long f = filesize(file_path); + if(f > UINT_MAX){ + printf("%s: file too big\n", file_path); + continue; + } + if(f < 0){ + printf("%s: can't read file\n", file_path); + continue; + } + char* file = (char*)malloc(f); + if(!file){ + exit(1); + } + FILE* stream = fopen(file_path, "rb"); + if (!stream){ + free(file); error = 1; continue; + } + if (fread(file, 1, f, stream) != f){ + fclose(stream); free(file); error = 1; continue; + } + fclose(stream); + if(!mz_zip_add_mem_to_archive_file_in_place(zipfilename.c_str(), name, file, f, 0, 0, file_path)){ + printf("can't add file '%s'\n", file_path); + free(file); error = 1; continue; + } + else{ + local_bytes += filesize(file_path); + } + free(file); + } + } + if(!paths.size()){ + if (!mz_zip_add_mem_to_archive_file_in_place(zipfilename.c_str(), (fold.erase(0, substr) + "/").c_str(), 0, 0, 0, 0, argv[args[i]])) { + printf("can't add directory '%s'\n", argv[args[i]]); + } + } +#else + printf("%s: Zipping folders is not supported\n", argv[args[i]]); +#endif + } + else{ + + const char* fname = argv[args[i]]; + long long f = filesize(fname); + if(f > UINT_MAX){ + printf("%s: file too big\n", fname); + continue; + } + if(f < 0){ + printf("%s: can't read file\n", fname); + continue; + } + char* file = (char*)malloc(f); + if(!file){ + exit(1); + } + + FILE * stream = fopen (fname, "rb"); + if (!stream){ + free(file); error = 1; continue; + } + if (fread(file, 1, f, stream) != f){ + fclose(stream); free(file); error = 1; continue; + } + + fclose(stream); + if (!mz_zip_add_mem_to_archive_file_in_place(zipfilename.c_str(), ((std::string)argv[args[i]]).substr(((std::string)argv[args[i]]).find_last_of("/\\") + 1).c_str(), file, f, 0, 0, argv[args[i]]) + ) { + printf("can't add file '%s'\n", argv[0]); + free(file); error = 1; continue; + } + local_bytes += filesize(argv[args[i]]); + + free(file); + + } + } + size_t localProcessedFiles = 0; + ReZipFile(zipfilename.c_str(), Options, &localProcessedFiles); + processedfiles.fetch_add(localProcessedFiles); + if(t >= 0){ + set_file_time(zipfilename.c_str(), t); + } + + bytes.fetch_add(local_bytes); + savings.fetch_add(local_bytes - filesize(zipfilename.c_str())); + return error; +} + +void multithreadFileLoop(const std::vector &fileList, std::atomic *pos, const ECTOptions &options, std::atomic *error) { + while (true) { + size_t nextPos = pos->fetch_add(1); + if (nextPos >= fileList.size()) { + break; + } + unsigned localError = fileHandler(fileList[nextPos].c_str(), options, 0); + error->fetch_or(localError); + } +} diff --git a/src/libect.cpp b/src/libect.cpp new file mode 100644 index 0000000..c8d64e0 --- /dev/null +++ b/src/libect.cpp @@ -0,0 +1,74 @@ +#include "libect.h" +#include "main.h" +#include + +extern "C" { + +void ect_init_options(LibECTOptions* options) { + memset(options, 0, sizeof(LibECTOptions)); + options->compression_level = 3; +} + +static void ect_options_to_internal(const LibECTOptions* options, struct ECTOptions* internal) { + internal->Mode = options->compression_level; + internal->strip = options->strip; + internal->Strict = options->strict_losslessness; + internal->Reuse = options->png_reuse_filter_color_types; + internal->Allfilters = options->png_allfilters; + internal->Allfiltersbrute = options->png_allfilters_brute_force; + internal->Allfilterscheap = 0; + internal->palette_sort = options->palette_sort << 8; + internal->Progressive = options->jpeg_progressive_encoding; + internal->Autorotate = options->jpeg_autorotate_mode; + internal->Arithmetic = options->arithmetic; + internal->DeflateMultithreading = options->deflate_threads; + internal->FileMultithreading = options->file_threads; + internal->keep = options->keep_modification_time; + internal->PNG_ACTIVE = true; + internal->JPEG_ACTIVE = true; + internal->Gzip = false; + internal->Zip = false; + internal->SavingsCounter = false; + internal->Recurse = false; +} + +int ect_optimize_png(const char* filepath, const LibECTOptions* options) { + LibECTOptions default_options; + if (!options) { + ect_init_options(&default_options); + options = &default_options; + } + + struct ECTOptions internal_options; + ect_options_to_internal(options, &internal_options); + + return OptimizePNG(filepath, internal_options); +} + +int ect_optimize_jpeg(const char* filepath, const LibECTOptions* options) { + LibECTOptions default_options; + if (!options) { + ect_init_options(&default_options); + options = &default_options; + } + + struct ECTOptions internal_options; + ect_options_to_internal(options, &internal_options); + + return OptimizeJPEG(filepath, internal_options); +} + +int ect_optimize_file(const char* filepath, const LibECTOptions* options) { + LibECTOptions default_options; + if (!options) { + ect_init_options(&default_options); + options = &default_options; + } + + struct ECTOptions internal_options; + ect_options_to_internal(options, &internal_options); + + return fileHandler(filepath, internal_options, 0); +} + +} // extern "C" diff --git a/src/libect.h b/src/libect.h new file mode 100644 index 0000000..d71570f --- /dev/null +++ b/src/libect.h @@ -0,0 +1,34 @@ +#ifndef LIBECT_H +#define LIBECT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct { + int compression_level; + int strip; + int jpeg_progressive_encoding; + int jpeg_autorotate_mode; + int keep_modification_time; + int strict_losslessness; + int png_reuse_filter_color_types; + int png_allfilters; + int png_allfilters_brute_force; + int palette_sort; + unsigned char deflate_threads; + unsigned char file_threads; +} LibECTOptions; + +void ect_init_options(LibECTOptions* options); +int ect_optimize_png(const char* filepath, const LibECTOptions* options); +int ect_optimize_jpeg(const char* filepath, const LibECTOptions* options); +int ect_optimize_file(const char* filepath, const LibECTOptions* options); + +#ifdef __cplusplus +} +#endif + +#endif // LIBECT_H diff --git a/src/main.cpp b/src/main.cpp index ff1b2dd..fd4764c 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,10 +24,6 @@ #include #endif -static std::atomic processedfiles; -static std::atomic bytes; -static std::atomic savings; - static void Usage() { printf ( "Efficient Compression Tool\n" @@ -84,443 +80,6 @@ static void Usage() { ); } -static void ECT_ReportSavings(){ - size_t localProcessedFiles = processedfiles.load(std::memory_order_seq_cst); - size_t localBytes = bytes.load(std::memory_order_seq_cst); - long long localSavings = savings.load(std::memory_order_seq_cst); - if (localProcessedFiles){ - printf("Processed %zu file%s\n", localProcessedFiles, localProcessedFiles > 1 ? "s":""); - if (localSavings < 0){ - printf("Result is bigger\n"); - return; - } - - int bk = 0; - int k = 0; - double smul = localSavings; - double bmul = localBytes; - while (smul > 1024) {smul /= 1024; k++;} - while (bmul > 1024) {bmul /= 1024; bk++;} - char *counter; - if (k == 1) {counter = (char *)"K";} - else if (k == 2) {counter = (char *)"M";} - else if (k == 3) {counter = (char *)"G";} - else {counter = (char *)"";} - char *counter2; - if (bk == 1){counter2 = (char *)"K";} - else if (bk == 2){counter2 = (char *)"M";} - else if (bk == 3){counter2 = (char *)"G";} - else {counter2 = (char *)"";} - printf("Saved "); - if (k == 0){printf("%0.0f", smul);} - else{printf("%0.2f", smul);} - printf("%sB out of ", counter); - if (bk == 0){printf("%0.0f", bmul);} - else{printf("%0.2f", bmul);} - printf("%sB (%0.4f%%)\n", counter2, (100.0 * localSavings)/localBytes);} - else {printf("No compatible files found\n");} -} - -static int ECTGzip(const char * Infile, const unsigned Mode, unsigned char multithreading, long long fs, unsigned ZIP, int strict){ - if (!fs){ - printf("%s: Compression of empty files is currently not supported\n", Infile); - return 2; - } - char* gzip_name = 0; - int isGZ = IsGzip(Infile, &gzip_name); - if(isGZ == 2){ - if (gzip_name) { - free(gzip_name); - } - return 2; - } - if(isGZ == 3 && strict){ - if (gzip_name) { - free(gzip_name); - } - fprintf(stderr, "%s: File includes extra field or comment, can't be optimized in strict mode\n", Infile); - return 2; - } - - std::string out_str = ((std::string)Infile).append(ZIP ? ".zip" : isGZ ? ".tmp" : ".gz"); - const char* out_name = out_str.c_str(); - if (ZIP || !isGZ){ - if (exists(out_name)) { - fprintf(stderr, "%s: Compressed file already exists\n", Infile); - return 2; - } - if (ZopfliGzip(Infile, out_name, Mode, multithreading, ZIP, 0, Infile)) {return 2;} - return 1; - } - else { - if (exists(out_name) || ZopfliGzip(Infile, out_name, Mode, multithreading, ZIP, 1, gzip_name)) { - if (gzip_name) { - free(gzip_name); - } - return 2; - } - if (gzip_name) { - free(gzip_name); - } - if (filesize(out_name) < filesize(Infile)){ - RenameAndReplace(out_name, Infile); - } - else { - unlink(out_name); - } - return 0; - } -} - -static unsigned char OptimizePNG(const char * Infile, const ECTOptions& Options){ - unsigned _mode = Options.Mode; - unsigned mode = (Options.Mode % 10000) > 9 ? 9 : (Options.Mode % 10000); - if (mode == 1 && Options.Reuse){ - mode++; - } - unsigned quiet = !Options.SavingsCounter; - - int x = 1; - long long size = filesize(Infile); - if(size < 0){ - printf("Can't read from %s\n", Infile); - return 1; - } - if(mode == 9 && !Options.Reuse && !Options.Allfilters){ - x = Zopflipng(Options.strip, Infile, Options.Strict, 3, 0, Options.DeflateMultithreading, quiet); - if(x < 0){ - return 1; - } - } - //Disabled as using this causes libpng warnings - //int filter = Optipng(Options.Mode, Infile, true, Options.Strict || Options.Mode > 1); - int filter = 0; - if (!Options.Allfilters){ - filter = Options.Reuse ? 6 : Optipng(mode, Infile, false, Options.Strict || mode > 1); - } - - if (filter == -1){ - return 1; - } - if(filter && !Options.Allfilters && Options.Allfilterscheap && !Options.Reuse){ - filter = 15; - } - if (mode != 1){ - if (Options.Allfilters){ - x = Zopflipng(Options.strip, Infile, Options.Strict, _mode, 6 + Options.palette_sort, Options.DeflateMultithreading, quiet); - if(x < 0){ - return 1; - } - Zopflipng(Options.strip, Infile, Options.Strict, _mode, Options.palette_sort, Options.DeflateMultithreading, quiet); - Zopflipng(Options.strip, Infile, Options.Strict, _mode, 5 + Options.palette_sort, Options.DeflateMultithreading, quiet); - Zopflipng(Options.strip, Infile, Options.Strict, _mode, 1 + Options.palette_sort, Options.DeflateMultithreading, quiet); - Zopflipng(Options.strip, Infile, Options.Strict, _mode, 2 + Options.palette_sort, Options.DeflateMultithreading, quiet); - Zopflipng(Options.strip, Infile, Options.Strict, _mode, 3 + Options.palette_sort, Options.DeflateMultithreading, quiet); - Zopflipng(Options.strip, Infile, Options.Strict, _mode, 4 + Options.palette_sort, Options.DeflateMultithreading, quiet); - Zopflipng(Options.strip, Infile, Options.Strict, _mode, 7 + Options.palette_sort, Options.DeflateMultithreading, quiet); - Zopflipng(Options.strip, Infile, Options.Strict, _mode, 8 + Options.palette_sort, Options.DeflateMultithreading, quiet); - Zopflipng(Options.strip, Infile, Options.Strict, _mode, 11 + Options.palette_sort, Options.DeflateMultithreading, quiet); - Zopflipng(Options.strip, Infile, Options.Strict, _mode, 12 + Options.palette_sort, Options.DeflateMultithreading, quiet); - Zopflipng(Options.strip, Infile, Options.Strict, _mode, 13 + Options.palette_sort, Options.DeflateMultithreading, quiet); - if (Options.Allfiltersbrute){ - Zopflipng(Options.strip, Infile, Options.Strict, _mode, 9 + Options.palette_sort, Options.DeflateMultithreading, quiet); - Zopflipng(Options.strip, Infile, Options.Strict, _mode, 10 + Options.palette_sort, Options.DeflateMultithreading, quiet); - Zopflipng(Options.strip, Infile, Options.Strict, _mode, 14 + Options.palette_sort, Options.DeflateMultithreading, quiet); - } - } - else if (mode == 9){ - Zopflipng(Options.strip, Infile, Options.Strict, _mode, filter + Options.palette_sort, Options.DeflateMultithreading, quiet); - } - else { - x = Zopflipng(Options.strip, Infile, Options.Strict, _mode, filter + Options.palette_sort, Options.DeflateMultithreading, quiet); - if(x < 0){ - return 1; - } - } - } - - if(Options.strip && x){ - Optipng(0, Infile, false, 0); - } - return 0; -} - -static unsigned char OptimizeJPEG(const char * Infile, const ECTOptions& Options){ - size_t stsize = 0; - - int res = mozjpegtran(Options.Arithmetic, Options.Progressive && (Options.Mode > 1 || filesize(Infile) > 5000), Options.strip, Options.Autorotate, Infile, Infile, &stsize); - if (Options.Progressive && Options.Mode > 1 && res != 2){ - if(res == 1 || (Options.Mode == 2 && stsize < 6500) || (Options.Mode == 3 && stsize < 10000) || (Options.Mode == 4 && stsize < 15000) || (Options.Mode > 4 && stsize < 20000)){ - res = mozjpegtran(Options.Arithmetic, false, Options.strip, Options.Autorotate, Infile, Infile, &stsize); - } - } - return res == 2; -} - -#ifdef MP3_SUPPORTED -#error MP3 code may corrupt metadata and has been disabled. -static void OptimizeMP3(const char * Infile, const ECTOptions& Options){ - ID3_Tag orig (Infile); - size_t start = orig.Size(); - ID3_Frame* picFrame = orig.Find(ID3FID_PICTURE); - if (picFrame) - { - ID3_Field* mime = picFrame->GetField(ID3FN_MIMETYPE); - if (mime){ - char mimetxt[20]; - mime->Get(mimetxt, 19); - ID3_Field* pic = picFrame->GetField(ID3FN_DATA); - bool ispng = memcmp(mimetxt, "image/png", 9) == 0 || memcmp(mimetxt, "PNG", 3) == 0; - if (pic && (memcmp(mimetxt, "image/jpeg", 10) == 0 || ispng)){ - pic->ToFile("out.jpg"); - if (ispng){ - OptimizePNG("out.jpg", Options); - } - else{ - OptimizeJPEG("out.jpg", Options); - } - pic->FromFile("out.jpg"); - unlink("out.jpg"); - orig.SetPadding(false); - //orig.SetCompression(true); - if (orig.Size() < start){ - orig.Update(); - } - } - } - } -} -#endif - -unsigned fileHandler(const char * Infile, const ECTOptions& Options, int internal){ - std::string Ext = Infile; - std::string x = Ext.substr(Ext.find_last_of(".") + 1); - time_t t = 0; - unsigned error = 0; - - if ((Options.PNG_ACTIVE && (x == "PNG" || x == "png")) || (Options.JPEG_ACTIVE && (x == "jpg" || x == "JPG" || x == "JPEG" || x == "jpeg")) || (Options.Gzip && !internal)){ - if(Options.keep){ - t = get_file_time(Infile); - } - long long size = filesize(Infile); - if (size < 0){ - printf("%s: bad file\n", Infile); - return 1; - } - int statcompressedfile = 0; - if (size < 1200000000) {//completely random value - if (Options.Gzip && !internal) { - statcompressedfile = ECTGzip(Infile, Options.Mode, Options.DeflateMultithreading, size, Options.Zip, Options.Strict); - if (statcompressedfile == 2){ - return 1; - } - } else if (x == "PNG" || x == "png") { - error = OptimizePNG(Infile, Options); - } else if (x == "jpg" || x == "JPG" || x == "JPEG" || x == "jpeg") { - error = OptimizeJPEG(Infile, Options); - } - if(Options.SavingsCounter && !internal){ - processedfiles.fetch_add(1); - bytes.fetch_add(size); - if (!statcompressedfile){ - savings.fetch_add(size - filesize(Infile)); - } - else if (statcompressedfile){ - savings.fetch_add((size - filesize(((std::string)Infile).append(Options.Zip ? ".zip" : ".gz").c_str()))); - } - } - } - else{printf("File too big\n");} - if(Options.keep && !statcompressedfile){ - set_file_time(Infile, t); - } - } -#ifdef MP3_SUPPORTED - else if(x == "mp3"){ - OptimizeMP3(Infile, Options); - } -#endif - return error; -} - -unsigned zipHandler(std::vector args, const char * argv[], int files, const ECTOptions& Options){ - std::string extension = ((std::string)argv[args[0]]).substr(((std::string)argv[args[0]]).find_last_of(".") + 1); - std::string zipfilename = argv[args[0]]; - size_t local_bytes = 0; - unsigned i = 0; - time_t t = -1; - if((extension=="zip" || extension=="ZIP" || IsZIP(argv[args[0]]) == 1) && !isDirectory(argv[args[0]])){ - i++; - if(exists(argv[args[0]])){ - local_bytes += filesize(zipfilename.c_str()); - if(Options.keep){ - t = get_file_time(argv[args[0]]); - } - } - } else { - //Construct name - if (!isDirectory(argv[args[0]]) -#ifdef FS_SUPPORTED - && std::filesystem::is_regular_file(argv[args[0]]) -#endif - ) { - // Cut off file extension, but handle file names beginning with a dot correctly - if(zipfilename.find_last_of(".") > zipfilename.find_last_of("/\\") + 1) { - zipfilename = zipfilename.substr(0, zipfilename.find_last_of(".")); - } - } else { - // Work around relative directory names ending in '.' or '..' - // TODO: Implement a proper file name parser and use the absolute path in all cases. -#ifndef _WIN32 - char abs_path[PATH_MAX]; - if (!realpath(argv[args[0]], abs_path)) { -#else - char abs_path[MAX_PATH]; - if (!GetFullPathNameA(argv[args[0]], MAX_PATH, abs_path, 0)) { -#endif - printf("Error: Could not find directory\n"); - return 1; - } - zipfilename = abs_path; - if(zipfilename.back() == '/' || zipfilename.back() == '\\') { - zipfilename.pop_back(); - } - } - zipfilename += ".zip"; - if(exists(zipfilename.c_str())){ - printf("Error: ZIP file for chosen file/folder already exists, but is not listed.\n"); - return 1; - } - } - - int error = 0; - for(; error == 0 && i < files; i++){ - if(isDirectory(argv[args[i]])){ -#ifdef FS_SUPPORTED - std::string fold = std::filesystem::canonical(argv[args[i]]).generic_string(); - int substr = std::filesystem::path(fold).has_parent_path() ? std::filesystem::path(fold).parent_path().generic_string().length() + 1 : 0; - - std::filesystem::recursive_directory_iterator a(fold), b; - std::vector paths(a, b); - for(unsigned j = 0; j < paths.size(); j++){ - std::string newfile = paths[j].generic_string(); - const char* name = newfile.erase(0, substr).c_str(); - std::string file_string = paths[j].generic_string(); - const char* file_path = file_string.c_str(); - - if(isDirectory(file_path)){ - //Only add dir if it is empty to minimize filesize - if (j + 1 < files) { - std::string next = paths[j + 1].generic_string(); - if (next.compare(0, file_string.size(), file_string) == 0) { - continue; - } - } - if (!mz_zip_add_mem_to_archive_file_in_place(zipfilename.c_str(), (((std::string)name) + "/").c_str(), 0, 0, 0, 0, file_path)) { - printf("can't add directory '%s'\n", file_path); - } - } - else{ - long long f = filesize(file_path); - if(f > UINT_MAX){ - printf("%s: file too big\n", file_path); - continue; - } - if(f < 0){ - printf("%s: can't read file\n", file_path); - continue; - } - char* file = (char*)malloc(f); - if(!file){ - exit(1); - } - FILE* stream = fopen(file_path, "rb"); - if (!stream){ - free(file); error = 1; continue; - } - if (fread(file, 1, f, stream) != f){ - fclose(stream); free(file); error = 1; continue; - } - fclose(stream); - if(!mz_zip_add_mem_to_archive_file_in_place(zipfilename.c_str(), name, file, f, 0, 0, file_path)){ - printf("can't add file '%s'\n", file_path); - free(file); error = 1; continue; - } - else{ - local_bytes += filesize(file_path); - } - free(file); - } - } - if(!paths.size()){ - if (!mz_zip_add_mem_to_archive_file_in_place(zipfilename.c_str(), (fold.erase(0, substr) + "/").c_str(), 0, 0, 0, 0, argv[args[i]])) { - printf("can't add directory '%s'\n", argv[args[i]]); - } - } -#else - printf("%s: Zipping folders is not supported\n", argv[args[i]]); -#endif - } - else{ - - const char* fname = argv[args[i]]; - long long f = filesize(fname); - if(f > UINT_MAX){ - printf("%s: file too big\n", fname); - continue; - } - if(f < 0){ - printf("%s: can't read file\n", fname); - continue; - } - char* file = (char*)malloc(f); - if(!file){ - exit(1); - } - - FILE * stream = fopen (fname, "rb"); - if (!stream){ - free(file); error = 1; continue; - } - if (fread(file, 1, f, stream) != f){ - fclose(stream); free(file); error = 1; continue; - } - - fclose(stream); - if (!mz_zip_add_mem_to_archive_file_in_place(zipfilename.c_str(), ((std::string)argv[args[i]]).substr(((std::string)argv[args[i]]).find_last_of("/\\") + 1).c_str(), file, f, 0, 0, argv[args[i]]) - ) { - printf("can't add file '%s'\n", argv[0]); - free(file); error = 1; continue; - } - local_bytes += filesize(argv[args[i]]); - - free(file); - - } - } - size_t localProcessedFiles = 0; - ReZipFile(zipfilename.c_str(), Options, &localProcessedFiles); - processedfiles.fetch_add(localProcessedFiles); - if(t >= 0){ - set_file_time(zipfilename.c_str(), t); - } - - bytes.fetch_add(local_bytes); - savings.fetch_add(local_bytes - filesize(zipfilename.c_str())); - return error; -} - -static void multithreadFileLoop(const std::vector &fileList, std::atomic *pos, const ECTOptions &options, std::atomic *error) { - while (true) { - size_t nextPos = pos->fetch_add(1); - if (nextPos >= fileList.size()) { - break; - } - unsigned localError = fileHandler(fileList[nextPos].c_str(), options, 0); - error->fetch_or(localError); - } -} - int main(int argc, const char * argv[]) { std::atomic error(0); ECTOptions Options; diff --git a/src/main.h b/src/main.h index e0e62db..5b4899a 100755 --- a/src/main.h +++ b/src/main.h @@ -50,3 +50,9 @@ void ZopfliBuffer(unsigned mode, unsigned multithreading, const unsigned char* i unsigned fileHandler(const char * Infile, const ECTOptions& Options, int internal); unsigned zipHandler(std::vector args, const char * argv[], int files, const ECTOptions& Options); void ReZipFile(const char* file_path, const ECTOptions& Options, size_t* files); +unsigned char OptimizePNG(const char* Infile, const ECTOptions& Options); +unsigned char OptimizeJPEG(const char* Infile, const ECTOptions& Options); +void OptimizeMP3(const char * Infile, const ECTOptions& Options); +void ECT_ReportSavings(); +int ECTGzip(const char * Infile, const unsigned Mode, unsigned char multithreading, long long fs, unsigned ZIP, int strict); +void multithreadFileLoop(const std::vector &fileList, std::atomic *pos, const ECTOptions &options, std::atomic *error); From 4f42a8bbe86c070ec01c79019e01c2f109b12bd5 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Tue, 10 Feb 2026 22:29:05 -0500 Subject: [PATCH 2/4] fix: add missing LibECTOptions.jpeg_arithmetic_encoding --- src/libect.cpp | 2 +- src/libect.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libect.cpp b/src/libect.cpp index c8d64e0..9253013 100644 --- a/src/libect.cpp +++ b/src/libect.cpp @@ -20,7 +20,7 @@ static void ect_options_to_internal(const LibECTOptions* options, struct ECTOpti internal->palette_sort = options->palette_sort << 8; internal->Progressive = options->jpeg_progressive_encoding; internal->Autorotate = options->jpeg_autorotate_mode; - internal->Arithmetic = options->arithmetic; + internal->Arithmetic = options->jpeg_arithmetic_encoding; internal->DeflateMultithreading = options->deflate_threads; internal->FileMultithreading = options->file_threads; internal->keep = options->keep_modification_time; diff --git a/src/libect.h b/src/libect.h index d71570f..ae1ad33 100644 --- a/src/libect.h +++ b/src/libect.h @@ -12,6 +12,7 @@ typedef struct { int strip; int jpeg_progressive_encoding; int jpeg_autorotate_mode; + int jpeg_arithmetic_encoding; int keep_modification_time; int strict_losslessness; int png_reuse_filter_color_types; From 6880cbe064eab05293ed94ce49d16a97549705a5 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 11 Feb 2026 17:45:13 -0500 Subject: [PATCH 3/4] feat: add ECTJPEGAutorotateMode and ECTPNGAllFiltersMode enums --- src/libect.cpp | 106 ++++++++++++++++++++++++++++--------------------- src/libect.h | 37 +++++++++++------ 2 files changed, 84 insertions(+), 59 deletions(-) diff --git a/src/libect.cpp b/src/libect.cpp index 9253013..14141a4 100644 --- a/src/libect.cpp +++ b/src/libect.cpp @@ -5,70 +5,84 @@ extern "C" { void ect_init_options(LibECTOptions* options) { - memset(options, 0, sizeof(LibECTOptions)); - options->compression_level = 3; + memset(options, 0, sizeof(LibECTOptions)); + options->compression_level = 3; } static void ect_options_to_internal(const LibECTOptions* options, struct ECTOptions* internal) { - internal->Mode = options->compression_level; - internal->strip = options->strip; - internal->Strict = options->strict_losslessness; - internal->Reuse = options->png_reuse_filter_color_types; - internal->Allfilters = options->png_allfilters; - internal->Allfiltersbrute = options->png_allfilters_brute_force; - internal->Allfilterscheap = 0; - internal->palette_sort = options->palette_sort << 8; - internal->Progressive = options->jpeg_progressive_encoding; - internal->Autorotate = options->jpeg_autorotate_mode; - internal->Arithmetic = options->jpeg_arithmetic_encoding; - internal->DeflateMultithreading = options->deflate_threads; - internal->FileMultithreading = options->file_threads; - internal->keep = options->keep_modification_time; - internal->PNG_ACTIVE = true; - internal->JPEG_ACTIVE = true; - internal->Gzip = false; - internal->Zip = false; - internal->SavingsCounter = false; - internal->Recurse = false; + internal->Mode = options->compression_level; + internal->strip = options->strip; + internal->Strict = options->strict_losslessness; + internal->Reuse = options->png_reuse_filter_color_types; + + switch (options->png_all_filters_mode) { + case ECT_PNG_ALL_FILTERS_OFF: + internal->Allfilters = 0; + internal->Allfiltersbrute = 0; + break; + case ECT_PNG_ALL_FILTERS_NORMAL: + internal->Allfilters = 1; + internal->Allfiltersbrute = 0; + break; + case ECT_PNG_ALL_FILTERS_BRUTEFORCE: + internal->Allfilters = 1; + internal->Allfiltersbrute = 1; + break; + } + + internal->Allfilterscheap = 0; + internal->palette_sort = options->palette_sort << 8; + internal->Progressive = options->jpeg_progressive_encoding; + internal->Autorotate = options->jpeg_autorotate_mode; + internal->Arithmetic = options->jpeg_arithmetic_encoding; + internal->DeflateMultithreading = options->deflate_threads; + internal->FileMultithreading = options->file_threads; + internal->keep = options->keep_modification_time; + internal->PNG_ACTIVE = true; + internal->JPEG_ACTIVE = true; + internal->Gzip = false; + internal->Zip = false; + internal->SavingsCounter = false; + internal->Recurse = false; } int ect_optimize_png(const char* filepath, const LibECTOptions* options) { - LibECTOptions default_options; - if (!options) { - ect_init_options(&default_options); - options = &default_options; - } + LibECTOptions default_options; + if (!options) { + ect_init_options(&default_options); + options = &default_options; + } - struct ECTOptions internal_options; - ect_options_to_internal(options, &internal_options); + struct ECTOptions internal_options; + ect_options_to_internal(options, &internal_options); - return OptimizePNG(filepath, internal_options); + return OptimizePNG(filepath, internal_options); } int ect_optimize_jpeg(const char* filepath, const LibECTOptions* options) { - LibECTOptions default_options; - if (!options) { - ect_init_options(&default_options); - options = &default_options; - } + LibECTOptions default_options; + if (!options) { + ect_init_options(&default_options); + options = &default_options; + } - struct ECTOptions internal_options; - ect_options_to_internal(options, &internal_options); + struct ECTOptions internal_options; + ect_options_to_internal(options, &internal_options); - return OptimizeJPEG(filepath, internal_options); + return OptimizeJPEG(filepath, internal_options); } int ect_optimize_file(const char* filepath, const LibECTOptions* options) { - LibECTOptions default_options; - if (!options) { - ect_init_options(&default_options); - options = &default_options; - } + LibECTOptions default_options; + if (!options) { + ect_init_options(&default_options); + options = &default_options; + } - struct ECTOptions internal_options; - ect_options_to_internal(options, &internal_options); + struct ECTOptions internal_options; + ect_options_to_internal(options, &internal_options); - return fileHandler(filepath, internal_options, 0); + return fileHandler(filepath, internal_options, 0); } } // extern "C" diff --git a/src/libect.h b/src/libect.h index ae1ad33..5327a5b 100644 --- a/src/libect.h +++ b/src/libect.h @@ -7,20 +7,31 @@ extern "C" { #include +typedef enum { + ECT_JPEG_AUTOROTATE_OFF = 0, + ECT_JPEG_AUTOROTATE_FORCE = 1, + ECT_JPEG_AUTOROTATE_PERFECT = 2 +} ECTJPEGAutorotateMode; + +typedef enum { + ECT_PNG_ALL_FILTERS_OFF = 0, + ECT_PNG_ALL_FILTERS_NORMAL = 1, + ECT_PNG_ALL_FILTERS_BRUTEFORCE = 2 +} ECTPNGAllFiltersMode; + typedef struct { - int compression_level; - int strip; - int jpeg_progressive_encoding; - int jpeg_autorotate_mode; - int jpeg_arithmetic_encoding; - int keep_modification_time; - int strict_losslessness; - int png_reuse_filter_color_types; - int png_allfilters; - int png_allfilters_brute_force; - int palette_sort; - unsigned char deflate_threads; - unsigned char file_threads; + int compression_level; + int strip; + int jpeg_progressive_encoding; + int jpeg_autorotate_mode; + int jpeg_arithmetic_encoding; + int keep_modification_time; + int strict_losslessness; + int png_reuse_filter_color_types; + int png_all_filters_mode; + int palette_sort; + unsigned char deflate_threads; + unsigned char file_threads; } LibECTOptions; void ect_init_options(LibECTOptions* options); From 55edc4d4978c82e2cc5d2d98172a8ffed03a2893 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Wed, 11 Feb 2026 17:49:00 -0500 Subject: [PATCH 4/4] feat: add compression level and palette sort range constants --- src/libect.cpp | 2 +- src/libect.h | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libect.cpp b/src/libect.cpp index 14141a4..62bf5dd 100644 --- a/src/libect.cpp +++ b/src/libect.cpp @@ -6,7 +6,7 @@ extern "C" { void ect_init_options(LibECTOptions* options) { memset(options, 0, sizeof(LibECTOptions)); - options->compression_level = 3; + options->compression_level = ECT_COMPRESSION_LEVEL_DEFAULT; } static void ect_options_to_internal(const LibECTOptions* options, struct ECTOptions* internal) { diff --git a/src/libect.h b/src/libect.h index 5327a5b..e4a163a 100644 --- a/src/libect.h +++ b/src/libect.h @@ -7,6 +7,13 @@ extern "C" { #include +#define ECT_COMPRESSION_LEVEL_MIN 1 +#define ECT_COMPRESSION_LEVEL_MAX 9 +#define ECT_COMPRESSION_LEVEL_DEFAULT 3 + +#define ECT_PNG_PALETTE_SORT_MIN 0 +#define ECT_PNG_PALETTE_SORT_MAX 120 + typedef enum { ECT_JPEG_AUTOROTATE_OFF = 0, ECT_JPEG_AUTOROTATE_FORCE = 1,