1cc1dc7a3Sopenharmony_ci# SPDX-License-Identifier: Apache-2.0
2cc1dc7a3Sopenharmony_ci# -----------------------------------------------------------------------------
3cc1dc7a3Sopenharmony_ci# Copyright 2019-2022 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_ciThis module contains code for loading image metadata from a file path on disk.
19cc1dc7a3Sopenharmony_ci
20cc1dc7a3Sopenharmony_ciThe directory path is structured:
21cc1dc7a3Sopenharmony_ci
22cc1dc7a3Sopenharmony_ci    TestSetName/TestFormat/FileName
23cc1dc7a3Sopenharmony_ci
24cc1dc7a3Sopenharmony_ci... and the file name is structured:
25cc1dc7a3Sopenharmony_ci
26cc1dc7a3Sopenharmony_ci    colorProfile-colorFormat-name[-flags].extension
27cc1dc7a3Sopenharmony_ci"""
28cc1dc7a3Sopenharmony_ci
29cc1dc7a3Sopenharmony_cifrom collections.abc import Iterable
30cc1dc7a3Sopenharmony_ciimport os
31cc1dc7a3Sopenharmony_ciimport re
32cc1dc7a3Sopenharmony_ciimport subprocess as sp
33cc1dc7a3Sopenharmony_ci
34cc1dc7a3Sopenharmony_cifrom PIL import Image as PILImage
35cc1dc7a3Sopenharmony_ci
36cc1dc7a3Sopenharmony_ciimport testlib.misc as misc
37cc1dc7a3Sopenharmony_ci
38cc1dc7a3Sopenharmony_ci
39cc1dc7a3Sopenharmony_ciCONVERT_BINARY = ["convert"]
40cc1dc7a3Sopenharmony_ci
41cc1dc7a3Sopenharmony_ci
42cc1dc7a3Sopenharmony_ciclass ImageException(Exception):
43cc1dc7a3Sopenharmony_ci    """
44cc1dc7a3Sopenharmony_ci    Exception thrown for bad image specification.
45cc1dc7a3Sopenharmony_ci    """
46cc1dc7a3Sopenharmony_ci
47cc1dc7a3Sopenharmony_ci
48cc1dc7a3Sopenharmony_ciclass TestImage():
49cc1dc7a3Sopenharmony_ci    """
50cc1dc7a3Sopenharmony_ci    Objects of this type contain metadata for a single test image on disk.
51cc1dc7a3Sopenharmony_ci
52cc1dc7a3Sopenharmony_ci    Attributes:
53cc1dc7a3Sopenharmony_ci        filePath: The path of the file on disk.
54cc1dc7a3Sopenharmony_ci        outFilePath: The path of the output file on disk.
55cc1dc7a3Sopenharmony_ci        testSet: The name of the test set.
56cc1dc7a3Sopenharmony_ci        testFormat: The test format group.
57cc1dc7a3Sopenharmony_ci        testFile: The test file name.
58cc1dc7a3Sopenharmony_ci        colorProfile: The image compression color profile.
59cc1dc7a3Sopenharmony_ci        colorFormat: The image color format.
60cc1dc7a3Sopenharmony_ci        name: The image human name.
61cc1dc7a3Sopenharmony_ci        is3D: True if the image is 3D, else False.
62cc1dc7a3Sopenharmony_ci        isAlphaScaled: True if the image wants alpha scaling, else False.
63cc1dc7a3Sopenharmony_ci        TEST_EXTS: Expected test image extensions.
64cc1dc7a3Sopenharmony_ci        PROFILES: Tuple of valid color profile values.
65cc1dc7a3Sopenharmony_ci        FORMATS: Tuple of valid color format values.
66cc1dc7a3Sopenharmony_ci        FLAGS: Map of valid flags (key) and their meaning (value).
67cc1dc7a3Sopenharmony_ci    """
68cc1dc7a3Sopenharmony_ci    TEST_EXTS = (".jpg", ".png", ".tga", ".dds", ".hdr", ".ktx")
69cc1dc7a3Sopenharmony_ci
70cc1dc7a3Sopenharmony_ci    PROFILES = ("ldr", "ldrs", "hdr")
71cc1dc7a3Sopenharmony_ci
72cc1dc7a3Sopenharmony_ci    FORMATS = ("l", "la", "xy", "rgb", "rgba")
73cc1dc7a3Sopenharmony_ci
74cc1dc7a3Sopenharmony_ci    FLAGS = {
75cc1dc7a3Sopenharmony_ci        # Flags for image compression control
76cc1dc7a3Sopenharmony_ci        "3": "3D image",
77cc1dc7a3Sopenharmony_ci        "m": "Mask image",
78cc1dc7a3Sopenharmony_ci        "a": "Alpha scaled image"
79cc1dc7a3Sopenharmony_ci    }
80cc1dc7a3Sopenharmony_ci
81cc1dc7a3Sopenharmony_ci    def __init__(self, filePath):
82cc1dc7a3Sopenharmony_ci        """
83cc1dc7a3Sopenharmony_ci        Create a new image definition, based on a structured file path.
84cc1dc7a3Sopenharmony_ci
85cc1dc7a3Sopenharmony_ci        Args:
86cc1dc7a3Sopenharmony_ci            filePath (str): The path of the image on disk.
87cc1dc7a3Sopenharmony_ci
88cc1dc7a3Sopenharmony_ci        Raises:
89cc1dc7a3Sopenharmony_ci            ImageException: The image couldn't be found or is unstructured.
90cc1dc7a3Sopenharmony_ci        """
91cc1dc7a3Sopenharmony_ci        self.filePath = os.path.abspath(filePath)
92cc1dc7a3Sopenharmony_ci        if not os.path.exists(self.filePath):
93cc1dc7a3Sopenharmony_ci            raise ImageException("Image doesn't exist (%s)" % filePath)
94cc1dc7a3Sopenharmony_ci
95cc1dc7a3Sopenharmony_ci        # Decode the path
96cc1dc7a3Sopenharmony_ci        scriptDir = os.path.dirname(__file__)
97cc1dc7a3Sopenharmony_ci        rootInDir = os.path.join(scriptDir, "..", "Images")
98cc1dc7a3Sopenharmony_ci        partialPath = os.path.relpath(self.filePath, rootInDir)
99cc1dc7a3Sopenharmony_ci        parts = misc.path_splitall(partialPath)
100cc1dc7a3Sopenharmony_ci        if len(parts) != 3:
101cc1dc7a3Sopenharmony_ci            raise ImageException("Image path not path triplet (%s)" % parts)
102cc1dc7a3Sopenharmony_ci        self.testSet = parts[0]
103cc1dc7a3Sopenharmony_ci        self.testFormat = parts[1]
104cc1dc7a3Sopenharmony_ci        self.testFile = parts[2]
105cc1dc7a3Sopenharmony_ci
106cc1dc7a3Sopenharmony_ci        # Decode the file name
107cc1dc7a3Sopenharmony_ci        self.decode_file_name(self.testFile)
108cc1dc7a3Sopenharmony_ci
109cc1dc7a3Sopenharmony_ci        # Output file path (store base without extension)
110cc1dc7a3Sopenharmony_ci        rootOutDir = os.path.join(scriptDir, "..", "..", "TestOutput")
111cc1dc7a3Sopenharmony_ci        outFilePath = os.path.join(rootOutDir, partialPath)
112cc1dc7a3Sopenharmony_ci        outFilePath = os.path.abspath(outFilePath)
113cc1dc7a3Sopenharmony_ci        outFilePath = os.path.splitext(outFilePath)[0]
114cc1dc7a3Sopenharmony_ci        self.outFilePath = outFilePath
115cc1dc7a3Sopenharmony_ci
116cc1dc7a3Sopenharmony_ci    def decode_file_name(self, fileName):
117cc1dc7a3Sopenharmony_ci        """
118cc1dc7a3Sopenharmony_ci        Utility function to decode metadata from an encoded file name.
119cc1dc7a3Sopenharmony_ci
120cc1dc7a3Sopenharmony_ci        Args:
121cc1dc7a3Sopenharmony_ci            fileName (str): The file name to tokenize.
122cc1dc7a3Sopenharmony_ci
123cc1dc7a3Sopenharmony_ci        Raises:
124cc1dc7a3Sopenharmony_ci            ImageException: The image file path is badly structured.
125cc1dc7a3Sopenharmony_ci        """
126cc1dc7a3Sopenharmony_ci        # Strip off the extension
127cc1dc7a3Sopenharmony_ci        rootName = os.path.splitext(fileName)[0]
128cc1dc7a3Sopenharmony_ci
129cc1dc7a3Sopenharmony_ci        parts = rootName.split("-")
130cc1dc7a3Sopenharmony_ci
131cc1dc7a3Sopenharmony_ci        # Decode the mandatory fields
132cc1dc7a3Sopenharmony_ci        if len(parts) >= 3:
133cc1dc7a3Sopenharmony_ci            self.colorProfile = parts[0]
134cc1dc7a3Sopenharmony_ci            if self.colorProfile not in self.PROFILES:
135cc1dc7a3Sopenharmony_ci                raise ImageException("Unknown color profile (%s)" % parts[0])
136cc1dc7a3Sopenharmony_ci
137cc1dc7a3Sopenharmony_ci            self.colorFormat = parts[1]
138cc1dc7a3Sopenharmony_ci            if self.colorFormat not in self.FORMATS:
139cc1dc7a3Sopenharmony_ci                raise ImageException("Unknown color format (%s)" % parts[1])
140cc1dc7a3Sopenharmony_ci
141cc1dc7a3Sopenharmony_ci            # Consistency check between directory and file names
142cc1dc7a3Sopenharmony_ci            reencode = "%s-%s" % (self.colorProfile, self.colorFormat)
143cc1dc7a3Sopenharmony_ci            compare = self.testFormat.lower()
144cc1dc7a3Sopenharmony_ci            if reencode != compare:
145cc1dc7a3Sopenharmony_ci                dat = (self.testFormat, reencode)
146cc1dc7a3Sopenharmony_ci                raise ImageException("Mismatched test and image (%s:%s)" % dat)
147cc1dc7a3Sopenharmony_ci
148cc1dc7a3Sopenharmony_ci            self.name = parts[2]
149cc1dc7a3Sopenharmony_ci
150cc1dc7a3Sopenharmony_ci        # Set default values for the optional fields
151cc1dc7a3Sopenharmony_ci        self.is3D = False
152cc1dc7a3Sopenharmony_ci        self.isAlphaScaled = False
153cc1dc7a3Sopenharmony_ci
154cc1dc7a3Sopenharmony_ci        # Decode the flags field if present
155cc1dc7a3Sopenharmony_ci        if len(parts) >= 4:
156cc1dc7a3Sopenharmony_ci            flags = parts[3]
157cc1dc7a3Sopenharmony_ci            seenFlags = set()
158cc1dc7a3Sopenharmony_ci            for flag in flags:
159cc1dc7a3Sopenharmony_ci                if flag in seenFlags:
160cc1dc7a3Sopenharmony_ci                    raise ImageException("Duplicate flag (%s)" % flag)
161cc1dc7a3Sopenharmony_ci                if flag not in self.FLAGS:
162cc1dc7a3Sopenharmony_ci                    raise ImageException("Unknown flag (%s)" % flag)
163cc1dc7a3Sopenharmony_ci                seenFlags.add(flag)
164cc1dc7a3Sopenharmony_ci
165cc1dc7a3Sopenharmony_ci            self.is3D = "3" in seenFlags
166cc1dc7a3Sopenharmony_ci            self.isAlphaScaled = "a" in seenFlags
167cc1dc7a3Sopenharmony_ci
168cc1dc7a3Sopenharmony_ci    def get_size(self):
169cc1dc7a3Sopenharmony_ci        """
170cc1dc7a3Sopenharmony_ci        Get the dimensions of this test image, if format is known.
171cc1dc7a3Sopenharmony_ci
172cc1dc7a3Sopenharmony_ci        Known cases today where the format is not known:
173cc1dc7a3Sopenharmony_ci
174cc1dc7a3Sopenharmony_ci        * 3D .dds files.
175cc1dc7a3Sopenharmony_ci        * Any .ktx, .hdr, .exr, or .astc file.
176cc1dc7a3Sopenharmony_ci
177cc1dc7a3Sopenharmony_ci        Returns:
178cc1dc7a3Sopenharmony_ci            tuple(int, int): The dimensions of a 2D image, or ``None`` if PIL
179cc1dc7a3Sopenharmony_ci            could not open the file.
180cc1dc7a3Sopenharmony_ci        """
181cc1dc7a3Sopenharmony_ci        try:
182cc1dc7a3Sopenharmony_ci            img = PILImage.open(self.filePath)
183cc1dc7a3Sopenharmony_ci        except IOError:
184cc1dc7a3Sopenharmony_ci            # HDR files
185cc1dc7a3Sopenharmony_ci            return None
186cc1dc7a3Sopenharmony_ci        except NotImplementedError:
187cc1dc7a3Sopenharmony_ci            # DDS files
188cc1dc7a3Sopenharmony_ci            return None
189cc1dc7a3Sopenharmony_ci
190cc1dc7a3Sopenharmony_ci        return (img.size[0], img.size[1])
191cc1dc7a3Sopenharmony_ci
192cc1dc7a3Sopenharmony_ci
193cc1dc7a3Sopenharmony_ciclass Image():
194cc1dc7a3Sopenharmony_ci    """
195cc1dc7a3Sopenharmony_ci    Wrapper around an image on the file system.
196cc1dc7a3Sopenharmony_ci    """
197cc1dc7a3Sopenharmony_ci
198cc1dc7a3Sopenharmony_ci    # TODO: We don't support KTX yet, as ImageMagick doesn't.
199cc1dc7a3Sopenharmony_ci    SUPPORTED_LDR = ["bmp", "jpg", "png", "tga"]
200cc1dc7a3Sopenharmony_ci    SUPPORTED_HDR = ["exr", "hdr"]
201cc1dc7a3Sopenharmony_ci
202cc1dc7a3Sopenharmony_ci    @classmethod
203cc1dc7a3Sopenharmony_ci    def is_format_supported(cls, fileFormat, profile=None):
204cc1dc7a3Sopenharmony_ci        """
205cc1dc7a3Sopenharmony_ci        Test if a given file format is supported by the library.
206cc1dc7a3Sopenharmony_ci
207cc1dc7a3Sopenharmony_ci        Args:
208cc1dc7a3Sopenharmony_ci            fileFormat (str): The file extension (excluding the ".").
209cc1dc7a3Sopenharmony_ci            profile (str or None): The profile (ldr or hdr) of the image.
210cc1dc7a3Sopenharmony_ci
211cc1dc7a3Sopenharmony_ci        Returns:
212cc1dc7a3Sopenharmony_ci            bool: `True` if the image is supported, `False` otherwise.
213cc1dc7a3Sopenharmony_ci        """
214cc1dc7a3Sopenharmony_ci        assert profile in [None, "ldr", "hdr"]
215cc1dc7a3Sopenharmony_ci
216cc1dc7a3Sopenharmony_ci        if profile == "ldr":
217cc1dc7a3Sopenharmony_ci            return fileFormat in cls.SUPPORTED_LDR
218cc1dc7a3Sopenharmony_ci
219cc1dc7a3Sopenharmony_ci        if profile == "hdr":
220cc1dc7a3Sopenharmony_ci            return fileFormat in cls.SUPPORTED_HDR
221cc1dc7a3Sopenharmony_ci
222cc1dc7a3Sopenharmony_ci        return fileFormat in cls.SUPPORTED_LDR or \
223cc1dc7a3Sopenharmony_ci            fileFormat in cls.SUPPORTED_HDR
224cc1dc7a3Sopenharmony_ci
225cc1dc7a3Sopenharmony_ci    def __init__(self, filePath):
226cc1dc7a3Sopenharmony_ci        """
227cc1dc7a3Sopenharmony_ci        Construct a new Image.
228cc1dc7a3Sopenharmony_ci
229cc1dc7a3Sopenharmony_ci        Args:
230cc1dc7a3Sopenharmony_ci            filePath (str): The path to the image on disk.
231cc1dc7a3Sopenharmony_ci        """
232cc1dc7a3Sopenharmony_ci        self.filePath = filePath
233cc1dc7a3Sopenharmony_ci        self.proxyPath = None
234cc1dc7a3Sopenharmony_ci
235cc1dc7a3Sopenharmony_ci    def get_colors(self, coords):
236cc1dc7a3Sopenharmony_ci        """
237cc1dc7a3Sopenharmony_ci        Get the image colors at the given coordinate.
238cc1dc7a3Sopenharmony_ci
239cc1dc7a3Sopenharmony_ci        Args:
240cc1dc7a3Sopenharmony_ci            coords (tuple or list): A single coordinate, or a list of
241cc1dc7a3Sopenharmony_ci                coordinates to sample.
242cc1dc7a3Sopenharmony_ci
243cc1dc7a3Sopenharmony_ci        Returns:
244cc1dc7a3Sopenharmony_ci            tuple: A single sample color (if `coords` was a coordinate).
245cc1dc7a3Sopenharmony_ci            list: A list of sample colors (if `coords` was a list).
246cc1dc7a3Sopenharmony_ci
247cc1dc7a3Sopenharmony_ci            Colors are returned as float values between 0.0 and 1.0 for LDR,
248cc1dc7a3Sopenharmony_ci            and float values which may exceed 1.0 for HDR.
249cc1dc7a3Sopenharmony_ci        """
250cc1dc7a3Sopenharmony_ci        colors = []
251cc1dc7a3Sopenharmony_ci
252cc1dc7a3Sopenharmony_ci        # We accept both a list of positions and a single position;
253cc1dc7a3Sopenharmony_ci        # canonicalize here so the main processing only handles lists
254cc1dc7a3Sopenharmony_ci        isList = len(coords) != 0 and isinstance(coords[0], Iterable)
255cc1dc7a3Sopenharmony_ci
256cc1dc7a3Sopenharmony_ci        if not isList:
257cc1dc7a3Sopenharmony_ci            coords = [coords]
258cc1dc7a3Sopenharmony_ci
259cc1dc7a3Sopenharmony_ci        for (x, y) in coords:
260cc1dc7a3Sopenharmony_ci            command = list(CONVERT_BINARY)
261cc1dc7a3Sopenharmony_ci            command += [self.filePath]
262cc1dc7a3Sopenharmony_ci
263cc1dc7a3Sopenharmony_ci            # Ensure convert factors in format origin if needed
264cc1dc7a3Sopenharmony_ci            command += ["-auto-orient"]
265cc1dc7a3Sopenharmony_ci
266cc1dc7a3Sopenharmony_ci            command += [
267cc1dc7a3Sopenharmony_ci                "-format", "%%[pixel:p{%u,%u}]" % (x, y),
268cc1dc7a3Sopenharmony_ci                "info:"
269cc1dc7a3Sopenharmony_ci            ]
270cc1dc7a3Sopenharmony_ci
271cc1dc7a3Sopenharmony_ci            if os.name == 'nt':
272cc1dc7a3Sopenharmony_ci                command.insert(0, "magick")
273cc1dc7a3Sopenharmony_ci
274cc1dc7a3Sopenharmony_ci            result = sp.run(command, stdout=sp.PIPE, stderr=sp.PIPE,
275cc1dc7a3Sopenharmony_ci                            check=True, universal_newlines=True)
276cc1dc7a3Sopenharmony_ci
277cc1dc7a3Sopenharmony_ci            rawcolor = result.stdout.strip()
278cc1dc7a3Sopenharmony_ci
279cc1dc7a3Sopenharmony_ci            # Decode ImageMagick's annoying named color outputs. Note that this
280cc1dc7a3Sopenharmony_ci            # only handles "known" cases triggered by our test images, we don't
281cc1dc7a3Sopenharmony_ci            # support the entire ImageMagick named color table.
282cc1dc7a3Sopenharmony_ci            if rawcolor == "black":
283cc1dc7a3Sopenharmony_ci                colors.append([0.0, 0.0, 0.0, 1.0])
284cc1dc7a3Sopenharmony_ci            elif rawcolor == "white":
285cc1dc7a3Sopenharmony_ci                colors.append([1.0, 1.0, 1.0, 1.0])
286cc1dc7a3Sopenharmony_ci            elif rawcolor == "red":
287cc1dc7a3Sopenharmony_ci                colors.append([1.0, 0.0, 0.0, 1.0])
288cc1dc7a3Sopenharmony_ci            elif rawcolor == "blue":
289cc1dc7a3Sopenharmony_ci                colors.append([0.0, 0.0, 1.0, 1.0])
290cc1dc7a3Sopenharmony_ci
291cc1dc7a3Sopenharmony_ci            # Decode ImageMagick's format tuples
292cc1dc7a3Sopenharmony_ci            elif rawcolor.startswith("srgba"):
293cc1dc7a3Sopenharmony_ci                rawcolor = rawcolor[6:]
294cc1dc7a3Sopenharmony_ci                rawcolor = rawcolor[:-1]
295cc1dc7a3Sopenharmony_ci                channels = rawcolor.split(",")
296cc1dc7a3Sopenharmony_ci                for i, channel in enumerate(channels):
297cc1dc7a3Sopenharmony_ci                    if (i < 3) and channel.endswith("%"):
298cc1dc7a3Sopenharmony_ci                        channels[i] = float(channel[:-1]) / 100.0
299cc1dc7a3Sopenharmony_ci                    elif (i < 3) and not channel.endswith("%"):
300cc1dc7a3Sopenharmony_ci                        channels[i] = float(channel) / 255.0
301cc1dc7a3Sopenharmony_ci                    else:
302cc1dc7a3Sopenharmony_ci                        channels[i] = float(channel)
303cc1dc7a3Sopenharmony_ci                colors.append(channels)
304cc1dc7a3Sopenharmony_ci            elif rawcolor.startswith("srgb"):
305cc1dc7a3Sopenharmony_ci                rawcolor = rawcolor[5:]
306cc1dc7a3Sopenharmony_ci                rawcolor = rawcolor[:-1]
307cc1dc7a3Sopenharmony_ci                channels = rawcolor.split(",")
308cc1dc7a3Sopenharmony_ci                for i, channel in enumerate(channels):
309cc1dc7a3Sopenharmony_ci                    if (i < 3) and channel.endswith("%"):
310cc1dc7a3Sopenharmony_ci                        channels[i] = float(channel[:-1]) / 100.0
311cc1dc7a3Sopenharmony_ci                    if (i < 3) and not channel.endswith("%"):
312cc1dc7a3Sopenharmony_ci                        channels[i] = float(channel) / 255.0
313cc1dc7a3Sopenharmony_ci                channels.append(1.0)
314cc1dc7a3Sopenharmony_ci                colors.append(channels)
315cc1dc7a3Sopenharmony_ci            elif rawcolor.startswith("rgba"):
316cc1dc7a3Sopenharmony_ci                rawcolor = rawcolor[5:]
317cc1dc7a3Sopenharmony_ci                rawcolor = rawcolor[:-1]
318cc1dc7a3Sopenharmony_ci                channels = rawcolor.split(",")
319cc1dc7a3Sopenharmony_ci                for i, channel in enumerate(channels):
320cc1dc7a3Sopenharmony_ci                    if (i < 3) and channel.endswith("%"):
321cc1dc7a3Sopenharmony_ci                        channels[i] = float(channel[:-1]) / 100.0
322cc1dc7a3Sopenharmony_ci                    elif (i < 3) and not channel.endswith("%"):
323cc1dc7a3Sopenharmony_ci                        channels[i] = float(channel) / 255.0
324cc1dc7a3Sopenharmony_ci                    else:
325cc1dc7a3Sopenharmony_ci                        channels[i] = float(channel)
326cc1dc7a3Sopenharmony_ci                colors.append(channels)
327cc1dc7a3Sopenharmony_ci            elif rawcolor.startswith("rgb"):
328cc1dc7a3Sopenharmony_ci                rawcolor = rawcolor[4:]
329cc1dc7a3Sopenharmony_ci                rawcolor = rawcolor[:-1]
330cc1dc7a3Sopenharmony_ci                channels = rawcolor.split(",")
331cc1dc7a3Sopenharmony_ci                for i, channel in enumerate(channels):
332cc1dc7a3Sopenharmony_ci                    if (i < 3) and channel.endswith("%"):
333cc1dc7a3Sopenharmony_ci                        channels[i] = float(channel[:-1]) / 100.0
334cc1dc7a3Sopenharmony_ci                    if (i < 3) and not channel.endswith("%"):
335cc1dc7a3Sopenharmony_ci                        channels[i] = float(channel) / 255.0
336cc1dc7a3Sopenharmony_ci                channels.append(1.0)
337cc1dc7a3Sopenharmony_ci                colors.append(channels)
338cc1dc7a3Sopenharmony_ci            else:
339cc1dc7a3Sopenharmony_ci                print(x, y)
340cc1dc7a3Sopenharmony_ci                print(rawcolor)
341cc1dc7a3Sopenharmony_ci                assert False
342cc1dc7a3Sopenharmony_ci
343cc1dc7a3Sopenharmony_ci        # ImageMagick decodes DDS files as BGRA not RGBA; manually correct
344cc1dc7a3Sopenharmony_ci        if self.filePath.endswith("dds"):
345cc1dc7a3Sopenharmony_ci            for color in colors:
346cc1dc7a3Sopenharmony_ci                tmp = color[0]
347cc1dc7a3Sopenharmony_ci                color[0] = color[2]
348cc1dc7a3Sopenharmony_ci                color[2] = tmp
349cc1dc7a3Sopenharmony_ci
350cc1dc7a3Sopenharmony_ci        # ImageMagick decodes EXR files with premult alpha; manually correct
351cc1dc7a3Sopenharmony_ci        if self.filePath.endswith("exr"):
352cc1dc7a3Sopenharmony_ci            for color in colors:
353cc1dc7a3Sopenharmony_ci                color[0] /= color[3]
354cc1dc7a3Sopenharmony_ci                color[1] /= color[3]
355cc1dc7a3Sopenharmony_ci                color[2] /= color[3]
356cc1dc7a3Sopenharmony_ci
357cc1dc7a3Sopenharmony_ci        # Undo list canonicalization if we were given a single scalar coord
358cc1dc7a3Sopenharmony_ci        if not isList:
359cc1dc7a3Sopenharmony_ci            return colors[0]
360cc1dc7a3Sopenharmony_ci
361cc1dc7a3Sopenharmony_ci        return colors
362