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