format_parser.py revision 848b8605
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 27VOID = 'x' 28UNSIGNED = 'u' 29SIGNED = 's' 30FLOAT = 'f' 31 32ARRAY = 'array' 33PACKED = 'packed' 34OTHER = 'other' 35 36RGB = 'rgb' 37SRGB = 'srgb' 38YUV = 'yuv' 39ZS = 'zs' 40 41def is_power_of_two(x): 42 return not bool(x & (x - 1)) 43 44VERY_LARGE = 99999999999999999999999 45 46class Channel: 47 """Describes a color channel.""" 48 49 def __init__(self, type, norm, size): 50 self.type = type 51 self.norm = norm 52 self.size = size 53 self.sign = type in (SIGNED, FLOAT) 54 self.name = None # Set when the channels are added to the format 55 self.shift = -1 # Set when the channels are added to the format 56 self.index = -1 # Set when the channels are added to the format 57 58 def __str__(self): 59 s = str(self.type) 60 if self.norm: 61 s += 'n' 62 s += str(self.size) 63 return s 64 65 def __eq__(self, other): 66 return self.type == other.type and self.norm == other.norm and self.size == other.size 67 68 def max(self): 69 """Returns the maximum representable number.""" 70 if self.type == FLOAT: 71 return VERY_LARGE 72 if self.norm: 73 return 1 74 if self.type == UNSIGNED: 75 return (1 << self.size) - 1 76 if self.type == SIGNED: 77 return (1 << (self.size - 1)) - 1 78 assert False 79 80 def min(self): 81 """Returns the minimum representable number.""" 82 if self.type == FLOAT: 83 return -VERY_LARGE 84 if self.type == UNSIGNED: 85 return 0 86 if self.norm: 87 return -1 88 if self.type == SIGNED: 89 return -(1 << (self.size - 1)) 90 assert False 91 92 def one(self): 93 """Returns the value that represents 1.0f.""" 94 if self.type == UNSIGNED: 95 return (1 << self.size) - 1 96 if self.type == SIGNED: 97 return (1 << (self.size - 1)) - 1 98 else: 99 return 1 100 101 def is_power_of_two(self): 102 """Returns true if the size of this channel is a power of two.""" 103 return is_power_of_two(self.size) 104 105class Swizzle: 106 """Describes a swizzle operation. 107 108 A Swizzle is a mapping from one set of channels in one format to the 109 channels in another. Each channel in the destination format is 110 associated with one of the following constants: 111 112 * SWIZZLE_X: The first channel in the source format 113 * SWIZZLE_Y: The second channel in the source format 114 * SWIZZLE_Z: The third channel in the source format 115 * SWIZZLE_W: The fourth channel in the source format 116 * SWIZZLE_ZERO: The numeric constant 0 117 * SWIZZLE_ONE: THe numeric constant 1 118 * SWIZZLE_NONE: No data available for this channel 119 120 Sometimes a Swizzle is represented by a 4-character string. In this 121 case, the source channels are represented by the characters "x", "y", 122 "z", and "w"; the numeric constants are represented as "0" and "1"; and 123 no mapping is represented by "_". For instance, the map from 124 luminance-alpha to rgba is given by "xxxy" because each of the three rgb 125 channels maps to the first luminance-alpha channel and the alpha channel 126 maps to second luminance-alpha channel. The mapping from bgr to rgba is 127 given by "zyx1" because the first three colors are reversed and alpha is 128 always 1. 129 """ 130 131 __identity_str = 'xyzw01_' 132 133 SWIZZLE_X = 0 134 SWIZZLE_Y = 1 135 SWIZZLE_Z = 2 136 SWIZZLE_W = 3 137 SWIZZLE_ZERO = 4 138 SWIZZLE_ONE = 5 139 SWIZZLE_NONE = 6 140 141 def __init__(self, swizzle): 142 """Creates a Swizzle object from a string or array.""" 143 if isinstance(swizzle, str): 144 swizzle = [Swizzle.__identity_str.index(c) for c in swizzle] 145 else: 146 swizzle = list(swizzle) 147 for s in swizzle: 148 assert isinstance(s, int) and 0 <= s and s <= Swizzle.SWIZZLE_NONE 149 150 assert len(swizzle) <= 4 151 152 self.__list = swizzle + [Swizzle.SWIZZLE_NONE] * (4 - len(swizzle)) 153 assert len(self.__list) == 4 154 155 def __iter__(self): 156 """Returns an iterator that iterates over this Swizzle. 157 158 The values that the iterator produces are described by the SWIZZLE_* 159 constants. 160 """ 161 return self.__list.__iter__() 162 163 def __str__(self): 164 """Returns a string representation of this Swizzle.""" 165 return ''.join(Swizzle.__identity_str[i] for i in self.__list) 166 167 def __getitem__(self, idx): 168 """Returns the SWIZZLE_* constant for the given destination channel. 169 170 Valid values for the destination channel include any of the SWIZZLE_* 171 constants or any of the following single-character strings: "x", "y", 172 "z", "w", "r", "g", "b", "a", "z" "s". 173 """ 174 175 if isinstance(idx, int): 176 assert idx >= Swizzle.SWIZZLE_X and idx <= Swizzle.SWIZZLE_NONE 177 if idx <= Swizzle.SWIZZLE_W: 178 return self.__list.__getitem__(idx) 179 else: 180 return idx 181 elif isinstance(idx, str): 182 if idx in 'xyzw': 183 idx = 'xyzw'.find(idx) 184 elif idx in 'rgba': 185 idx = 'rgba'.find(idx) 186 elif idx in 'zs': 187 idx = 'zs'.find(idx) 188 else: 189 assert False 190 return self.__list.__getitem__(idx) 191 else: 192 assert False 193 194 def __mul__(self, other): 195 """Returns the composition of this Swizzle with another Swizzle. 196 197 The resulting swizzle is such that, for any valid input to 198 __getitem__, (a * b)[i] = a[b[i]]. 199 """ 200 assert isinstance(other, Swizzle) 201 return Swizzle(self[x] for x in other) 202 203 def inverse(self): 204 """Returns a pseudo-inverse of this swizzle. 205 206 Since swizzling isn't necisaraly a bijection, a Swizzle can never 207 be truely inverted. However, the swizzle returned is *almost* the 208 inverse of this swizzle in the sense that, for each i in range(3), 209 a[a.inverse()[i]] is either i or SWIZZLE_NONE. If swizzle is just 210 a permutation with no channels added or removed, then this 211 function returns the actual inverse. 212 213 This "pseudo-inverse" idea can be demonstrated by mapping from 214 luminance-alpha to rgba that is given by "xxxy". To get from rgba 215 to lumanence-alpha, we use Swizzle("xxxy").inverse() or "xw__". 216 This maps the first component in the lumanence-alpha texture is 217 the red component of the rgba image and the second to the alpha 218 component, exactly as you would expect. 219 """ 220 rev = [Swizzle.SWIZZLE_NONE] * 4 221 for i in xrange(4): 222 for j in xrange(4): 223 if self.__list[j] == i and rev[i] == Swizzle.SWIZZLE_NONE: 224 rev[i] = j 225 return Swizzle(rev) 226 227 228class Format: 229 """Describes a pixel format.""" 230 231 def __init__(self, name, layout, block_width, block_height, channels, swizzle, colorspace): 232 """Constructs a Format from some metadata and a list of channels. 233 234 The channel objects must be unique to this Format and should not be 235 re-used to construct another Format. This is because certain channel 236 information such as shift, offset, and the channel name are set when 237 the Format is created and are calculated based on the entire list of 238 channels. 239 240 Arguments: 241 name -- Name of the format such as 'MESA_FORMAT_A8R8G8B8' 242 layout -- One of 'array', 'packed' 'other', or a compressed layout 243 block_width -- The block width if the format is compressed, 1 otherwise 244 block_height -- The block height 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.channels = channels 254 assert isinstance(swizzle, Swizzle) 255 self.swizzle = swizzle 256 self.name = name 257 assert colorspace in (RGB, SRGB, YUV, ZS) 258 self.colorspace = colorspace 259 260 # Name the channels 261 chan_names = ['']*4 262 if self.colorspace in (RGB, SRGB): 263 for (i, s) in enumerate(swizzle): 264 if s < 4: 265 chan_names[s] += 'rgba'[i] 266 elif colorspace == ZS: 267 for (i, s) in enumerate(swizzle): 268 if s < 4: 269 chan_names[s] += 'zs'[i] 270 else: 271 chan_names = ['x', 'y', 'z', 'w'] 272 273 for c, name in zip(self.channels, chan_names): 274 assert c.name is None 275 if name == 'rgb': 276 c.name = 'l' 277 elif name == 'rgba': 278 c.name = 'i' 279 elif name == '': 280 c.name = 'x' 281 else: 282 c.name = name 283 284 # Set indices and offsets 285 if self.layout == PACKED: 286 shift = 0 287 for channel in self.channels: 288 assert channel.shift == -1 289 channel.shift = shift 290 shift += channel.size 291 for idx, channel in enumerate(self.channels): 292 assert channel.index == -1 293 channel.index = idx 294 else: 295 pass # Shift means nothing here 296 297 def __str__(self): 298 return self.name 299 300 def short_name(self): 301 """Returns a short name for a format. 302 303 The short name should be suitable to be used as suffix in function 304 names. 305 """ 306 307 name = self.name 308 if name.startswith('MESA_FORMAT_'): 309 name = name[len('MESA_FORMAT_'):] 310 name = name.lower() 311 return name 312 313 def block_size(self): 314 """Returns the block size (in bits) of the format.""" 315 size = 0 316 for channel in self.channels: 317 size += channel.size 318 return size 319 320 def num_channels(self): 321 """Returns the number of channels in the format.""" 322 nr_channels = 0 323 for channel in self.channels: 324 if channel.size: 325 nr_channels += 1 326 return nr_channels 327 328 def array_element(self): 329 """Returns a non-void channel if this format is an array, otherwise None. 330 331 If the returned channel is not None, then this format can be 332 considered to be an array of num_channels() channels identical to the 333 returned channel. 334 """ 335 if self.layout == ARRAY: 336 return self.channels[0] 337 elif self.layout == PACKED: 338 ref_channel = self.channels[0] 339 if ref_channel.type == VOID: 340 ref_channel = self.channels[1] 341 for channel in self.channels: 342 if channel.size == 0 or channel.type == VOID: 343 continue 344 if channel.size != ref_channel.size or channel.size % 8 != 0: 345 return None 346 if channel.type != ref_channel.type: 347 return None 348 if channel.norm != ref_channel.norm: 349 return None 350 return ref_channel 351 else: 352 return None 353 354 def is_array(self): 355 """Returns true if this format can be considered an array format. 356 357 This function will return true if self.layout == 'array'. However, 358 some formats, such as MESA_FORMAT_A8G8B8R8, can be considered as 359 array formats even though they are technically packed. 360 """ 361 return self.array_element() != None 362 363 def is_compressed(self): 364 """Returns true if this is a compressed format.""" 365 return self.block_width != 1 or self.block_height != 1 366 367 def is_int(self): 368 """Returns true if this format is an integer format. 369 370 See also: is_norm() 371 """ 372 if self.layout not in (ARRAY, PACKED): 373 return False 374 for channel in self.channels: 375 if channel.type not in (VOID, UNSIGNED, SIGNED): 376 return False 377 return True 378 379 def is_float(self): 380 """Returns true if this format is an floating-point format.""" 381 if self.layout not in (ARRAY, PACKED): 382 return False 383 for channel in self.channels: 384 if channel.type not in (VOID, FLOAT): 385 return False 386 return True 387 388 def channel_type(self): 389 """Returns the type of the channels in this format.""" 390 _type = VOID 391 for c in self.channels: 392 if c.type == VOID: 393 continue 394 if _type == VOID: 395 _type = c.type 396 assert c.type == _type 397 return _type 398 399 def channel_size(self): 400 """Returns the size (in bits) of the channels in this format. 401 402 This function should only be called if all of the channels have the 403 same size. This is always the case if is_array() returns true. 404 """ 405 size = None 406 for c in self.channels: 407 if c.type == VOID: 408 continue 409 if size is None: 410 size = c.size 411 assert c.size == size 412 return size 413 414 def max_channel_size(self): 415 """Returns the size of the largest channel.""" 416 size = 0 417 for c in self.channels: 418 if c.type == VOID: 419 continue 420 size = max(size, c.size) 421 return size 422 423 def is_normalized(self): 424 """Returns true if this format is normalized. 425 426 While only integer formats can be normalized, not all integer formats 427 are normalized. Normalized integer formats are those where the 428 integer value is re-interpreted as a fixed point value in the range 429 [0, 1]. 430 """ 431 norm = None 432 for c in self.channels: 433 if c.type == VOID: 434 continue 435 if norm is None: 436 norm = c.norm 437 assert c.norm == norm 438 return norm 439 440 def has_channel(self, name): 441 """Returns true if this format has the given channel.""" 442 if self.is_compressed(): 443 # Compressed formats are a bit tricky because the list of channels 444 # contains a single channel of type void. Since we don't have any 445 # channel information there, we pull it from the swizzle. 446 if str(self.swizzle) == 'xxxx': 447 return name == 'i' 448 elif str(self.swizzle)[0:3] in ('xxx', 'yyy'): 449 if name == 'l': 450 return True 451 elif name == 'a': 452 return self.swizzle['a'] <= Swizzle.SWIZZLE_W 453 else: 454 return False 455 elif name in 'rgba': 456 return self.swizzle[name] <= Swizzle.SWIZZLE_W 457 else: 458 return False 459 else: 460 for channel in self.channels: 461 if channel.name == name: 462 return True 463 return False 464 465 def get_channel(self, name): 466 """Returns the channel with the given name if it exists.""" 467 for channel in self.channels: 468 if channel.name == name: 469 return channel 470 return None 471 472def _parse_channels(fields, layout, colorspace, swizzle): 473 channels = [] 474 for field in fields: 475 if not field: 476 continue 477 478 type = field[0] if field[0] else 'x' 479 480 if field[1] == 'n': 481 norm = True 482 size = int(field[2:]) 483 else: 484 norm = False 485 size = int(field[1:]) 486 487 channel = Channel(type, norm, size) 488 channels.append(channel) 489 490 return channels 491 492def parse(filename): 493 """Parse a format descrition in CSV format. 494 495 This function parses the given CSV file and returns an iterable of 496 channels.""" 497 498 with open(filename) as stream: 499 for line in stream: 500 try: 501 comment = line.index('#') 502 except ValueError: 503 pass 504 else: 505 line = line[:comment] 506 line = line.strip() 507 if not line: 508 continue 509 510 fields = [field.strip() for field in line.split(',')] 511 512 name = fields[0] 513 layout = fields[1] 514 block_width = int(fields[2]) 515 block_height = int(fields[3]) 516 colorspace = fields[9] 517 518 swizzle = Swizzle(fields[8]) 519 channels = _parse_channels(fields[4:8], layout, colorspace, swizzle) 520 521 yield Format(name, layout, block_width, block_height, channels, swizzle, colorspace) 522