2
# Copyright 2009 VMware, Inc.
3
# Copyright 2014 Intel Corporation
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:
14
# The above copyright notice and this permission notice (including the
15
# next paragraph) shall be included in all copies or substantial portions
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.
42
VERY_LARGE = 99999999999999999999999
45
"""Describes a color channel."""
47
def __init__(self, type, norm, 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
63
def __eq__(self, other):
67
return self.type == other.type and self.norm == other.norm and self.size == other.size
69
def __ne__(self, other):
70
return not self.__eq__(other)
73
"""Returns the maximum representable number."""
74
if self.type == FLOAT:
78
if self.type == UNSIGNED:
79
return (1 << self.size) - 1
80
if self.type == SIGNED:
81
return (1 << (self.size - 1)) - 1
85
"""Returns the minimum representable number."""
86
if self.type == FLOAT:
88
if self.type == UNSIGNED:
92
if self.type == SIGNED:
93
return -(1 << (self.size - 1))
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
106
"""Returns the datatype corresponding to a channel type and size"""
107
return _get_datatype(self.type, self.size)
110
"""Describes a swizzle operation.
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:
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
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
135
__identity_str = 'xyzw01_'
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]
150
swizzle = list(swizzle)
152
assert isinstance(s, int) and 0 <= s and s <= Swizzle.SWIZZLE_NONE
154
assert len(swizzle) <= 4
156
self.__list = swizzle + [Swizzle.SWIZZLE_NONE] * (4 - len(swizzle))
157
assert len(self.__list) == 4
160
"""Returns an iterator that iterates over this Swizzle.
162
The values that the iterator produces are described by the SWIZZLE_*
165
return self.__list.__iter__()
168
"""Returns a string representation of this Swizzle."""
169
return ''.join(Swizzle.__identity_str[i] for i in self.__list)
171
def __getitem__(self, idx):
172
"""Returns the SWIZZLE_* constant for the given destination channel.
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".
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)
185
elif isinstance(idx, str):
187
idx = 'xyzw'.find(idx)
189
idx = 'rgba'.find(idx)
194
return self.__list.__getitem__(idx)
198
def __mul__(self, other):
199
"""Returns the composition of this Swizzle with another Swizzle.
201
The resulting swizzle is such that, for any valid input to
202
__getitem__, (a * b)[i] = a[b[i]].
204
assert isinstance(other, Swizzle)
205
return Swizzle(self[x] for x in other)
208
"""Returns a pseudo-inverse of this swizzle.
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.
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.
224
rev = [Swizzle.SWIZZLE_NONE] * 4
227
if self.__list[j] == i and rev[i] == Swizzle.SWIZZLE_NONE:
233
"""Describes a pixel format."""
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.
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
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'
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
263
assert colorspace in (RGB, SRGB, YUV, ZS)
264
self.colorspace = colorspace
268
if self.colorspace in (RGB, SRGB):
269
for (i, s) in enumerate(swizzle):
271
chan_names[s] += 'rgba'[i]
272
elif colorspace == ZS:
273
for (i, s) in enumerate(swizzle):
275
chan_names[s] += 'zs'[i]
277
chan_names = ['x', 'y', 'z', 'w']
279
for c, name in zip(self.channels, chan_names):
280
assert c.name is None
290
# Set indices and offsets
291
if self.layout == PACKED:
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
301
pass # Shift means nothing here
306
def short_name(self):
307
"""Returns a short name for a format.
309
The short name should be suitable to be used as suffix in function
314
if name.startswith('MESA_FORMAT_'):
315
name = name[len('MESA_FORMAT_'):]
319
def block_size(self):
320
"""Returns the block size (in bits) of the format."""
322
for channel in self.channels:
326
def num_channels(self):
327
"""Returns the number of channels in the format."""
329
for channel in self.channels:
334
def array_element(self):
335
"""Returns a non-void channel if this format is an array, otherwise None.
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
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:
350
if channel.size != ref_channel.size or channel.size % 8 != 0:
352
if channel.type != ref_channel.type:
354
if channel.norm != ref_channel.norm:
361
"""Returns true if this format can be considered an array format.
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.
367
return self.array_element() != None
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
374
"""Returns true if this format is an integer format.
378
if self.layout not in (ARRAY, PACKED):
380
for channel in self.channels:
381
if channel.type not in (VOID, UNSIGNED, SIGNED):
386
"""Returns true if this format is an floating-point format."""
387
if self.layout not in (ARRAY, PACKED):
389
for channel in self.channels:
390
if channel.type not in (VOID, FLOAT):
394
def channel_type(self):
395
"""Returns the type of the channels in this format."""
397
for c in self.channels:
402
assert c.type == _type
405
def channel_size(self):
406
"""Returns the size (in bits) of the channels in this format.
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.
412
for c in self.channels:
417
assert c.size == size
420
def max_channel_size(self):
421
"""Returns the size of the largest channel."""
423
for c in self.channels:
426
size = max(size, c.size)
429
def is_normalized(self):
430
"""Returns true if this format is normalized.
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
438
for c in self.channels:
443
assert c.norm == norm
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':
454
elif str(self.swizzle)[0:3] in ('xxx', 'yyy'):
458
return self.swizzle['a'] <= Swizzle.SWIZZLE_W
462
return self.swizzle[name] <= Swizzle.SWIZZLE_W
466
for channel in self.channels:
467
if channel.name == name:
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:
479
"""Returns the datatype corresponding to a format's channel type and size"""
480
if self.layout == PACKED:
481
if self.block_size() == 8:
483
if self.block_size() == 16:
485
if self.block_size() == 32:
490
return _get_datatype(self.channel_type(), self.channel_size())
492
def _get_datatype(type, size):
500
elif type == UNSIGNED:
521
def _parse_channels(fields):
527
type = field[0] if field[0] else 'x'
531
size = int(field[2:])
534
size = int(field[1:])
536
channel = Channel(type, norm, size)
537
channels.append(channel)
542
"""Parse a format description in CSV format.
544
This function parses the given CSV file and returns an iterable of
547
with open(filename) as stream:
550
comment = line.index('#')
554
line = line[:comment]
559
fields = [field.strip() for field in line.split(',')]
563
block_width = int(fields[2])
564
block_height = int(fields[3])
565
block_depth = int(fields[4])
566
colorspace = fields[10]
569
swizzle = Swizzle(fields[9])
571
sys.exit("error parsing swizzle for format " + name)
573
channels = _parse_channels(fields[5:9])
575
yield Format(name, layout, block_width, block_height, block_depth, channels, swizzle, colorspace)