• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright 2009 VMware, Inc.
4# Copyright 2014 Intel Corporation
5# All Rights Reserved.
6#
7# Permission is hereby granted, free of charge, to any person obtaining a
8# copy of this software and associated documentation files (the
9# "Software"), to deal in the Software without restriction, including
10# without limitation the rights to use, copy, modify, merge, publish,
11# distribute, sub license, and/or sell copies of the Software, and to
12# permit persons to whom the Software is furnished to do so, subject to
13# the following conditions:
14#
15# The above copyright notice and this permission notice (including the
16# next paragraph) shall be included in all copies or substantial portions
17# of the Software.
18#
19# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
22# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
23# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
27import sys
28
29VOID = 'x'
30UNSIGNED = 'u'
31SIGNED = 's'
32FLOAT = 'f'
33
34ARRAY = 'array'
35PACKED = 'packed'
36OTHER = 'other'
37
38RGB = 'rgb'
39SRGB = 'srgb'
40YUV = 'yuv'
41ZS = 'zs'
42
43VERY_LARGE = 99999999999999999999999
44
45class Channel:
46   """Describes a color channel."""
47
48   def __init__(self, type, norm, size):
49      self.type = type
50      self.norm = norm
51      self.size = size
52      self.sign = type in (SIGNED, FLOAT)
53      self.name = None # Set when the channels are added to the format
54      self.shift = -1 # Set when the channels are added to the format
55      self.index = -1 # Set when the channels are added to the format
56
57   def __str__(self):
58      s = str(self.type)
59      if self.norm:
60         s += 'n'
61      s += str(self.size)
62      return s
63
64   def __eq__(self, other):
65      return self.type == other.type and self.norm == other.norm and self.size == other.size
66
67   def max(self):
68      """Returns the maximum representable number."""
69      if self.type == FLOAT:
70         return VERY_LARGE
71      if self.norm:
72         return 1
73      if self.type == UNSIGNED:
74         return (1 << self.size) - 1
75      if self.type == SIGNED:
76         return (1 << (self.size - 1)) - 1
77      assert False
78
79   def min(self):
80      """Returns the minimum representable number."""
81      if self.type == FLOAT:
82         return -VERY_LARGE
83      if self.type == UNSIGNED:
84         return 0
85      if self.norm:
86         return -1
87      if self.type == SIGNED:
88         return -(1 << (self.size - 1))
89      assert False
90
91   def one(self):
92      """Returns the value that represents 1.0f."""
93      if self.type == UNSIGNED:
94         return (1 << self.size) - 1
95      if self.type == SIGNED:
96         return (1 << (self.size - 1)) - 1
97      else:
98         return 1
99
100   def datatype(self):
101      """Returns the datatype corresponding to a channel type and size"""
102      return _get_datatype(self.type, self.size)
103
104class Swizzle:
105   """Describes a swizzle operation.
106
107   A Swizzle is a mapping from one set of channels in one format to the
108   channels in another.  Each channel in the destination format is
109   associated with one of the following constants:
110
111    * SWIZZLE_X: The first channel in the source format
112    * SWIZZLE_Y: The second channel in the source format
113    * SWIZZLE_Z: The third channel in the source format
114    * SWIZZLE_W: The fourth channel in the source format
115    * SWIZZLE_ZERO: The numeric constant 0
116    * SWIZZLE_ONE: THe numeric constant 1
117    * SWIZZLE_NONE: No data available for this channel
118
119   Sometimes a Swizzle is represented by a 4-character string.  In this
120   case, the source channels are represented by the characters "x", "y",
121   "z", and "w"; the numeric constants are represented as "0" and "1"; and
122   no mapping is represented by "_".  For instance, the map from
123   luminance-alpha to rgba is given by "xxxy" because each of the three rgb
124   channels maps to the first luminance-alpha channel and the alpha channel
125   maps to second luminance-alpha channel.  The mapping from bgr to rgba is
126   given by "zyx1" because the first three colors are reversed and alpha is
127   always 1.
128   """
129
130   __identity_str = 'xyzw01_'
131
132   SWIZZLE_X = 0
133   SWIZZLE_Y = 1
134   SWIZZLE_Z = 2
135   SWIZZLE_W = 3
136   SWIZZLE_ZERO = 4
137   SWIZZLE_ONE = 5
138   SWIZZLE_NONE = 6
139
140   def __init__(self, swizzle):
141      """Creates a Swizzle object from a string or array."""
142      if isinstance(swizzle, str):
143         swizzle = [Swizzle.__identity_str.index(c) for c in swizzle]
144      else:
145         swizzle = list(swizzle)
146         for s in swizzle:
147            assert isinstance(s, int) and 0 <= s and s <= Swizzle.SWIZZLE_NONE
148
149      assert len(swizzle) <= 4
150
151      self.__list = swizzle + [Swizzle.SWIZZLE_NONE] * (4 - len(swizzle))
152      assert len(self.__list) == 4
153
154   def __iter__(self):
155      """Returns an iterator that iterates over this Swizzle.
156
157      The values that the iterator produces are described by the SWIZZLE_*
158      constants.
159      """
160      return self.__list.__iter__()
161
162   def __str__(self):
163      """Returns a string representation of this Swizzle."""
164      return ''.join(Swizzle.__identity_str[i] for i in self.__list)
165
166   def __getitem__(self, idx):
167      """Returns the SWIZZLE_* constant for the given destination channel.
168
169      Valid values for the destination channel include any of the SWIZZLE_*
170      constants or any of the following single-character strings: "x", "y",
171      "z", "w", "r", "g", "b", "a", "z" "s".
172      """
173
174      if isinstance(idx, int):
175         assert idx >= Swizzle.SWIZZLE_X and idx <= Swizzle.SWIZZLE_NONE
176         if idx <= Swizzle.SWIZZLE_W:
177            return self.__list.__getitem__(idx)
178         else:
179            return idx
180      elif isinstance(idx, str):
181         if idx in 'xyzw':
182            idx = 'xyzw'.find(idx)
183         elif idx in 'rgba':
184            idx = 'rgba'.find(idx)
185         elif idx in 'zs':
186            idx = 'zs'.find(idx)
187         else:
188            assert False
189         return self.__list.__getitem__(idx)
190      else:
191         assert False
192
193   def __mul__(self, other):
194      """Returns the composition of this Swizzle with another Swizzle.
195
196      The resulting swizzle is such that, for any valid input to
197      __getitem__, (a * b)[i] = a[b[i]].
198      """
199      assert isinstance(other, Swizzle)
200      return Swizzle(self[x] for x in other)
201
202   def inverse(self):
203      """Returns a pseudo-inverse of this swizzle.
204
205      Since swizzling isn't necisaraly a bijection, a Swizzle can never
206      be truely inverted.  However, the swizzle returned is *almost* the
207      inverse of this swizzle in the sense that, for each i in range(3),
208      a[a.inverse()[i]] is either i or SWIZZLE_NONE.  If swizzle is just
209      a permutation with no channels added or removed, then this
210      function returns the actual inverse.
211
212      This "pseudo-inverse" idea can be demonstrated by mapping from
213      luminance-alpha to rgba that is given by "xxxy".  To get from rgba
214      to lumanence-alpha, we use Swizzle("xxxy").inverse() or "xw__".
215      This maps the first component in the lumanence-alpha texture is
216      the red component of the rgba image and the second to the alpha
217      component, exactly as you would expect.
218      """
219      rev = [Swizzle.SWIZZLE_NONE] * 4
220      for i in xrange(4):
221         for j in xrange(4):
222            if self.__list[j] == i and rev[i] == Swizzle.SWIZZLE_NONE:
223               rev[i] = j
224      return Swizzle(rev)
225
226
227class Format:
228   """Describes a pixel format."""
229
230   def __init__(self, name, layout, block_width, block_height, block_depth, channels, swizzle, colorspace):
231      """Constructs a Format from some metadata and a list of channels.
232
233      The channel objects must be unique to this Format and should not be
234      re-used to construct another Format.  This is because certain channel
235      information such as shift, offset, and the channel name are set when
236      the Format is created and are calculated based on the entire list of
237      channels.
238
239      Arguments:
240      name -- Name of the format such as 'MESA_FORMAT_A8R8G8B8'
241      layout -- One of 'array', 'packed' 'other', or a compressed layout
242      block_width -- The block width if the format is compressed, 1 otherwise
243      block_height -- The block height if the format is compressed, 1 otherwise
244      block_depth -- The block depth if the format is compressed, 1 otherwise
245      channels -- A list of Channel objects
246      swizzle -- A Swizzle from this format to rgba
247      colorspace -- one of 'rgb', 'srgb', 'yuv', or 'zs'
248      """
249      self.name = name
250      self.layout = layout
251      self.block_width = block_width
252      self.block_height = block_height
253      self.block_depth = block_depth
254      self.channels = channels
255      assert isinstance(swizzle, Swizzle)
256      self.swizzle = swizzle
257      self.name = name
258      assert colorspace in (RGB, SRGB, YUV, ZS)
259      self.colorspace = colorspace
260
261      # Name the channels
262      chan_names = ['']*4
263      if self.colorspace in (RGB, SRGB):
264         for (i, s) in enumerate(swizzle):
265            if s < 4:
266               chan_names[s] += 'rgba'[i]
267      elif colorspace == ZS:
268         for (i, s) in enumerate(swizzle):
269            if s < 4:
270               chan_names[s] += 'zs'[i]
271      else:
272         chan_names = ['x', 'y', 'z', 'w']
273
274      for c, name in zip(self.channels, chan_names):
275         assert c.name is None
276         if name == 'rgb':
277            c.name = 'l'
278         elif name == 'rgba':
279            c.name = 'i'
280         elif name == '':
281            c.name = 'x'
282         else:
283            c.name = name
284
285      # Set indices and offsets
286      if self.layout == PACKED:
287         shift = 0
288         for channel in self.channels:
289            assert channel.shift == -1
290            channel.shift = shift
291            shift += channel.size
292      for idx, channel in enumerate(self.channels):
293         assert channel.index == -1
294         channel.index = idx
295      else:
296         pass # Shift means nothing here
297
298   def __str__(self):
299      return self.name
300
301   def short_name(self):
302      """Returns a short name for a format.
303
304      The short name should be suitable to be used as suffix in function
305      names.
306      """
307
308      name = self.name
309      if name.startswith('MESA_FORMAT_'):
310         name = name[len('MESA_FORMAT_'):]
311      name = name.lower()
312      return name
313
314   def block_size(self):
315      """Returns the block size (in bits) of the format."""
316      size = 0
317      for channel in self.channels:
318         size += channel.size
319      return size
320
321   def num_channels(self):
322      """Returns the number of channels in the format."""
323      nr_channels = 0
324      for channel in self.channels:
325         if channel.size:
326            nr_channels += 1
327      return nr_channels
328
329   def array_element(self):
330      """Returns a non-void channel if this format is an array, otherwise None.
331
332      If the returned channel is not None, then this format can be
333      considered to be an array of num_channels() channels identical to the
334      returned channel.
335      """
336      if self.layout == ARRAY:
337         return self.channels[0]
338      elif self.layout == PACKED:
339         ref_channel = self.channels[0]
340         if ref_channel.type == VOID:
341            ref_channel = self.channels[1]
342         for channel in self.channels:
343            if channel.size == 0 or channel.type == VOID:
344               continue
345            if channel.size != ref_channel.size or channel.size % 8 != 0:
346               return None
347            if channel.type != ref_channel.type:
348               return None
349            if channel.norm != ref_channel.norm:
350               return None
351         return ref_channel
352      else:
353         return None
354
355   def is_array(self):
356      """Returns true if this format can be considered an array format.
357
358      This function will return true if self.layout == 'array'.  However,
359      some formats, such as MESA_FORMAT_A8G8B8R8, can be considered as
360      array formats even though they are technically packed.
361      """
362      return self.array_element() != None
363
364   def is_compressed(self):
365      """Returns true if this is a compressed format."""
366      return self.block_width != 1 or self.block_height != 1 or self.block_depth != 1
367
368   def is_int(self):
369      """Returns true if this format is an integer format.
370
371      See also: is_norm()
372      """
373      if self.layout not in (ARRAY, PACKED):
374         return False
375      for channel in self.channels:
376         if channel.type not in (VOID, UNSIGNED, SIGNED):
377            return False
378      return True
379
380   def is_float(self):
381      """Returns true if this format is an floating-point format."""
382      if self.layout not in (ARRAY, PACKED):
383         return False
384      for channel in self.channels:
385         if channel.type not in (VOID, FLOAT):
386            return False
387      return True
388
389   def channel_type(self):
390      """Returns the type of the channels in this format."""
391      _type = VOID
392      for c in self.channels:
393         if c.type == VOID:
394            continue
395         if _type == VOID:
396            _type = c.type
397         assert c.type == _type
398      return _type
399
400   def channel_size(self):
401      """Returns the size (in bits) of the channels in this format.
402
403      This function should only be called if all of the channels have the
404      same size.  This is always the case if is_array() returns true.
405      """
406      size = None
407      for c in self.channels:
408         if c.type == VOID:
409            continue
410         if size is None:
411            size = c.size
412         assert c.size == size
413      return size
414
415   def max_channel_size(self):
416      """Returns the size of the largest channel."""
417      size = 0
418      for c in self.channels:
419         if c.type == VOID:
420            continue
421         size = max(size, c.size)
422      return size
423
424   def is_normalized(self):
425      """Returns true if this format is normalized.
426
427      While only integer formats can be normalized, not all integer formats
428      are normalized.  Normalized integer formats are those where the
429      integer value is re-interpreted as a fixed point value in the range
430      [0, 1].
431      """
432      norm = None
433      for c in self.channels:
434         if c.type == VOID:
435            continue
436         if norm is None:
437            norm = c.norm
438         assert c.norm == norm
439      return norm
440
441   def has_channel(self, name):
442      """Returns true if this format has the given channel."""
443      if self.is_compressed():
444         # Compressed formats are a bit tricky because the list of channels
445         # contains a single channel of type void.  Since we don't have any
446         # channel information there, we pull it from the swizzle.
447         if str(self.swizzle) == 'xxxx':
448            return name == 'i'
449         elif str(self.swizzle)[0:3] in ('xxx', 'yyy'):
450            if name == 'l':
451               return True
452            elif name == 'a':
453               return self.swizzle['a'] <= Swizzle.SWIZZLE_W
454            else:
455               return False
456         elif name in 'rgba':
457            return self.swizzle[name] <= Swizzle.SWIZZLE_W
458         else:
459            return False
460      else:
461         for channel in self.channels:
462            if channel.name == name:
463               return True
464         return False
465
466   def get_channel(self, name):
467      """Returns the channel with the given name if it exists."""
468      for channel in self.channels:
469         if channel.name == name:
470            return channel
471      return None
472
473   def datatype(self):
474      """Returns the datatype corresponding to a format's channel type and size"""
475      if self.layout == PACKED:
476         if self.block_size() == 8:
477            return 'uint8_t'
478         if self.block_size() == 16:
479            return 'uint16_t'
480         if self.block_size() == 32:
481            return 'uint32_t'
482         else:
483            assert False
484      else:
485         return _get_datatype(self.channel_type(), self.channel_size())
486
487def _get_datatype(type, size):
488   if type == FLOAT:
489      if size == 32:
490         return 'float'
491      elif size == 16:
492         return 'uint16_t'
493      else:
494         assert False
495   elif type == UNSIGNED:
496      if size <= 8:
497         return 'uint8_t'
498      elif size <= 16:
499         return 'uint16_t'
500      elif size <= 32:
501         return 'uint32_t'
502      else:
503         assert False
504   elif type == SIGNED:
505      if size <= 8:
506         return 'int8_t'
507      elif size <= 16:
508         return 'int16_t'
509      elif size <= 32:
510         return 'int32_t'
511      else:
512         assert False
513   else:
514      assert False
515
516def _parse_channels(fields, layout, colorspace, swizzle):
517   channels = []
518   for field in fields:
519      if not field:
520         continue
521
522      type = field[0] if field[0] else 'x'
523
524      if field[1] == 'n':
525         norm = True
526         size = int(field[2:])
527      else:
528         norm = False
529         size = int(field[1:])
530
531      channel = Channel(type, norm, size)
532      channels.append(channel)
533
534   return channels
535
536def parse(filename):
537   """Parse a format description in CSV format.
538
539   This function parses the given CSV file and returns an iterable of
540   channels."""
541
542   with open(filename) as stream:
543      for line in stream:
544         try:
545            comment = line.index('#')
546         except ValueError:
547            pass
548         else:
549            line = line[:comment]
550         line = line.strip()
551         if not line:
552            continue
553
554         fields = [field.strip() for field in line.split(',')]
555
556         name = fields[0]
557         layout = fields[1]
558         block_width = int(fields[2])
559         block_height = int(fields[3])
560         block_depth = int(fields[4])
561         colorspace = fields[10]
562
563         try:
564            swizzle = Swizzle(fields[9])
565         except:
566            sys.exit("error parsing swizzle for format " + name)
567         channels = _parse_channels(fields[5:9], layout, colorspace, swizzle)
568
569         yield Format(name, layout, block_width, block_height, block_depth, channels, swizzle, colorspace)
570