From fad6100704bb22cfe429b6a5e29d6fd837aec44f Mon Sep 17 00:00:00 2001 From: DRC Date: Tue, 20 Aug 2024 18:52:53 -0400 Subject: [PATCH] Replace TJExample with IJG workalike programs --- .gitattributes | 1 + CMakeLists.txt | 38 ++- ChangeLog.md | 4 + README.md | 6 +- cmakescripts/testclean.cmake | 4 +- cmakescripts/tjbenchtest.cmake | 6 - cmakescripts/tjexampletest.cmake | 26 ++ java/CMakeLists.txt | 6 +- java/MANIFEST.MF | 2 +- java/README | 6 +- java/TJComp.java | 299 ++++++++++++++++++++++ java/TJDecomp.java | 340 +++++++++++++++++++++++++ java/TJExample.java | 402 ------------------------------ java/TJTran.java | 315 +++++++++++++++++++++++ release/installer.nsi.in | 16 +- src/example.c | 7 +- src/md5/CMakeLists.txt | 2 + src/md5/md5sum.c | 60 +++++ src/tjcomp.c | 321 ++++++++++++++++++++++++ src/tjdecomp.c | 351 ++++++++++++++++++++++++++ src/tjexample.c | 413 ------------------------------- src/tjtran.c | 309 +++++++++++++++++++++++ test/tjcomptest.in | 183 ++++++++++++++ test/tjdecomptest.in | 200 +++++++++++++++ test/tjexampletest.in | 186 -------------- test/tjtrantest.in | 153 ++++++++++++ testimages/big_building16.pgm | Bin 0 -> 67663 bytes testimages/testorig.pgm | 4 + 28 files changed, 2626 insertions(+), 1034 deletions(-) create mode 100644 cmakescripts/tjexampletest.cmake create mode 100644 java/TJComp.java create mode 100644 java/TJDecomp.java delete mode 100644 java/TJExample.java create mode 100644 java/TJTran.java create mode 100644 src/md5/md5sum.c create mode 100644 src/tjcomp.c create mode 100644 src/tjdecomp.c delete mode 100644 src/tjexample.c create mode 100644 src/tjtran.c create mode 100755 test/tjcomptest.in create mode 100755 test/tjdecomptest.in delete mode 100755 test/tjexampletest.in create mode 100755 test/tjtrantest.in create mode 100644 testimages/big_building16.pgm create mode 100644 testimages/testorig.pgm diff --git a/.gitattributes b/.gitattributes index 46de94f5..193a4cc3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,4 +2,5 @@ /.gitattributes export-ignore /.github export-ignore *.ppm binary +*.pgm binary /ChangeLog.md conflict-marker-size=8 diff --git a/CMakeLists.txt b/CMakeLists.txt index d08edb69..3f97ed72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -736,8 +736,14 @@ if(WITH_TURBOJPEG) target_link_libraries(tjbench m) endif() - add_executable(tjexample src/tjexample.c) - target_link_libraries(tjexample turbojpeg) + add_executable(tjcomp src/tjcomp.c) + target_link_libraries(tjcomp turbojpeg) + + add_executable(tjdecomp src/tjdecomp.c) + target_link_libraries(tjdecomp turbojpeg) + + add_executable(tjtran src/tjtran.c) + target_link_libraries(tjtran turbojpeg) add_custom_target(tjdoc COMMAND doxygen -s ../doc/doxygen.config WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src) @@ -1881,20 +1887,20 @@ add_custom_target(croptest if(WITH_TURBOJPEG) configure_file(test/tjbenchtest.in test/tjbenchtest @ONLY) - configure_file(test/tjexampletest.in test/tjexampletest @ONLY) + configure_file(test/tjcomptest.in test/tjcomptest @ONLY) + configure_file(test/tjdecomptest.in test/tjdecomptest @ONLY) + configure_file(test/tjtrantest.in test/tjtrantest @ONLY) if(WIN32) set(BASH bash) endif() add_custom_target(tjtest COMMAND ${CMAKE_COMMAND} -DWITH_JAVA=${WITH_JAVA} -DPRECISION=8 -P ${CMAKE_SOURCE_DIR}/cmakescripts/tjbenchtest.cmake DEPENDS ${CMAKE_SOURCE_DIR}/cmakescripts/tjbenchtest.cmake - ${CMAKE_CURRENT_BINARY_DIR}/test/tjbenchtest - ${CMAKE_CURRENT_BINARY_DIR}/test/tjexampletest) + ${CMAKE_CURRENT_BINARY_DIR}/test/tjbenchtest) add_custom_target(tjtest12 COMMAND ${CMAKE_COMMAND} -DWITH_JAVA=${WITH_JAVA} -DPRECISION=12 -P ${CMAKE_SOURCE_DIR}/cmakescripts/tjbenchtest.cmake DEPENDS ${CMAKE_SOURCE_DIR}/cmakescripts/tjbenchtest.cmake - ${CMAKE_CURRENT_BINARY_DIR}/test/tjbenchtest - ${CMAKE_CURRENT_BINARY_DIR}/test/tjexampletest) + ${CMAKE_CURRENT_BINARY_DIR}/test/tjbenchtest) add_custom_target(tjtest16 COMMAND ${CMAKE_COMMAND} -DWITH_JAVA=${WITH_JAVA} -DPRECISION=2 -P ${CMAKE_SOURCE_DIR}/cmakescripts/tjbenchtest.cmake COMMAND ${CMAKE_COMMAND} -DWITH_JAVA=${WITH_JAVA} @@ -1922,8 +1928,14 @@ if(WITH_TURBOJPEG) COMMAND ${CMAKE_COMMAND} -DWITH_JAVA=${WITH_JAVA} -DPRECISION=16 -P ${CMAKE_SOURCE_DIR}/cmakescripts/tjbenchtest.cmake DEPENDS ${CMAKE_SOURCE_DIR}/cmakescripts/tjbenchtest.cmake - ${CMAKE_CURRENT_BINARY_DIR}/test/tjbenchtest - ${CMAKE_CURRENT_BINARY_DIR}/test/tjexampletest) + ${CMAKE_CURRENT_BINARY_DIR}/test/tjbenchtest) + add_custom_target(tjexampletest COMMAND ${CMAKE_COMMAND} + -DWITH_JAVA=${WITH_JAVA} + -P ${CMAKE_SOURCE_DIR}/cmakescripts/tjexampletest.cmake + DEPENDS ${CMAKE_SOURCE_DIR}/cmakescripts/tjexampletest.cmake + ${CMAKE_CURRENT_BINARY_DIR}/test/tjcomptest + ${CMAKE_CURRENT_BINARY_DIR}/test/tjdecomptest + ${CMAKE_CURRENT_BINARY_DIR}/test/tjtrantest) endif() @@ -1991,7 +2003,9 @@ install(TARGETS rdjpgcom wrjpgcom install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/README.ijg ${CMAKE_CURRENT_SOURCE_DIR}/README.md ${CMAKE_CURRENT_SOURCE_DIR}/src/example.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/tjexample.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/tjcomp.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/tjdecomp.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/tjtran.c ${CMAKE_CURRENT_SOURCE_DIR}/doc/libjpeg.txt ${CMAKE_CURRENT_SOURCE_DIR}/doc/structure.txt ${CMAKE_CURRENT_SOURCE_DIR}/doc/usage.txt @@ -1999,7 +2013,9 @@ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/README.ijg ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.md DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT doc) if(WITH_JAVA) - install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/java/TJExample.java + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/java/TJComp.java + ${CMAKE_CURRENT_SOURCE_DIR}/java/TJDeomp.java + ${CMAKE_CURRENT_SOURCE_DIR}/java/TJTran.java DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT doc) endif() diff --git a/ChangeLog.md b/ChangeLog.md index 6c18ed10..cc17dfa9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -61,6 +61,10 @@ djpeg, and jpegtran.) work similarly to the `tj3LoadImage*()` and `tj3SaveImage*()` functions in the C API. +8. TJExample has been replaced with three programs (TJComp, TJDecomp, and +TJTran) that demonstrate how to approximate the functionality of cjpeg, djpeg, +and jpegtran using the TurboJPEG C and Java APIs. + 3.0.4 ===== diff --git a/README.md b/README.md index 45986f0e..86b5ea67 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,10 @@ JPEG images: generating planar YUV images and performing multiple simultaneous lossless transforms on an image. The Java interface for libjpeg-turbo is written on top of the TurboJPEG API. The TurboJPEG API is recommended for first-time - users of libjpeg-turbo. Refer to [tjexample.c](src/tjexample.c) and - [TJExample.java](java/TJExample.java) for examples of its usage and to + users of libjpeg-turbo. Refer to [tjcomp.c](src/tjcomp.c), + [tjdecomp.c](src/tjdecomp.c), [tjtran.c](src/tjtran.c), + [TJComp.java](java/TJComp.java), [TJDecomp.java](java/TJDecomp.java), and + [TJTran.java](java/TJTran.java) for examples of its usage and to for API documentation. diff --git a/cmakescripts/testclean.cmake b/cmakescripts/testclean.cmake index 5f951094..a2c13059 100644 --- a/cmakescripts/testclean.cmake +++ b/cmakescripts/testclean.cmake @@ -40,7 +40,9 @@ file(GLOB FILES *_LOSSL*S_*.jpg croptest.log tjbenchtest*.log - tjexampletest*.log) + tjcomptest*.log + tjdecomptest*.log + tjtrantest*.log) if(NOT FILES STREQUAL "") message(STATUS "Removing test files") diff --git a/cmakescripts/tjbenchtest.cmake b/cmakescripts/tjbenchtest.cmake index d0f2d6a8..29162cb9 100644 --- a/cmakescripts/tjbenchtest.cmake +++ b/cmakescripts/tjbenchtest.cmake @@ -45,9 +45,6 @@ if(PRECISION EQUAL 8) endif() run_test(tjbenchtest "-precision;${PRECISION};-lossless") run_test(tjbenchtest "-precision;${PRECISION};-lossless;-alloc") -if(PRECISION EQUAL 8) - run_test(tjexampletest "") -endif() if(WITH_JAVA) if(PRECISION EQUAL 8 OR PRECISION EQUAL 12) run_test(tjbenchtest "-java;-precision;${PRECISION}") @@ -71,7 +68,4 @@ if(WITH_JAVA) run_test(tjbenchtest "-java;-precision;${PRECISION};-arithmetic;-yuv") endif() run_test(tjbenchtest "-java;-precision;${PRECISION};-lossless") - if(PRECISION EQUAL 8) - run_test(tjexampletest "-java") - endif() endif() diff --git a/cmakescripts/tjexampletest.cmake b/cmakescripts/tjexampletest.cmake new file mode 100644 index 00000000..b0189b23 --- /dev/null +++ b/cmakescripts/tjexampletest.cmake @@ -0,0 +1,26 @@ +if(NOT DEFINED WITH_JAVA) + message(FATAL_ERROR "WITH_JAVA must be specified") +endif() + +macro(check_error program) + if(NOT RESULT EQUAL 0) + message(FATAL_ERROR "${program} failed.") + endif() +endmacro() + +macro(run_test PROG ARGS) + string(REPLACE ";" " " SPACED_ARGS "${ARGS}") + message(STATUS "${PROG} ${SPACED_ARGS}") + execute_process(COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test/${PROG} ${ARGS} + RESULT_VARIABLE RESULT) + check_error("${PROG} ${SPACED_ARGS}") +endmacro() + +run_test(tjcomptest "") +run_test(tjdecomptest "") +run_test(tjtrantest "") +if(WITH_JAVA) + run_test(tjcomptest "-java") + run_test(tjdecomptest "-java") + run_test(tjtrantest "-java") +endif() diff --git a/java/CMakeLists.txt b/java/CMakeLists.txt index e699e94b..03121417 100644 --- a/java/CMakeLists.txt +++ b/java/CMakeLists.txt @@ -25,7 +25,9 @@ set(JAVA_SOURCES org/libjpegturbo/turbojpeg/TJ.java org/libjpegturbo/turbojpeg/TJTransformer.java org/libjpegturbo/turbojpeg/YUVImage.java TJUnitTest.java - TJExample.java + TJComp.java + TJDecomp.java + TJTran.java TJBench.java) set(TURBOJPEG_DLL_NAME "turbojpeg") @@ -50,7 +52,7 @@ if(MSYS) set(CMAKE_HOST_SYSTEM_NAME "MSYS") endif() add_jar(turbojpeg-java ${JAVA_SOURCES} OUTPUT_NAME turbojpeg - ENTRY_POINT TJExample) + ENTRY_POINT TJBench) if(MSYS) set(CMAKE_HOST_SYSTEM_NAME ${CMAKE_HOST_SYSTEM_NAME_BAK}) endif() diff --git a/java/MANIFEST.MF b/java/MANIFEST.MF index 723bc51a..0719b47e 100644 --- a/java/MANIFEST.MF +++ b/java/MANIFEST.MF @@ -1,2 +1,2 @@ Manifest-Version: 1.0 -Main-Class: TJExample +Main-Class: TJBench diff --git a/java/README b/java/README index 5af1e31d..80f0c403 100644 --- a/java/README +++ b/java/README @@ -10,9 +10,9 @@ directly into both open source and proprietary projects without restriction. A Java archive (JAR) file containing these classes is also shipped with the "official" distribution packages of libjpeg-turbo. -TJExample.java, which should also be located in the same directory as this -README file, demonstrates how to use the TurboJPEG Java API to compress and -decompress JPEG images in memory. +TJComp.java, TJDecomp.java, and TJTran.java, which should be located in the +same directory as this README file, demonstrate how to use the TurboJPEG Java +API to compress, decompress, and transform JPEG images in memory. Performance Pitfalls diff --git a/java/TJComp.java b/java/TJComp.java new file mode 100644 index 00000000..e37e1868 --- /dev/null +++ b/java/TJComp.java @@ -0,0 +1,299 @@ +/* + * Copyright (C)2011-2012, 2014-2015, 2017-2018, 2022-2024 D. R. Commander. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the libjpeg-turbo Project nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This program demonstrates how to use the TurboJPEG Java API to approximate + * the functionality of the IJG's cjpeg program. cjpeg features that are not + * covered: + * + * - GIF and Targa input file formats [legacy feature] + * - Separate quality settings for luminance and chrominance + * - The floating-point DCT method [legacy feature] + * - Embedding an ICC color management profile + * - Input image smoothing + * - Progress reporting + * - Debug output + * - Forcing baseline-compatible quantization tables + * - Specifying arbitrary quantization tables + * - Specifying arbitrary sampling factors + * - Scan scripts + */ + +import java.io.*; +import java.util.*; +import org.libjpegturbo.turbojpeg.*; + + +@SuppressWarnings("checkstyle:JavadocType") +final class TJComp { + + private TJComp() {} + + static final String CLASS_NAME = + new TJComp().getClass().getName(); + + static final int DEFAULT_SUBSAMP = TJ.SAMP_420; + static final int DEFAULT_QUALITY = 75; + + + static final String[] SUBSAMP_NAME = { + "444", "422", "420", "GRAY", "440", "411", "441" + }; + + + static void usage() { + System.out.println("\nUSAGE: java [Java options] " + CLASS_NAME + + " [options] \n"); + + System.out.println("The input image can be in Windows BMP or PBMPLUS (PPM/PGM) format.\n"); + + System.out.println("GENERAL OPTIONS (CAN BE ABBREVIATED)"); + System.out.println("------------------------------------"); + System.out.println("-lossless PSV[,Pt]"); + System.out.println(" Create a lossless JPEG image (implies -subsamp 444) using predictor"); + System.out.println(" selection value PSV (1-7) and optional point transform Pt (0 through"); + System.out.println(" {data precision} - 1)"); + System.out.println("-maxmemory N"); + System.out.println(" Memory limit (in megabytes) for intermediate buffers used with progressive"); + System.out.println(" JPEG compression, lossless JPEG compression, and Huffman table optimization"); + System.out.println(" [default = no limit]"); + System.out.println("-precision N"); + System.out.println(" Create a JPEG image with N-bit data precision [N = 2..16; default = 8; if N"); + System.out.println(" is not 8 or 12, then -lossless must also be specified] (-precision 12"); + System.out.println(" implies -optimize unless -arithmetic is also specified)"); + System.out.println("-restart N"); + System.out.println(" Add a restart marker every N MCU rows [default = 0 (no restart markers)]."); + System.out.println(" Append 'B' to specify the restart marker interval in MCUs (lossy only.)\n"); + + System.out.println("LOSSY JPEG OPTIONS (CAN BE ABBREVIATED)"); + System.out.println("---------------------------------------"); + System.out.println("-arithmetic"); + System.out.println(" Use arithmetic entropy coding instead of Huffman entropy coding (can be"); + System.out.println(" combined with -progressive)"); + System.out.println("-dct fast"); + System.out.println(" Use less accurate DCT algorithm [legacy feature]"); + System.out.println("-dct int"); + System.out.println(" Use more accurate DCT algorithm [default]"); + System.out.println("-grayscale"); + System.out.println(" Create a grayscale JPEG image from a full-color input image"); + System.out.println("-optimize"); + System.out.println(" Use Huffman table optimization"); + System.out.println("-progressive"); + System.out.println(" Create a progressive JPEG image instead of a single-scan JPEG image (can be"); + System.out.println(" combined with -arithmetic; implies -optimize unless -arithmetic is also"); + System.out.println(" specified)"); + System.out.println("-quality {1..100}"); + System.out.format(" Create a JPEG image with the specified quality level [default = %d]\n", + DEFAULT_QUALITY); + System.out.println("-rgb"); + System.out.println(" Create a JPEG image that uses the RGB colorspace instead of the YCbCr"); + System.out.println(" colorspace"); + System.out.println("-subsamp {444|422|440|420|411|441}"); + System.out.println(" Create a JPEG image that uses the specified chrominance subsampling level"); + System.out.format(" [default = %s]\n\n", SUBSAMP_NAME[DEFAULT_SUBSAMP]); + + System.exit(1); + } + + + static boolean matchArg(String arg, String string, int minChars) { + if (arg.length() > string.length() || arg.length() < minChars) + return false; + + int cmpChars = Math.max(arg.length(), minChars); + string = string.substring(0, cmpChars); + + return arg.equalsIgnoreCase(string); + } + + + public static void main(String[] argv) { + int exitStatus = 0; + TJCompressor tjc = null; + FileOutputStream fos = null; + + try { + int i; + int arithmetic = -1, colorspace = -1, fastDCT = -1, losslessPSV = -1, + losslessPt = -1, maxMemory = -1, optimize = -1, + pixelFormat = TJ.PF_UNKNOWN, precision = -1, progressive = -1, + quality = DEFAULT_QUALITY, restartIntervalBlocks = -1, + restartIntervalRows = -1, subsamp = DEFAULT_SUBSAMP; + byte[] jpegBuf; + + for (i = 0; i < argv.length; i++) { + if (matchArg(argv[i], "-arithmetic", 2)) + arithmetic = 1; + else if (matchArg(argv[i], "-dct", 2) && i < argv.length - 1) { + i++; + if (matchArg(argv[i], "fast", 1)) + fastDCT = 1; + else if (!matchArg(argv[i], "int", 1)) + usage(); + } else if (matchArg(argv[i], "-grayscale", 2) || + matchArg(argv[i], "-greyscale", 2)) + colorspace = TJ.CS_GRAY; + else if (matchArg(argv[i], "-lossless", 2) && i < argv.length - 1) { + Scanner scanner = new Scanner(argv[++i]).useDelimiter(","); + + try { + if (scanner.hasNextInt()) + losslessPSV = scanner.nextInt(); + if (scanner.hasNextInt()) + losslessPt = scanner.nextInt(); + } catch (Exception e) {} + + if (losslessPSV < 1 || losslessPSV > 7) + usage(); + } else if (matchArg(argv[i], "-maxmemory", 2) && i < argv.length - 1) { + int temp = -1; + + try { + temp = Integer.parseInt(argv[++i]); + } catch (NumberFormatException e) {} + if (temp < 0) + usage(); + maxMemory = temp; + } else if (matchArg(argv[i], "-optimize", 2) || + matchArg(argv[i], "-optimise", 2)) + optimize = 1; + else if (matchArg(argv[i], "-precision", 4) && i < argv.length - 1) { + int temp = 0; + + try { + temp = Integer.parseInt(argv[++i]); + } catch (NumberFormatException e) {} + if (temp < 2 || temp > 16) + usage(); + precision = temp; + } else if (matchArg(argv[i], "-progressive", 2)) + progressive = 1; + else if (matchArg(argv[i], "-quality", 2) && i < argv.length - 1) { + int temp = 0; + + try { + temp = Integer.parseInt(argv[++i]); + } catch (NumberFormatException e) {} + if (temp < 1 || temp > 100) + usage(); + quality = temp; + } else if (matchArg(argv[i], "-rgb", 3)) + colorspace = TJ.CS_RGB; + else if (matchArg(argv[i], "-restart", 2) && i < argv.length - 1) { + int temp = -1; + String arg = argv[++i]; + Scanner scanner = new Scanner(arg).useDelimiter("b|B"); + + try { + temp = scanner.nextInt(); + } catch (Exception e) {} + + if (temp < 0 || temp > 65535 || scanner.hasNext()) + usage(); + if (arg.endsWith("B") || arg.endsWith("b")) + restartIntervalBlocks = temp; + else + restartIntervalRows = temp; + } else if (matchArg(argv[i], "-subsamp", 2) && i < argv.length - 1) { + i++; + if (matchArg(argv[i], "444", 3)) + subsamp = TJ.SAMP_444; + else if (matchArg(argv[i], "422", 3)) + subsamp = TJ.SAMP_422; + else if (matchArg(argv[i], "440", 3)) + subsamp = TJ.SAMP_440; + else if (matchArg(argv[i], "420", 3)) + subsamp = TJ.SAMP_420; + else if (matchArg(argv[i], "411", 3)) + subsamp = TJ.SAMP_411; + else if (matchArg(argv[i], "441", 3)) + subsamp = TJ.SAMP_441; + else + usage(); + } else break; + } + + if (i != argv.length - 2) + usage(); + if (losslessPSV == -1 && precision != -1 && precision != 8 && + precision != 12) + usage(); + + tjc = new TJCompressor(); + + tjc.set(TJ.PARAM_QUALITY, quality); + tjc.set(TJ.PARAM_SUBSAMP, subsamp); + if (precision >= 0) + tjc.set(TJ.PARAM_PRECISION, precision); + if (fastDCT >= 0) + tjc.set(TJ.PARAM_FASTDCT, fastDCT); + if (optimize >= 0) + tjc.set(TJ.PARAM_OPTIMIZE, optimize); + if (progressive >= 0) + tjc.set(TJ.PARAM_PROGRESSIVE, progressive); + if (arithmetic >= 0) + tjc.set(TJ.PARAM_ARITHMETIC, arithmetic); + if (losslessPSV >= 1 && losslessPSV <= 7) { + tjc.set(TJ.PARAM_LOSSLESS, 1); + tjc.set(TJ.PARAM_LOSSLESSPSV, losslessPSV); + if (losslessPt >= 0) + tjc.set(TJ.PARAM_LOSSLESSPT, losslessPt); + } + if (restartIntervalBlocks >= 0) + tjc.set(TJ.PARAM_RESTARTBLOCKS, restartIntervalBlocks); + if (restartIntervalRows >= 0) + tjc.set(TJ.PARAM_RESTARTROWS, restartIntervalRows); + if (maxMemory >= 0) + tjc.set(TJ.PARAM_MAXMEMORY, maxMemory); + + tjc.loadSourceImage(argv[i], 1, pixelFormat); + pixelFormat = tjc.getPixelFormat(); + + if (pixelFormat == TJ.PF_GRAY && colorspace < 0) + colorspace = TJ.CS_GRAY; + if (colorspace >= 0) + tjc.set(TJ.PARAM_COLORSPACE, colorspace); + + jpegBuf = tjc.compress(); + + File outFile = new File(argv[++i]); + fos = new FileOutputStream(outFile); + fos.write(jpegBuf, 0, tjc.getCompressedSize()); + } catch (Exception e) { + e.printStackTrace(); + exitStatus = -1; + } + + try { + if (fos != null) fos.close(); + if (tjc != null) tjc.close(); + } catch (Exception e) {} + System.exit(exitStatus); + } +}; diff --git a/java/TJDecomp.java b/java/TJDecomp.java new file mode 100644 index 00000000..dc46a50e --- /dev/null +++ b/java/TJDecomp.java @@ -0,0 +1,340 @@ +/* + * Copyright (C)2011-2012, 2014-2015, 2017-2018, 2022-2024 D. R. Commander. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the libjpeg-turbo Project nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This program demonstrates how to use the TurboJPEG C API to approximate the + * functionality of the IJG's djpeg program. djpeg features that are not + * covered: + * + * - OS/2 BMP, GIF, and Targa output file formats [legacy feature] + * - Color quantization and dithering [legacy feature] + * - The floating-point IDCT method [legacy feature] + * - Extracting an ICC color management profile + * - Progress reporting + * - Skipping rows (i.e. exclusive rather than inclusive partial decompression) + * - Debug output + */ + +import java.io.*; +import java.util.*; +import org.libjpegturbo.turbojpeg.*; + + +@SuppressWarnings("checkstyle:JavadocType") +final class TJDecomp { + + private TJDecomp() {} + + static final String CLASS_NAME = + new TJDecomp().getClass().getName(); + + + private static boolean isCropped(java.awt.Rectangle cr) { + return (cr.x != 0 || cr.y != 0 || cr.width != 0 || cr.height != 0); + } + + + private static String tjErrorMsg; + private static int tjErrorCode = -1; + + static void handleTJException(TJException e, int stopOnWarning) + throws TJException { + String errorMsg = e.getMessage(); + int errorCode = e.getErrorCode(); + + if (stopOnWarning != 1 && errorCode == TJ.ERR_WARNING) { + if (tjErrorMsg == null || !tjErrorMsg.equals(errorMsg) || + tjErrorCode != errorCode) { + tjErrorMsg = errorMsg; + tjErrorCode = errorCode; + System.out.println("WARNING: " + errorMsg); + } + } else + throw e; + } + + + static void usage() { + int i; + TJScalingFactor[] scalingFactors = TJ.getScalingFactors(); + int numScalingFactors = scalingFactors.length; + + System.out.println("\nUSAGE: java [Java options] " + CLASS_NAME + + " [options] \n"); + + System.out.println("The output image will be in Windows BMP or PBMPLUS (PPM/PGM) format, depending"); + System.out.println("on the file extension.\n"); + + System.out.println("GENERAL OPTIONS (CAN BE ABBREVBIATED)"); + System.out.println("-------------------------------------"); + System.out.println("-strict"); + System.out.println(" Treat all warnings as fatal; abort immediately if incomplete or corrupt"); + System.out.println(" data is encountered in the JPEG image, rather than trying to salvage the"); + System.out.println(" rest of the image\n"); + + System.out.println("LOSSY JPEG OPTIONS (CAN BE ABBREVIATED)"); + System.out.println("---------------------------------------"); + System.out.println("-crop WxH+X+Y"); + System.out.println(" Decompress only the specified region of the JPEG image. (W, H, X, and Y"); + System.out.println(" are the width, height, left boundary, and upper boundary of the region, all"); + System.out.println(" specified relative to the scaled image dimensions.) If necessary, X will"); + System.out.println(" be shifted left to the nearest iMCU boundary, and W will be increased"); + System.out.println(" accordingly."); + System.out.println("-dct fast"); + System.out.println(" Use less accurate IDCT algorithm [legacy feature]"); + System.out.println("-dct int"); + System.out.println(" Use more accurate IDCT algorithm [default]"); + System.out.println("-grayscale"); + System.out.println(" Decompress a full-color JPEG image into a grayscale output image"); + System.out.println("-maxmemory N"); + System.out.println(" Memory limit (in megabytes) for intermediate buffers used with progressive"); + System.out.println(" JPEG decompression [default = no limit]"); + System.out.println("-maxscans N"); + System.out.println(" Refuse to decompress progressive JPEG images that have more than N scans"); + System.out.println("-nosmooth"); + System.out.println(" Use the fastest chrominance upsampling algorithm available"); + System.out.println("-rgb"); + System.out.println(" Decompress a grayscale JPEG image into a full-color output image"); + System.out.println("-scale M/N"); + System.out.println(" Scale the width/height of the JPEG image by a factor of M/N when"); + System.out.print(" decompressing it (M/N = "); + for (i = 0; i < numScalingFactors; i++) { + System.out.format("%d/%d", scalingFactors[i].getNum(), + scalingFactors[i].getDenom()); + if (numScalingFactors == 2 && i != numScalingFactors - 1) + System.out.print(" or "); + else if (numScalingFactors > 2) { + if (i != numScalingFactors - 1) + System.out.print(", "); + if (i == numScalingFactors - 2) + System.out.print("or "); + } + if (i % 8 == 0 && i != 0) System.out.print("\n "); + } + System.out.println(")\n"); + + System.exit(1); + } + + + static boolean matchArg(String arg, String string, int minChars) { + if (arg.length() > string.length() || arg.length() < minChars) + return false; + + int cmpChars = Math.max(arg.length(), minChars); + string = string.substring(0, cmpChars); + + return arg.equalsIgnoreCase(string); + } + + + public static void main(String[] argv) { + int exitStatus = 0; + TJDecompressor tjd = null; + FileInputStream fis = null; + + try { + + int i; + int colorspace, fastDCT = -1, fastUpsample = -1, maxMemory = -1, + maxScans = -1, pixelFormat = TJ.PF_UNKNOWN, precision, + stopOnWarning = -1, subsamp; + java.awt.Rectangle croppingRegion = TJ.UNCROPPED; + TJScalingFactor scalingFactor = TJ.UNSCALED; + int width, height; + byte[] jpegBuf; + Object dstBuf = null; + + for (i = 0; i < argv.length; i++) { + if (matchArg(argv[i], "-crop", 2) && i < argv.length - 1) { + int tempWidth = -1, tempHeight = -1, tempX = -1, tempY = -1; + Scanner scanner = new Scanner(argv[++i]).useDelimiter("x|X|\\+"); + + try { + tempWidth = scanner.nextInt(); + tempHeight = scanner.nextInt(); + tempX = scanner.nextInt(); + tempY = scanner.nextInt(); + } catch (Exception e) {} + + if (tempWidth < 1 || tempHeight < 1 || tempX < 0 || tempY < 0) + usage(); + croppingRegion.width = tempWidth; + croppingRegion.height = tempHeight; + croppingRegion.x = tempX; + croppingRegion.y = tempY; + } else if (matchArg(argv[i], "-dct", 2) && i < argv.length - 1) { + i++; + if (matchArg(argv[i], "fast", 1)) + fastDCT = 1; + else if (!matchArg(argv[i], "int", 1)) + usage(); + } else if (matchArg(argv[i], "-grayscale", 2) || + matchArg(argv[i], "-greyscale", 2)) + pixelFormat = TJ.PF_GRAY; + else if (matchArg(argv[i], "-maxscans", 5) && i < argv.length - 1) { + int temp = -1; + + try { + temp = Integer.parseInt(argv[++i]); + } catch (NumberFormatException e) {} + if (temp < 0) + usage(); + maxScans = temp; + } else if (matchArg(argv[i], "-maxmemory", 2) && i < argv.length - 1) { + int temp = -1; + + try { + temp = Integer.parseInt(argv[++i]); + } catch (NumberFormatException e) {} + if (temp < 0) + usage(); + maxMemory = temp; + } else if (matchArg(argv[i], "-nosmooth", 2)) + fastUpsample = 1; + else if (matchArg(argv[i], "-rgb", 2)) + pixelFormat = TJ.PF_RGB; + else if (matchArg(argv[i], "-strict", 3)) + stopOnWarning = 1; + else if (matchArg(argv[i], "-scale", 2) && i < argv.length - 1) { + int tempNum = 0, tempDenom = 0; + boolean match = false, scanned = true; + Scanner scanner = new Scanner(argv[++i]).useDelimiter("/"); + + try { + tempNum = scanner.nextInt(); + tempDenom = scanner.nextInt(); + } catch (Exception e) {} + + if (tempNum < 1 || tempDenom < 1) + usage(); + + TJScalingFactor[] scalingFactors = TJ.getScalingFactors(); + for (int j = 0; j < scalingFactors.length; j++) { + if ((double)tempNum / (double)tempDenom == + (double)scalingFactors[j].getNum() / + (double)scalingFactors[j].getDenom()) { + scalingFactor = scalingFactors[j]; + match = true; break; + } + } + if (!match) usage(); + } else break; + } + + if (i != argv.length - 2) + usage(); + + tjd = new TJDecompressor(); + + if (stopOnWarning >= 0) + tjd.set(TJ.PARAM_STOPONWARNING, stopOnWarning); + if (fastUpsample >= 0) + tjd.set(TJ.PARAM_FASTUPSAMPLE, fastUpsample); + if (fastDCT >= 0) + tjd.set(TJ.PARAM_FASTDCT, fastDCT); + if (maxScans >= 0) + tjd.set(TJ.PARAM_SCANLIMIT, maxScans); + if (maxMemory >= 0) + tjd.set(TJ.PARAM_MAXMEMORY, maxMemory); + + File jpegFile = new File(argv[i++]); + fis = new FileInputStream(jpegFile); + int jpegSize = fis.available(); + if (jpegSize < 1) + throw new Exception("Input file contains no data"); + jpegBuf = new byte[jpegSize]; + fis.read(jpegBuf); + fis.close(); fis = null; + + try { + tjd.setSourceImage(jpegBuf, jpegSize); + } catch (TJException e) { handleTJException(e, stopOnWarning); } + subsamp = tjd.get(TJ.PARAM_SUBSAMP); + width = tjd.get(TJ.PARAM_JPEGWIDTH); + height = tjd.get(TJ.PARAM_JPEGHEIGHT); + precision = tjd.get(TJ.PARAM_PRECISION); + colorspace = tjd.get(TJ.PARAM_COLORSPACE); + + if (pixelFormat == TJ.PF_UNKNOWN) { + if (colorspace == TJ.CS_GRAY) + pixelFormat = TJ.PF_GRAY; + else if (colorspace == TJ.CS_CMYK || colorspace == TJ.CS_YCCK) + pixelFormat = TJ.PF_CMYK; + else + pixelFormat = TJ.PF_RGB; + } + + if (tjd.get(TJ.PARAM_LOSSLESS) == 0) { + tjd.setScalingFactor(scalingFactor); + width = scalingFactor.getScaled(width); + height = scalingFactor.getScaled(height); + + if (isCropped(croppingRegion)) { + int adjustment; + + if (subsamp == TJ.SAMP_UNKNOWN) + throw new Exception("Could not determine subsampling level of JPEG image"); + adjustment = croppingRegion.x % + scalingFactor.getScaled(TJ.getMCUWidth(subsamp)); + croppingRegion.x -= adjustment; + croppingRegion.width += adjustment; + tjd.setCroppingRegion(croppingRegion); + width = croppingRegion.width; + height = croppingRegion.height; + } + } + + if (precision <= 8) + dstBuf = new byte[width * height * TJ.getPixelSize(pixelFormat)]; + else + dstBuf = new short[width * height * TJ.getPixelSize(pixelFormat)]; + + try { + if (precision <= 8) + tjd.decompress8((byte[])dstBuf, 0, 0, 0, pixelFormat); + else if (precision <= 12) + tjd.decompress12((short[])dstBuf, 0, 0, 0, pixelFormat); + else + tjd.decompress16((short[])dstBuf, 0, 0, 0, pixelFormat); + } catch (TJException e) { handleTJException(e, stopOnWarning); } + + tjd.saveImage(argv[i], dstBuf, 0, 0, width, 0, height, pixelFormat); + } catch (Exception e) { + e.printStackTrace(); + exitStatus = -1; + } + + try { + if (fis != null) fis.close(); + if (tjd != null) tjd.close(); + } catch (Exception e) {} + System.exit(exitStatus); + } +}; diff --git a/java/TJExample.java b/java/TJExample.java deleted file mode 100644 index 96a6828f..00000000 --- a/java/TJExample.java +++ /dev/null @@ -1,402 +0,0 @@ -/* - * Copyright (C)2011-2012, 2014-2015, 2017-2018, 2022-2024 D. R. Commander. - * All Rights Reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - Neither the name of the libjpeg-turbo Project nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -/* - * This program demonstrates how to compress, decompress, and transform JPEG - * images using the TurboJPEG Java API - */ - -import java.io.*; -import java.awt.*; -import java.awt.image.*; -import java.nio.*; -import javax.imageio.*; -import javax.swing.*; -import org.libjpegturbo.turbojpeg.*; - - -@SuppressWarnings("checkstyle:JavadocType") -class TJExample implements TJCustomFilter { - - static final String CLASS_NAME = - new TJExample().getClass().getName(); - - static final int DEFAULT_SUBSAMP = TJ.SAMP_444; - static final int DEFAULT_QUALITY = 95; - - - static final String[] SUBSAMP_NAME = { - "4:4:4", "4:2:2", "4:2:0", "Grayscale", "4:4:0", "4:1:1", "4:4:1" - }; - - static final String[] COLORSPACE_NAME = { - "RGB", "YCbCr", "GRAY", "CMYK", "YCCK" - }; - - - /* DCT filter example. This produces a negative of the image. */ - - @SuppressWarnings("checkstyle:JavadocMethod") - public void customFilter(ShortBuffer coeffBuffer, Rectangle bufferRegion, - Rectangle planeRegion, int componentIndex, - int transformIndex, TJTransform transform) - throws TJException { - for (int i = 0; i < bufferRegion.width * bufferRegion.height; i++) { - coeffBuffer.put(i, (short)(-coeffBuffer.get(i))); - } - } - - - static void usage() throws Exception { - System.out.println("\nUSAGE: java [Java options] " + CLASS_NAME + - " [options]\n"); - - System.out.println("Input and output images can be in any image format that the Java Image I/O"); - System.out.println("extensions understand. If either filename ends in a .jpg extension, then"); - System.out.println("the TurboJPEG API will be used to compress or decompress the image.\n"); - - System.out.println("Compression Options (used if the output image is a JPEG image)"); - System.out.println("--------------------------------------------------------------\n"); - - System.out.println("-subsamp <444|422|420|gray> = Apply this level of chrominance subsampling when"); - System.out.println(" compressing the output image. The default is to use the same level of"); - System.out.println(" subsampling as in the input image, if the input image is also a JPEG"); - System.out.println(" image, or to use grayscale if the input image is a grayscale non-JPEG"); - System.out.println(" image, or to use " + - SUBSAMP_NAME[DEFAULT_SUBSAMP] + - " subsampling otherwise.\n"); - - System.out.println("-q <1-100> = Compress the output image with this JPEG quality level"); - System.out.println(" (default = " + DEFAULT_QUALITY + ").\n"); - - System.out.println("Decompression Options (used if the input image is a JPEG image)"); - System.out.println("---------------------------------------------------------------\n"); - - System.out.println("-scale M/N = Scale the input image by a factor of M/N when decompressing it."); - System.out.print("(M/N = "); - for (int i = 0; i < SCALING_FACTORS.length; i++) { - System.out.print(SCALING_FACTORS[i].getNum() + "/" + - SCALING_FACTORS[i].getDenom()); - if (SCALING_FACTORS.length == 2 && i != SCALING_FACTORS.length - 1) - System.out.print(" or "); - else if (SCALING_FACTORS.length > 2) { - if (i != SCALING_FACTORS.length - 1) - System.out.print(", "); - if (i == SCALING_FACTORS.length - 2) - System.out.print("or "); - } - } - System.out.println(")\n"); - - System.out.println("-hflip, -vflip, -transpose, -transverse, -rot90, -rot180, -rot270 ="); - System.out.println(" Perform one of these lossless transform operations on the input image"); - System.out.println(" prior to decompressing it (these options are mutually exclusive.)\n"); - - System.out.println("-grayscale = Perform lossless grayscale conversion on the input image prior"); - System.out.println(" to decompressing it (can be combined with the other transform operations"); - System.out.println(" above.)\n"); - - System.out.println("-crop WxH+X+Y = Perform lossless cropping on the input image prior to"); - System.out.println(" decompressing it. X and Y specify the upper left corner of the cropping"); - System.out.println(" region, and W and H specify the width and height of the cropping region."); - System.out.println(" X and Y must be evenly divible by the iMCU size (8x8 if the input image"); - System.out.println(" was compressed using no subsampling or grayscale, 16x8 if it was"); - System.out.println(" compressed using 4:2:2 subsampling, or 16x16 if it was compressed using"); - System.out.println(" 4:2:0 subsampling.)\n"); - - System.out.println("General Options"); - System.out.println("---------------\n"); - - System.out.println("-display = Display output image (Output filename need not be specified in this"); - System.out.println(" case.)\n"); - - System.out.println("-fastupsample = Use the fastest chrominance upsampling algorithm available\n"); - - System.out.println("-fastdct = Use the fastest DCT/IDCT algorithm available\n"); - - System.exit(1); - } - - - public static void main(String[] argv) { - - try { - - TJScalingFactor scalingFactor = TJ.UNSCALED; - int outSubsamp = -1, outQual = -1; - TJTransform xform = new TJTransform(); - boolean display = false, fastUpsample = false, fastDCT = false; - int width, height; - String inFormat = "jpg", outFormat = "jpg"; - BufferedImage img = null; - byte[] imgBuf = null; - - if (argv.length < 2) - usage(); - - if (argv[1].substring(0, 2).equalsIgnoreCase("-d")) - display = true; - - /* Parse arguments. */ - for (int i = 2; i < argv.length; i++) { - if (argv[i].length() < 2) - continue; - else if (argv[i].length() > 2 && - argv[i].substring(0, 3).equalsIgnoreCase("-sc") && - i < argv.length - 1) { - int match = 0; - String[] scaleArg = argv[++i].split("/"); - if (scaleArg.length == 2) { - TJScalingFactor tempsf = - new TJScalingFactor(Integer.parseInt(scaleArg[0]), - Integer.parseInt(scaleArg[1])); - for (int j = 0; j < SCALING_FACTORS.length; j++) { - if (tempsf.equals(SCALING_FACTORS[j])) { - scalingFactor = SCALING_FACTORS[j]; - match = 1; - break; - } - } - } - if (match != 1) - usage(); - } else if (argv[i].length() > 2 && - argv[i].substring(0, 3).equalsIgnoreCase("-su") && - i < argv.length - 1) { - i++; - if (argv[i].substring(0, 1).equalsIgnoreCase("g")) - outSubsamp = TJ.SAMP_GRAY; - else if (argv[i].equals("444")) - outSubsamp = TJ.SAMP_444; - else if (argv[i].equals("422")) - outSubsamp = TJ.SAMP_422; - else if (argv[i].equals("420")) - outSubsamp = TJ.SAMP_420; - else - usage(); - } else if (argv[i].substring(0, 2).equalsIgnoreCase("-q") && - i < argv.length - 1) { - outQual = Integer.parseInt(argv[++i]); - if (outQual < 1 || outQual > 100) - usage(); - } else if (argv[i].substring(0, 2).equalsIgnoreCase("-g")) - xform.options |= TJTransform.OPT_GRAY; - else if (argv[i].equalsIgnoreCase("-hflip")) - xform.op = TJTransform.OP_HFLIP; - else if (argv[i].equalsIgnoreCase("-vflip")) - xform.op = TJTransform.OP_VFLIP; - else if (argv[i].equalsIgnoreCase("-transpose")) - xform.op = TJTransform.OP_TRANSPOSE; - else if (argv[i].equalsIgnoreCase("-transverse")) - xform.op = TJTransform.OP_TRANSVERSE; - else if (argv[i].equalsIgnoreCase("-rot90")) - xform.op = TJTransform.OP_ROT90; - else if (argv[i].equalsIgnoreCase("-rot180")) - xform.op = TJTransform.OP_ROT180; - else if (argv[i].equalsIgnoreCase("-rot270")) - xform.op = TJTransform.OP_ROT270; - else if (argv[i].equalsIgnoreCase("-custom")) - xform.cf = new TJExample(); - else if (argv[i].length() > 2 && - argv[i].substring(0, 2).equalsIgnoreCase("-c") && - i < argv.length - 1) { - String[] cropArg = argv[++i].split("[x\\+]"); - if (cropArg.length != 4) - usage(); - xform.width = Integer.parseInt(cropArg[0]); - xform.height = Integer.parseInt(cropArg[1]); - xform.x = Integer.parseInt(cropArg[2]); - xform.y = Integer.parseInt(cropArg[3]); - if (xform.x < 0 || xform.y < 0 || xform.width < 1 || - xform.height < 1) - usage(); - xform.options |= TJTransform.OPT_CROP; - } else if (argv[i].substring(0, 2).equalsIgnoreCase("-d")) - display = true; - else if (argv[i].equalsIgnoreCase("-fastupsample")) { - System.out.println("Using fast upsampling code"); - fastUpsample = true; - } else if (argv[i].equalsIgnoreCase("-fastdct")) { - System.out.println("Using fastest DCT/IDCT algorithm"); - fastDCT = true; - } else usage(); - } - - /* Determine input and output image formats based on file extensions. */ - String[] inFileTokens = argv[0].split("\\."); - if (inFileTokens.length > 1) - inFormat = inFileTokens[inFileTokens.length - 1]; - String[] outFileTokens; - if (display) - outFormat = "bmp"; - else { - outFileTokens = argv[1].split("\\."); - if (outFileTokens.length > 1) - outFormat = outFileTokens[outFileTokens.length - 1]; - } - - if (inFormat.equalsIgnoreCase("jpg")) { - /* Input image is a JPEG image. Decompress and/or transform it. */ - boolean doTransform = (xform.op != TJTransform.OP_NONE || - xform.options != 0 || xform.cf != null); - - /* Read the JPEG file into memory. */ - File jpegFile = new File(argv[0]); - FileInputStream fis = new FileInputStream(jpegFile); - int jpegSize = fis.available(); - if (jpegSize < 1) { - System.out.println("Input file contains no data"); - System.exit(1); - } - byte[] jpegBuf = new byte[jpegSize]; - fis.read(jpegBuf); - fis.close(); - - TJDecompressor tjd; - if (doTransform) { - /* Transform it. */ - TJTransformer tjt = new TJTransformer(jpegBuf); - TJTransform[] xforms = new TJTransform[1]; - xforms[0] = xform; - xforms[0].options |= TJTransform.OPT_TRIM; - TJDecompressor[] tjds = tjt.transform(xforms); - tjd = tjds[0]; - tjt.close(); - } else - tjd = new TJDecompressor(jpegBuf); - tjd.set(TJ.PARAM_FASTUPSAMPLE, fastUpsample ? 1 : 0); - tjd.set(TJ.PARAM_FASTDCT, fastDCT ? 1 : 0); - - width = tjd.getWidth(); - height = tjd.getHeight(); - int inSubsamp = tjd.get(TJ.PARAM_SUBSAMP); - int inColorspace = tjd.get(TJ.PARAM_COLORSPACE); - - if (tjd.get(TJ.PARAM_LOSSLESS) == 1) - scalingFactor = TJ.UNSCALED; - - System.out.println((doTransform ? "Transformed" : "Input") + - " Image (jpg): " + width + " x " + height + - " pixels, " + SUBSAMP_NAME[inSubsamp] + - " subsampling, " + COLORSPACE_NAME[inColorspace]); - - if (outFormat.equalsIgnoreCase("jpg") && doTransform && - scalingFactor.isOne() && outSubsamp < 0 && outQual < 0) { - /* Input image has been transformed, and no re-compression options - have been selected. Write the transformed image to disk and - exit. */ - File outFile = new File(argv[1]); - FileOutputStream fos = new FileOutputStream(outFile); - fos.write(tjd.getJPEGBuf(), 0, tjd.getJPEGSize()); - fos.close(); - System.exit(0); - } - - /* Scaling and/or a non-JPEG output image format and/or compression - options have been selected, so we need to decompress the - input/transformed image. */ - tjd.setScalingFactor(scalingFactor); - width = scalingFactor.getScaled(width); - height = scalingFactor.getScaled(height); - if (outSubsamp < 0) - outSubsamp = inSubsamp; - - if (!outFormat.equalsIgnoreCase("jpg")) - img = tjd.decompress8(BufferedImage.TYPE_INT_RGB); - else - imgBuf = tjd.decompress8(0, TJ.PF_BGRX); - tjd.close(); - } else { - /* Input image is not a JPEG image. Load it into memory. */ - img = ImageIO.read(new File(argv[0])); - if (img == null) - throw new Exception("Input image type not supported."); - width = img.getWidth(); - height = img.getHeight(); - if (outSubsamp < 0) { - if (img.getType() == BufferedImage.TYPE_BYTE_GRAY) - outSubsamp = TJ.SAMP_GRAY; - else - outSubsamp = DEFAULT_SUBSAMP; - } - System.out.println("Input Image: " + width + " x " + height + - " pixels"); - } - System.gc(); - if (!display) - System.out.print("Output Image (" + outFormat + "): " + width + - " x " + height + " pixels"); - - if (display) { - /* Display the uncompressed image */ - ImageIcon icon = new ImageIcon(img); - JLabel label = new JLabel(icon, JLabel.CENTER); - JOptionPane.showMessageDialog(null, label, "Output Image", - JOptionPane.PLAIN_MESSAGE); - } else if (outFormat.equalsIgnoreCase("jpg")) { - /* Output image format is JPEG. Compress the uncompressed image. */ - if (outQual < 0) - outQual = DEFAULT_QUALITY; - System.out.println(", " + SUBSAMP_NAME[outSubsamp] + - " subsampling, quality = " + outQual); - - TJCompressor tjc = new TJCompressor(); - tjc.set(TJ.PARAM_SUBSAMP, outSubsamp); - tjc.set(TJ.PARAM_QUALITY, outQual); - tjc.set(TJ.PARAM_FASTDCT, fastDCT ? 1 : 0); - if (img != null) - tjc.setSourceImage(img, 0, 0, 0, 0); - else - tjc.setSourceImage(imgBuf, 0, 0, width, 0, height, TJ.PF_BGRX); - byte[] jpegBuf = tjc.compress(); - int jpegSize = tjc.getCompressedSize(); - tjc.close(); - - /* Write the JPEG image to disk. */ - File outFile = new File(argv[1]); - FileOutputStream fos = new FileOutputStream(outFile); - fos.write(jpegBuf, 0, jpegSize); - fos.close(); - } else { - /* Output image format is not JPEG. Save the uncompressed image - directly to disk. */ - System.out.print("\n"); - File outFile = new File(argv[1]); - ImageIO.write(img, outFormat, outFile); - } - - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } - - static final TJScalingFactor[] SCALING_FACTORS = - TJ.getScalingFactors(); -}; diff --git a/java/TJTran.java b/java/TJTran.java new file mode 100644 index 00000000..c129a818 --- /dev/null +++ b/java/TJTran.java @@ -0,0 +1,315 @@ +/* + * Copyright (C)2011-2012, 2014-2015, 2017-2018, 2022-2024 D. R. Commander. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the libjpeg-turbo Project nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This program demonstrates how to use the TurboJPEG C API to approximate the + * functionality of the IJG's jpegtran program. jpegtran features that are not + * covered: + * + * - Adding restart markers to the output image + * - Scan scripts + * - Expanding the input image when cropping + * - Wiping a region of the input image + * - Dropping another JPEG image into the input image + * - Copying only comment markers or ICC profile markers + * - Embedding an ICC color management profile + * - Progress reporting + * - Treating warnings as non-fatal [limitation of the TurboJPEG Java API] + * - Debug output + */ + +import java.io.*; +import java.util.*; +import org.libjpegturbo.turbojpeg.*; + + +@SuppressWarnings("checkstyle:JavadocType") +final class TJTran { + + private TJTran() {} + + static final String CLASS_NAME = + new TJTran().getClass().getName(); + + + private static boolean isCropped(java.awt.Rectangle cr) { + return (cr.x != 0 || cr.y != 0 || cr.width != 0 || cr.height != 0); + } + + + static void usage() { + int i; + TJScalingFactor[] scalingFactors = TJ.getScalingFactors(); + int numScalingFactors = scalingFactors.length; + + System.out.println("\nUSAGE: java [Java options] " + CLASS_NAME + + " [options] \n"); + + System.out.println("This program reads the DCT coefficients from the lossy JPEG input image,"); + System.out.println("optionally transforms them, and writes them to a lossy JPEG output image.\n"); + + System.out.println("OPTIONS (CAN BE ABBREVBIATED)"); + System.out.println("-----------------------------"); + System.out.println("-arithmetic"); + System.out.println(" Use arithmetic entropy coding in the output image instead of Huffman"); + System.out.println(" entropy coding (can be combined with -progressive)"); + System.out.println("-copy all"); + System.out.println(" Copy all extra markers (including comments, JFIF thumbnails, Exif data, and"); + System.out.println(" ICC profile data) from the input image to the output image [default]"); + System.out.println("-copy none"); + System.out.println(" Copy no extra markers from the input image to the output image"); + System.out.println("-crop WxH+X+Y"); + System.out.println(" Include only the specified region of the input image. (W, H, X, and Y are"); + System.out.println(" the width, height, left boundary, and upper boundary of the region, all"); + System.out.println(" specified relative to the transformed image dimensions.) If necessary, X"); + System.out.println(" and Y will be shifted up and left to the nearest iMCU boundary, and W and H"); + System.out.println(" will be increased accordingly."); + System.out.println("-flip {horizontal|vertical}, -rotate {90|180|270}, -transpose, -transverse"); + System.out.println(" Perform the specified lossless transform operation (these options are"); + System.out.println(" mutually exclusive)"); + System.out.println("-grayscale"); + System.out.println(" Create a grayscale output image from a full-color input image"); + System.out.println("-maxmemory N"); + System.out.println(" Memory limit (in megabytes) for intermediate buffers used with progressive"); + System.out.println(" JPEG compression, Huffman table optimization, and lossless transformation"); + System.out.println(" [default = no limit]"); + System.out.println("-maxscans N"); + System.out.println(" Refuse to transform progressive JPEG images that have more than N scans"); + System.out.println("-optimize"); + System.out.println(" Use Huffman table optimization in the output image"); + System.out.println("-perfect"); + System.out.println(" Abort if the requested transform operation is imperfect (non-reversible.)"); + System.out.println(" '-flip horizontal', '-rotate 180', '-rotate 270', and '-transverse' are"); + System.out.println(" imperfect if the image width is not evenly divisible by the iMCU width."); + System.out.println(" '-flip vertical', '-rotate 90', '-rotate 180', and '-transverse' are"); + System.out.println(" imperfect if the image height is not evenly divisible by the iMCU height."); + System.out.println("-progressive"); + System.out.println(" Create a progressive output image instead of a single-scan output image"); + System.out.println(" (can be combined with -arithmetic; implies -optimize unless -arithmetic is"); + System.out.println(" also specified)"); + System.out.println("-trim"); + System.out.println(" If necessary, trim the partial iMCUs at the right or bottom edge of the"); + System.out.println(" image to make the requested transform perfect\n"); + + System.exit(1); + } + + + static boolean matchArg(String arg, String string, int minChars) { + if (arg.length() > string.length() || arg.length() < minChars) + return false; + + int cmpChars = Math.max(arg.length(), minChars); + string = string.substring(0, cmpChars); + + return arg.equalsIgnoreCase(string); + } + + + public static void main(String[] argv) { + int exitStatus = 0; + TJTransformer tjt = null; + FileInputStream fis = null; + FileOutputStream fos = null; + + try { + + int i; + int arithmetic = 0, maxMemory = -1, maxScans = -1, optimize = -1, + progressive = 0, subsamp; + TJTransform[] xform = new TJTransform[1]; + xform[0] = new TJTransform(); + byte[] srcBuf; + int width, height; + byte[][] dstBuf; + + for (i = 0; i < argv.length; i++) { + if (matchArg(argv[i], "-arithmetic", 2)) + arithmetic = 1; + else if (matchArg(argv[i], "-crop", 3) && i < argv.length - 1) { + int tempWidth = -1, tempHeight = -1, tempX = -1, tempY = -1; + Scanner scanner = new Scanner(argv[++i]).useDelimiter("x|X|\\+"); + + try { + tempWidth = scanner.nextInt(); + tempHeight = scanner.nextInt(); + tempX = scanner.nextInt(); + tempY = scanner.nextInt(); + } catch (Exception e) {} + + if (tempWidth < 1 || tempHeight < 1 || tempX < 0 || tempY < 0) + usage(); + xform[0].options |= TJTransform.OPT_CROP; + xform[0].width = tempWidth; + xform[0].height = tempHeight; + xform[0].x = tempX; + xform[0].y = tempY; + } else if (matchArg(argv[i], "-copy", 2) && i < argv.length - 1) { + i++; + if (matchArg(argv[i], "none", 1)) + xform[0].options |= TJTransform.OPT_COPYNONE; + else if (!matchArg(argv[i], "all", 1)) + usage(); + } else if (matchArg(argv[i], "-flip", 2) && i < argv.length - 1) { + i++; + if (matchArg(argv[i], "horizontal", 1)) + xform[0].op = TJTransform.OP_HFLIP; + else if (matchArg(argv[i], "vertical", 1)) + xform[0].op = TJTransform.OP_VFLIP; + else + usage(); + } else if (matchArg(argv[i], "-grayscale", 2) || + matchArg(argv[i], "-greyscale", 2)) + xform[0].options |= TJTransform.OPT_GRAY; + else if (matchArg(argv[i], "-maxscans", 5) && i < argv.length - 1) { + int temp = -1; + + try { + temp = Integer.parseInt(argv[++i]); + } catch (NumberFormatException e) {} + if (temp < 0) + usage(); + maxScans = temp; + } else if (matchArg(argv[i], "-maxmemory", 2) && i < argv.length - 1) { + int temp = -1; + + try { + temp = Integer.parseInt(argv[++i]); + } catch (NumberFormatException e) {} + if (temp < 0) + usage(); + maxMemory = temp; + } else if (matchArg(argv[i], "-optimize", 2) || + matchArg(argv[i], "-optimise", 2)) + optimize = 1; + else if (matchArg(argv[i], "-perfect", 3)) + xform[0].options |= TJTransform.OPT_PERFECT; + else if (matchArg(argv[i], "-progressive", 2)) + progressive = 1; + else if (matchArg(argv[i], "-rotate", 2) && i < argv.length - 1) { + i++; + if (matchArg(argv[i], "90", 2)) + xform[0].op = TJTransform.OP_ROT90; + else if (matchArg(argv[i], "180", 3)) + xform[0].op = TJTransform.OP_ROT180; + else if (matchArg(argv[i], "270", 3)) + xform[0].op = TJTransform.OP_ROT270; + else + usage(); + } else if (matchArg(argv[i], "-transverse", 7)) + xform[0].op = TJTransform.OP_TRANSVERSE; + else if (matchArg(argv[i], "-trim", 4)) + xform[0].options |= TJTransform.OPT_TRIM; + else if (matchArg(argv[i], "-transpose", 2)) + xform[0].op = TJTransform.OP_TRANSPOSE; + else break; + } + + if (i != argv.length - 2) + usage(); + + tjt = new TJTransformer(); + + if (optimize >= 0) + tjt.set(TJ.PARAM_OPTIMIZE, optimize); + if (maxScans >= 0) + tjt.set(TJ.PARAM_SCANLIMIT, maxScans); + if (maxMemory >= 0) + tjt.set(TJ.PARAM_MAXMEMORY, maxMemory); + + File inFile = new File(argv[i++]); + fis = new FileInputStream(inFile); + int srcSize = fis.available(); + if (srcSize < 1) + throw new Exception("Input file contains no data"); + srcBuf = new byte[srcSize]; + fis.read(srcBuf); + fis.close(); fis = null; + + tjt.setSourceImage(srcBuf, srcSize); + subsamp = tjt.get(TJ.PARAM_SUBSAMP); + if (xform[0].op == TJTransform.OP_TRANSPOSE || + xform[0].op == TJTransform.OP_TRANSVERSE || + xform[0].op == TJTransform.OP_ROT90 || + xform[0].op == TJTransform.OP_ROT270) { + width = tjt.get(TJ.PARAM_JPEGHEIGHT); + height = tjt.get(TJ.PARAM_JPEGWIDTH); + } else { + width = tjt.get(TJ.PARAM_JPEGWIDTH); + height = tjt.get(TJ.PARAM_JPEGHEIGHT); + } + if ((xform[0].options & TJTransform.OPT_GRAY) != 0) + subsamp = TJ.SAMP_GRAY; + + if (progressive >= 0) + tjt.set(TJ.PARAM_PROGRESSIVE, progressive); + if (arithmetic >= 0) + tjt.set(TJ.PARAM_ARITHMETIC, arithmetic); + + if (isCropped(xform[0])) { + int xAdjust, yAdjust; + + if (subsamp == TJ.SAMP_UNKNOWN) + throw new Exception("Could not determine subsampling level of input image"); + if (xform[0].op == TJTransform.OP_TRANSPOSE || + xform[0].op == TJTransform.OP_TRANSVERSE || + xform[0].op == TJTransform.OP_ROT90 || + xform[0].op == TJTransform.OP_ROT270) { + xAdjust = xform[0].x % TJ.getMCUHeight(subsamp); + yAdjust = xform[0].y % TJ.getMCUWidth(subsamp); + } else { + xAdjust = xform[0].x % TJ.getMCUWidth(subsamp); + yAdjust = xform[0].y % TJ.getMCUHeight(subsamp); + } + xform[0].x -= xAdjust; + xform[0].width += xAdjust; + xform[0].y -= yAdjust; + xform[0].height += yAdjust; + } + + dstBuf = new byte[1][TJ.bufSize(width, height, subsamp)]; + + tjt.transform(dstBuf, xform); + + File outFile = new File(argv[i]); + fos = new FileOutputStream(outFile); + fos.write(dstBuf[0], 0, tjt.getTransformedSizes()[0]); + } catch (Exception e) { + e.printStackTrace(); + exitStatus = -1; + } + + try { + if (fis != null) fis.close(); + if (tjt != null) tjt.close(); + if (fos != null) fos.close(); + } catch (Exception e) {} + System.exit(exitStatus); + } +}; diff --git a/release/installer.nsi.in b/release/installer.nsi.in index 52cfb8f5..885a2561 100644 --- a/release/installer.nsi.in +++ b/release/installer.nsi.in @@ -95,8 +95,12 @@ Section "@CMAKE_PROJECT_NAME@ SDK for @INST_PLATFORM@ (required)" File "@CMAKE_CURRENT_SOURCE_DIR@\doc\structure.txt" File "@CMAKE_CURRENT_SOURCE_DIR@\doc\usage.txt" File "@CMAKE_CURRENT_SOURCE_DIR@\doc\wizard.txt" - File "@CMAKE_CURRENT_SOURCE_DIR@\src\tjexample.c" - File "@CMAKE_CURRENT_SOURCE_DIR@\java\TJExample.java" + File "@CMAKE_CURRENT_SOURCE_DIR@\src\tjcomp.c" + File "@CMAKE_CURRENT_SOURCE_DIR@\src\tjdecomp.c" + File "@CMAKE_CURRENT_SOURCE_DIR@\src\tjtran.c" + File "@CMAKE_CURRENT_SOURCE_DIR@\java\TJComp.java" + File "@CMAKE_CURRENT_SOURCE_DIR@\java\TJDecomp.java" + File "@CMAKE_CURRENT_SOURCE_DIR@\java\TJTran.java" !ifdef GCC SetOutPath $INSTDIR\man\man1 File "@CMAKE_CURRENT_SOURCE_DIR@\doc\cjpeg.1" @@ -173,8 +177,12 @@ Section "Uninstall" Delete $INSTDIR\doc\structure.txt Delete $INSTDIR\doc\usage.txt Delete $INSTDIR\doc\wizard.txt - Delete $INSTDIR\doc\tjexample.c - Delete $INSTDIR\doc\TJExample.java + Delete $INSTDIR\doc\tjcomp.c + Delete $INSTDIR\doc\tjdecomp.c + Delete $INSTDIR\doc\tjtran.c + Delete $INSTDIR\doc\TJComp.java + Delete $INSTDIR\doc\TJDecomp.java + Delete $INSTDIR\doc\TJTran.java !ifdef GCC Delete $INSTDIR\man\man1\cjpeg.1 Delete $INSTDIR\man\man1\djpeg.1 diff --git a/src/example.c b/src/example.c index 43d5f521..94056114 100644 --- a/src/example.c +++ b/src/example.c @@ -19,9 +19,10 @@ */ /* First-time users of libjpeg-turbo might be better served by looking at - * tjexample.c, which uses the more straightforward TurboJPEG API. Note that - * this example, like cjpeg and djpeg, interleaves disk I/O with JPEG - * compression/decompression, so it is not suitable for benchmarking purposes. + * tjcomp.c, tjdecomp.c, and tjtran.c, which use the more straightforward + * TurboJPEG API and are more full-featured. Note that this example, like + * cjpeg and djpeg, interleaves disk I/O with JPEG compression/decompression, + * so it is not suitable for benchmarking purposes. */ #ifdef _MSC_VER diff --git a/src/md5/CMakeLists.txt b/src/md5/CMakeLists.txt index 526ef08b..2a460c4c 100644 --- a/src/md5/CMakeLists.txt +++ b/src/md5/CMakeLists.txt @@ -1 +1,3 @@ add_executable(md5cmp md5cmp.c md5.c md5hl.c) + +add_executable(md5sum md5sum.c md5.c md5hl.c) diff --git a/src/md5/md5sum.c b/src/md5/md5sum.c new file mode 100644 index 00000000..ce9bdc02 --- /dev/null +++ b/src/md5/md5sum.c @@ -0,0 +1,60 @@ +/* + * Copyright (C)2013, 2016, 2024 D. R. Commander. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the libjpeg-turbo Project nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#include +#include +#include +#include "./md5.h" + +int main(int argc, char *argv[]) +{ + char *md5sum = NULL, buf[65]; + int i; + + if (argc < 2) { + fprintf(stderr, "USAGE: %s \n", argv[0]); + return -1; + } + + for (i = 1; i < argc; i++) { + md5sum = MD5File(argv[i], buf); + if (!md5sum) { + fprintf(stderr, "Could not obtain MD5 sum for %s:\n%s\n", argv[i], + strerror(errno)); + return -1; + } + + printf("%s %s\n", md5sum, argv[i]); + } + + return 0; +} diff --git a/src/tjcomp.c b/src/tjcomp.c new file mode 100644 index 00000000..8fa3e2bf --- /dev/null +++ b/src/tjcomp.c @@ -0,0 +1,321 @@ +/* + * Copyright (C)2011-2012, 2014-2015, 2017, 2019, 2021-2024 + * D. R. Commander. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the libjpeg-turbo Project nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This program demonstrates how to use the TurboJPEG C API to approximate the + * functionality of the IJG's cjpeg program. cjpeg features that are not + * covered: + * + * - GIF and Targa input file formats [legacy feature] + * - Separate quality settings for luminance and chrominance + * - The floating-point DCT method [legacy feature] + * - Embedding an ICC color management profile + * - Input image smoothing + * - Progress reporting + * - Debug output + * - Forcing baseline-compatible quantization tables + * - Specifying arbitrary quantization tables + * - Specifying arbitrary sampling factors + * - Scan scripts + */ + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#include +#include +#include +#include +#include +#if !defined(_MSC_VER) || _MSC_VER > 1600 +#include +#endif +#include + + +#ifdef _WIN32 +#define strncasecmp strnicmp +#endif + +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#define MATCH_ARG(arg, string, minChars) \ + !strncasecmp(arg, string, max(strlen(arg), minChars)) + +#define THROW(action, message) { \ + printf("ERROR in line %d while %s:\n%s\n", __LINE__, action, message); \ + retval = -1; goto bailout; \ +} + +#define THROW_TJ(action) THROW(action, tj3GetErrorStr(tjInstance)) + +#define THROW_UNIX(action) THROW(action, strerror(errno)) + +#define DEFAULT_SUBSAMP TJSAMP_420 +#define DEFAULT_QUALITY 75 + + +static const char *subsampName[TJ_NUMSAMP] = { + "444", "422", "420", "GRAY", "440", "411", "441" +}; + + +static void usage(char *programName) +{ + printf("\nUSAGE: %s [options] \n\n", programName); + + printf("The input image can be in Windows BMP or PBMPLUS (PPM/PGM) format.\n\n"); + + printf("GENERAL OPTIONS (CAN BE ABBREVIATED)\n"); + printf("------------------------------------\n"); + printf("-lossless PSV[,Pt]\n"); + printf(" Create a lossless JPEG image (implies -subsamp 444) using predictor\n"); + printf(" selection value PSV (1-7) and optional point transform Pt (0 through\n"); + printf(" {data precision} - 1)\n"); + printf("-maxmemory N\n"); + printf(" Memory limit (in megabytes) for intermediate buffers used with progressive\n"); + printf(" JPEG compression, lossless JPEG compression, and Huffman table optimization\n"); + printf(" [default = no limit]\n"); + printf("-precision N\n"); + printf(" Create a JPEG image with N-bit data precision [N = 2..16; default = 8; if N\n"); + printf(" is not 8 or 12, then -lossless must also be specified] (-precision 12\n"); + printf(" implies -optimize unless -arithmetic is also specified)\n"); + printf("-restart N\n"); + printf(" Add a restart marker every N MCU rows [default = 0 (no restart markers)].\n"); + printf(" Append 'B' to specify the restart marker interval in MCUs (lossy only.)\n\n"); + + printf("LOSSY JPEG OPTIONS (CAN BE ABBREVIATED)\n"); + printf("---------------------------------------\n"); + printf("-arithmetic\n"); + printf(" Use arithmetic entropy coding instead of Huffman entropy coding (can be\n"); + printf(" combined with -progressive)\n"); + printf("-dct fast\n"); + printf(" Use less accurate DCT algorithm [legacy feature]\n"); + printf("-dct int\n"); + printf(" Use more accurate DCT algorithm [default]\n"); + printf("-grayscale\n"); + printf(" Create a grayscale JPEG image from a full-color input image\n"); + printf("-optimize\n"); + printf(" Use Huffman table optimization\n"); + printf("-progressive\n"); + printf(" Create a progressive JPEG image instead of a single-scan JPEG image (can be\n"); + printf(" combined with -arithmetic; implies -optimize unless -arithmetic is also\n"); + printf(" specified)\n"); + printf("-quality {1..100}\n"); + printf(" Create a JPEG image with the specified quality level [default = %d]\n", + DEFAULT_QUALITY); + printf("-rgb\n"); + printf(" Create a JPEG image that uses the RGB colorspace instead of the YCbCr\n"); + printf(" colorspace\n"); + printf("-subsamp {444|422|440|420|411|441}\n"); + printf(" Create a JPEG image that uses the specified chrominance subsampling level\n"); + printf(" [default = %s]\n\n", subsampName[DEFAULT_SUBSAMP]); + + exit(1); +} + + +int main(int argc, char **argv) +{ + int i, retval = 0; + int arithmetic = -1, colorspace = -1, fastDCT = -1, losslessPSV = -1, + losslessPt = -1, maxMemory = -1, optimize = -1, pixelFormat = TJPF_UNKNOWN, + precision = -1, progressive = -1, quality = DEFAULT_QUALITY, + restartIntervalBlocks = -1, restartIntervalRows = -1, + subsamp = DEFAULT_SUBSAMP; + tjhandle tjInstance = NULL; + void *srcBuf = NULL; + int width, height; + unsigned char *jpegBuf = NULL; + size_t jpegSize = 0; + FILE *jpegFile = NULL; + + for (i = 1; i < argc; i++) { + if (MATCH_ARG(argv[i], "-arithmetic", 2)) + arithmetic = 1; + else if (MATCH_ARG(argv[i], "-dct", 2) && i < argc - 1) { + i++; + if (MATCH_ARG(argv[i], "fast", 1)) + fastDCT = 1; + else if (!MATCH_ARG(argv[i], "int", 1)) + usage(argv[0]); + } else if (MATCH_ARG(argv[i], "-grayscale", 2) || + MATCH_ARG(argv[i], "-greyscale", 2)) + colorspace = TJCS_GRAY; + else if (MATCH_ARG(argv[i], "-lossless", 2) && i < argc - 1) { + if (sscanf(argv[++i], "%d,%d", &losslessPSV, &losslessPt) < 1 || + losslessPSV < 1 || losslessPSV > 7) + usage(argv[0]); + } else if (MATCH_ARG(argv[i], "-maxmemory", 2) && i < argc - 1) { + int tempi = atoi(argv[++i]); + + if (tempi < 0) usage(argv[0]); + maxMemory = tempi; + } else if (MATCH_ARG(argv[i], "-optimize", 2) || + MATCH_ARG(argv[i], "-optimise", 2)) + optimize = 1; + else if (MATCH_ARG(argv[i], "-precision", 4) && i < argc - 1) { + int tempi = atoi(argv[++i]); + + if (tempi < 2 || tempi > 16) + usage(argv[0]); + precision = tempi; + } else if (MATCH_ARG(argv[i], "-progressive", 2)) + progressive = 1; + else if (MATCH_ARG(argv[i], "-quality", 2) && i < argc - 1) { + int tempi = atoi(argv[++i]); + + if (tempi < 1 || tempi > 100) + usage(argv[0]); + quality = tempi; + } else if (MATCH_ARG(argv[i], "-rgb", 3)) + colorspace = TJCS_RGB; + else if (MATCH_ARG(argv[i], "-restart", 2) && i < argc - 1) { + int tempi = -1, nscan; char tempc = 0; + + if ((nscan = sscanf(argv[++i], "%d%c", &tempi, &tempc)) < 1 || + tempi < 0 || tempi > 65535 || + (nscan == 2 && tempc != 'B' && tempc != 'b')) + usage(argv[0]); + + if (tempc == 'B' || tempc == 'b') + restartIntervalBlocks = tempi; + else + restartIntervalRows = tempi; + } else if (MATCH_ARG(argv[i], "-subsamp", 2) && i < argc - 1) { + i++; + if (MATCH_ARG(argv[i], "444", 3)) + subsamp = TJSAMP_444; + else if (MATCH_ARG(argv[i], "422", 3)) + subsamp = TJSAMP_422; + else if (MATCH_ARG(argv[i], "440", 3)) + subsamp = TJSAMP_440; + else if (MATCH_ARG(argv[i], "420", 3)) + subsamp = TJSAMP_420; + else if (MATCH_ARG(argv[i], "411", 3)) + subsamp = TJSAMP_411; + else if (MATCH_ARG(argv[i], "441", 3)) + subsamp = TJSAMP_441; + else + usage(argv[0]); + } else break; + } + + if (i != argc - 2) + usage(argv[0]); + if (losslessPSV == -1 && precision != -1 && precision != 8 && + precision != 12) + usage(argv[0]); + + if ((tjInstance = tj3Init(TJINIT_COMPRESS)) == NULL) + THROW_TJ("creating TurboJPEG instance"); + + if (tj3Set(tjInstance, TJPARAM_QUALITY, quality) < 0) + THROW_TJ("setting TJPARAM_QUALITY"); + if (tj3Set(tjInstance, TJPARAM_SUBSAMP, subsamp) < 0) + THROW_TJ("setting TJPARAM_SUBSAMP"); + if (precision >= 0 && tj3Set(tjInstance, TJPARAM_PRECISION, precision) < 0) + THROW_TJ("setting TJPARAM_PRECISION"); + if (fastDCT >= 0 && tj3Set(tjInstance, TJPARAM_FASTDCT, fastDCT) < 0) + THROW_TJ("setting TJPARAM_FASTDCT"); + if (optimize >= 0 && tj3Set(tjInstance, TJPARAM_OPTIMIZE, optimize) < 0) + THROW_TJ("setting TJPARAM_OPTIMIZE"); + if (progressive >= 0 && + tj3Set(tjInstance, TJPARAM_PROGRESSIVE, progressive) < 0) + THROW_TJ("setting TJPARAM_PROGRESSIVE"); + if (arithmetic >= 0 && + tj3Set(tjInstance, TJPARAM_ARITHMETIC, arithmetic) < 0) + THROW_TJ("setting TJPARAM_ARITHMETIC"); + if (losslessPSV >= 1 && losslessPSV <= 7) { + if (tj3Set(tjInstance, TJPARAM_LOSSLESS, 1) < 0) + THROW_TJ("setting TJPARAM_LOSSLESS"); + if (tj3Set(tjInstance, TJPARAM_LOSSLESSPSV, losslessPSV) < 0) + THROW_TJ("setting TJPARAM_LOSSLESSPSV"); + if (losslessPt >= 0 && + tj3Set(tjInstance, TJPARAM_LOSSLESSPT, losslessPt) < 0) + THROW_TJ("setting TJPARAM_LOSSLESSPT"); + } + if (restartIntervalBlocks >= 0 && + tj3Set(tjInstance, TJPARAM_RESTARTBLOCKS, restartIntervalBlocks) < 0) + THROW_TJ("setting TJPARAM_RESTARTBLOCKS"); + if (restartIntervalRows >= 0 && + tj3Set(tjInstance, TJPARAM_RESTARTROWS, restartIntervalRows) < 0) + THROW_TJ("setting TJPARAM_RESTARTROWS"); + if (maxMemory >= 0 && tj3Set(tjInstance, TJPARAM_MAXMEMORY, maxMemory) < 0) + THROW_TJ("setting TJPARAM_MAXMEMORY"); + + if (precision <= 8) { + if ((srcBuf = tj3LoadImage8(tjInstance, argv[i], &width, 1, &height, + &pixelFormat)) == NULL) + THROW_TJ("loading input image"); + } else if (precision <= 12) { + if ((srcBuf = tj3LoadImage12(tjInstance, argv[i], &width, 1, &height, + &pixelFormat)) == NULL) + THROW_TJ("loading input image"); + } else { + if ((srcBuf = tj3LoadImage16(tjInstance, argv[i], &width, 1, &height, + &pixelFormat)) == NULL) + THROW_TJ("loading input image"); + } + + if (pixelFormat == TJPF_GRAY && colorspace < 0) + colorspace = TJCS_GRAY; + if (colorspace >= 0 && + tj3Set(tjInstance, TJPARAM_COLORSPACE, colorspace) < 0) + THROW_TJ("setting TJPARAM_COLORSPACE"); + + if (precision <= 8) { + if (tj3Compress8(tjInstance, srcBuf, width, 0, height, pixelFormat, + &jpegBuf, &jpegSize) < 0) + THROW_TJ("compressing image"); + } else if (precision <= 12) { + if (tj3Compress12(tjInstance, srcBuf, width, 0, height, pixelFormat, + &jpegBuf, &jpegSize) < 0) + THROW_TJ("compressing image"); + } else { + if (tj3Compress16(tjInstance, srcBuf, width, 0, height, pixelFormat, + &jpegBuf, &jpegSize) < 0) + THROW_TJ("compressing image"); + } + + if ((jpegFile = fopen(argv[++i], "wb")) == NULL) + THROW_UNIX("opening output file"); + if (fwrite(jpegBuf, jpegSize, 1, jpegFile) < 1) + THROW_UNIX("writing output file"); + +bailout: + tj3Destroy(tjInstance); + tj3Free(srcBuf); + tj3Free(jpegBuf); + if (jpegFile) fclose(jpegFile); + return retval; +} diff --git a/src/tjdecomp.c b/src/tjdecomp.c new file mode 100644 index 00000000..dcc9e30b --- /dev/null +++ b/src/tjdecomp.c @@ -0,0 +1,351 @@ +/* + * Copyright (C)2011-2012, 2014-2015, 2017, 2019, 2021-2024 + * D. R. Commander. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the libjpeg-turbo Project nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This program demonstrates how to use the TurboJPEG C API to approximate the + * functionality of the IJG's djpeg program. djpeg features that are not + * covered: + * + * - OS/2 BMP, GIF, and Targa output file formats [legacy feature] + * - Color quantization and dithering [legacy feature] + * - The floating-point IDCT method [legacy feature] + * - Extracting an ICC color management profile + * - Progress reporting + * - Skipping rows (i.e. exclusive rather than inclusive partial decompression) + * - Debug output + */ + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#include +#include +#include +#include +#include +#if !defined(_MSC_VER) || _MSC_VER > 1600 +#include +#endif +#include + + +#ifdef _WIN32 +#define strncasecmp strnicmp +#endif + +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#define MATCH_ARG(arg, string, minChars) \ + !strncasecmp(arg, string, max(strlen(arg), minChars)) + +#define IS_CROPPED(cr) (cr.x != 0 || cr.y != 0 || cr.w != 0 || cr.h != 0) + +#define THROW(action, message) { \ + printf("ERROR in line %d while %s:\n%s\n", __LINE__, action, message); \ + retval = -1; goto bailout; \ +} + +#define THROW_TJ(action) { \ + int errorCode = tj3GetErrorCode(tjInstance); \ + printf("%s in line %d while %s:\n%s\n", \ + errorCode == TJERR_WARNING ? "WARNING" : "ERROR", __LINE__, action, \ + tj3GetErrorStr(tjInstance)); \ + if (errorCode == TJERR_FATAL || stopOnWarning == 1) { \ + retval = -1; goto bailout; \ + } \ +} + +#define THROW_UNIX(action) THROW(action, strerror(errno)) + +#define DEFAULT_SUBSAMP TJSAMP_444 +#define DEFAULT_QUALITY 95 + + +static tjscalingfactor *scalingFactors = NULL; +static int numScalingFactors = 0; + + +static void usage(char *programName) +{ + int i; + + printf("\nUSAGE: %s [options] \n\n", programName); + + printf("The output image will be in Windows BMP or PBMPLUS (PPM/PGM) format, depending\n"); + printf("on the file extension.\n\n"); + + printf("GENERAL OPTIONS (CAN BE ABBREVBIATED)\n"); + printf("-------------------------------------\n"); + printf("-strict\n"); + printf(" Treat all warnings as fatal; abort immediately if incomplete or corrupt\n"); + printf(" data is encountered in the JPEG image, rather than trying to salvage the\n"); + printf(" rest of the image\n\n"); + + printf("LOSSY JPEG OPTIONS (CAN BE ABBREVIATED)\n"); + printf("---------------------------------------\n"); + printf("-crop WxH+X+Y\n"); + printf(" Decompress only the specified region of the JPEG image. (W, H, X, and Y\n"); + printf(" are the width, height, left boundary, and upper boundary of the region, all\n"); + printf(" specified relative to the scaled image dimensions.) If necessary, X will\n"); + printf(" be shifted left to the nearest iMCU boundary, and W will be increased\n"); + printf(" accordingly.\n"); + printf("-dct fast\n"); + printf(" Use less accurate IDCT algorithm [legacy feature]\n"); + printf("-dct int\n"); + printf(" Use more accurate IDCT algorithm [default]\n"); + printf("-grayscale\n"); + printf(" Decompress a full-color JPEG image into a grayscale output image\n"); + printf("-maxmemory N\n"); + printf(" Memory limit (in megabytes) for intermediate buffers used with progressive\n"); + printf(" JPEG decompression [default = no limit]\n"); + printf("-maxscans N\n"); + printf(" Refuse to decompress progressive JPEG images that have more than N scans\n"); + printf("-nosmooth\n"); + printf(" Use the fastest chrominance upsampling algorithm available\n"); + printf("-rgb\n"); + printf(" Decompress a grayscale JPEG image into a full-color output image\n"); + printf("-scale M/N\n"); + printf(" Scale the width/height of the JPEG image by a factor of M/N when\n"); + printf(" decompressing it (M/N = "); + for (i = 0; i < numScalingFactors; i++) { + printf("%d/%d", scalingFactors[i].num, scalingFactors[i].denom); + if (numScalingFactors == 2 && i != numScalingFactors - 1) + printf(" or "); + else if (numScalingFactors > 2) { + if (i != numScalingFactors - 1) + printf(", "); + if (i == numScalingFactors - 2) + printf("or "); + } + if (i % 8 == 0 && i != 0) printf("\n "); + } + printf(")\n\n"); + + exit(1); +} + + +int main(int argc, char **argv) +{ + int i, retval = 0; + int colorspace, fastDCT = -1, fastUpsample = -1, maxMemory = -1, + maxScans = -1, pixelFormat = TJPF_UNKNOWN, precision, stopOnWarning = -1, + subsamp; + tjregion croppingRegion = TJUNCROPPED; + tjscalingfactor scalingFactor = TJUNSCALED; + tjhandle tjInstance = NULL; + FILE *jpegFile = NULL; + long size = 0; + size_t jpegSize, sampleSize; + int width, height; + unsigned char *jpegBuf = NULL; + void *dstBuf = NULL; + + if ((scalingFactors = tj3GetScalingFactors(&numScalingFactors)) == NULL) + THROW_TJ("getting scaling factors"); + + for (i = 1; i < argc; i++) { + + if (MATCH_ARG(argv[i], "-crop", 2) && i < argc - 1) { + char tempc = -1; + + if (sscanf(argv[++i], "%d%c%d+%d+%d", &croppingRegion.w, &tempc, + &croppingRegion.h, &croppingRegion.x, + &croppingRegion.y) != 5 || croppingRegion.w < 1 || + (tempc != 'x' && tempc != 'X') || croppingRegion.h < 1 || + croppingRegion.x < 0 || croppingRegion.y < 0) + usage(argv[0]); + } else if (MATCH_ARG(argv[i], "-dct", 2) && i < argc - 1) { + i++; + if (MATCH_ARG(argv[i], "fast", 1)) + fastDCT = 1; + else if (!MATCH_ARG(argv[i], "int", 1)) + usage(argv[0]); + } else if (MATCH_ARG(argv[i], "-grayscale", 2) || + MATCH_ARG(argv[i], "-greyscale", 2)) + pixelFormat = TJPF_GRAY; + else if (MATCH_ARG(argv[i], "-maxscans", 5) && i < argc - 1) { + int tempi = atoi(argv[++i]); + + if (tempi < 0) usage(argv[0]); + maxScans = tempi; + } else if (MATCH_ARG(argv[i], "-maxmemory", 2) && i < argc - 1) { + int tempi = atoi(argv[++i]); + + if (tempi < 0) usage(argv[0]); + maxMemory = tempi; + } else if (MATCH_ARG(argv[i], "-nosmooth", 2)) + fastUpsample = 1; + else if (MATCH_ARG(argv[i], "-rgb", 2)) + pixelFormat = TJPF_RGB; + else if (MATCH_ARG(argv[i], "-strict", 3)) + stopOnWarning = 1; + else if (MATCH_ARG(argv[i], "-scale", 2) && i < argc - 1) { + int match = 0, temp_num = 0, temp_denom = 0, j; + + if (sscanf(argv[++i], "%d/%d", &temp_num, &temp_denom) < 2) + usage(argv[0]); + if (temp_num < 1 || temp_denom < 1) + usage(argv[0]); + for (j = 0; j < numScalingFactors; j++) { + if ((double)temp_num / (double)temp_denom == + (double)scalingFactors[j].num / (double)scalingFactors[j].denom) { + scalingFactor = scalingFactors[j]; + match = 1; + break; + } + } + if (match != 1) + usage(argv[0]); + } else break; + } + + if (i != argc - 2) + usage(argv[0]); + + if ((tjInstance = tj3Init(TJINIT_DECOMPRESS)) == NULL) + THROW_TJ("creating TurboJPEG instance"); + + if (stopOnWarning >= 0 && + tj3Set(tjInstance, TJPARAM_STOPONWARNING, stopOnWarning) < 0) + THROW_TJ("setting TJPARAM_STOPONWARNING"); + if (fastUpsample >= 0 && + tj3Set(tjInstance, TJPARAM_FASTUPSAMPLE, fastUpsample) < 0) + THROW_TJ("setting TJPARAM_FASTUPSAMPLE"); + if (fastDCT >= 0 && tj3Set(tjInstance, TJPARAM_FASTDCT, fastDCT) < 0) + THROW_TJ("setting TJPARAM_FASTDCT"); + if (maxScans >= 0 && tj3Set(tjInstance, TJPARAM_SCANLIMIT, maxScans) < 0) + THROW_TJ("setting TJPARAM_SCANLIMIT"); + if (maxMemory >= 0 && tj3Set(tjInstance, TJPARAM_MAXMEMORY, maxMemory) < 0) + THROW_TJ("setting TJPARAM_MAXMEMORY"); + + if ((jpegFile = fopen(argv[i++], "rb")) == NULL) + THROW_UNIX("opening input file"); + if (fseek(jpegFile, 0, SEEK_END) < 0 || ((size = ftell(jpegFile)) < 0) || + fseek(jpegFile, 0, SEEK_SET) < 0) + THROW_UNIX("determining input file size"); + if (size == 0) + THROW("determining input file size", "Input file contains no data"); + jpegSize = size; + if ((jpegBuf = (unsigned char *)malloc(jpegSize)) == NULL) + THROW_UNIX("allocating JPEG buffer"); + if (fread(jpegBuf, jpegSize, 1, jpegFile) < 1) + THROW_UNIX("reading input file"); + fclose(jpegFile); jpegFile = NULL; + + if (tj3DecompressHeader(tjInstance, jpegBuf, jpegSize) < 0) + THROW_TJ("reading JPEG header"); + subsamp = tj3Get(tjInstance, TJPARAM_SUBSAMP); + width = tj3Get(tjInstance, TJPARAM_JPEGWIDTH); + height = tj3Get(tjInstance, TJPARAM_JPEGHEIGHT); + precision = tj3Get(tjInstance, TJPARAM_PRECISION); + sampleSize = (precision <= 8 ? sizeof(unsigned char) : sizeof(short)); + colorspace = tj3Get(tjInstance, TJPARAM_COLORSPACE); + + if (pixelFormat == TJPF_UNKNOWN) { + if (colorspace == TJCS_GRAY) + pixelFormat = TJPF_GRAY; + else if (colorspace == TJCS_CMYK || colorspace == TJCS_YCCK) + pixelFormat = TJPF_CMYK; + else + pixelFormat = TJPF_RGB; + } + + if (!tj3Get(tjInstance, TJPARAM_LOSSLESS)) { + if (tj3SetScalingFactor(tjInstance, scalingFactor) < 0) + THROW_TJ("setting scaling factor"); + width = TJSCALED(width, scalingFactor); + height = TJSCALED(height, scalingFactor); + + if (IS_CROPPED(croppingRegion)) { + int adjustment; + + if (subsamp == TJSAMP_UNKNOWN) + THROW("adjusting cropping region", + "Could not determine subsampling level of JPEG image"); + adjustment = + croppingRegion.x % TJSCALED(tjMCUWidth[subsamp], scalingFactor); + croppingRegion.x -= adjustment; + croppingRegion.w += adjustment; + if (tj3SetCroppingRegion(tjInstance, croppingRegion) < 0) + THROW_TJ("setting cropping region"); + width = croppingRegion.w; + height = croppingRegion.h; + } + } + +#if ULLONG_MAX > SIZE_MAX + if ((unsigned long long)width * height * tjPixelSize[pixelFormat] * + sampleSize > (unsigned long long)((size_t)-1)) + THROW("allocating uncompressed image buffer", "Image is too large"); +#endif + if ((dstBuf = + (unsigned char *)malloc(sizeof(unsigned char) * width * height * + tjPixelSize[pixelFormat] * sampleSize)) == NULL) + THROW_UNIX("allocating uncompressed image buffer"); + + if (precision <= 8) { + if (tj3Decompress8(tjInstance, jpegBuf, jpegSize, dstBuf, 0, + pixelFormat) < 0) + THROW_TJ("decompressing JPEG image"); + } else if (precision <= 12) { + if (tj3Decompress12(tjInstance, jpegBuf, jpegSize, dstBuf, 0, + pixelFormat) < 0) + THROW_TJ("decompressing JPEG image"); + } else { + if (tj3Decompress16(tjInstance, jpegBuf, jpegSize, dstBuf, 0, + pixelFormat) < 0) + THROW_TJ("decompressing JPEG image"); + } + tj3Free(jpegBuf); jpegBuf = NULL; + + if (precision <= 8) { + if (tj3SaveImage8(tjInstance, argv[i], dstBuf, width, 0, height, + pixelFormat) < 0) + THROW_TJ("saving output image"); + } else if (precision <= 12) { + if (tj3SaveImage12(tjInstance, argv[i], dstBuf, width, 0, height, + pixelFormat) < 0) + THROW_TJ("saving output image"); + } else { + if (tj3SaveImage16(tjInstance, argv[i], dstBuf, width, 0, height, + pixelFormat) < 0) + THROW_TJ("saving output image"); + } + +bailout: + tj3Destroy(tjInstance); + if (jpegFile) fclose(jpegFile); + tj3Free(jpegBuf); + free(dstBuf); + return retval; +} diff --git a/src/tjexample.c b/src/tjexample.c deleted file mode 100644 index ca61f553..00000000 --- a/src/tjexample.c +++ /dev/null @@ -1,413 +0,0 @@ -/* - * Copyright (C)2011-2012, 2014-2015, 2017, 2019, 2021-2024 - * D. R. Commander. All Rights Reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - Neither the name of the libjpeg-turbo Project nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -/* - * This program demonstrates how to compress, decompress, and transform JPEG - * images using the TurboJPEG C API - */ - -#ifdef _MSC_VER -#define _CRT_SECURE_NO_DEPRECATE -#endif - -#include -#include -#include -#include -#include -#if !defined(_MSC_VER) || _MSC_VER > 1600 -#include -#endif -#include - - -#ifdef _WIN32 -#define strcasecmp stricmp -#define strncasecmp strnicmp -#endif - -#define THROW(action, message) { \ - printf("ERROR in line %d while %s:\n%s\n", __LINE__, action, message); \ - retval = -1; goto bailout; \ -} - -#define THROW_TJ(action) THROW(action, tj3GetErrorStr(tjInstance)) - -#define THROW_UNIX(action) THROW(action, strerror(errno)) - -#define DEFAULT_SUBSAMP TJSAMP_444 -#define DEFAULT_QUALITY 95 - - -static const char *subsampName[TJ_NUMSAMP] = { - "4:4:4", "4:2:2", "4:2:0", "Grayscale", "4:4:0", "4:1:1", "4:4:1" -}; - -static const char *colorspaceName[TJ_NUMCS] = { - "RGB", "YCbCr", "GRAY", "CMYK", "YCCK" -}; - -static tjscalingfactor *scalingFactors = NULL; -static int numScalingFactors = 0; - - -/* DCT filter example. This produces a negative of the image. */ - -static int customFilter(short *coeffs, tjregion arrayRegion, - tjregion planeRegion, int componentIndex, - int transformIndex, tjtransform *transform) -{ - int i; - - for (i = 0; i < arrayRegion.w * arrayRegion.h; i++) - coeffs[i] = -coeffs[i]; - - return 0; -} - - -static void usage(char *programName) -{ - int i; - - printf("\nUSAGE: %s [options]\n\n", - programName); - - printf("Input and output images can be in Windows BMP or PBMPLUS (PPM/PGM) format. If\n"); - printf("either filename ends in a .jpg extension, then the TurboJPEG API will be used\n"); - printf("to compress or decompress the image.\n\n"); - - printf("Compression Options (used if the output image is a JPEG image)\n"); - printf("--------------------------------------------------------------\n\n"); - - printf("-subsamp <444|422|420|gray> = Apply this level of chrominance subsampling when\n"); - printf(" compressing the output image. The default is to use the same level of\n"); - printf(" subsampling as in the input image, if the input image is also a JPEG\n"); - printf(" image, or to use grayscale if the input image is a grayscale non-JPEG\n"); - printf(" image, or to use %s subsampling otherwise.\n\n", - subsampName[DEFAULT_SUBSAMP]); - - printf("-q <1-100> = Compress the output image with this JPEG quality level\n"); - printf(" (default = %d).\n\n", DEFAULT_QUALITY); - - printf("Decompression Options (used if the input image is a JPEG image)\n"); - printf("---------------------------------------------------------------\n\n"); - - printf("-scale M/N = Scale the input image by a factor of M/N when decompressing it.\n"); - printf("(M/N = "); - for (i = 0; i < numScalingFactors; i++) { - printf("%d/%d", scalingFactors[i].num, scalingFactors[i].denom); - if (numScalingFactors == 2 && i != numScalingFactors - 1) - printf(" or "); - else if (numScalingFactors > 2) { - if (i != numScalingFactors - 1) - printf(", "); - if (i == numScalingFactors - 2) - printf("or "); - } - } - printf(")\n\n"); - - printf("-hflip, -vflip, -transpose, -transverse, -rot90, -rot180, -rot270 =\n"); - printf(" Perform one of these lossless transform operations on the input image\n"); - printf(" prior to decompressing it (these options are mutually exclusive.)\n\n"); - - printf("-grayscale = Perform lossless grayscale conversion on the input image prior\n"); - printf(" to decompressing it (can be combined with the other transform operations\n"); - printf(" above.)\n\n"); - - printf("-crop WxH+X+Y = Perform lossless cropping on the input image prior to\n"); - printf(" decompressing it. X and Y specify the upper left corner of the cropping\n"); - printf(" region, and W and H specify the width and height of the cropping region.\n"); - printf(" X and Y must be evenly divible by the iMCU size (8x8 if the input image\n"); - printf(" was compressed using no subsampling or grayscale, 16x8 if it was\n"); - printf(" compressed using 4:2:2 subsampling, or 16x16 if it was compressed using\n"); - printf(" 4:2:0 subsampling.)\n\n"); - - printf("General Options\n"); - printf("---------------\n\n"); - - printf("-fastupsample = Use the fastest chrominance upsampling algorithm available\n\n"); - - printf("-fastdct = Use the fastest DCT/IDCT algorithm available\n\n"); - - exit(1); -} - - -int main(int argc, char **argv) -{ - tjscalingfactor scalingFactor = TJUNSCALED; - int outSubsamp = -1, outQual = -1; - tjtransform xform; - int fastUpsample = 0, fastDCT = 0; - int width, height; - char *inFormat, *outFormat; - FILE *jpegFile = NULL; - unsigned char *imgBuf = NULL, *jpegBuf = NULL; - int retval = 0, i, pixelFormat = TJPF_UNKNOWN; - tjhandle tjInstance = NULL; - - if ((scalingFactors = tj3GetScalingFactors(&numScalingFactors)) == NULL) - THROW_TJ("getting scaling factors"); - memset(&xform, 0, sizeof(tjtransform)); - - if (argc < 3) - usage(argv[0]); - - /* Parse arguments. */ - for (i = 3; i < argc; i++) { - if (!strncasecmp(argv[i], "-sc", 3) && i < argc - 1) { - int match = 0, temp1 = 0, temp2 = 0, j; - - if (sscanf(argv[++i], "%d/%d", &temp1, &temp2) < 2) - usage(argv[0]); - for (j = 0; j < numScalingFactors; j++) { - if ((double)temp1 / (double)temp2 == (double)scalingFactors[j].num / - (double)scalingFactors[j].denom) { - scalingFactor = scalingFactors[j]; - match = 1; - break; - } - } - if (match != 1) - usage(argv[0]); - } else if (!strncasecmp(argv[i], "-su", 3) && i < argc - 1) { - i++; - if (!strncasecmp(argv[i], "g", 1)) - outSubsamp = TJSAMP_GRAY; - else if (!strcasecmp(argv[i], "444")) - outSubsamp = TJSAMP_444; - else if (!strcasecmp(argv[i], "422")) - outSubsamp = TJSAMP_422; - else if (!strcasecmp(argv[i], "420")) - outSubsamp = TJSAMP_420; - else - usage(argv[0]); - } else if (!strncasecmp(argv[i], "-q", 2) && i < argc - 1) { - outQual = atoi(argv[++i]); - if (outQual < 1 || outQual > 100) - usage(argv[0]); - } else if (!strncasecmp(argv[i], "-g", 2)) - xform.options |= TJXOPT_GRAY; - else if (!strcasecmp(argv[i], "-hflip")) - xform.op = TJXOP_HFLIP; - else if (!strcasecmp(argv[i], "-vflip")) - xform.op = TJXOP_VFLIP; - else if (!strcasecmp(argv[i], "-transpose")) - xform.op = TJXOP_TRANSPOSE; - else if (!strcasecmp(argv[i], "-transverse")) - xform.op = TJXOP_TRANSVERSE; - else if (!strcasecmp(argv[i], "-rot90")) - xform.op = TJXOP_ROT90; - else if (!strcasecmp(argv[i], "-rot180")) - xform.op = TJXOP_ROT180; - else if (!strcasecmp(argv[i], "-rot270")) - xform.op = TJXOP_ROT270; - else if (!strcasecmp(argv[i], "-custom")) - xform.customFilter = customFilter; - else if (!strncasecmp(argv[i], "-c", 2) && i < argc - 1) { - if (sscanf(argv[++i], "%dx%d+%d+%d", &xform.r.w, &xform.r.h, &xform.r.x, - &xform.r.y) < 4 || - xform.r.x < 0 || xform.r.y < 0 || xform.r.w < 1 || xform.r.h < 1) - usage(argv[0]); - xform.options |= TJXOPT_CROP; - } else if (!strcasecmp(argv[i], "-fastupsample")) { - printf("Using fast upsampling code\n"); - fastUpsample = 1; - } else if (!strcasecmp(argv[i], "-fastdct")) { - printf("Using fastest DCT/IDCT algorithm\n"); - fastDCT = 1; - } else usage(argv[0]); - } - - /* Determine input and output image formats based on file extensions. */ - inFormat = strrchr(argv[1], '.'); - outFormat = strrchr(argv[2], '.'); - if (inFormat == NULL || outFormat == NULL || strlen(inFormat) < 2 || - strlen(outFormat) < 2) - usage(argv[0]); - inFormat = &inFormat[1]; - outFormat = &outFormat[1]; - - if ((tjInstance = tj3Init(TJINIT_TRANSFORM)) == NULL) - THROW_TJ("creating TurboJPEG instance"); - - if (!strcasecmp(inFormat, "jpg")) { - /* Input image is a JPEG image. Decompress and/or transform it. */ - long size; - int inSubsamp, inColorspace; - int doTransform = (xform.op != TJXOP_NONE || xform.options != 0 || - xform.customFilter != NULL); - size_t jpegSize; - - /* Read the JPEG file into memory. */ - if ((jpegFile = fopen(argv[1], "rb")) == NULL) - THROW_UNIX("opening input file"); - if (fseek(jpegFile, 0, SEEK_END) < 0 || ((size = ftell(jpegFile)) < 0) || - fseek(jpegFile, 0, SEEK_SET) < 0) - THROW_UNIX("determining input file size"); - if (size == 0) - THROW("determining input file size", "Input file contains no data"); - jpegSize = size; - if ((jpegBuf = tj3Alloc(jpegSize)) == NULL) - THROW_UNIX("allocating JPEG buffer"); - if (fread(jpegBuf, jpegSize, 1, jpegFile) < 1) - THROW_UNIX("reading input file"); - fclose(jpegFile); jpegFile = NULL; - - if (doTransform) { - /* Transform it. */ - unsigned char *dstBuf = NULL; /* Dynamically allocate the JPEG buffer */ - size_t dstSize = 0; - - xform.options |= TJXOPT_TRIM; - if (tj3Transform(tjInstance, jpegBuf, jpegSize, 1, &dstBuf, &dstSize, - &xform) < 0) { - tj3Free(dstBuf); - THROW_TJ("transforming input image"); - } - tj3Free(jpegBuf); - jpegBuf = dstBuf; - jpegSize = dstSize; - } - if (tj3Set(tjInstance, TJPARAM_FASTUPSAMPLE, fastUpsample) < 0) - THROW_TJ("setting TJPARAM_FASTUPSAMPLE"); - if (tj3Set(tjInstance, TJPARAM_FASTDCT, fastDCT) < 0) - THROW_TJ("setting TJPARAM_FASTDCT"); - - if (tj3DecompressHeader(tjInstance, jpegBuf, jpegSize) < 0) - THROW_TJ("reading JPEG header"); - width = tj3Get(tjInstance, TJPARAM_JPEGWIDTH); - height = tj3Get(tjInstance, TJPARAM_JPEGHEIGHT); - inSubsamp = tj3Get(tjInstance, TJPARAM_SUBSAMP); - inColorspace = tj3Get(tjInstance, TJPARAM_COLORSPACE); - - if (tj3Get(tjInstance, TJPARAM_LOSSLESS)) - scalingFactor = TJUNSCALED; - - printf("%s Image: %d x %d pixels, %s subsampling, %s colorspace\n", - (doTransform ? "Transformed" : "Input"), width, height, - subsampName[inSubsamp], colorspaceName[inColorspace]); - - if (!strcasecmp(outFormat, "jpg") && doTransform && - scalingFactor.num == 1 && scalingFactor.denom == 1 && outSubsamp < 0 && - outQual < 0) { - /* Input image has been transformed, and no re-compression options - have been selected. Write the transformed image to disk and exit. */ - if ((jpegFile = fopen(argv[2], "wb")) == NULL) - THROW_UNIX("opening output file"); - if (fwrite(jpegBuf, jpegSize, 1, jpegFile) < 1) - THROW_UNIX("writing output file"); - goto bailout; - } - - /* Scaling and/or a non-JPEG output image format and/or compression options - have been selected, so we need to decompress the input/transformed - image. */ - if (tj3SetScalingFactor(tjInstance, scalingFactor) < 0) - THROW_TJ("setting scaling factor"); - width = TJSCALED(width, scalingFactor); - height = TJSCALED(height, scalingFactor); - if (outSubsamp < 0) - outSubsamp = inSubsamp; - - pixelFormat = TJPF_BGRX; -#if ULLONG_MAX > SIZE_MAX - if ((unsigned long long)width * height * tjPixelSize[pixelFormat] > - (unsigned long long)((size_t)-1)) - THROW("allocating uncompressed image buffer", "Image is too large"); -#endif - if ((imgBuf = - (unsigned char *)malloc(sizeof(unsigned char) * width * height * - tjPixelSize[pixelFormat])) == NULL) - THROW_UNIX("allocating uncompressed image buffer"); - - if (tj3Decompress8(tjInstance, jpegBuf, jpegSize, imgBuf, 0, - pixelFormat) < 0) - THROW_TJ("decompressing JPEG image"); - tj3Free(jpegBuf); jpegBuf = NULL; - } else { - /* Input image is not a JPEG image. Load it into memory. */ - if ((imgBuf = tj3LoadImage8(tjInstance, argv[1], &width, 1, &height, - &pixelFormat)) == NULL) - THROW_TJ("loading input image"); - if (outSubsamp < 0) { - if (pixelFormat == TJPF_GRAY) - outSubsamp = TJSAMP_GRAY; - else - outSubsamp = TJSAMP_444; - } - printf("Input Image: %d x %d pixels\n", width, height); - } - - printf("Output Image (%s): %d x %d pixels", outFormat, width, height); - - if (!strcasecmp(outFormat, "jpg")) { - /* Output image format is JPEG. Compress the uncompressed image. */ - size_t jpegSize = 0; - - jpegBuf = NULL; /* Dynamically allocate the JPEG buffer */ - - if (outQual < 0) - outQual = DEFAULT_QUALITY; - printf(", %s subsampling, quality = %d\n", subsampName[outSubsamp], - outQual); - - if (tj3Set(tjInstance, TJPARAM_SUBSAMP, outSubsamp) < 0) - THROW_TJ("setting TJPARAM_SUBSAMP"); - if (tj3Set(tjInstance, TJPARAM_QUALITY, outQual) < 0) - THROW_TJ("setting TJPARAM_QUALITY"); - if (tj3Set(tjInstance, TJPARAM_FASTDCT, fastDCT) < 0) - THROW_TJ("setting TJPARAM_FASTDCT"); - if (tj3Compress8(tjInstance, imgBuf, width, 0, height, pixelFormat, - &jpegBuf, &jpegSize) < 0) - THROW_TJ("compressing image"); - - /* Write the JPEG image to disk. */ - if ((jpegFile = fopen(argv[2], "wb")) == NULL) - THROW_UNIX("opening output file"); - if (fwrite(jpegBuf, jpegSize, 1, jpegFile) < 1) - THROW_UNIX("writing output file"); - } else { - /* Output image format is not JPEG. Save the uncompressed image - directly to disk. */ - printf("\n"); - if (tj3SaveImage8(tjInstance, argv[2], imgBuf, width, 0, height, - pixelFormat) < 0) - THROW_TJ("saving output image"); - } - -bailout: - tj3Free(imgBuf); - tj3Destroy(tjInstance); - tj3Free(jpegBuf); - if (jpegFile) fclose(jpegFile); - return retval; -} diff --git a/src/tjtran.c b/src/tjtran.c new file mode 100644 index 00000000..58cb4871 --- /dev/null +++ b/src/tjtran.c @@ -0,0 +1,309 @@ +/* + * Copyright (C)2011-2012, 2014-2015, 2017, 2019, 2021-2024 + * D. R. Commander. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the libjpeg-turbo Project nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This program demonstrates how to use the TurboJPEG C API to approximate the + * functionality of the IJG's jpegtran program. jpegtran features that are not + * covered: + * + * - Adding restart markers to the output image + * - Scan scripts + * - Expanding the input image when cropping + * - Wiping a region of the input image + * - Dropping another JPEG image into the input image + * - Copying only comment markers or ICC profile markers + * - Embedding an ICC color management profile + * - Progress reporting + * - Debug output + */ + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#include +#include +#include +#include +#include +#if !defined(_MSC_VER) || _MSC_VER > 1600 +#include +#endif +#include + + +#ifdef _WIN32 +#define strncasecmp strnicmp +#endif + +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#define MATCH_ARG(arg, string, minChars) \ + !strncasecmp(arg, string, max(strlen(arg), minChars)) + +#define IS_CROPPED(cr) (cr.x != 0 || cr.y != 0 || cr.w != 0 || cr.h != 0) + +#define THROW(action, message) { \ + printf("ERROR in line %d while %s:\n%s\n", __LINE__, action, message); \ + retval = -1; goto bailout; \ +} + +#define THROW_TJ(action) { \ + int errorCode = tj3GetErrorCode(tjInstance); \ + printf("%s in line %d while %s:\n%s\n", \ + errorCode == TJERR_WARNING ? "WARNING" : "ERROR", __LINE__, action, \ + tj3GetErrorStr(tjInstance)); \ + if (errorCode == TJERR_FATAL || stopOnWarning == 1) { \ + retval = -1; goto bailout; \ + } \ +} + +#define THROW_UNIX(action) THROW(action, strerror(errno)) + + +static void usage(char *programName) +{ + printf("\nUSAGE: %s [options] \n\n", + programName); + + printf("This program reads the DCT coefficients from the lossy JPEG input image,\n"); + printf("optionally transforms them, and writes them to a lossy JPEG output image.\n\n"); + + printf("OPTIONS (CAN BE ABBREVBIATED)\n"); + printf("-----------------------------\n"); + printf("-arithmetic\n"); + printf(" Use arithmetic entropy coding in the output image instead of Huffman\n"); + printf(" entropy coding (can be combined with -progressive)\n"); + printf("-copy all\n"); + printf(" Copy all extra markers (including comments, JFIF thumbnails, Exif data, and\n"); + printf(" ICC profile data) from the input image to the output image [default]\n"); + printf("-copy none\n"); + printf(" Copy no extra markers from the input image to the output image\n"); + printf("-crop WxH+X+Y\n"); + printf(" Include only the specified region of the input image. (W, H, X, and Y are\n"); + printf(" the width, height, left boundary, and upper boundary of the region, all\n"); + printf(" specified relative to the transformed image dimensions.) If necessary, X\n"); + printf(" and Y will be shifted up and left to the nearest iMCU boundary, and W and H\n"); + printf(" will be increased accordingly.\n"); + printf("-flip {horizontal|vertical}, -rotate {90|180|270}, -transpose, -transverse\n"); + printf(" Perform the specified lossless transform operation (these options are\n"); + printf(" mutually exclusive)\n"); + printf("-grayscale\n"); + printf(" Create a grayscale output image from a full-color input image\n"); + printf("-maxmemory N\n"); + printf(" Memory limit (in megabytes) for intermediate buffers used with progressive\n"); + printf(" JPEG compression, Huffman table optimization, and lossless transformation\n"); + printf(" [default = no limit]\n"); + printf("-maxscans N\n"); + printf(" Refuse to transform progressive JPEG images that have more than N scans\n"); + printf("-optimize\n"); + printf(" Use Huffman table optimization in the output image\n"); + printf("-perfect\n"); + printf(" Abort if the requested transform operation is imperfect (non-reversible.)\n"); + printf(" '-flip horizontal', '-rotate 180', '-rotate 270', and '-transverse' are\n"); + printf(" imperfect if the image width is not evenly divisible by the iMCU width.\n"); + printf(" '-flip vertical', '-rotate 90', '-rotate 180', and '-transverse' are\n"); + printf(" imperfect if the image height is not evenly divisible by the iMCU height.\n"); + printf("-progressive\n"); + printf(" Create a progressive output image instead of a single-scan output image\n"); + printf(" (can be combined with -arithmetic; implies -optimize unless -arithmetic is\n"); + printf(" also specified)\n"); + printf("-strict\n"); + printf(" Treat all warnings as fatal; abort immediately if incomplete or corrupt\n"); + printf(" data is encountered in the input image, rather than trying to salvage the\n"); + printf(" rest of the image\n"); + printf("-trim\n"); + printf(" If necessary, trim the partial iMCUs at the right or bottom edge of the\n"); + printf(" image to make the requested transform perfect\n\n"); + + exit(1); +} + + +int main(int argc, char **argv) +{ + int i, retval = 0; + int arithmetic = 0, maxMemory = -1, maxScans = -1, optimize = -1, + progressive = 0, stopOnWarning = -1, subsamp; + tjtransform xform; + tjhandle tjInstance = NULL; + FILE *jpegFile = NULL; + long size = 0; + size_t srcSize, dstSize; + unsigned char *srcBuf = NULL, *dstBuf = NULL; + + memset(&xform, 0, sizeof(tjtransform)); + + for (i = 1; i < argc; i++) { + if (MATCH_ARG(argv[i], "-arithmetic", 2)) + arithmetic = 1; + else if (MATCH_ARG(argv[i], "-crop", 3) && i < argc - 1) { + char tempc = -1; + + if (sscanf(argv[++i], "%d%c%d+%d+%d", &xform.r.w, &tempc, &xform.r.h, + &xform.r.x, &xform.r.y) != 5 || xform.r.w < 1 || + (tempc != 'x' && tempc != 'X') || xform.r.h < 1 || xform.r.x < 0 || + xform.r.y < 0) + usage(argv[0]); + xform.options |= TJXOPT_CROP; + } else if (MATCH_ARG(argv[i], "-copy", 2) && i < argc - 1) { + i++; + if (MATCH_ARG(argv[i], "none", 1)) + xform.options |= TJXOPT_COPYNONE; + else if (!MATCH_ARG(argv[i], "all", 1)) + usage(argv[0]); + } else if (MATCH_ARG(argv[i], "-flip", 2) && i < argc - 1) { + i++; + if (MATCH_ARG(argv[i], "horizontal", 1)) + xform.op = TJXOP_HFLIP; + else if (MATCH_ARG(argv[i], "vertical", 1)) + xform.op = TJXOP_VFLIP; + else + usage(argv[0]); + } else if (MATCH_ARG(argv[i], "-grayscale", 2) || + MATCH_ARG(argv[i], "-greyscale", 2)) + xform.options |= TJXOPT_GRAY; + else if (MATCH_ARG(argv[i], "-maxscans", 5) && i < argc - 1) { + int tempi = atoi(argv[++i]); + + if (tempi < 0) usage(argv[0]); + maxScans = tempi; + } else if (MATCH_ARG(argv[i], "-maxmemory", 2) && i < argc - 1) { + int tempi = atoi(argv[++i]); + + if (tempi < 0) usage(argv[0]); + maxMemory = tempi; + } else if (MATCH_ARG(argv[i], "-optimize", 2) || + MATCH_ARG(argv[i], "-optimise", 2)) + optimize = 1; + else if (MATCH_ARG(argv[i], "-perfect", 3)) + xform.options |= TJXOPT_PERFECT; + else if (MATCH_ARG(argv[i], "-progressive", 2)) + progressive = 1; + else if (MATCH_ARG(argv[i], "-rotate", 2) && i < argc - 1) { + i++; + if (MATCH_ARG(argv[i], "90", 2)) + xform.op = TJXOP_ROT90; + else if (MATCH_ARG(argv[i], "180", 3)) + xform.op = TJXOP_ROT180; + else if (MATCH_ARG(argv[i], "270", 3)) + xform.op = TJXOP_ROT270; + else + usage(argv[0]); + } else if (MATCH_ARG(argv[i], "-strict", 2)) + stopOnWarning = 1; + else if (MATCH_ARG(argv[i], "-transverse", 7)) + xform.op = TJXOP_TRANSVERSE; + else if (MATCH_ARG(argv[i], "-trim", 4)) + xform.options |= TJXOPT_TRIM; + else if (MATCH_ARG(argv[i], "-transpose", 2)) + xform.op = TJXOP_TRANSPOSE; + else break; + } + + if (i != argc - 2) + usage(argv[0]); + + if ((tjInstance = tj3Init(TJINIT_TRANSFORM)) == NULL) + THROW_TJ("creating TurboJPEG instance"); + + if (stopOnWarning >= 0 && + tj3Set(tjInstance, TJPARAM_STOPONWARNING, stopOnWarning) < 0) + THROW_TJ("setting TJPARAM_STOPONWARNING"); + if (optimize >= 0 && tj3Set(tjInstance, TJPARAM_OPTIMIZE, optimize) < 0) + THROW_TJ("setting TJPARAM_OPTIMIZE"); + if (maxScans >= 0 && tj3Set(tjInstance, TJPARAM_SCANLIMIT, maxScans) < 0) + THROW_TJ("setting TJPARAM_SCANLIMIT"); + if (maxMemory >= 0 && tj3Set(tjInstance, TJPARAM_MAXMEMORY, maxMemory) < 0) + THROW_TJ("setting TJPARAM_MAXMEMORY"); + + if ((jpegFile = fopen(argv[i++], "rb")) == NULL) + THROW_UNIX("opening input file"); + if (fseek(jpegFile, 0, SEEK_END) < 0 || ((size = ftell(jpegFile)) < 0) || + fseek(jpegFile, 0, SEEK_SET) < 0) + THROW_UNIX("determining input file size"); + if (size == 0) + THROW("determining input file size", "Input file contains no data"); + srcSize = size; + if ((srcBuf = tj3Alloc(srcSize)) == NULL) + THROW_UNIX("allocating JPEG buffer"); + if (fread(srcBuf, srcSize, 1, jpegFile) < 1) + THROW_UNIX("reading input file"); + fclose(jpegFile); jpegFile = NULL; + + if (tj3DecompressHeader(tjInstance, srcBuf, srcSize) < 0) + THROW_TJ("reading JPEG header"); + subsamp = tj3Get(tjInstance, TJPARAM_SUBSAMP); + if (xform.options & TJXOPT_GRAY) + subsamp = TJSAMP_GRAY; + + if (tj3Set(tjInstance, TJPARAM_PROGRESSIVE, progressive) < 0) + THROW_TJ("setting TJPARAM_PROGRESSIVE"); + if (tj3Set(tjInstance, TJPARAM_ARITHMETIC, arithmetic) < 0) + THROW_TJ("setting TJPARAM_ARITHMETIC"); + + if (IS_CROPPED(xform.r)) { + int xAdjust, yAdjust; + + if (subsamp == TJSAMP_UNKNOWN) + THROW("adjusting cropping region", + "Could not determine subsampling level of input image"); + if (xform.op == TJXOP_TRANSPOSE || xform.op == TJXOP_TRANSVERSE || + xform.op == TJXOP_ROT90 || xform.op == TJXOP_ROT270) { + xAdjust = xform.r.x % tjMCUHeight[subsamp]; + yAdjust = xform.r.y % tjMCUWidth[subsamp]; + } else { + xAdjust = xform.r.x % tjMCUWidth[subsamp]; + yAdjust = xform.r.y % tjMCUHeight[subsamp]; + } + xform.r.x -= xAdjust; + xform.r.w += xAdjust; + xform.r.y -= yAdjust; + xform.r.h += yAdjust; + } + + if (tj3Transform(tjInstance, srcBuf, srcSize, 1, &dstBuf, &dstSize, + &xform) < 0) + THROW_TJ("transforming input image"); + tj3Free(srcBuf); srcBuf = NULL; + + if ((jpegFile = fopen(argv[i], "wb")) == NULL) + THROW_UNIX("opening output file"); + if (fwrite(dstBuf, dstSize, 1, jpegFile) < 1) + THROW_UNIX("writing output file"); + +bailout: + tj3Destroy(tjInstance); + tj3Free(srcBuf); + if (jpegFile) fclose(jpegFile); + tj3Free(dstBuf); + return retval; +} diff --git a/test/tjcomptest.in b/test/tjcomptest.in new file mode 100755 index 00000000..9e9d29e8 --- /dev/null +++ b/test/tjcomptest.in @@ -0,0 +1,183 @@ +#/bin/bash + +set -u +set -e +trap onexit INT +trap onexit TERM +trap onexit EXIT + +onexit() +{ + if [ -d $OUTDIR ]; then + rm -rf $OUTDIR + fi +} + +runme() +{ + echo \*\*\* $* + "$@" +} + +IMGDIR=@CMAKE_SOURCE_DIR@/testimages +OUTDIR=`mktemp -d /tmp/__tjcomptest_output.XXXXXX` +EXEDIR=@CMAKE_BINARY_DIR@ +JAVA="@Java_JAVA_EXECUTABLE@" +JAVAARGS="-cp $EXEDIR/java/turbojpeg.jar -Djava.library.path=$EXEDIR" +TJCOMP=$EXEDIR/tjcomp +JAVAARG= + +if [ -d $OUTDIR ]; then + rm -rf $OUTDIR +fi +mkdir -p $OUTDIR + +while [ $# -gt 0 ]; do + case "$1" in + -java) + JAVAARG=-java + TJCOMP="$JAVA $JAVAARGS TJComp" + ;; + esac + shift +done + +exec >$EXEDIR/tjcomptest$JAVAARG.log + +SUBSAMPOPT=(444 422 440 420 411 441) +SAMPOPT=(1x1 2x1 1x2 2x2 4x1 1x4) + +for precision in 8 12; do + if [ $precision -le 8 ]; then + RGBIMG=$IMGDIR/testorig.ppm + GRAYIMG=$IMGDIR/testorig.pgm + else + RGBIMG=$IMGDIR/big_building16.ppm + GRAYIMG=$IMGDIR/big_building16.pgm + fi + for restartarg in "" "-r 1" "-r 1b"; do + for ariarg in "" "-a"; do + for dctarg in "" "-dc fa"; do + for optarg in "" "-o"; do + if [ "$optarg" = "-o" ]; then + if [[ "$ariarg" = "-a" || $precision -eq 12 ]]; then + continue + fi + fi + for progarg in "" "-p"; do + if [[ "$progarg" = "-p" && "$optarg" = "-o" ]]; then + continue + fi + for qualarg in "" "-q 1" "-q 100"; do + blarg= + if [ "$qualarg" = "-q 1" ]; then + blarg=-baseline + fi + for sampi in {0..5}; do + basename=`basename $RGBIMG .ppm` + runme $TJCOMP -pre $precision $restartarg $ariarg $dctarg \ + $optarg $progarg $qualarg -s ${SUBSAMPOPT[$sampi]} \ + $RGBIMG $OUTDIR/${basename}-tjcomp.jpg + runme $EXEDIR/cjpeg -pre $precision $restartarg $ariarg \ + $dctarg $optarg $progarg $qualarg $blarg \ + -sa ${SAMPOPT[$sampi]} \ + -outf $OUTDIR/${basename}-cjpeg.jpg $RGBIMG + $EXEDIR/src/md5/md5sum $OUTDIR/${basename}-tjcomp.jpg \ + $OUTDIR/${basename}-cjpeg.jpg + cmp $OUTDIR/${basename}-tjcomp.jpg \ + $OUTDIR/${basename}-cjpeg.jpg + rm $OUTDIR/${basename}-tjcomp.jpg $OUTDIR/${basename}-cjpeg.jpg + echo + + runme $TJCOMP -pre $precision $restartarg $ariarg $dctarg \ + $optarg $progarg $qualarg -s ${SUBSAMPOPT[$sampi]} \ + -g $RGBIMG $OUTDIR/${basename}-tjcomp.jpg + runme $EXEDIR/cjpeg -pre $precision $restartarg $ariarg \ + $dctarg $optarg $progarg $qualarg $blarg \ + -sa ${SAMPOPT[$sampi]} \ + -gr -outf $OUTDIR/${basename}-cjpeg.jpg $RGBIMG + $EXEDIR/src/md5/md5sum $OUTDIR/${basename}-tjcomp.jpg \ + $OUTDIR/${basename}-cjpeg.jpg + cmp $OUTDIR/${basename}-tjcomp.jpg \ + $OUTDIR/${basename}-cjpeg.jpg + rm $OUTDIR/${basename}-tjcomp.jpg $OUTDIR/${basename}-cjpeg.jpg + echo + + runme $TJCOMP -pre $precision $restartarg $ariarg $dctarg \ + $optarg $progarg $qualarg -s ${SUBSAMPOPT[$sampi]} \ + -rg $RGBIMG $OUTDIR/${basename}-tjcomp.jpg + runme $EXEDIR/cjpeg -pre $precision $restartarg $ariarg \ + $dctarg $optarg $progarg $qualarg $blarg \ + -sa ${SAMPOPT[$sampi]} \ + -rgb -outf $OUTDIR/${basename}-cjpeg.jpg $RGBIMG + $EXEDIR/src/md5/md5sum $OUTDIR/${basename}-tjcomp.jpg \ + $OUTDIR/${basename}-cjpeg.jpg + cmp $OUTDIR/${basename}-tjcomp.jpg \ + $OUTDIR/${basename}-cjpeg.jpg + rm $OUTDIR/${basename}-tjcomp.jpg $OUTDIR/${basename}-cjpeg.jpg + echo + + basename=`basename $GRAYIMG .pgm` + runme $TJCOMP -pre $precision $restartarg $ariarg $dctarg \ + $optarg $progarg $qualarg -s ${SUBSAMPOPT[$sampi]} \ + $GRAYIMG $OUTDIR/${basename}-tjcomp.jpg + runme $EXEDIR/cjpeg -pre $precision $restartarg $ariarg \ + $dctarg $optarg $progarg $qualarg $blarg \ + -sa ${SAMPOPT[$sampi]} \ + -outf $OUTDIR/${basename}-cjpeg.jpg $GRAYIMG + $EXEDIR/src/md5/md5sum $OUTDIR/${basename}-tjcomp.jpg \ + $OUTDIR/${basename}-cjpeg.jpg + cmp $OUTDIR/${basename}-tjcomp.jpg \ + $OUTDIR/${basename}-cjpeg.jpg + rm $OUTDIR/${basename}-tjcomp.jpg $OUTDIR/${basename}-cjpeg.jpg + echo + done + done + done + done + done + done + done +done + +for precision in {2..16}; do + if [ $precision -le 8 ]; then + RGBIMG=$IMGDIR/testorig.ppm + GRAYIMG=$IMGDIR/testorig.pgm + else + RGBIMG=$IMGDIR/big_building16.ppm + GRAYIMG=$IMGDIR/big_building16.pgm + fi + for psv in {1..7}; do + for pt in {0..15}; do + if [ $pt -ge $precision ]; then + continue + fi + for restartarg in "" "-r 1"; do + basename=`basename $RGBIMG .ppm` + runme $TJCOMP -pre $precision -l $psv,$pt $restartarg \ + $RGBIMG $OUTDIR/${basename}-tjcomp.jpg + runme $EXEDIR/cjpeg -pre $precision -l $psv,$pt $restartarg \ + -outf $OUTDIR/${basename}-cjpeg.jpg $RGBIMG + $EXEDIR/src/md5/md5sum $OUTDIR/${basename}-tjcomp.jpg \ + $OUTDIR/${basename}-cjpeg.jpg + cmp $OUTDIR/${basename}-tjcomp.jpg $OUTDIR/${basename}-cjpeg.jpg + rm $OUTDIR/${basename}-tjcomp.jpg $OUTDIR/${basename}-cjpeg.jpg + echo + + basename=`basename $GRAYIMG .pgm` + runme $TJCOMP -pre $precision -l $psv,$pt $restartarg \ + $GRAYIMG $OUTDIR/${basename}-tjcomp.jpg + runme $EXEDIR/cjpeg -pre $precision -l $psv,$pt $restartarg \ + -outf $OUTDIR/${basename}-cjpeg.jpg $GRAYIMG + $EXEDIR/src/md5/md5sum $OUTDIR/${basename}-tjcomp.jpg \ + $OUTDIR/${basename}-cjpeg.jpg + cmp $OUTDIR/${basename}-tjcomp.jpg $OUTDIR/${basename}-cjpeg.jpg + rm $OUTDIR/${basename}-tjcomp.jpg $OUTDIR/${basename}-cjpeg.jpg + echo + done + done + done +done + +echo "GREAT SUCCESS!" diff --git a/test/tjdecomptest.in b/test/tjdecomptest.in new file mode 100755 index 00000000..652975a3 --- /dev/null +++ b/test/tjdecomptest.in @@ -0,0 +1,200 @@ +#/bin/bash + +set -u +set -e +trap onexit INT +trap onexit TERM +trap onexit EXIT + +onexit() +{ + if [ -d $OUTDIR ]; then + rm -rf $OUTDIR + fi +} + +runme() +{ + echo \*\*\* $* + "$@" +} + +IMGDIR=@CMAKE_SOURCE_DIR@/testimages +OUTDIR=`mktemp -d /tmp/__tjdecomptest_output.XXXXXX` +EXEDIR=@CMAKE_BINARY_DIR@ +JAVA="@Java_JAVA_EXECUTABLE@" +JAVAARGS="-cp $EXEDIR/java/turbojpeg.jar -Djava.library.path=$EXEDIR" +TJDECOMP=$EXEDIR/tjdecomp +JAVAARG= + +if [ -d $OUTDIR ]; then + rm -rf $OUTDIR +fi +mkdir -p $OUTDIR + +while [ $# -gt 0 ]; do + case "$1" in + -java) + JAVAARG=-java + TJDECOMP="$JAVA $JAVAARGS TJDecomp" + ;; + esac + shift +done + +exec >$EXEDIR/tjdecomptest$JAVAARG.log + +SUBSAMPOPT=(444 422 440 420 411 441 410) +SAMPOPT=(1x1 2x1 1x2 2x2 4x1 1x4 4x2) + +for precision in 8 12; do + if [ $precision -le 8 ]; then + RGBIMG=$IMGDIR/testorig.ppm + GRAYIMG=$IMGDIR/testorig.pgm + else + RGBIMG=$IMGDIR/big_building16.ppm + GRAYIMG=$IMGDIR/big_building16.pgm + fi + + for sampi in {0..6}; do + runme $EXEDIR/cjpeg -pre $precision -sa ${SAMPOPT[$sampi]} \ + -outf $OUTDIR/`basename $RGBIMG .ppm`-${SUBSAMPOPT[$sampi]}.jpg $RGBIMG + done + runme $EXEDIR/cjpeg -pre $precision \ + -outf $OUTDIR/`basename $GRAYIMG .pgm`-gray.jpg $GRAYIMG + echo + + for subsamp in ${SUBSAMPOPT[*]} gray; do + for croparg in "" "-cr 14x14+23+23" "-cr 21x21+4+4" "-cr 18x18+13+13" \ + "-cr 21x21+0+0" "-cr 24x26+20+18"; do + if [[ "$croparg" != "" && "$subsamp" = "410" ]]; then + continue + fi + for scalearg in "" "-s 16/8" "-s 15/8" "-s 14/8" "-s 13/8" "-s 12/8" \ + "-s 11/8" "-s 10/8" "-s 9/8" "-s 7/8" "-s 6/8" "-s 5/8" "-s 4/8" \ + "-s 3/8" "-s 2/8" "-s 1/8"; do + if [[ ("$scalearg" = "-s 1/8" || "$scalearg" = "-s 2/8" || \ + "$scalearg" = "-s 3/8") && "$croparg" != "" ]]; then + continue + fi + for nsarg in "" "-nos"; do + if [[ "$nsarg" = "-nos" && "$subsamp" != "422" && \ + "$subsamp" != "420" && "$subsamp" != "440" ]]; then + continue + fi + for dctarg in "" "-dc fa"; do + if [[ "$dctarg" = "-dc fa" && \ + ("$scalearg" != "-s 4/8" || \ + ("$subsamp" != "420" && "$subsamp" != "410")) && \ + "$scalearg" != "" ]]; then + continue + fi + if [ "$subsamp" = "gray" ]; then + basename=`basename $GRAYIMG .pgm` + runme $TJDECOMP $croparg $dctarg $nsarg $scalearg \ + $OUTDIR/${basename}-$subsamp.jpg \ + $OUTDIR/${basename}-tjdecomp.pgm + runme $EXEDIR/djpeg $croparg $dctarg $nsarg $scalearg \ + -outf $OUTDIR/${basename}-djpeg.pgm \ + $OUTDIR/${basename}-$subsamp.jpg + $EXEDIR/src/md5/md5sum $OUTDIR/${basename}-tjdecomp.pgm \ + $OUTDIR/${basename}-djpeg.pgm + cmp $OUTDIR/${basename}-tjdecomp.pgm \ + $OUTDIR/${basename}-djpeg.pgm + rm $OUTDIR/${basename}-tjdecomp.pgm $OUTDIR/${basename}-djpeg.pgm + echo + + runme $TJDECOMP $croparg $dctarg $nsarg $scalearg \ + -r $OUTDIR/${basename}-$subsamp.jpg \ + $OUTDIR/${basename}-tjdecomp.ppm + runme $EXEDIR/djpeg $croparg $dctarg $nsarg $scalearg \ + -rg -outf $OUTDIR/${basename}-djpeg.ppm \ + $OUTDIR/${basename}-$subsamp.jpg + $EXEDIR/src/md5/md5sum $OUTDIR/${basename}-tjdecomp.ppm \ + $OUTDIR/${basename}-djpeg.ppm + cmp $OUTDIR/${basename}-tjdecomp.ppm \ + $OUTDIR/${basename}-djpeg.ppm + rm $OUTDIR/${basename}-tjdecomp.ppm $OUTDIR/${basename}-djpeg.ppm + echo + else + basename=`basename $RGBIMG .ppm` + runme $TJDECOMP $croparg $dctarg $nsarg $scalearg \ + $OUTDIR/${basename}-$subsamp.jpg \ + $OUTDIR/${basename}-tjdecomp.ppm + runme $EXEDIR/djpeg $croparg $dctarg $nsarg $scalearg \ + -outf $OUTDIR/${basename}-djpeg.ppm \ + $OUTDIR/${basename}-$subsamp.jpg + $EXEDIR/src/md5/md5sum $OUTDIR/${basename}-tjdecomp.ppm \ + $OUTDIR/${basename}-djpeg.ppm + cmp $OUTDIR/${basename}-tjdecomp.ppm \ + $OUTDIR/${basename}-djpeg.ppm + rm $OUTDIR/${basename}-tjdecomp.ppm $OUTDIR/${basename}-djpeg.ppm + echo + + if [[ "$nsarg" = "" ]]; then + runme $TJDECOMP $croparg $dctarg $nsarg $scalearg \ + -g $OUTDIR/${basename}-$subsamp.jpg \ + $OUTDIR/${basename}-tjdecomp.pgm + runme $EXEDIR/djpeg $croparg $dctarg $nsarg $scalearg \ + -gr -outf $OUTDIR/${basename}-djpeg.pgm \ + $OUTDIR/${basename}-$subsamp.jpg + $EXEDIR/src/md5/md5sum $OUTDIR/${basename}-tjdecomp.pgm \ + $OUTDIR/${basename}-djpeg.pgm + cmp $OUTDIR/${basename}-tjdecomp.pgm \ + $OUTDIR/${basename}-djpeg.pgm + rm $OUTDIR/${basename}-tjdecomp.pgm \ + $OUTDIR/${basename}-djpeg.pgm + echo + fi + fi + done + done + done + done + rm $OUTDIR/${basename}-$subsamp.jpg + done +done + +for precision in {2..16}; do + if [ $precision -le 8 ]; then + RGBIMG=$IMGDIR/testorig.ppm + GRAYIMG=$IMGDIR/testorig.pgm + else + RGBIMG=$IMGDIR/big_building16.ppm + GRAYIMG=$IMGDIR/big_building16.pgm + fi + + basename=`basename $RGBIMG .ppm` + runme $EXEDIR/cjpeg -pre $precision -l 1 \ + -outf $OUTDIR/${basename}-rgb.jpg $RGBIMG + echo + + runme $TJDECOMP $OUTDIR/${basename}-rgb.jpg \ + $OUTDIR/${basename}-tjdecomp.ppm + runme $EXEDIR/djpeg -outf $OUTDIR/${basename}-djpeg.ppm \ + $OUTDIR/${basename}-rgb.jpg + $EXEDIR/src/md5/md5sum $OUTDIR/${basename}-tjdecomp.ppm \ + $OUTDIR/${basename}-djpeg.ppm + cmp $OUTDIR/${basename}-tjdecomp.ppm $OUTDIR/${basename}-djpeg.ppm + rm $OUTDIR/${basename}-tjdecomp.ppm $OUTDIR/${basename}-djpeg.ppm + rm $OUTDIR/${basename}-rgb.jpg + echo + + basename=`basename $GRAYIMG .pgm` + runme $EXEDIR/cjpeg -pre $precision -l 1 \ + -outf $OUTDIR/${basename}-gray.jpg $GRAYIMG + echo + + runme $TJDECOMP $OUTDIR/${basename}-gray.jpg \ + $OUTDIR/${basename}-tjdecomp.pgm + runme $EXEDIR/djpeg -outf $OUTDIR/${basename}-djpeg.pgm \ + $OUTDIR/${basename}-gray.jpg + $EXEDIR/src/md5/md5sum $OUTDIR/${basename}-tjdecomp.pgm \ + $OUTDIR/${basename}-djpeg.pgm + cmp $OUTDIR/${basename}-tjdecomp.pgm $OUTDIR/${basename}-djpeg.pgm + rm $OUTDIR/${basename}-tjdecomp.pgm $OUTDIR/${basename}-djpeg.pgm + rm $OUTDIR/${basename}-gray.jpg + echo +done + +echo "GREAT SUCCESS!" diff --git a/test/tjexampletest.in b/test/tjexampletest.in deleted file mode 100755 index b5aeda53..00000000 --- a/test/tjexampletest.in +++ /dev/null @@ -1,186 +0,0 @@ -#!/bin/bash - -set -u -set -e -trap onexit INT -trap onexit TERM -trap onexit EXIT - -onexit() -{ - if [ -d $OUTDIR ]; then - rm -rf $OUTDIR - fi -} - -runme() -{ - echo \*\*\* $* - $* -} - -IMAGES="vgl_5674_0098.bmp vgl_6434_0018a.bmp vgl_6548_0026a.bmp big_tree8.bmp" -IMGDIR=@CMAKE_SOURCE_DIR@/testimages -OUTDIR=`mktemp -d /tmp/__tjexampletest_output.XXXXXX` -EXEDIR=@CMAKE_BINARY_DIR@ -JAVA="@Java_JAVA_EXECUTABLE@" -JAVAARGS="-cp $EXEDIR/java/turbojpeg.jar -Djava.library.path=$EXEDIR" -TJEXAMPLE=$EXEDIR/tjexample -JAVAARG= - -if [ -d $OUTDIR ]; then - rm -rf $OUTDIR -fi -mkdir -p $OUTDIR - -while [ $# -gt 0 ]; do - case "$1" in - -java) - JAVAARG=-java - TJEXAMPLE="$JAVA $JAVAARGS TJExample" - # The Java version of TJExample can't currently handle pixel density - # information, so it fails on big_tree8.bmp. - IMAGES="vgl_5674_0098.bmp vgl_6434_0018a.bmp vgl_6548_0026a.bmp" - ;; - esac - shift -done - -exec >$EXEDIR/tjexampletest$JAVAARG.log - -for image in $IMAGES; do - - cp $IMGDIR/$image $OUTDIR - basename=`basename $image .bmp` - runme $EXEDIR/cjpeg -quality 95 -dct fast -grayscale -outfile $OUTDIR/${basename}_GRAY_fast_cjpeg.jpg $IMGDIR/${basename}.bmp - runme $EXEDIR/cjpeg -quality 95 -dct fast -sample 2x2 -outfile $OUTDIR/${basename}_420_fast_cjpeg.jpg $IMGDIR/${basename}.bmp - runme $EXEDIR/cjpeg -quality 95 -dct fast -sample 2x1 -outfile $OUTDIR/${basename}_422_fast_cjpeg.jpg $IMGDIR/${basename}.bmp - runme $EXEDIR/cjpeg -quality 95 -dct fast -sample 1x1 -outfile $OUTDIR/${basename}_444_fast_cjpeg.jpg $IMGDIR/${basename}.bmp - runme $EXEDIR/cjpeg -quality 95 -dct int -grayscale -outfile $OUTDIR/${basename}_GRAY_accurate_cjpeg.jpg $IMGDIR/${basename}.bmp - runme $EXEDIR/cjpeg -quality 95 -dct int -sample 2x2 -outfile $OUTDIR/${basename}_420_accurate_cjpeg.jpg $IMGDIR/${basename}.bmp - runme $EXEDIR/cjpeg -quality 95 -dct int -sample 2x1 -outfile $OUTDIR/${basename}_422_accurate_cjpeg.jpg $IMGDIR/${basename}.bmp - runme $EXEDIR/cjpeg -quality 95 -dct int -sample 1x1 -outfile $OUTDIR/${basename}_444_accurate_cjpeg.jpg $IMGDIR/${basename}.bmp - for samp in GRAY 420 422 444; do - runme $EXEDIR/djpeg -dct fast -rgb -bmp -outfile $OUTDIR/${basename}_${samp}_fast_djpeg.bmp $OUTDIR/${basename}_${samp}_fast_cjpeg.jpg - runme $EXEDIR/djpeg -dct int -rgb -bmp -outfile $OUTDIR/${basename}_${samp}_accurate_djpeg.bmp $OUTDIR/${basename}_${samp}_accurate_cjpeg.jpg - done - for samp in 420 422; do - runme $EXEDIR/djpeg -dct fast -nosmooth -bmp -outfile $OUTDIR/${basename}_${samp}_fast_nosmooth_djpeg.bmp $OUTDIR/${basename}_${samp}_fast_cjpeg.jpg - runme $EXEDIR/djpeg -dct int -nosmooth -bmp -outfile $OUTDIR/${basename}_${samp}_accurate_nosmooth_djpeg.bmp $OUTDIR/${basename}_${samp}_accurate_cjpeg.jpg - done - runme $EXEDIR/cjpeg -quality 95 -dct fast -grayscale -outfile $OUTDIR/${basename}_GRAY_fast_cjpeg2.jpg $OUTDIR/${basename}_GRAY_fast_djpeg.bmp - runme $EXEDIR/cjpeg -quality 95 -dct fast -sample 2x2 -outfile $OUTDIR/${basename}_420_fast_cjpeg2.jpg $OUTDIR/${basename}_420_fast_djpeg.bmp - runme $EXEDIR/cjpeg -quality 95 -dct fast -sample 2x1 -outfile $OUTDIR/${basename}_422_fast_cjpeg2.jpg $OUTDIR/${basename}_422_fast_djpeg.bmp - runme $EXEDIR/cjpeg -quality 95 -dct fast -sample 1x1 -outfile $OUTDIR/${basename}_444_fast_cjpeg2.jpg $OUTDIR/${basename}_444_fast_djpeg.bmp - runme $EXEDIR/cjpeg -quality 95 -dct int -grayscale -outfile $OUTDIR/${basename}_GRAY_accurate_cjpeg2.jpg $OUTDIR/${basename}_GRAY_accurate_djpeg.bmp - runme $EXEDIR/cjpeg -quality 95 -dct int -sample 2x2 -outfile $OUTDIR/${basename}_420_accurate_cjpeg2.jpg $OUTDIR/${basename}_420_accurate_djpeg.bmp - runme $EXEDIR/cjpeg -quality 95 -dct int -sample 2x1 -outfile $OUTDIR/${basename}_422_accurate_cjpeg2.jpg $OUTDIR/${basename}_422_accurate_djpeg.bmp - runme $EXEDIR/cjpeg -quality 95 -dct int -sample 1x1 -outfile $OUTDIR/${basename}_444_accurate_cjpeg2.jpg $OUTDIR/${basename}_444_accurate_djpeg.bmp - - # Compression - for dct in fast accurate; do - dctarg= - if [ "${dct}" = "fast" ]; then - dctarg=-fastdct - fi - for samp in GRAY 420 422 444; do - runme $TJEXAMPLE $OUTDIR/$image $OUTDIR/${basename}_${samp}_${dct}.jpg -q 95 -subsamp ${samp} ${dctarg} - runme cmp $OUTDIR/${basename}_${samp}_${dct}.jpg $OUTDIR/${basename}_${samp}_${dct}_cjpeg.jpg - done - done - - # Recompression - for dct in fast accurate; do - dctarg= - if [ "${dct}" = "fast" ]; then - dctarg=-fastdct - fi - for samp in GRAY 420 422 444; do - runme $TJEXAMPLE $OUTDIR/${basename}_${samp}_${dct}.jpg $OUTDIR/${basename}_${samp}_${dct}_recomp.jpg -q 95 -subsamp ${samp} ${dctarg} - runme cmp $OUTDIR/${basename}_${samp}_${dct}_recomp.jpg $OUTDIR/${basename}_${samp}_${dct}_cjpeg2.jpg - done - done - - # Decompression - for dct in fast accurate; do - dctarg= - if [ "${dct}" = "fast" ]; then - dctarg=-fastdct - fi - for samp in GRAY 420 422 444; do - runme $TJEXAMPLE $OUTDIR/${basename}_${samp}_${dct}.jpg $OUTDIR/${basename}_${samp}_${dct}.bmp ${dctarg} - runme cmp -i 54:54 $OUTDIR/${basename}_${samp}_${dct}.bmp $OUTDIR/${basename}_${samp}_${dct}_djpeg.bmp - rm $OUTDIR/${basename}_${samp}_${dct}.bmp - done - for samp in 420 422; do - runme $TJEXAMPLE $OUTDIR/${basename}_${samp}_${dct}.jpg $OUTDIR/${basename}_${samp}_${dct}_nosmooth.bmp -fastupsample ${dctarg} - runme cmp -i 54:54 $OUTDIR/${basename}_${samp}_${dct}_nosmooth.bmp $OUTDIR/${basename}_${samp}_${dct}_nosmooth_djpeg.bmp - rm $OUTDIR/${basename}_${samp}_${dct}_nosmooth.bmp - done - done - - # Scaled decompression - for scale in 2_1 15_8 7_4 13_8 3_2 11_8 5_4 9_8 7_8 3_4 5_8 1_2 3_8 1_4 1_8; do - scalearg=`echo $scale | sed 's/\_/\//g'` - for samp in GRAY 420 422 444; do - runme $EXEDIR/djpeg -rgb -bmp -scale ${scalearg} -outfile $OUTDIR/${basename}_${samp}_${scale}_djpeg.bmp $OUTDIR/${basename}_${samp}_fast_cjpeg.jpg - runme $TJEXAMPLE $OUTDIR/${basename}_${samp}_fast.jpg $OUTDIR/${basename}_${samp}_${scale}.bmp -scale ${scalearg} - runme cmp -i 54:54 $OUTDIR/${basename}_${samp}_${scale}.bmp $OUTDIR/${basename}_${samp}_${scale}_djpeg.bmp - rm $OUTDIR/${basename}_${samp}_${scale}.bmp - done - done - - # Transforms - for samp in GRAY 420 422 444; do - runme $EXEDIR/jpegtran -crop 70x60+16+16 -flip horizontal -trim -outfile $OUTDIR/${basename}_${samp}_hflip_jpegtran.jpg $OUTDIR/${basename}_${samp}_fast.jpg - runme $EXEDIR/jpegtran -crop 70x60+16+16 -flip vertical -trim -outfile $OUTDIR/${basename}_${samp}_vflip_jpegtran.jpg $OUTDIR/${basename}_${samp}_fast.jpg - runme $EXEDIR/jpegtran -crop 70x60+16+16 -transpose -trim -outfile $OUTDIR/${basename}_${samp}_transpose_jpegtran.jpg $OUTDIR/${basename}_${samp}_fast.jpg - runme $EXEDIR/jpegtran -crop 70x60+16+16 -transverse -trim -outfile $OUTDIR/${basename}_${samp}_transverse_jpegtran.jpg $OUTDIR/${basename}_${samp}_fast.jpg - runme $EXEDIR/jpegtran -crop 70x60+16+16 -rotate 90 -trim -outfile $OUTDIR/${basename}_${samp}_rot90_jpegtran.jpg $OUTDIR/${basename}_${samp}_fast.jpg - runme $EXEDIR/jpegtran -crop 70x60+16+16 -rotate 180 -trim -outfile $OUTDIR/${basename}_${samp}_rot180_jpegtran.jpg $OUTDIR/${basename}_${samp}_fast.jpg - runme $EXEDIR/jpegtran -crop 70x60+16+16 -rotate 270 -trim -outfile $OUTDIR/${basename}_${samp}_rot270_jpegtran.jpg $OUTDIR/${basename}_${samp}_fast.jpg - done - for xform in hflip vflip transpose transverse rot90 rot180 rot270; do - for samp in GRAY 420 422 444; do - runme $TJEXAMPLE $OUTDIR/${basename}_${samp}_fast.jpg $OUTDIR/${basename}_${samp}_${xform}.jpg -$xform -crop 70x60+16+16 - runme cmp $OUTDIR/${basename}_${samp}_${xform}.jpg $OUTDIR/${basename}_${samp}_${xform}_jpegtran.jpg - runme $EXEDIR/djpeg -rgb -bmp -outfile $OUTDIR/${basename}_${samp}_${xform}_jpegtran.bmp $OUTDIR/${basename}_${samp}_${xform}_jpegtran.jpg - runme $TJEXAMPLE $OUTDIR/${basename}_${samp}_fast.jpg $OUTDIR/${basename}_${samp}_${xform}.bmp -$xform -crop 70x60+16+16 - runme cmp -i 54:54 $OUTDIR/${basename}_${samp}_${xform}.bmp $OUTDIR/${basename}_${samp}_${xform}_jpegtran.bmp - rm $OUTDIR/${basename}_${samp}_${xform}.bmp - done - for samp in 420 422; do - runme $EXEDIR/djpeg -nosmooth -rgb -bmp -outfile $OUTDIR/${basename}_${samp}_${xform}_jpegtran.bmp $OUTDIR/${basename}_${samp}_${xform}_jpegtran.jpg - runme $TJEXAMPLE $OUTDIR/${basename}_${samp}_fast.jpg $OUTDIR/${basename}_${samp}_${xform}.bmp -$xform -crop 70x60+16+16 -fastupsample - runme cmp -i 54:54 $OUTDIR/${basename}_${samp}_${xform}.bmp $OUTDIR/${basename}_${samp}_${xform}_jpegtran.bmp - rm $OUTDIR/${basename}_${samp}_${xform}.bmp - done - done - - # Grayscale transform - for xform in hflip vflip transpose transverse rot90 rot180 rot270; do - for samp in GRAY 444 422 420; do - runme $TJEXAMPLE $OUTDIR/${basename}_${samp}_fast.jpg $OUTDIR/${basename}_${samp}_${xform}.jpg -$xform -grayscale -crop 70x60+16+16 - runme cmp $OUTDIR/${basename}_${samp}_${xform}.jpg $OUTDIR/${basename}_GRAY_${xform}_jpegtran.jpg - runme $TJEXAMPLE $OUTDIR/${basename}_${samp}_fast.jpg $OUTDIR/${basename}_${samp}_${xform}.bmp -$xform -grayscale -crop 70x60+16+16 - runme cmp -i 54:54 $OUTDIR/${basename}_${samp}_${xform}.bmp $OUTDIR/${basename}_GRAY_${xform}_jpegtran.bmp - rm $OUTDIR/${basename}_${samp}_${xform}.bmp - done - done - - # Transforms with scaling - for xform in hflip vflip transpose transverse rot90 rot180 rot270; do - for samp in GRAY 444 422 420; do - for scale in 2_1 15_8 7_4 13_8 3_2 11_8 5_4 9_8 7_8 3_4 5_8 1_2 3_8 1_4 1_8; do - scalearg=`echo $scale | sed 's/\_/\//g'` - runme $EXEDIR/djpeg -rgb -bmp -scale ${scalearg} -outfile $OUTDIR/${basename}_${samp}_${xform}_${scale}_jpegtran.bmp $OUTDIR/${basename}_${samp}_${xform}_jpegtran.jpg - runme $TJEXAMPLE $OUTDIR/${basename}_${samp}_fast.jpg $OUTDIR/${basename}_${samp}_${xform}_${scale}.bmp -$xform -scale ${scalearg} -crop 70x60+16+16 - runme cmp -i 54:54 $OUTDIR/${basename}_${samp}_${xform}_${scale}.bmp $OUTDIR/${basename}_${samp}_${xform}_${scale}_jpegtran.bmp - rm $OUTDIR/${basename}_${samp}_${xform}_${scale}.bmp - done - done - done - -done - -echo SUCCESS! diff --git a/test/tjtrantest.in b/test/tjtrantest.in new file mode 100755 index 00000000..e13d0f75 --- /dev/null +++ b/test/tjtrantest.in @@ -0,0 +1,153 @@ +#/bin/bash + +set -u +set -e +trap onexit INT +trap onexit TERM +trap onexit EXIT + +onexit() +{ + if [ -d $OUTDIR ]; then + rm -rf $OUTDIR + fi +} + +runme() +{ + echo \*\*\* $* + "$@" +} + +IMGDIR=@CMAKE_SOURCE_DIR@/testimages +OUTDIR=`mktemp -d /tmp/__tjtrantest_output.XXXXXX` +EXEDIR=@CMAKE_BINARY_DIR@ +JAVA="@Java_JAVA_EXECUTABLE@" +JAVAARGS="-cp $EXEDIR/java/turbojpeg.jar -Djava.library.path=$EXEDIR" +TJTRAN=$EXEDIR/tjtran +JAVAARG= + +if [ -d $OUTDIR ]; then + rm -rf $OUTDIR +fi +mkdir -p $OUTDIR + +while [ $# -gt 0 ]; do + case "$1" in + -java) + JAVAARG=-java + TJTRAN="$JAVA $JAVAARGS TJTran" + ;; + esac + shift +done + +exec >$EXEDIR/tjtrantest$JAVAARG.log + +SUBSAMPOPT=(444 422 440 420 411 441 410) +SAMPOPT=(1x1 2x1 1x2 2x2 4x1 1x4 4x2) + +for precision in 8 12; do + if [ $precision -le 8 ]; then + RGBIMG=$IMGDIR/testorig.ppm + GRAYIMG=$IMGDIR/testorig.pgm + else + RGBIMG=$IMGDIR/big_building16.ppm + GRAYIMG=$IMGDIR/big_building16.pgm + fi + + for sampi in {0..6}; do + EXTRA_ARGS= + if [ $sampi = 1 ]; then + EXTRA_ARGS=-p + elif [ $sampi = 2 ]; then + EXTRA_ARGS=-a + elif [ $sampi = 3 ]; then + EXTRA_ARGS=-o + fi + runme $EXEDIR/cjpeg -pre $precision -sa ${SAMPOPT[$sampi]} $EXTRA_ARGS \ + -outf $OUTDIR/`basename $RGBIMG .ppm`-${SUBSAMPOPT[$sampi]}.jpg $RGBIMG + if [ $sampi = 4 ]; then + $EXEDIR/wrjpgcom -comment "This is a test" \ + $OUTDIR/`basename $RGBIMG .ppm`-${SUBSAMPOPT[$sampi]}.jpg \ + >$OUTDIR/temp.jpg + mv $OUTDIR/temp.jpg \ + $OUTDIR/`basename $RGBIMG .ppm`-${SUBSAMPOPT[$sampi]}.jpg + fi + done + runme $EXEDIR/cjpeg -pre $precision \ + -outf $OUTDIR/`basename $GRAYIMG .pgm`-gray.jpg $GRAYIMG + echo + + for subsamp in ${SUBSAMPOPT[*]} gray; do + if [ "$subsamp" = "gray" ]; then + basename=`basename $GRAYIMG .pgm` + else + basename=`basename $RGBIMG .ppm` + fi + for ariarg in "" "-a"; do + for copyarg in "" "-c n"; do + if [[ "$copyarg" = "-c n" && "$subsamp" != "411" ]]; then + continue + fi + for croparg in "" "-cr 14x14+23+23" "-cr 21x21+4+4" "-cr 18x18+13+13" \ + "-cr 21x21+0+0" "-cr 24x26+20+18"; do + for xformarg in "" "-f h" "-f v" "-ro 90" "-ro 180" "-ro 270" "-t" \ + "-transv"; do + for grayarg in "" "-g"; do + if [ "$grayarg" = "" ]; then + if [[ "$subsamp" = "410" && "$croparg" != "" ]]; then + continue + fi + else + if [ "$subsamp" = "gray" ]; then + continue + fi + fi + for optarg in "" "-o"; do + if [ "$optarg" = "-o" ]; then + if [[ "$ariarg" = "-a" || $precision -eq 12 ]]; then + continue + fi + fi + for progarg in "" "-p"; do + if [[ "$progarg" = "-p" && "$optarg" = "-o" ]]; then + continue + fi + for trimarg in "" "-tri"; do + if [ "$trimarg" = "-tri" ]; then + if [[ "$xformarg" = "-t" || "$xformarg" = "" ]]; then + continue + fi + if [ "$croparg" != "" ]; then + continue + fi + fi + runme $TJTRAN $ariarg $copyarg $croparg $xformarg \ + $grayarg $optarg $progarg $trimarg \ + $OUTDIR/${basename}-$subsamp.jpg \ + $OUTDIR/${basename}-tjtran.jpg + runme $EXEDIR/jpegtran $ariarg $copyarg $croparg \ + $xformarg $grayarg $optarg $progarg $trimarg \ + -outf $OUTDIR/${basename}-jpegtran.jpg \ + $OUTDIR/${basename}-$subsamp.jpg + $EXEDIR/src/md5/md5sum $OUTDIR/${basename}-tjtran.jpg \ + $OUTDIR/${basename}-jpegtran.jpg + cmp $OUTDIR/${basename}-tjtran.jpg \ + $OUTDIR/${basename}-jpegtran.jpg + rm $OUTDIR/${basename}-tjtran.jpg \ + $OUTDIR/${basename}-jpegtran.jpg + echo + done + done + done + done + done + done + done + done + rm $OUTDIR/${basename}-$subsamp.jpg + done +done + +echo "GREAT SUCCESS!" diff --git a/testimages/big_building16.pgm b/testimages/big_building16.pgm new file mode 100644 index 0000000000000000000000000000000000000000..87a0149a195815c7fe57798421a870f1be945026 GIT binary patch literal 67663 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 JlE8?ooEh1%bgTdX literal 0 HcmV?d00001 diff --git a/testimages/testorig.pgm b/testimages/testorig.pgm new file mode 100644 index 00000000..69f2f39a --- /dev/null +++ b/testimages/testorig.pgm @@ -0,0 +1,4 @@ +P5 +227 149 +255 +//012233443344443321123311111111///...---------.+,--./0033456677;<>@BCDDFEDCDDEGBDFJPUWY]]_`bccbihiihgeddcbbcb_^WXXWWWVVUVUUTRRROOONNNMMOONNOOPQQPOSWZ\]XZ[YTJ>56765210033322211////////.-,++19?JS\^gt}|dPJNKKSe}{fWNSSS//001233443344443211112311111111///..---......./---./01133455677:<=?ABCCDDCBBCDFACFJNRTUWWX[^_``ffghhhffdcbbcb_]WXWWWVVVUVUUSRQROOONNMMMONNNNOPQRQPSW[\]Z[[YSH=67775321133322211/////////.-+,07=EOW[cpy}w_NHJKKPZo~jZSUTT.//0112233333333222112232222222200///...00000000../00122334456679:<>@AAAAAAAABBCCCFILNPQNPQTX\__ccefghggdcccdc^\VWWWVVUVTUTSSRQQOONNNMMMNNMMNOPPRQQTW[]]^^^ZRH>78876543333322211010000000/.,,059@HQV]hrx~oYJFFJLLM\tygZSVVV-../0112222222212100001222222222////..--00000000000112222334566689;<>>??>>>??@ABEFGHKKMMIKMRV[_`bbcfgfffdcdddb][VVVVUUUUUTTRRQQPNNNNMMLLMMMMMNOORRQUY\]\`a_ZQF=8998765443332221111000000210.-.26=CKPU^jry}ziVJFEJKGFM_mx}~ymaXSXXX--../0011111110000////0011111111///..---000000001111222222344566679::<<<<;<=>?@AFHHIJKKKIJLPU[_bccceeecccdcdda\XVVUUUTTTSSRRQPOPNOONNMMMMMMMLNNPRRSUY]]\]_^YNC;7::9876543332221110110000222/-.03:?EJMUcpv|~whYMHGGFEEGLPadgd^YWWZYY,,-../00//////..//....///////////...---,0000000011111111122345565679899:::;<=?@AFHGHIIIIIJKNRY]`baccddcbcbbdc`ZVUUUTTSSTSTRQPOOOONNNMMMLMLLLLMNORSTX[]]][^`[PC;69997755433322212112211111221/./089>CFOarzyl`RHHEBCEEC@GKOONOSVZZY+,,--///.........-,,,,--,,,,,,,,---,,,++......./111100001123445545677777:::;:9776543333222121122221101210./0347=BMcx~ujYIEC@BEDB?AFKNNPTWZZY++,-,-//........-,,++,,-++++++++---,,+++--------00000000112334553456666699:;<>?@BBCCEEFEFEFGJPTV\]`bdfffa`bca]WRUUUTTSSSRSRQOONNNNNMMLLLLLKKKLMNSSUY\^^^fmsncULG998754333332222221222211/1220//0/04;@Mg|q]JBA@ACDDDOT[]]Z[][ZZ------,-...---,,,,,,,,--,,,,,,,,,,,--..///......-./00000000012341345666677789;=>?AABBA@@CDFHLNQQVWZ^bdeedffd_ZVUTSSRQQRROPNONMMMLLLKKKJJLLLLMNOPTTX^_]^cnu{yti\PKF>96432433210/01111000011111111-267>Qq~rg_MFA@CHT^nz}vqh\\\]------,,..---,,,+++,,,,,,,,,,,,,,,,--...........-.//000/////01332235677666779:<=??@AB@??BBEGJMNPTVY[^accefgd`ZVTUSSRRRQRPPONMNMMLLKKKJJJKKKKMNPPVVZ_`_bht||qcUMIC>:730221000//0100000011111110.047AWu|vpb[VW]es~|paYZ\........,--,,++++*****))++++++++*++++,,,,------.--.////...../01223455656666689;<=>?@??>=?ABEILNOSTTUY\`adffd^YUSTSRQQQQQNONNNMMLKKKKJJIIJJJLMOQRYXZ_abgp~|m`PMHC?;5010//.///////////00000000.017E^z|{{vdVXZ........,,,,,+++**))))((************++++,,,,,---,-....------./01345655555555689:;<=>>==;?@BEHKMORRRRUX[^cceb]WTRSRQPPPOQONNMMLLKKJJJIIIIIJKLNPRSZXY]adnwwjWSMGB<620/.--.//////////0000000//./6IdwaSUY........---,,,++***))(((**************))+++,,---,--.--,,-,,--/0133565544544456789:;<<<<:>>ADHKNORQQQSVX[]_`_ZVRQQQPONOOONMMLLLKKJJIIIHHHHJKLOQSUYWX]bjt~rb\RIC>971/-,,-./........///////.0,,5Ift]QSV////////...---,,,++*))((**********)))(((***+,---,,---,+*----./01345655444433456679:;;::9;;31.,*+,-,,,,,---.......-.+,5Hb}Ŀr[OOP////////0///...---,+*))())))))))*)))((''))*+,--.,,,,,+*)----.01234555443333234556789::9889;>CFIJMNOPQRRSRUWWVRPPOONMLMMMLLMKKJJIHHHHGGFFKKMORUXZWVZcmu~smcYQIB>630-**,-++++++++,,-----,,,.4E^zſmXLJI////000/0000//../..-,*))))))))*)*))()'''))*,,-./,,,,,+))..../012556654333322234467899988568<@DHILMNOPPPOOQVVUQPPONMLLMMNLLLKKJIIHHIHHGGGKLMPSW[[VW]hr}xskaXOE?850-**+,+,++++++,,,,------.4B[w|hUJGD///0011133332100110/..--,,.-,+*)****+*++,,,--...--,-.///.-./233355777655433223343456775410028=DHLMOPPNMJMOQRSQNLMMMNMMLLNMMLKJIIFLHBHHGKHMSWXXZYZaluzqlg`TJ?:2-+**)++*((***++++++,++,-.=YuvfWME@00001112334432110000/..---..-+*)****++++,---.../......//..0013346667656543322334445565331/026>==<:98776666779988767789:99999;=@CFHGGFHILLNNOOONMKJJIHHHIIIJIED^iPGRUQSVWXZ_ejqty|xqeXLA82/.-,,,,--,,,+++**4?FP`mt||}ueWB:5777788999:;<==<<;;:999888888764411122222222333444556899:89:;<>?@???>=;:9999889::89:<<=<<::;;;;<<7:?DIKLMQOLJHJMONNMMKJIJJJJJJJJIEQkSXWRUVWY^biouy{z||rf[NC;54210/..00/...--8DPW`jsu{y||sfWG=877888999:;<======<<;;::9::::9865223232232233344445689;<=9;;<>?@@@@@?=<:9::::::;;9:<>?>??:;;;====?BIOUY\]a\SKGHJNMMLKKKJJKKKJJIIH@V{WWQSUVXZ_ekrw{zvqopxnbUG@985320/0322100/0EQ^diquw}z}|sfXMC>9999:;<<<<==>?@@AAA@@???<<<<;<;;9:9988888876654466678:;<;==@BBBBBBA@?==<:::999::6FB=C=6C;>=88?FGWUY\XQQV`bhi`SNQLMHFHMLHKJGGGGC@c~`phS_W\ad``gpkzt_VW\imy~vj^SLG;;==>>?@@@BBAA@@?????>>=<;::::;:::8887655466678:;<<=<<;:;:::;;<;47AFIRRUUUW^a`TRTWRLLQVRRVZY]cb^VNLONJGRWM@@Tijl\[xlN^d[\fkhhlvxtfXVWW]_dnrqrv|~{wuvh`YUSB%866:==AGPZado}zre[RPO8889;=>???@@@@AABBBBAAAABAA@>==<;;;;<<<=9987765566678:;<<=?AACBBBA@?>=<<<;<;;<=RamuuUY[^dkjfUTSTQMNSZQJNSWY]IS]de`SGFC@BJXfogVZPhxZdg`bnqknv~uja_`]Z`]]`_\^bhilnqsuwtsqpqswzzrtze<28@B<7@LNV^cr}xoe]UVV7789;=?@AAAAAAAACCCCBBBBDCBA?>=<;;<<<===:998776677779:<==?@@BCBAAA@?>=<;<<<<<=>>=4LkrttfKNPS[cb^YYWVVUY\]WQPQQNLSUTTXelmRF?LbneX]PYV`shc\gqrkkztlddgjf_ie``^]^caacceghiljhiimpswy{wulB<:?AAAABBBBCDDDDDDDDCBA@?>><<<<<<<<;::9877677789;<=>?ABCCBAAA@?==;;==<<=???=AayodYEJLLNV_^ZWWVUW[_`TURPPRUVbb]XYclp^fmniaZXWYRYTXmaVgogeuib_afijipha_^]`deeffgiklnnomnnnnqlquorz}oP22DOJAJZl{þvlf`WWY56678:<=??@ABBCCDDDEEEFFDECBBA@@>>==<<<;;;::98877788:;=>@@ABCCAAA@?>==;:<<<<>???DWpte\VJKMLKPVWSNOONRZ]\UURLNW^bT]ffdaZTZhrn_UU\T^NWPP{{ab^\ktjd_^`dmvpia_][^cddefgjlnhhjkiigergksttxxvZF==EMSg~Ǿyohd[\]5556789:==>@ACDDEEEFFFGGEFDDDDBCBBA@>===;;;:99888889:;=>@ABCBBBA@@?>=<;:;;;;=>?>?_jYNONNJNPMNPPLKMMKQY[XYWRNR\`\Y\WQMSY[SUWXWXWXUWSWTYuraYfz~rglfa_][cmngcba^`edcbccfgijklnnnmlpkoss|oP@I[iƺ~rliedc55555688;<=?ACDEEEEFGGGHGGGEEEEEDEDB?>><;;;;:8888889:<=>A@BBCBBBA@?><;:::::;=>>=LrrQHLJKPWYWTTQMOPPNS[]XSSQT]f`SRY]YUUTSVXZZYXWW\R_VVZXyb`xqeic^]]YRQVidbddbdhkigedefhjkkkkkkkluzqhzuh`vollki12457888:;<>@BDDGGGGIJKKJFEGJKE@FDABDC>:=<:99:86;;::;;<=?@AAABB@@@=<:;<=@89A>7@BCDGGGGIJKLJIJPVZVQJFA?@A?<<:8899985569:;==?@AABBAA>>=<:978><=?=;GVdRHLOLMROQUWVSRRPPPQTVWUUQU_bZSRVWXZ[[[ZYXYZYZZZ\WX\YRWbchpslcbjeeaZURRS\ejfcehikjlnlhhlqnjjlptutrru|vonllv}Rdz./01356699;=?ABCFHGIIJKKHKR_iomk\SIBA@><=;989:::5679;<<@ABFFIIKKLLJP^m|yobVOHC>A<97899878:;=<;;?@ABAAAA<==;;::99<:67DNUOMPUTNMPORWYYXYZYVRPQRSRTV]a[OOXWWXYZ[ZXYYXXWVUUTSV]]Y[aSTYdmld]_^[XSRQSMYegcdlteeippkiklnpsvxusowztt|{smq_BQƹ00//0134679:=>@AEFHJKMLLKTey{qh^SMID>96655678:;<=@?@ABBBBA@>;87766:44?OXRKRRTTTSUVORTUUWY[[XVTTUUSU`_OKW\TWWXYYYXWYYXXXWUTXUUUVX\^xpf`be`YTUTTSUX[[YZ_dgjofdhqtqnojkoswxwuyzww|zjytv`z:85311234578;<>?DDHKLMLKLYo|tfYOHDB@>;9:;=@@@ABBBBA>@BFGHGFF==LYXOKSSSTVVVUQRTUVWY[YYWWXYVU]]YRT]]VWXXYXXWWYYYXYWVVYXVUUWXWmvwnc^ZWUUUTSUY\`\]cgffihehpuurrllnrvwwxzyx~wo|yplttty=;7422234568:<=>CDILLNMKLZp{l`XRONJD>;;<>?ABCBBCACIS[bdca[NN]dXPPVVVVWXUQQSTVUWY\XXWYZZYWbYTY__[YXWWYXXWWXXYZYXWWVWWUWYWUTm}k\USVVVTSTWZ`^bhicbfjfiqwvtunnoqvyz{x|ysu~qyszqrs>=<:864422347:=?ADHKLMNPLTgvi^VVVSLA;=;976533346:=>@CFIKLMOMSd~vkd]\YTLEBA=;:665568;>9877799;?AEFGFHJOOYn|pid^TKKJLS]hpsqstoiaXOZfi_\baXWVVXZ^acdb_\XWVUSVY[[ZYYRXYRRX]Z\fnk_UVZ_\YUSSTU\]^^___^Ww[[pfp[XUVVXXVUW\bc^ZXaaelnjijy~tru}{yy}rvjK[KJIGECAA<;:99::<;:;;<@BEGGGIILKTjvpjikjebabceda][rg]XZ]YVVWVVTTRQUTTUUWYZ`^]ZYXVUUTTV[\VOWSS[dfc]USRTX]``\[ZYXVSSP\rW_YqkcYUVXXXUXZXUUUVXX^fjhgj}}y~ziNTiONLJHGED@@=<:;<=@CFGGGIIJISjtqkgdbbadcdec_[Zpd\bgc[V_`_]ZVRPQSTVWWWWc_YX[\WSYRPXXRU`UWYXZ\bgrngca^XSVUWXWXWWOLz^cZlrh\UUVWUVZ[WRSVYQRW`dcde}{~yvĴeMRcOPNNKKIIDDBA?@?@CEFIJKLLONVkzfda`aabba_eX`aNilfca``^\_\`hfXQTSSTRQSVYc[UV]_ZSRSSRRSTUPSUVVUUWVY[ZWUUWXVUX[\XTLQuwYlSgniaZVUWYYVTUVWVURTWYYZZ\}~kTOnPPOMLKJIEECBAAABCDFHJKLLONWkmb`__``ab_]b[`]Qogda^\\[ZcZZcf^TSRSTUUVZ\a]YXZYXTXVVWYYWTUWWVTSSTTVVUTTUWTUXXYWXX^Hb]eubbtqib\XVUYWUVXXYVSTVWXY[^~~|xw{kYVrRQQPNMMLIHGFEEEDEFGIKLLLNOXk~saba`a``a`_Z`__XVvdb`]YXYZeWQ\fbWRQSUVWZ]aUWXXXY[]QPOQSTQLba`\WTTVWURRTTTSQUYXUTX\_OqZadmuusnhb[VSUSRTVWUURSUVVX\_v|~q~owywg]\vRRQQPONNKJIHHHHHFGIKLMMLMOXj{~tg]cddccbbbb[^c]RZybca^[Z[^bVNW`_XSSUXZ[\`bUUVXYZ\]UWWVVWYXgfb]XUWX[VRRSUSQTVWVTUX[XZ}fURcz~p`SKUTSTUUUTSTTTSW[_gz||z{gxjqwrcabyRRQQPOOOMLLKKKKKHIJLMNMMLNWgvyyxtnfa`effffecce__eZN_rdcca`_abZTQTYWUVVXZ[Z\]__[XXZZVRRVXUQRZa^^[WSRTVZVSUX[YX\XUUWXXX[X^dR^WZW\dlqssr\ZVVVVVUSSTSSUY\f|}|}}~hlvxlnqQQQPPOOONNMLLMMNJKLMNONNNNUamw~xrnrpmlga_cfhihhgeefb^eWNeib_]_accbTSTTSTUVWZ[[ZZZ[ZVUY]`\XRVXUQR[`YYXVUUVYUTV[^aaad]WUWYXUXWQeVeUPTWYZXTQNa\WSRQQQSSTSRTWYk~zjhsPPPPOOOOONNMMNNOJKMMONNOPMQYbir}{vpkfhdehfb`deghhgfeebd]cSPj_\WTV]bb^TVVUSTUUVXZZYYYYSUY[]_``a`aabbaaVXYYXWX[TX\```abgd]XVUUVQYbgUT\[VXY[[ZYXhbZRONOORTUTSRSUd|}vlfnĿPPOOOONNONNMMNONLLNOOONOQNOUZ`isy{~zvtqmi\Y]fgcaddffffede`d]bRSoZVOKOZ`]XWWWUVWVTRUXYYWXZ\`c`ZVVXYURVZYRJPSUWVUUVW[`b_\[\ghbZSPTWTWe\WF^ZSUUUVUTTvncZUTUUQSVUSQQRTr|tkckĺOOOOOOOOMMNNNOONNNOPOQPQNNOPTX\_intv{z{}vhWVY`b_dleeddfgfd^\YWTT^iU[SSb\PWQUWVTUWWWXWTRU]d`]ZVSSSRTOPVWRLJVOMRROQXZ\^^[WUU[YYsCKfNP^g^SRZ``[URTYYUv_QTRMPKLLNORUUR\voiie}þOOOOOOOOMMNNNOOONNOOPQPQRQPPRUXZ_flov}x}yhQMPX__cgdccdfeccYXUSQR[febWV``UNSVXXWWXXXUTUY]__WVUUVWYZVRRVTOHGMY]SNSUQa`^\[[]`V\YfgQTDWag_VUY\ZXSSVYYVt]PVVRURRPQQOPOT\soiii|NNNNNNNNLLMMMNNNNNOOPQQPTRRPQRTUW]bgq|}}iWRPW_bcbcbcefea_WSQQPQYcc[Y]cmdMTUWYZYWWVSQYbfaZXXVUUTUVVSRUUOIJRZ^`cc]T[ZYXYYY[T`Y^eRQ]bb]XWWVTTSUWYYX}qYNY]Z\^]^\YVRPUZnnhikx{{{~{ruzMMMMMMMMLLMMMNNNNNOOOQQQSSQQPQRRTY^cm{}igc`bccbbedfghe_[ZURRPQXa\U^derrUVSSWZZWWUSU]dd\UYYXVUUTTRPQUUQPUaQVvvdbSTYZ[XRNTYTex_i_][XWVTQQQSVWUVYukVP]b]^babb_[WUTVi}zldhmprllorw{}|z~MMMMMMMMLLMMMNNNNNOOOPQQQPPQQQRRSWY_jw||vfjmnicabdffghhd_[ZVRQPPU\g`ge]kug]TOTYWUWQU[_`]WSVWXZ[]^_VUXZYWZabZf|jf\\[]]ZTORNWy}ybic\WUXXURRQSWUPS[kdUS`c\[YZ[]][XWQRdyvmdhpkdahmrw{ǺMMMMMMMMLLMMMNNNNNOOOPQQPPQQRRSSRTUZesxw~rb_gmh_^adgefggd_ZYSOPPQUYkdb\Vcym]PSWVVXRX__ZVUU]]^^^^]\[[^`[Z`i[t|vk_h`WRRUUUWOhjnZZk_WVZ[XWSRSVRMU_ecXU_`XYVWY[\]]]SP]ttpfjrg[_lszñMMMMMMMMMMNNNOONNNOOOPQQQRRRRRRRRSTYetzx|ueY`gd``badddeea]XWQNPSTX]`^VUX[nmXWWUUYVZ]]XVY[_^[YVTQOUW[[XWaljzopgWf[NGIOSTXUtxcaSYsdWVXWVXUQQSOLWhee[W]]WZYXYZ[\]^WPWi||ooegpaT]kuӿLMLMLMLMLMMNMONNNNOOOPPPTTTSSRQPTTU[ix||l^bgedfc^babbb_ZWWROSWY]bY\TX_RUty`YYVUZY[[YXY\_USSSRPONJMRVSUaoztspcU_WMJLPRPPQpV[QO`ueWSTRSVSPNQMKZogh]VZYV]YXWVVWXY\OSdv{xjlacl[M[ft̼MMMMMMMMNNNNNNNNMNOONPRTUTRSTTRPSQU_n}peaeiebba_^]\ZWUNWSN[b\WZZXYZ]ZV\tydU[`YUkU]_VkSTWVMQVVLKXLQXN}W~xtkaX[\XMHJNPOQ_WPUTcjiaWRSTSVRICIVafneYTV[]]ZZZYYZZ[\OYWx{rmcedyPHWl|MMMMMMMMNNNNNNNNNOQPPPQSTSQQRSSRSTXbq~sjmplea^^\]][WURTPS[ZU\VWWWX[[ZYdni]UW[]nW^`XjRQRVXXTTVOPPfD]jM}{re^]T]_SD>=>DTndST\vee_VRUWXZVRQSX_fjbXTVZ[[ZZZYYZZ[]PWVv~xrkdglkLHXm{m[NNNNNNNNOOOOOOOONPRRRQQSSRQQSUXYZ[akywkkmlgc][[\]\YVTRQZbWNXVVXYXYZ\WWcogSR``mX^a_n]TNO\XRP[RaTQ\xdwqqh]X^SZ[UOKD?=JaWLSa~^a_ZX\aaa^_ca]blb^WVW[[[ZZZYYZZ[^QSTszvphflsVFI[p|`L=OOOOOOOOOOOOOOOONPRSROPQPSWZ[]`adhnwxqmieb\\[\]]ZWQTU^lbQNWW\]\XX[YU`okYXcZdWZ^busg\MXTZRWaBYR^j|r^cda][ZZVVU\iqj^QSgkqysyT\`^^adcc^_eb[cq\YVWY[[ZZZZYYZZ[_SPSn}wpkehnvEDObr~y^PDPPPPPPPPOOOOOOOOMORRQPPRRYbilkklorxy{vlb]\\\]^][XOYSSjugVUTX_^XWZW[`ffba`T]XWXav}uTVRj\QRcWW_MOG\cYW]ZQTZep~{\`zl^S]db]\ZX^XVXVUapWWVVYZ[[ZZZYYZY[_UMQfyqjgbjon>EVfurg^PPPPPPPPPPPPPPPPNPPQPPTU\dpxxvutvy~v`dltvuod[_^^__^\XSZMFa~|mWPPY\XWZU\^[_fd[U[[WT[mz]]On\TXKJz`[X@J^eXQVULUlijzuniUTajni`[WS[YWTU[gqVWVXXZ[\ZZYXYYY[]XMP]wmfddmk\>FXft~zrQQQQQQQQPPPPPPPPQRQQQSW\ir}{zz{oVWY^gpmdb``_`^ZXUZNIby~|gXPUZWV[WZ[[`f`WYW[WVZ_hq|iuWi[dZQSiYXC[[_b]TPWao|{}xv`SVUdjrsi_ZZXY]]W[ekiWXWXXY[]ZZYXYYY[[[LPT}uleefphGCFV`p|xpQQQQQQQQPPPPPPPPSSRRQV\`t|}{{ý{mZTatwqdb`a_^YWUXUWhos{zeTUYVV[ZXY_ed]W[UYY[]UYezygk`~ar^P[m``e`Vf~||njXSXR[ejj`WVZ]Q\^X[hg[XYYXWY[]ZZYXYYY[Z]LPP}{ulfhjsf:GERZnxzoh`NPRRQQRSTQOPSUTSURRSSUcs{}ws|Įk`mmb\^^ZX\VR^WanezypaWUYZYUVZ`_YX[VVWYZ[[[YkzqZY{}y}zlbb^VYdxtfYVTSV[eb_\ZZ\[_^^^_]ZWWXZZXXZ\[[ZYZZZ[aWRJUrtomhetoM3;DK]dpuibPNKNPRSRQRSURQRTUTRXSRV\bmy}zstv|ǿiyylbbca[X]V\QWc^sxbQPWXTZ]`^ZWXY]\YWWWXYZ_kxvqt{r\WVYSLQ_u~~ug^YZYWVY`_][Z[]_^]]^^\YVWYZZXXZ[[[ZYZZZ[^\UMRu{tokhjqdI69?EQ^k{vk`UOOPPNPSSSSTUVUTUWWURWSS\gpx~xmjrz¯zap~kdec^[_[`UQWT^v^RUXUYej_TSVTZWUUW\_cMTdw|vtjkrzbVX]^WR`rotzvf\Z[aa^YY\\ZZZ]`a]]]]][YUXYZZXWY[[[[ZZZZ[\dWPK{}vtmefnpWF@;ACFGIMPRSTW\dmrsutqjecbjpy}nf_]][\`dnnn}sxQNTTTTUXY\YYZYXXXY]\ZYXVUTRUZ]^\YWQ[flnk`TVXYYWVVXZYXWTRPPQQUXYZ]_[\]^^^]\[[]\[YXWZZZZ[Z[ZZYZZ[[[[^UWvorlgbgriJ278;?@>?D?L^kptuwwy{~~~zyywuuwwutuwz9ADHLNORX_hprqkc[Y\chsy|tga\\^[Z[dqstYUVVVUUVVW\WUX\\VNVVVWXZYWVWVVWXYZcb_Z[^^ZVWZZYWWWTTUUUTTSSSUY_a^\[\]^]]]\\]]\\ZXWZZZZYZYYWXYZZ[ZZ[Ul{mglaehpjB'5049??<>DWw{rswy}~}zxwxyzyw~258;>AEHJLOV_hmmldZTU]gnw}{sc][]^\[[[inp{[TVVWWVVVV[ZXXXWVUXWXWVVY[YZYXWVTS^^[WWZYTQSXZZYXYZ\\\\\\\XWX[^_\X[\]^]\\[\\^^\ZXWYYYYYYXYXZ[\\\\[W^wflbc_qvR1/406>?:;HYutx{~{yxy{{zz/268;>BEHHLT_fiid]VSWbmt}ypa[Z^a_]\\jpqz~yWQWXXWWXWVXYZXWUXZ[_`]WSVYWWXYXVTSUZ]^^]VMNRW\\\]^]\\[ZYYW\[Z]]\YX[\]^]\\[]]^^\[XWXXXYXXXYXYZ\\]][Wkvqcm^e_rl>.9448:75@`ľ{vx{~{zyz|}||-/3569=?EEHP[ddba\XXbnx~sj^YZ_c`_`^mqqx{~uxaRWVWXYXYXWRVXYXVVWXajg\TTYVVVVVWWUU[__aa[STW]__]\\YYXYWVVV]]^][ZZZ[\]]]]\[^^_^][XWWXWXXXWXVVYZ[[ZZZypjgebdliP98;57789>U~~uv|}~|zz{~~}+-02369CIPUZ]]`fny|vmd^_^^^^_`abadknpt|vk_WX[[YWWXXXXXWQTVZ\ZWTUYpgSXWQZYXXXWWWWYYVSTZ`j`XYZXY]\WU]hnje_^]][\]]\\^^____^]]^_\YVUVVVWWWWTZTNUWW]wkcbeglWMB=<<>B6=Lelfpty|z{~~//.-/135;=AFMRWY\`gr}zsmg`]__^]]^_ad]\eosssa^[ZZ[YYWXYXXXWWSSTWYYWTRhe`[SVRWWXWWUUTYXVUVY^ab\XZ[\^b[cp{|ug]Z[[[\]^^[]]_^_^^]]\\^[WUTUUVVVVVSYQOTLRqrdahnljHFC@;68=2@]nY_mrw|{z|32101235:<@EJPRUW\do{zqjd`]\_^^]]^^_[X\jrrjdVXYZYYYZWXXYXXWXUTRTWYWURt\W_PWWTUVWVUUSVUTX]bcbZ\^]ZY\`wwcZYZZZ[\]^_\]^_^^^^^]\]^\XVTUUTUUTTRXPPTFSzoddlplhQJDDC=734Gi{XPZhrx~zz{~77655667<>AEJMPRUY`kv~{xqib^]\[^^^^^_aa]]`imf\YUWZYWWYZXXYYXYXYWSPRUYXUX|]T^OSXVVWWXXWWTSTZ`ba\]ab]X[fo~iXTZc\\[[Z[\\]]]^^^]]_^]^_]YWSSSSSRRRRTOOOI^rnjkmnifQLGFC@ADQb|]JMZerw~zz~<=<<<<<<@ADGJNPQVY_fq|~{z{}|wunic^]]]]^_^_`abbjd``\VTUY[YXVWX[XXYYYYYYXSNOSXXV_|iY]PKTUSSTTUVW]]]`bc]Y^``^cssZOS\_^^^]\[[Z[\]]^^^]]_^^_`]ZWRRRRRQPOQNONLWqonmlkjhhIKI@45H]w[EAKYcrx~{}AABBBBABDEGJMPQRYZ\ais}xspqsuvurnkda^]^_`__^_abceblbXVTRTX[ZXXXXYYXYZZYZYYZSMLRXXUZpua^YHUWUSTTWZ\fcbba`]]^^[`n[UQV`e_W^^]]\]\^\]^^^]]\]]]^_]YWQRQPPOONRJPLGh|rmihhjlmKIB;7Eg`PB?@ESasy~CDEFFGEFGHJLOPRTXXX\bmv|{slihjklkhdb^^]^`aaa```aacdceYQRVXXYYYXYZZWVXYZZYZZZZSLKRXXWSb|haaJWa^]\]`dgb`^[Z[^`b_\bmq^GJS[^^]_a]]]\]_^_^^^^_^\\\\\]]\XUPQPPOPONQFOKDtuwogcglopSE9<@@H]lz~uUUUUUUUUTTUVWXYY]]\\\]_`aabb`]YXVTQNMOQRWXY[]_ac`gcaji_]YWURSVXYZYZ[ZZYXXXXXXXYYYWQIIPVXZUQS[dkm\WTV[```_]ZZ[^_`TSOMMPUW_`a_]\]^]]]]]_^_]^___^][\^a`\UOMPOOMLJIICCIXhmf^_bgilljlogLLKG@:87:@>BC?<<<9?=;>?G\o|woj\\\\\\\\[[\\]^^_^_`aaa``aaa````a^ZUPMJJJMNPRVY[\__[cxvf_[YYVUVYZYXYZZYYXXXXXXXXYZWQKHKQXSUVVWY\\_\YXYZYW\[Z]]ZTMPQQQQQPOTW\_`_^^]]]]^^^_]]^^^_^^^_]XOKLMMMLJIHGGMAAVc__fddefgjkoeN;-67:=>=;98?<:=>F[p}yyrm]]]]]]]]]^^^__````abbbbbba````ab_^XRMIGGJJKMORTTXWT_tvmna][YXXYXXXXYZYYYXXXXXXXYZWROIHNUXYVTQSZ_\[]^__^]Y[]`_YQLQSSSSRPOORX\]^___^^^__```abba``_[ZUOKJLNLLKJHFEEG@G]ha]deeddfilov~w\A778:;;>@@;68>;9==FZp}{t|tn___________```aabbbbaaaaa````aaa`^[WRLIGFGHIJKLLORQVbcfthb]ZZ[YTWWXYYYYYXYYYYYYYXVSQKFKTXYXSNOV][^adddaaRW[\YTOMRSTTSSQPNPSUVZ^b______``ccca_\ZYTQNKJJKJLKKHGEDDABPbe^]fdefffhijb\OC<:;<;>@EFD=87=;9<=EYo}{y{}tn`````````````aaaedaa`_``aaaaa`a`a`^[WQMKEFFFGGGGJPPNOMYone][\[VQWXYZZZZZYYYYYYYYWTSSLEIRNSYZWW]bcehf`YRNLQVWSPNPQQRSSTSRQQPPRW^d`__`_``adca_[WSRMKHIKLJGLJJIGFDD@FU`^X^jcdfhjhghXI;=DC:46<<>:C[q~|y|}tmaaaaaaaaaaaaaaa`aaaaaaaaabbbbbbb``__]YQMIGCBCDCBDBCIHBYxzuibc_YXZ[[YXXXXYYYYYYY\][TLFEGLPV_fjlmde`VQSTQRQQOMMNMMLMNOPPQSQPOPQTTYZ^abbbb^\WRNLKKMMKJIIIJHGFFEDCACS]ZX]`^`ebdjd_jV^eaO>;C[r~y|~}tmaaaaaaaabbbbbbbabbbbbbbbbbbbbbbbbaaa_\UQKIECCCBAC=EI?Bbj{thjf[VWWWWVUTVVVWWWWWQRQLGEHKZ^eknlheba[SQTVUTSQONNMLIIJKLLMLPPNMMOQQSUWZ[\][TROMKLLMLLJIHIIIHHGEDCBALW\XU[`_]bbejeer{sS?EUmkbRF@=:7;:;?RmmUk}tmhcXVTTVXYY]]\]]^]]_``a`bfkonljgecbd`YSRTWXUSQOMMLLHHHIIJJJJIJKLNQQQQRRRRQPNMKKJJKMKJIHHGHHGGGEDBCBRXYUU[ab_c`ahip|bLIZlodUHB@>;7:8;?=DZp~zvzz{rkaaaaaaaabbbbbbbabbbbbbbccccbbbbbeccdca]ZROKFDDB@EDFAEfoQIXsskpgc][]adehhiijjjjssrpooprkifedeghh`YTSSTUSRPNLKKJHHHIIIIIGHHIKKMMNNNONONNMLKJJJJJIHGFEEFFFFFDA@BFVXVUW]ce`dacjil|ocSLSboucUG@A@>;8:8;@=CWm{uw{{qiaaaaaaaacccccccbcccccdddddddcccceccddc`]WSNJHDCACEDDRjcDSWnx}smd`adeefhgiijjjihgffffeddeefdbbh_VUTRRRONMKJJJJIIIIIJJJJJIIHGGFKJKLNPPQLMLKKJIHHGFDDCCECEFC@@EHYWUW[`dd\bdimdY^JHIUjvpcYK@?CA<99:7;A=ATiw|tyyohaaaaaaaacccccccccccddddddddddcccedcdedb_[WQLIFCABDCPa`SP^[e|~wnib_ab`^aabbccddbbegijjideffdb^]e[STUQNPMLKJHIJJIJIJJJJJIIIIHFEEHHIIJLNOKKKKJJIIFFDCBBBC@CEC@AEK\YXZ_a``^aagl^KHITakrqeTOB;>CB;9;;8;A=?Qdt~wy{~~}yogaaaaaaaacccccccccdddddddddddddcddcccedb`]YSNKHDBCBF^mUJe\WVauucfb_`dd`\aabcdddebbcfhgdaedbaacdfbWPSTQMOKJJHHIIKIIHIIIIIEFGGHHHGHGFFFEGHIIJJJJIIGEDDBBCC?BDB@@FM_ZX\`a^[ab^ah\HESesqha[WF94;A@;;<<8;A<>Oaqzv|z}}yxngaabbbcccbbbbbbbbccccccccdddddddd_cffdabbaa]VQNHAF?Mo|mg[\_bdgihbbcedaab_`aabbbaddeeefggedccbbcda[SOOONLMLKJIHJJJJJIIIHHHHHHHHHHGGFFFFGGFFGFFEEEEEDDCBAA=B@?A?CT]]]\]^_`a_dhaTRYwqic`[SM?:78=A?=7789;=?BZk}}yyxz|~}zxwkbaabbbcccbbbbbbbbccccccccdddddddddcdfhhfdciifc]ULI?AT_YSVjihhfffdncYY`c_Z`abddcbbdeefffggfecbcccdc_XSNLLKMLKKIIJIJJJIIHHHGGGGHHHHGGGGGGGGGGGGFFFEEEDCBBBB>C?>A>BS]]\]^^_a[]bhiilrmg_ZXUPKB=88GBEIOX`hkqh_\`cb^_`bdeeeedddeeffgfedccccccc]VOKIIMLKKIIJIJJIIIHHHGGHHHHHHGGGGGGGFHHGGGFFFDDCCBABABR[\]^_abbbdddiorq^XROPQOMFA;9;=<::;::;=>@Rct~}vvvuy{}~}{ywrg^aabbbcccbbbbbbbbccccccccddddddddfdaabdfffuyaQEEC>>F=;;;=AEGXbhe_]bf_`acdeeeeddeeeffeeddcccdbcaZQLIJMLKKIIJJIIIIHHGGGGHHHHHHGGGFFFFFHHGGFFFFDCCBAAA?=@=AQ[\]_abcdfhc]^ebXPLHGHLNOHD>;;;:9;;9;;<=>O_p|vtvvtyz{|{zxwqe\aabbbcccbbbbbbbbccccccccddddddddhjhebfox~zePHFCCFKGD>;878;K\`^aba`abddeedeeeeeeeeeddccccd`ab\UOLMMLKKIIJJIIIHHGGGGHHHHHHHGGGFFEEEGGFFFEEFCBBA@@??=;98:::9:;<=L\n{wqsuutwxyyywvvpe\aabbbcccbbbbbbbbccccccccddddddddbfgdendOGBABAA@?@BCE5>FQanl`edeffeedeeeedddddddccddc^_`^YROMLLKKJIJJIHHHGHGFHHHHHHHHGGGFEDDDEEEFDEEDBBA@@?>?=B>>>=A><>:>L[\]`aa```XOLG?8555444579ABDEB?<:54567:;;;<>:4A^xqqpmlifeeeeddcccdcbbbbdcca``^XNGLKKKJJJJHHHGGFGGHHHHHHHHHGFFEEDDDCBCBBAAAA@???>>;>:=M[]_aa`__TIBB>64:67664467>BFGEA=:324569;=>;;Msrkkkiieie]_he[aaaaaa__da_^\WOJMLLKJJJJHGGFFGFFGGGGGGGGIHGFECBBABBBBBBB=ADD@==>;A<798;I\^\[_bZNDB?<9987876654456:?CDB>;101348;<;Ndyrnjjnpqptxxtruvui[OccccccccccccccccccdddeeeeeeeeeeednͺgNGBA?==?;BJTm߽voohbcdgie]]b``aaa`_`ba`_]XPJMLLKJJJJGGGFGGFFGGGGHHHHHGFEDCBBBBBBBBBBABDDB?==;=87;79HV]b_[UJ>>==;::898776545569>BCB@>5420259;;=:69=69MY_`YME=5799:;:998877656668;?ABCB;830/269;Mf|{mkhijllmmrrqpsqn`QCccccccccccccccccccdddeeeeeefffggjջiOFHHD@HbġyXhliabih_]^_``ba`^_aa_XRMMLLKJJJJGGHGGGFFIIIIHHHHDDDCBA@@AAAAAAAAEB@ABB>:>;7;<3;Wc]TH>:::5789::9888776777679;>ADE@=61.0369MjpigffghiiknppqroiYH:ccccccccccccccccccdddeeeeeffggghq͹ZJHHEA_~y_enri^\`^^_`aaaa^_ba]WRNMLKKJJJJHHHGGGFFIIIHHGGGBCCBAA@@AAAAAAAAC@>?AA>;<=9::4@\^RB879:;789::976876778765679;?BDB?9411247OowdeddddefgjnpprsmeP@2ccccccccccccccccccdddeeeeeffghhin®eNIJNOzߡu]gl]Ye__````a_`aa_ZTPNLLKKJJJJHHHGGGFFIIHGGFEEBCBBAA@@AAAAAAAAA@??AA@?;?;9=>FZJ?668865999987656666766655678;>@A@<964225Pr~l]aaccbacdjmpqsri_F8,ccccccccccccccccccdddeeeeefgghiiisȳnVPUak~fdecba`aaa`_^aba^VQONMLKKJJJJHHHGGGFFHHGFEDDDBCBBBAAA@@@@AAA@@ABBABDFAF@>LUQS;648:534776555565655555565555789?@?>;7416Qp}{pcY]`cba`acimoqrpeW>2'ccccccccccccccccccdccdeeeefghiijnsдu^Ybt֋adhZbbaaa`^^ccb\UPNOMLKKJJIIHHHGGGFFGGFEDDCBCCCBBBBB@@@@@@@@?BEDCCGLJLEE]k^P:67:834;543224564544444476655565@CCCDFIIOMcn{K888777665554443323222222332112348:=@A?=:;Maf]ROP\]]]\\_agknooiYH.*(eeeeeeeeeeeeeeeeeeeedeffdgijfl̴n_h|Ɍ\aadd_WY_`bc^YTSSPONLJIIIHHHGFEDDDDEEDBB?BBBAAA@@=>?@BBCCABCDEFHKMUUivK8777666655544433333333334332233457;=??>>?LY[RJKQZ]^][Z[\ejnomfUD.+)eeeeeeeeeeeeeeeeeeeedeffhiijjwȯvmxک[c_U^qnZ^bd`XRRTOPMKKJJJHHHGFEEEDDDDDCA@CBBBAAA@???@ABDEEDCEGJKKOY[k~E776655555554443333333333332112332368;>>@AHNLDAGOXZ\[YXWXchmoj`M<.+)eeeeeeeeeeeeeeeeeeeedeffiijlpůʌ^_kc\`[`c_WPPTOONMKLLLHHHGHGGGEEEDCCBACBBBBAAADCA@@ABDGFEFJLLKNX]kp;6665544455544433222222222211112211347:=>?CEA;;AIRVYZYWWVahnni\G5,)(eeeeeeeeeeeeeeeeeeeedeffgilotɼmY^ecZ^a^VQOQOONMLLLLIIIHHHHHGFEDDCCCBBBBABAAEDBBBBCDIHIJLMMLNXbl^7666554445554443322222222210000123211357::<>=:9=AINRVWWXYaipofWB1+(&eeeeeeeeeeeeeeeeeeeedefffilpuvig_\^_^YTPNPONNMMLLIIIIIIIHIHGEFEEECCBBBBBBAABBDFGHHKPQPNNOSXgqxL:776655555554443322222222100//0014321124558<<;;;.+(&eeeeeeeeeeeeeeeeddeedeffhjkmpՁ]kb```^ZSOPONNMMNMIIIIIIIILJIFFGFGCCCBBBBBAADEGGGHHPXYUQPRVXkt~i:>87776666555444331111111100////002211223437:>?>=;;>CGJOSW]flh^N:,.+)eeeeeeeeeeeeeeeeddeedeffkkjikyzgdbbc_WOPOONMMMNIIIIIIJJLLIHGGHHCCCCCCBCCEGHHFEEHT^`XSRVVUktz].@8887776655544433111111110//..//000123344469>AA?>8;?BEJOS[djfYJ9)0-+feeeeeeeffffffffeeffefggmkloqwo]bib[^MMLJIIKNHIHJKKJIKKJJIJHHCCDEEEDEFDLLA<;3RMHJXd`SV^q}iF5:9998877777766554554443330000000/33222234026;?BFG?=:9;AJQW_dbTC60--,eeeeeeeeffffffffeeffefggmjlpqy~feecaUTRPNLJGKJIKJHHLIIIHHGGGEFGHGHHHLEKM@9:7JIDCKY__Xdtv]@7?;;:::9999998887766655544221100////..//0133359@DHCB>;;>DHT[a_SD82/..eeeeeeeeffffffffeeeeefggljlqrzʼna[eh^QHHJMNIFGLLHJQGGGGFGFGGHIJIIIHOEKTH<=@CEB=?L^h_nwjL::C=<<<;;;;<<<;;;::99887777443210//-----./0530049AFHGEA=;<>MTZZQE;5220ddeeefffffffffffeeeeegghkjlpqyזZQPQUXTI@VNKMLDAEGGGGGFFFGHHIHHHGKCTi]JFIADC?>GWckvt\B8<@===<<<;;==<<<<<<;;;:::9977643100/////01142//17<@IIIFA=::FKRSLD;7332dddeefffffffffffeeeeegghlknonuָz\OOTWMJIMPQRSHHHHHHHHJJKJJJHHHIc|pVJJABDDCELSuxlSA?><===<<;;;<<<<<<<<====<<;;::976433110000110//11569CGJJFA><@DJLIB;6321cddeefggffffffffeeeeegghkknoltվwbcZJ@ACAIIIKKKKLMOONNMMKKOj}nVICBBDGGDGKvqcQHID<>>==<<<<;;<<<===?>>>=====<;:98773210////./012223>>=<<<==>>>???>>===>>>=<<<;875310/./00111005;CIIHFFACGHGB<7321cbdeeffgeffffffeeeffegghijopnxƾސRDJJNNOOPPQQONNNMLKJKOXUEAGEIJMOKJWhc]YY[UNIAAA@@@??===>>@@A>>?=>==?>>=>>=;9753221110111208@FIHGGDEGIIC=8432bcddfghhefeedefffhgggfedfhkknMMKJNNMMNNMNLMNNMLJIMKFBCGIHRPKIR`aXXYZZXURPECA?>=>?===?>@@@@?>>=?>@?@?@@BBC???>;97623557;AFMNMLKKJJGLQSRNB7320bcdeghiigfffeffgghggffffgilnt߆LMJJNNNNNNNNLMMNMMJJMKGDEILMPQW^_ZTSTTUVVUTSKJGDA???<==??@??@@?>>?@@@@@@ABCCDECCA?=;<;::>FQXec`YRLHFJPX[ZVJ?20.bcdfgiijhhhhgghhghgfffffggjpyބJLIINNOOONNMLLMMMMKLMKHHIMQUT[l{s[LMOOOPQSTURPMIEAA@<=>??@??@@????@@@@@@ABDCFGFGECB@CA>=CO_itrlaTIB@IQ[`a\PD642bcdfhjjkjjiihhiiihgfeefgedgnyžޅILKKNNOPONMLKLLLMNLMOLLNPRY_m{q]TMLKKLOQSTROLHDCB=>>???>>A@@??@@AA@@ABCDDDFEFDCB@B@==DQalpnh\NB;:CLY_a]PD852bcdfhjkllkjjiiiiihfeeeggfegnyľKNLMNNPPPNLKKKKKLNNOQNOTVW_iw_MLJIILMPQPNLJFED=>>???>>AA@@@@AAAAAABCDDDEDEDBA@?>==CO]gjidXI=77>HU]`\PC30, \ No newline at end of file