1cc1dc7a3Sopenharmony_ci# SPDX-License-Identifier: Apache-2.0
2cc1dc7a3Sopenharmony_ci# -----------------------------------------------------------------------------
3cc1dc7a3Sopenharmony_ci# Copyright 2019-2023 Arm Limited
4cc1dc7a3Sopenharmony_ci#
5cc1dc7a3Sopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License"); you may not
6cc1dc7a3Sopenharmony_ci# use this file except in compliance with the License. You may obtain a copy
7cc1dc7a3Sopenharmony_ci# of the License at:
8cc1dc7a3Sopenharmony_ci#
9cc1dc7a3Sopenharmony_ci#     http://www.apache.org/licenses/LICENSE-2.0
10cc1dc7a3Sopenharmony_ci#
11cc1dc7a3Sopenharmony_ci# Unless required by applicable law or agreed to in writing, software
12cc1dc7a3Sopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13cc1dc7a3Sopenharmony_ci# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14cc1dc7a3Sopenharmony_ci# License for the specific language governing permissions and limitations
15cc1dc7a3Sopenharmony_ci# under the License.
16cc1dc7a3Sopenharmony_ci# -----------------------------------------------------------------------------
17cc1dc7a3Sopenharmony_ci"""
18cc1dc7a3Sopenharmony_ciThese classes provide an abstraction around the astcenc command line tool,
19cc1dc7a3Sopenharmony_ciallowing the rest of the image test suite to ignore changes in the command line
20cc1dc7a3Sopenharmony_ciinterface.
21cc1dc7a3Sopenharmony_ci"""
22cc1dc7a3Sopenharmony_ci
23cc1dc7a3Sopenharmony_ciimport os
24cc1dc7a3Sopenharmony_ciimport re
25cc1dc7a3Sopenharmony_ciimport subprocess as sp
26cc1dc7a3Sopenharmony_ciimport sys
27cc1dc7a3Sopenharmony_ci
28cc1dc7a3Sopenharmony_ci
29cc1dc7a3Sopenharmony_ciclass EncoderBase():
30cc1dc7a3Sopenharmony_ci    """
31cc1dc7a3Sopenharmony_ci    This class is a Python wrapper for the `astcenc` binary, providing an
32cc1dc7a3Sopenharmony_ci    abstract means to set command line options and parse key results.
33cc1dc7a3Sopenharmony_ci
34cc1dc7a3Sopenharmony_ci    This is an abstract base class providing some generic helper functionality
35cc1dc7a3Sopenharmony_ci    used by concrete instantiations of subclasses.
36cc1dc7a3Sopenharmony_ci
37cc1dc7a3Sopenharmony_ci    Attributes:
38cc1dc7a3Sopenharmony_ci        binary: The encoder binary path.
39cc1dc7a3Sopenharmony_ci        variant: The encoder SIMD variant being tested.
40cc1dc7a3Sopenharmony_ci        name: The encoder name to use in reports.
41cc1dc7a3Sopenharmony_ci        VERSION: The encoder version or branch.
42cc1dc7a3Sopenharmony_ci        SWITCHES: Dict of switch replacements for different color formats.
43cc1dc7a3Sopenharmony_ci        OUTPUTS: Dict of output file extensions for different color formats.
44cc1dc7a3Sopenharmony_ci    """
45cc1dc7a3Sopenharmony_ci
46cc1dc7a3Sopenharmony_ci    VERSION = None
47cc1dc7a3Sopenharmony_ci    SWITCHES = None
48cc1dc7a3Sopenharmony_ci    OUTPUTS = None
49cc1dc7a3Sopenharmony_ci
50cc1dc7a3Sopenharmony_ci    def __init__(self, name, variant, binary):
51cc1dc7a3Sopenharmony_ci        """
52cc1dc7a3Sopenharmony_ci        Create a new encoder instance.
53cc1dc7a3Sopenharmony_ci
54cc1dc7a3Sopenharmony_ci        Args:
55cc1dc7a3Sopenharmony_ci            name (str): The name of the encoder.
56cc1dc7a3Sopenharmony_ci            variant (str): The SIMD variant of the encoder.
57cc1dc7a3Sopenharmony_ci            binary (str): The path to the binary on the file system.
58cc1dc7a3Sopenharmony_ci        """
59cc1dc7a3Sopenharmony_ci        self.name = name
60cc1dc7a3Sopenharmony_ci        self.variant = variant
61cc1dc7a3Sopenharmony_ci        self.binary = binary
62cc1dc7a3Sopenharmony_ci
63cc1dc7a3Sopenharmony_ci    def build_cli(self, image, blockSize="6x6", preset="-thorough",
64cc1dc7a3Sopenharmony_ci                  keepOutput=True, threads=None):
65cc1dc7a3Sopenharmony_ci        """
66cc1dc7a3Sopenharmony_ci        Build the command line needed for the given test.
67cc1dc7a3Sopenharmony_ci
68cc1dc7a3Sopenharmony_ci        Args:
69cc1dc7a3Sopenharmony_ci            image (TestImage): The test image to compress.
70cc1dc7a3Sopenharmony_ci            blockSize (str): The block size to use.
71cc1dc7a3Sopenharmony_ci            preset (str): The quality-performance preset to use.
72cc1dc7a3Sopenharmony_ci            keepOutput (bool): Should the test preserve output images? This is
73cc1dc7a3Sopenharmony_ci                only a hint and discarding output may be ignored if the encoder
74cc1dc7a3Sopenharmony_ci                version used can't do it natively.
75cc1dc7a3Sopenharmony_ci            threads (int or None): The thread count to use.
76cc1dc7a3Sopenharmony_ci
77cc1dc7a3Sopenharmony_ci        Returns:
78cc1dc7a3Sopenharmony_ci            list(str): A list of command line arguments.
79cc1dc7a3Sopenharmony_ci        """
80cc1dc7a3Sopenharmony_ci        # pylint: disable=unused-argument,no-self-use,redundant-returns-doc
81cc1dc7a3Sopenharmony_ci        assert False, "Missing subclass implementation"
82cc1dc7a3Sopenharmony_ci
83cc1dc7a3Sopenharmony_ci    def execute(self, command):
84cc1dc7a3Sopenharmony_ci        """
85cc1dc7a3Sopenharmony_ci        Run a subprocess with the specified command.
86cc1dc7a3Sopenharmony_ci
87cc1dc7a3Sopenharmony_ci        Args:
88cc1dc7a3Sopenharmony_ci            command (list(str)): The list of command line arguments.
89cc1dc7a3Sopenharmony_ci
90cc1dc7a3Sopenharmony_ci        Returns:
91cc1dc7a3Sopenharmony_ci            list(str): The output log (stdout) split into lines.
92cc1dc7a3Sopenharmony_ci        """
93cc1dc7a3Sopenharmony_ci        # pylint: disable=no-self-use
94cc1dc7a3Sopenharmony_ci        try:
95cc1dc7a3Sopenharmony_ci            result = sp.run(command, stdout=sp.PIPE, stderr=sp.PIPE,
96cc1dc7a3Sopenharmony_ci                            check=True, universal_newlines=True)
97cc1dc7a3Sopenharmony_ci        except (OSError, sp.CalledProcessError):
98cc1dc7a3Sopenharmony_ci            print("ERROR: Test run failed")
99cc1dc7a3Sopenharmony_ci            print("  + %s" % " ".join(command))
100cc1dc7a3Sopenharmony_ci            qcommand = ["\"%s\"" % x for x in command]
101cc1dc7a3Sopenharmony_ci            print("  + %s" % ", ".join(qcommand))
102cc1dc7a3Sopenharmony_ci            sys.exit(1)
103cc1dc7a3Sopenharmony_ci
104cc1dc7a3Sopenharmony_ci        return result.stdout.splitlines()
105cc1dc7a3Sopenharmony_ci
106cc1dc7a3Sopenharmony_ci    def parse_output(self, image, output):
107cc1dc7a3Sopenharmony_ci        """
108cc1dc7a3Sopenharmony_ci        Parse the log output for PSNR and performance metrics.
109cc1dc7a3Sopenharmony_ci
110cc1dc7a3Sopenharmony_ci        Args:
111cc1dc7a3Sopenharmony_ci            image (TestImage): The test image to compress.
112cc1dc7a3Sopenharmony_ci            output (list(str)): The output log from the compression process.
113cc1dc7a3Sopenharmony_ci
114cc1dc7a3Sopenharmony_ci        Returns:
115cc1dc7a3Sopenharmony_ci            tuple(float, float, float): PSNR in dB, TotalTime in seconds, and
116cc1dc7a3Sopenharmony_ci            CodingTime in seconds.
117cc1dc7a3Sopenharmony_ci        """
118cc1dc7a3Sopenharmony_ci        # Regex pattern for image quality
119cc1dc7a3Sopenharmony_ci        patternPSNR = re.compile(self.get_psnr_pattern(image))
120cc1dc7a3Sopenharmony_ci        patternTTime = re.compile(self.get_total_time_pattern())
121cc1dc7a3Sopenharmony_ci        patternCTime = re.compile(self.get_coding_time_pattern())
122cc1dc7a3Sopenharmony_ci        patternCRate = re.compile(self.get_coding_rate_pattern())
123cc1dc7a3Sopenharmony_ci
124cc1dc7a3Sopenharmony_ci        # Extract results from the log
125cc1dc7a3Sopenharmony_ci        runPSNR = None
126cc1dc7a3Sopenharmony_ci        runTTime = None
127cc1dc7a3Sopenharmony_ci        runCTime = None
128cc1dc7a3Sopenharmony_ci        runCRate = None
129cc1dc7a3Sopenharmony_ci
130cc1dc7a3Sopenharmony_ci        for line in output:
131cc1dc7a3Sopenharmony_ci            match = patternPSNR.match(line)
132cc1dc7a3Sopenharmony_ci            if match:
133cc1dc7a3Sopenharmony_ci                runPSNR = float(match.group(1))
134cc1dc7a3Sopenharmony_ci
135cc1dc7a3Sopenharmony_ci            match = patternTTime.match(line)
136cc1dc7a3Sopenharmony_ci            if match:
137cc1dc7a3Sopenharmony_ci                runTTime = float(match.group(1))
138cc1dc7a3Sopenharmony_ci
139cc1dc7a3Sopenharmony_ci            match = patternCTime.match(line)
140cc1dc7a3Sopenharmony_ci            if match:
141cc1dc7a3Sopenharmony_ci                runCTime = float(match.group(1))
142cc1dc7a3Sopenharmony_ci
143cc1dc7a3Sopenharmony_ci            match = patternCRate.match(line)
144cc1dc7a3Sopenharmony_ci            if match:
145cc1dc7a3Sopenharmony_ci                runCRate = float(match.group(1))
146cc1dc7a3Sopenharmony_ci
147cc1dc7a3Sopenharmony_ci        stdout = "\n".join(output)
148cc1dc7a3Sopenharmony_ci        assert runPSNR is not None, "No coding PSNR found %s" % stdout
149cc1dc7a3Sopenharmony_ci        assert runTTime is not None, "No total time found %s" % stdout
150cc1dc7a3Sopenharmony_ci        assert runCTime is not None, "No coding time found %s" % stdout
151cc1dc7a3Sopenharmony_ci        assert runCRate is not None, "No coding rate found %s" % stdout
152cc1dc7a3Sopenharmony_ci        return (runPSNR, runTTime, runCTime, runCRate)
153cc1dc7a3Sopenharmony_ci
154cc1dc7a3Sopenharmony_ci    def get_psnr_pattern(self, image):
155cc1dc7a3Sopenharmony_ci        """
156cc1dc7a3Sopenharmony_ci        Get the regex pattern to match the image quality metric.
157cc1dc7a3Sopenharmony_ci
158cc1dc7a3Sopenharmony_ci        Note, while this function is called PSNR for some images we may choose
159cc1dc7a3Sopenharmony_ci        to match another metric (e.g. mPSNR for HDR images).
160cc1dc7a3Sopenharmony_ci
161cc1dc7a3Sopenharmony_ci        Args:
162cc1dc7a3Sopenharmony_ci            image (TestImage): The test image we are compressing.
163cc1dc7a3Sopenharmony_ci
164cc1dc7a3Sopenharmony_ci        Returns:
165cc1dc7a3Sopenharmony_ci            str: The string for a regex pattern.
166cc1dc7a3Sopenharmony_ci        """
167cc1dc7a3Sopenharmony_ci        # pylint: disable=unused-argument,no-self-use,redundant-returns-doc
168cc1dc7a3Sopenharmony_ci        assert False, "Missing subclass implementation"
169cc1dc7a3Sopenharmony_ci
170cc1dc7a3Sopenharmony_ci    def get_total_time_pattern(self):
171cc1dc7a3Sopenharmony_ci        """
172cc1dc7a3Sopenharmony_ci        Get the regex pattern to match the total compression time.
173cc1dc7a3Sopenharmony_ci
174cc1dc7a3Sopenharmony_ci        Returns:
175cc1dc7a3Sopenharmony_ci            str: The string for a regex pattern.
176cc1dc7a3Sopenharmony_ci        """
177cc1dc7a3Sopenharmony_ci        # pylint: disable=unused-argument,no-self-use,redundant-returns-doc
178cc1dc7a3Sopenharmony_ci        assert False, "Missing subclass implementation"
179cc1dc7a3Sopenharmony_ci
180cc1dc7a3Sopenharmony_ci    def get_coding_time_pattern(self):
181cc1dc7a3Sopenharmony_ci        """
182cc1dc7a3Sopenharmony_ci        Get the regex pattern to match the coding compression time.
183cc1dc7a3Sopenharmony_ci
184cc1dc7a3Sopenharmony_ci        Returns:
185cc1dc7a3Sopenharmony_ci            str: The string for a regex pattern.
186cc1dc7a3Sopenharmony_ci        """
187cc1dc7a3Sopenharmony_ci        # pylint: disable=unused-argument,no-self-use,redundant-returns-doc
188cc1dc7a3Sopenharmony_ci        assert False, "Missing subclass implementation"
189cc1dc7a3Sopenharmony_ci
190cc1dc7a3Sopenharmony_ci    def run_test(self, image, blockSize, preset, testRuns, keepOutput=True,
191cc1dc7a3Sopenharmony_ci                 threads=None):
192cc1dc7a3Sopenharmony_ci        """
193cc1dc7a3Sopenharmony_ci        Run the test N times.
194cc1dc7a3Sopenharmony_ci
195cc1dc7a3Sopenharmony_ci        Args:
196cc1dc7a3Sopenharmony_ci            image (TestImage): The test image to compress.
197cc1dc7a3Sopenharmony_ci            blockSize (str): The block size to use.
198cc1dc7a3Sopenharmony_ci            preset (str): The quality-performance preset to use.
199cc1dc7a3Sopenharmony_ci            testRuns (int): The number of test runs.
200cc1dc7a3Sopenharmony_ci            keepOutput (bool): Should the test preserve output images? This is
201cc1dc7a3Sopenharmony_ci                only a hint and discarding output may be ignored if the encoder
202cc1dc7a3Sopenharmony_ci                version used can't do it natively.
203cc1dc7a3Sopenharmony_ci            threads (int or None): The thread count to use.
204cc1dc7a3Sopenharmony_ci
205cc1dc7a3Sopenharmony_ci        Returns:
206cc1dc7a3Sopenharmony_ci            tuple(float, float, float, float): Returns the best results from
207cc1dc7a3Sopenharmony_ci            the N test runs, as PSNR (dB), total time (seconds), coding time
208cc1dc7a3Sopenharmony_ci            (seconds), and coding rate (M pixels/s).
209cc1dc7a3Sopenharmony_ci        """
210cc1dc7a3Sopenharmony_ci        # pylint: disable=assignment-from-no-return
211cc1dc7a3Sopenharmony_ci        command = self.build_cli(image, blockSize, preset, keepOutput, threads)
212cc1dc7a3Sopenharmony_ci
213cc1dc7a3Sopenharmony_ci        # Execute test runs
214cc1dc7a3Sopenharmony_ci        bestPSNR = 0
215cc1dc7a3Sopenharmony_ci        bestTTime = sys.float_info.max
216cc1dc7a3Sopenharmony_ci        bestCTime = sys.float_info.max
217cc1dc7a3Sopenharmony_ci        bestCRate = 0
218cc1dc7a3Sopenharmony_ci
219cc1dc7a3Sopenharmony_ci        for _ in range(0, testRuns):
220cc1dc7a3Sopenharmony_ci            output = self.execute(command)
221cc1dc7a3Sopenharmony_ci            result = self.parse_output(image, output)
222cc1dc7a3Sopenharmony_ci
223cc1dc7a3Sopenharmony_ci            # Keep the best results (highest PSNR, lowest times, highest rate)
224cc1dc7a3Sopenharmony_ci            bestPSNR = max(bestPSNR, result[0])
225cc1dc7a3Sopenharmony_ci            bestTTime = min(bestTTime, result[1])
226cc1dc7a3Sopenharmony_ci            bestCTime = min(bestCTime, result[2])
227cc1dc7a3Sopenharmony_ci            bestCRate = max(bestCRate, result[3])
228cc1dc7a3Sopenharmony_ci
229cc1dc7a3Sopenharmony_ci        return (bestPSNR, bestTTime, bestCTime, bestCRate)
230cc1dc7a3Sopenharmony_ci
231cc1dc7a3Sopenharmony_ci
232cc1dc7a3Sopenharmony_ciclass Encoder2x(EncoderBase):
233cc1dc7a3Sopenharmony_ci    """
234cc1dc7a3Sopenharmony_ci    This class wraps the latest `astcenc` 2.x series binaries from main branch.
235cc1dc7a3Sopenharmony_ci    """
236cc1dc7a3Sopenharmony_ci    VERSION = "main"
237cc1dc7a3Sopenharmony_ci
238cc1dc7a3Sopenharmony_ci    SWITCHES = {
239cc1dc7a3Sopenharmony_ci        "ldr": "-tl",
240cc1dc7a3Sopenharmony_ci        "ldrs": "-ts",
241cc1dc7a3Sopenharmony_ci        "hdr": "-th",
242cc1dc7a3Sopenharmony_ci        "hdra": "-tH"
243cc1dc7a3Sopenharmony_ci    }
244cc1dc7a3Sopenharmony_ci
245cc1dc7a3Sopenharmony_ci    OUTPUTS = {
246cc1dc7a3Sopenharmony_ci        "ldr": ".png",
247cc1dc7a3Sopenharmony_ci        "ldrs": ".png",
248cc1dc7a3Sopenharmony_ci        "hdr": ".exr",
249cc1dc7a3Sopenharmony_ci        "hdra": ".exr"
250cc1dc7a3Sopenharmony_ci    }
251cc1dc7a3Sopenharmony_ci
252cc1dc7a3Sopenharmony_ci    def __init__(self, variant, binary=None):
253cc1dc7a3Sopenharmony_ci        name = "astcenc-%s-%s" % (variant, self.VERSION)
254cc1dc7a3Sopenharmony_ci
255cc1dc7a3Sopenharmony_ci        if binary is None:
256cc1dc7a3Sopenharmony_ci            if variant != "universal":
257cc1dc7a3Sopenharmony_ci                binary = f"./bin/astcenc-{variant}"
258cc1dc7a3Sopenharmony_ci            else:
259cc1dc7a3Sopenharmony_ci                binary = "./bin/astcenc"
260cc1dc7a3Sopenharmony_ci
261cc1dc7a3Sopenharmony_ci            if os.name == 'nt':
262cc1dc7a3Sopenharmony_ci                binary = f"{binary}.exe"
263cc1dc7a3Sopenharmony_ci
264cc1dc7a3Sopenharmony_ci        super().__init__(name, variant, binary)
265cc1dc7a3Sopenharmony_ci
266cc1dc7a3Sopenharmony_ci    def build_cli(self, image, blockSize="6x6", preset="-thorough",
267cc1dc7a3Sopenharmony_ci                  keepOutput=True, threads=None):
268cc1dc7a3Sopenharmony_ci        opmode = self.SWITCHES[image.colorProfile]
269cc1dc7a3Sopenharmony_ci        srcPath = image.filePath
270cc1dc7a3Sopenharmony_ci
271cc1dc7a3Sopenharmony_ci        if keepOutput:
272cc1dc7a3Sopenharmony_ci            dstPath = image.outFilePath + self.OUTPUTS[image.colorProfile]
273cc1dc7a3Sopenharmony_ci            dstDir = os.path.dirname(dstPath)
274cc1dc7a3Sopenharmony_ci            dstFile = os.path.basename(dstPath)
275cc1dc7a3Sopenharmony_ci            dstPath = os.path.join(dstDir, self.name, preset[1:], blockSize, dstFile)
276cc1dc7a3Sopenharmony_ci
277cc1dc7a3Sopenharmony_ci            dstDir = os.path.dirname(dstPath)
278cc1dc7a3Sopenharmony_ci            os.makedirs(dstDir, exist_ok=True)
279cc1dc7a3Sopenharmony_ci        elif sys.platform == "win32":
280cc1dc7a3Sopenharmony_ci            dstPath = "nul"
281cc1dc7a3Sopenharmony_ci        else:
282cc1dc7a3Sopenharmony_ci            dstPath = "/dev/null"
283cc1dc7a3Sopenharmony_ci
284cc1dc7a3Sopenharmony_ci        command = [
285cc1dc7a3Sopenharmony_ci            self.binary, opmode, srcPath, dstPath,
286cc1dc7a3Sopenharmony_ci            blockSize, preset, "-silent"
287cc1dc7a3Sopenharmony_ci        ]
288cc1dc7a3Sopenharmony_ci
289cc1dc7a3Sopenharmony_ci        if image.colorFormat == "xy":
290cc1dc7a3Sopenharmony_ci            command.append("-normal")
291cc1dc7a3Sopenharmony_ci
292cc1dc7a3Sopenharmony_ci        if image.isAlphaScaled:
293cc1dc7a3Sopenharmony_ci            command.append("-a")
294cc1dc7a3Sopenharmony_ci            command.append("1")
295cc1dc7a3Sopenharmony_ci
296cc1dc7a3Sopenharmony_ci        if threads is not None:
297cc1dc7a3Sopenharmony_ci            command.append("-j")
298cc1dc7a3Sopenharmony_ci            command.append("%u" % threads)
299cc1dc7a3Sopenharmony_ci
300cc1dc7a3Sopenharmony_ci        return command
301cc1dc7a3Sopenharmony_ci
302cc1dc7a3Sopenharmony_ci    def get_psnr_pattern(self, image):
303cc1dc7a3Sopenharmony_ci        if image.colorProfile != "hdr":
304cc1dc7a3Sopenharmony_ci            if image.colorFormat != "rgba":
305cc1dc7a3Sopenharmony_ci                patternPSNR = r"\s*PSNR \(LDR-RGB\):\s*([0-9.]*) dB"
306cc1dc7a3Sopenharmony_ci            else:
307cc1dc7a3Sopenharmony_ci                patternPSNR = r"\s*PSNR \(LDR-RGBA\):\s*([0-9.]*) dB"
308cc1dc7a3Sopenharmony_ci        else:
309cc1dc7a3Sopenharmony_ci            patternPSNR = r"\s*mPSNR \(RGB\)(?: \[.*?\] )?:\s*([0-9.]*) dB.*"
310cc1dc7a3Sopenharmony_ci        return patternPSNR
311cc1dc7a3Sopenharmony_ci
312cc1dc7a3Sopenharmony_ci    def get_total_time_pattern(self):
313cc1dc7a3Sopenharmony_ci        return r"\s*Total time:\s*([0-9.]*) s"
314cc1dc7a3Sopenharmony_ci
315cc1dc7a3Sopenharmony_ci    def get_coding_time_pattern(self):
316cc1dc7a3Sopenharmony_ci        return r"\s*Coding time:\s*([0-9.]*) s"
317cc1dc7a3Sopenharmony_ci
318cc1dc7a3Sopenharmony_ci    def get_coding_rate_pattern(self):
319cc1dc7a3Sopenharmony_ci        return r"\s*Coding rate:\s*([0-9.]*) MT/s"
320cc1dc7a3Sopenharmony_ci
321cc1dc7a3Sopenharmony_ci
322cc1dc7a3Sopenharmony_ciclass Encoder2xRel(Encoder2x):
323cc1dc7a3Sopenharmony_ci    """
324cc1dc7a3Sopenharmony_ci    This class wraps a released 2.x series binary.
325cc1dc7a3Sopenharmony_ci    """
326cc1dc7a3Sopenharmony_ci    def __init__(self, version, variant):
327cc1dc7a3Sopenharmony_ci
328cc1dc7a3Sopenharmony_ci        self.VERSION = version
329cc1dc7a3Sopenharmony_ci
330cc1dc7a3Sopenharmony_ci        if variant != "universal":
331cc1dc7a3Sopenharmony_ci            binary = f"./Binaries/{version}/astcenc-{variant}"
332cc1dc7a3Sopenharmony_ci        else:
333cc1dc7a3Sopenharmony_ci            binary = f"./Binaries/{version}/astcenc"
334cc1dc7a3Sopenharmony_ci
335cc1dc7a3Sopenharmony_ci        if os.name == 'nt':
336cc1dc7a3Sopenharmony_ci            binary = f"{binary}.exe"
337cc1dc7a3Sopenharmony_ci
338cc1dc7a3Sopenharmony_ci        super().__init__(variant, binary)
339cc1dc7a3Sopenharmony_ci
340cc1dc7a3Sopenharmony_ci
341cc1dc7a3Sopenharmony_ciclass Encoder1_7(EncoderBase):
342cc1dc7a3Sopenharmony_ci    """
343cc1dc7a3Sopenharmony_ci    This class wraps the 1.7 series binaries.
344cc1dc7a3Sopenharmony_ci    """
345cc1dc7a3Sopenharmony_ci    VERSION = "1.7"
346cc1dc7a3Sopenharmony_ci
347cc1dc7a3Sopenharmony_ci    SWITCHES = {
348cc1dc7a3Sopenharmony_ci        "ldr": "-tl",
349cc1dc7a3Sopenharmony_ci        "ldrs": "-ts",
350cc1dc7a3Sopenharmony_ci        "hdr": "-t"
351cc1dc7a3Sopenharmony_ci    }
352cc1dc7a3Sopenharmony_ci
353cc1dc7a3Sopenharmony_ci    OUTPUTS = {
354cc1dc7a3Sopenharmony_ci        "ldr": ".tga",
355cc1dc7a3Sopenharmony_ci        "ldrs": ".tga",
356cc1dc7a3Sopenharmony_ci        "hdr": ".htga"
357cc1dc7a3Sopenharmony_ci    }
358cc1dc7a3Sopenharmony_ci
359cc1dc7a3Sopenharmony_ci    def __init__(self):
360cc1dc7a3Sopenharmony_ci        name = "astcenc-%s" % self.VERSION
361cc1dc7a3Sopenharmony_ci        if os.name == 'nt':
362cc1dc7a3Sopenharmony_ci            binary = "./Binaries/1.7/astcenc.exe"
363cc1dc7a3Sopenharmony_ci        else:
364cc1dc7a3Sopenharmony_ci            binary = "./Binaries/1.7/astcenc"
365cc1dc7a3Sopenharmony_ci
366cc1dc7a3Sopenharmony_ci        super().__init__(name, None, binary)
367cc1dc7a3Sopenharmony_ci
368cc1dc7a3Sopenharmony_ci    def build_cli(self, image, blockSize="6x6", preset="-thorough",
369cc1dc7a3Sopenharmony_ci                  keepOutput=True, threads=None):
370cc1dc7a3Sopenharmony_ci
371cc1dc7a3Sopenharmony_ci        if preset == "-fastest":
372cc1dc7a3Sopenharmony_ci            preset = "-fast"
373cc1dc7a3Sopenharmony_ci
374cc1dc7a3Sopenharmony_ci        opmode = self.SWITCHES[image.colorProfile]
375cc1dc7a3Sopenharmony_ci        srcPath = image.filePath
376cc1dc7a3Sopenharmony_ci
377cc1dc7a3Sopenharmony_ci        dstPath = image.outFilePath + self.OUTPUTS[image.colorProfile]
378cc1dc7a3Sopenharmony_ci        dstDir = os.path.dirname(dstPath)
379cc1dc7a3Sopenharmony_ci        dstFile = os.path.basename(dstPath)
380cc1dc7a3Sopenharmony_ci        dstPath = os.path.join(dstDir, self.name, preset[1:], blockSize, dstFile)
381cc1dc7a3Sopenharmony_ci
382cc1dc7a3Sopenharmony_ci        dstDir = os.path.dirname(dstPath)
383cc1dc7a3Sopenharmony_ci        os.makedirs(dstDir, exist_ok=True)
384cc1dc7a3Sopenharmony_ci
385cc1dc7a3Sopenharmony_ci        command = [
386cc1dc7a3Sopenharmony_ci            self.binary, opmode, srcPath, dstPath,
387cc1dc7a3Sopenharmony_ci            blockSize, preset, "-silentmode", "-time", "-showpsnr"
388cc1dc7a3Sopenharmony_ci        ]
389cc1dc7a3Sopenharmony_ci
390cc1dc7a3Sopenharmony_ci        if image.colorFormat == "xy":
391cc1dc7a3Sopenharmony_ci            command.append("-normal_psnr")
392cc1dc7a3Sopenharmony_ci
393cc1dc7a3Sopenharmony_ci        if image.colorProfile == "hdr":
394cc1dc7a3Sopenharmony_ci            command.append("-hdr")
395cc1dc7a3Sopenharmony_ci
396cc1dc7a3Sopenharmony_ci        if image.isAlphaScaled:
397cc1dc7a3Sopenharmony_ci            command.append("-alphablend")
398cc1dc7a3Sopenharmony_ci
399cc1dc7a3Sopenharmony_ci        if threads is not None:
400cc1dc7a3Sopenharmony_ci            command.append("-j")
401cc1dc7a3Sopenharmony_ci            command.append("%u" % threads)
402cc1dc7a3Sopenharmony_ci
403cc1dc7a3Sopenharmony_ci        return command
404cc1dc7a3Sopenharmony_ci
405cc1dc7a3Sopenharmony_ci    def get_psnr_pattern(self, image):
406cc1dc7a3Sopenharmony_ci        if image.colorProfile != "hdr":
407cc1dc7a3Sopenharmony_ci            if image.colorFormat != "rgba":
408cc1dc7a3Sopenharmony_ci                patternPSNR = r"PSNR \(LDR-RGB\):\s*([0-9.]*) dB"
409cc1dc7a3Sopenharmony_ci            else:
410cc1dc7a3Sopenharmony_ci                patternPSNR = r"PSNR \(LDR-RGBA\):\s*([0-9.]*) dB"
411cc1dc7a3Sopenharmony_ci        else:
412cc1dc7a3Sopenharmony_ci            patternPSNR = r"mPSNR \(RGB\)(?: \[.*?\] )?:\s*([0-9.]*) dB.*"
413cc1dc7a3Sopenharmony_ci        return patternPSNR
414cc1dc7a3Sopenharmony_ci
415cc1dc7a3Sopenharmony_ci    def get_total_time_pattern(self):
416cc1dc7a3Sopenharmony_ci        # Pattern match on a new pattern for a 2.1 compatible variant
417cc1dc7a3Sopenharmony_ci        # return r"Elapsed time:\s*([0-9.]*) seconds.*"
418cc1dc7a3Sopenharmony_ci        return r"\s*Total time:\s*([0-9.]*) s"
419cc1dc7a3Sopenharmony_ci
420cc1dc7a3Sopenharmony_ci    def get_coding_time_pattern(self):
421cc1dc7a3Sopenharmony_ci        # Pattern match on a new pattern for a 2.1 compatible variant
422cc1dc7a3Sopenharmony_ci        # return r".* coding time: \s*([0-9.]*) seconds"
423cc1dc7a3Sopenharmony_ci        return r"\s*Coding time:\s*([0-9.]*) s"
424cc1dc7a3Sopenharmony_ci
425cc1dc7a3Sopenharmony_ci    def get_coding_rate_pattern(self):
426cc1dc7a3Sopenharmony_ci        # Pattern match on a new pattern for a 2.1 compatible variant
427cc1dc7a3Sopenharmony_ci        return r"\s*Coding rate:\s*([0-9.]*) MT/s"
428