1# 2# Copyright 2009 VMware, Inc. 3# Copyright 2014 Intel Corporation 4# All Rights Reserved. 5# 6# Permission is hereby granted, free of charge, to any person obtaining a 7# copy of this software and associated documentation files (the 8# "Software"), to deal in the Software without restriction, including 9# without limitation the rights to use, copy, modify, merge, publish, 10# distribute, sub license, and/or sell copies of the Software, and to 11# permit persons to whom the Software is furnished to do so, subject to 12# the following conditions: 13# 14# The above copyright notice and this permission notice (including the 15# next paragraph) shall be included in all copies or substantial portions 16# of the Software. 17# 18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. 21# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR 22# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 26import sys 27 28VOID = 'x' 29UNSIGNED = 'u' 30SIGNED = 's' 31FLOAT = 'f' 32 33ARRAY = 'array' 34PACKED = 'packed' 35OTHER = 'other' 36 37RGB = 'rgb' 38SRGB = 'srgb' 39YUV = 'yuv' 40ZS = 'zs' 41 42VERY_LARGE = 99999999999999999999999 43 44class Channel: 45 """Describes a color channel.""" 46 47 def __init__(self, type, norm, size): 48 self.type = type 49 self.norm = norm 50 self.size = size 51 self.sign = type in (SIGNED, FLOAT) 52 self.name = None # Set when the channels are added to the format 53 self.shift = -1 # Set when the channels are added to the format 54 self.index = -1 # Set when the channels are added to the format 55 56 def __str__(self): 57 s = str(self.type) 58 if self.norm: 59 s += 'n' 60 s += str(self.size) 61 return s 62 63 def __eq__(self, other): 64 return self.type == other.type and self.norm == other.norm and self.size == other.size 65 66 def max(self): 67 """Returns the maximum representable number.""" 68 if self.type == FLOAT: 69 return VERY_LARGE 70 if self.norm: 71 return 1 72 if self.type == UNSIGNED: 73 return (1 << self.size) - 1 74 if self.type == SIGNED: 75 return (1 << (self.size - 1)) - 1 76 assert False 77 78 def min(self): 79 """Returns the minimum representable number.""" 80 if self.type == FLOAT: 81 return -VERY_LARGE 82 if self.type == UNSIGNED: 83 return 0 84 if self.norm: 85 return -1 86 if self.type == SIGNED: 87 return -(1 << (self.size - 1)) 88 assert False 89 90 def one(self): 91 """Returns the value that represents 1.0f.""" 92 if self.type == UNSIGNED: 93 return (1 << self.size) - 1 94 if self.type == SIGNED: 95 return (1 << (self.size - 1)) - 1 96 else: 97 return 1 98 99 def datatype(self): 100 """Returns the datatype corresponding to a channel type and size""" 101 return _get_datatype(self.type, self.size) 102 103class Swizzle: 104 """Describes a swizzle operation. 105 106 A Swizzle is a mapping from one set of channels in one format to the 107 channels in another. Each channel in the destination format is 108 associated with one of the following constants: 109 110 * SWIZZLE_X: The first channel in the source format 111 * SWIZZLE_Y: The second channel in the source format 112 * SWIZZLE_Z: The third channel in the source format 113 * SWIZZLE_W: The fourth channel in the source format 114 * SWIZZLE_ZERO: The numeric constant 0 115 * SWIZZLE_ONE: THe numeric constant 1 116 * SWIZZLE_NONE: No data available for this channel 117 118 Sometimes a Swizzle is represented by a 4-character string. In this 119 case, the source channels are represented by the characters "x", "y", 120 "z", and "w"; the numeric constants are represented as "0" and "1"; and 121 no mapping is represented by "_". For instance, the map from 122 luminance-alpha to rgba is given by "xxxy" because each of the three rgb 123 channels maps to the first luminance-alpha channel and the alpha channel 124 maps to second luminance-alpha channel. The mapping from bgr to rgba is 125 given by "zyx1" because the first three colors are reversed and alpha is 126 always 1. 127 """ 128 129 __identity_str = 'xyzw01_' 130 131 SWIZZLE_X = 0 132 SWIZZLE_Y = 1 133 SWIZZLE_Z = 2 134 SWIZZLE_W = 3 135 SWIZZLE_ZERO = 4 136 SWIZZLE_ONE = 5 137 SWIZZLE_NONE = 6 138 139 def __init__(self, swizzle): 140 """Creates a Swizzle object from a string or array.""" 141 if isinstance(swizzle, str): 142 swizzle = [Swizzle.__identity_str.index(c) for c in swizzle] 143 else: 144 swizzle = list(swizzle) 145 for s in swizzle: 146 assert isinstance(s, int) and 0 <= s and s <= Swizzle.SWIZZLE_NONE 147 148 assert len(swizzle) <= 4 149 150 self.__list = swizzle + [Swizzle.SWIZZLE_NONE] * (4 - len(swizzle)) 151 assert len(self.__list) == 4 152 153 def __iter__(self): 154 """Returns an iterator that iterates over this Swizzle. 155 156 The values that the iterator produces are described by the SWIZZLE_* 157 constants. 158 """ 159 return self.__list.__iter__() 160 161 def __str__(self): 162 """Returns a string representation of this Swizzle.""" 163 return ''.join(Swizzle.__identity_str[i] for i in self.__list) 164 165 def __getitem__(self, idx): 166 """Returns the SWIZZLE_* constant for the given destination channel. 167 168 Valid values for the destination channel include any of the SWIZZLE_* 169 constants or any of the following single-character strings: "x", "y", 170 "z", "w", "r", "g", "b", "a", "z" "s". 171 """ 172 173 if isinstance(idx, int): 174 assert idx >= Swizzle.SWIZZLE_X and idx <= Swizzle.SWIZZLE_NONE 175 if idx <= Swizzle.SWIZZLE_W: 176 return self.__list.__getitem__(idx) 177 else: 178 return idx 179 elif isinstance(idx, str): 180 if idx in 'xyzw': 181 idx = 'xyzw'.find(idx) 182 elif idx in 'rgba': 183 idx = 'rgba'.find(idx) 184 elif idx in 'zs': 185 idx = 'zs'.find(idx) 186 else: 187 assert False 188 return self.__list.__getitem__(idx) 189 else: 190 assert False 191 192 def __mul__(self, other): 193 """Returns the composition of this Swizzle with another Swizzle. 194 195 The resulting swizzle is such that, for any valid input to 196 __getitem__, (a * b)[i] = a[b[i]]. 197 """ 198 assert isinstance(other, Swizzle) 199 return Swizzle(self[x] for x in other) 200 201 def inverse(self): 202 """Returns a pseudo-inverse of this swizzle. 203 204 Since swizzling isn't necisaraly a bijection, a Swizzle can never 205 be truely inverted. However, the swizzle returned is *almost* the 206 inverse of this swizzle in the sense that, for each i in range(3), 207 a[a.inverse()[i]] is either i or SWIZZLE_NONE. If swizzle is just 208 a permutation with no channels added or removed, then this 209 function returns the actual inverse. 210 211 This "pseudo-inverse" idea can be demonstrated by mapping from 212 luminance-alpha to rgba that is given by "xxxy". To get from rgba 213 to lumanence-alpha, we use Swizzle("xxxy").inverse() or "xw__". 214 This maps the first component in the lumanence-alpha texture is 215 the red component of the rgba image and the second to the alpha 216 component, exactly as you would expect. 217 """ 218 rev = [Swizzle.SWIZZLE_NONE] * 4 219 for i in xrange(4): 220 for j in xrange(4): 221 if self.__list[j] == i and rev[i] == Swizzle.SWIZZLE_NONE: 222 rev[i] = j 223 return Swizzle(rev) 224 225 226class Format: 227 """Describes a pixel format.""" 228 229 def __init__(self, name, layout, block_width, block_height, block_depth, channels, swizzle, colorspace): 230 """Constructs a Format from some metadata and a list of channels. 231 232 The channel objects must be unique to this Format and should not be 233 re-used to construct another Format. This is because certain channel 234 information such as shift, offset, and the channel name are set when 235 the Format is created and are calculated based on the entire list of 236 channels. 237 238 Arguments: 239 name -- Name of the format such as 'MESA_FORMAT_A8R8G8B8' 240 layout -- One of 'array', 'packed' 'other', or a compressed layout 241 block_width -- The block width if the format is compressed, 1 otherwise 242 block_height -- The block height if the format is compressed, 1 otherwise 243 block_depth -- The block depth if the format is compressed, 1 otherwise 244 channels -- A list of Channel objects 245 swizzle -- A Swizzle from this format to rgba 246 colorspace -- one of 'rgb', 'srgb', 'yuv', or 'zs' 247 """ 248 self.name = name 249 self.layout = layout 250 self.block_width = block_width 251 self.block_height = block_height 252 self.block_depth = block_depth 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 or self.block_depth != 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 472 def datatype(self): 473 """Returns the datatype corresponding to a format's channel type and size""" 474 if self.layout == PACKED: 475 if self.block_size() == 8: 476 return 'uint8_t' 477 if self.block_size() == 16: 478 return 'uint16_t' 479 if self.block_size() == 32: 480 return 'uint32_t' 481 else: 482 assert False 483 else: 484 return _get_datatype(self.channel_type(), self.channel_size()) 485 486def _get_datatype(type, size): 487 if type == FLOAT: 488 if size == 32: 489 return 'float' 490 elif size == 16: 491 return 'uint16_t' 492 else: 493 assert False 494 elif type == UNSIGNED: 495 if size <= 8: 496 return 'uint8_t' 497 elif size <= 16: 498 return 'uint16_t' 499 elif size <= 32: 500 return 'uint32_t' 501 else: 502 assert False 503 elif type == SIGNED: 504 if size <= 8: 505 return 'int8_t' 506 elif size <= 16: 507 return 'int16_t' 508 elif size <= 32: 509 return 'int32_t' 510 else: 511 assert False 512 else: 513 assert False 514 515def _parse_channels(fields, layout, colorspace, swizzle): 516 channels = [] 517 for field in fields: 518 if not field: 519 continue 520 521 type = field[0] if field[0] else 'x' 522 523 if field[1] == 'n': 524 norm = True 525 size = int(field[2:]) 526 else: 527 norm = False 528 size = int(field[1:]) 529 530 channel = Channel(type, norm, size) 531 channels.append(channel) 532 533 return channels 534 535def parse(filename): 536 """Parse a format description in CSV format. 537 538 This function parses the given CSV file and returns an iterable of 539 channels.""" 540 541 with open(filename) as stream: 542 for line in stream: 543 try: 544 comment = line.index('#') 545 except ValueError: 546 pass 547 else: 548 line = line[:comment] 549 line = line.strip() 550 if not line: 551 continue 552 553 fields = [field.strip() for field in line.split(',')] 554 555 name = fields[0] 556 layout = fields[1] 557 block_width = int(fields[2]) 558 block_height = int(fields[3]) 559 block_depth = int(fields[4]) 560 colorspace = fields[10] 561 562 try: 563 swizzle = Swizzle(fields[9]) 564 except: 565 sys.exit("error parsing swizzle for format " + name) 566 channels = _parse_channels(fields[5:9], layout, colorspace, swizzle) 567 568 yield Format(name, layout, block_width, block_height, block_depth, channels, swizzle, colorspace) 569