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