Class VideoColor
VideoFrame are to be turned into colour: which luma/chroma matrix
was used to encode them, whether the codes cover the full range or the studio range, and what
transfer function the encoded values carry. Layout is a separate question and lives on
PixelFormat: the same I420 buffer is BT.601 or BT.709 depending only on this.
Immutable and interned: one instance per matrix, range and transfer, plus
unspecified(), and nothing else can be constructed. Reference comparison is therefore
exact and cheap, and the coefficient accessors are the one place the decode matrix exists; a CPU
converter and a GPU shader that both read them cannot drift apart.
The coefficient accessors give the folded, ready-to-use form for a picture of a given bit
depth: codes in [0..maxCode] in, RGB in [0..maxCode] out, as
int neutral = chromaNeutral(depth);
double y = yScale(depth) * (Y - yOffset(depth));
double r = y + crToR(depth) * (Cr - neutral);
double g = y + (cbToG(depth) * (Cb - neutral) + crToG(depth) * (Cr - neutral));
double b = y + cbToB(depth) * (Cb - neutral);
Green's two chroma terms are summed before the luma is added, as bracketed: adding them one at a
time re-associates the arithmetic and rounds some codes to a different integer, so a consumer
that regroups them stops matching every other consumer of this table.
Every accessor takes the bit depth and none of them assumes eight. Studio black, the chroma neutral and the studio gain all move with the depth, so a 10-bit picture decoded through the 8-bit table is not slightly wrong; it is four times too bright with black four times too high. The argument exists so that omitting it is a compile error rather than a picture.
The chroma neutral is an integer code, never a normalized 0.5; sampled as a normalized value
it is 128/255 at eight bits, and using 0.5 instead tints neutral grey by about a
code.
Any thread: every instance is immutable and every accessor is a field read or one multiply.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic enumThe luma/chroma matrix a stream was encoded with.static enumWhich codes a stream's samples occupy.static enumWhat the decoded values are, once the matrix has been undone: numbers a display can show, or a curve that has to be inverted before they mean light. -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final VideoColorBT.2020 with full range.static final VideoColorBT.2020 with studio range.static final VideoColorBT.601 with full range.static final VideoColorBT.601 with studio range.static final VideoColorBT.709 with full range.static final VideoColorBT.709 with studio range. -
Method Summary
Modifier and TypeMethodDescriptiondoublecbToB(int bitDepth) doublecbToG(int bitDepth) intchromaNeutral(int bitDepth) doublecrToG(int bitDepth) doublecrToR(int bitDepth) booleanWhether the decoded values are already numbers a display can show, which is the question that decides whether a consumer may composite them as an ordinary picture or owes them the inverse of a curve first.booleanmatrix()static VideoColorof(VideoColor.Matrix matrix, VideoColor.Range range) static VideoColorof(VideoColor.Matrix matrix, VideoColor.Range range, VideoColor.Transfer transfer) range()toString()Never throws:VideoColor[BT709 LIMITED],VideoColor[BT2020 LIMITED PQ], orVideoColor[unspecified].transfer()static VideoColorThe interpretation for a stream that carried no colour information at all.withTransfer(VideoColor.Transfer transfer) intyOffset(int bitDepth) doubleyScale(int bitDepth)
-
Field Details
-
BT601_LIMITED
BT.601 with studio range. Standard-definition broadcast content. -
BT601_FULL
BT.601 with full range. The motion-JPEG convention. -
BT709_LIMITED
BT.709 with studio range. High-definition broadcast content. -
BT709_FULL
BT.709 with full range. High-definition content encoded over the whole code span. -
BT2020_LIMITED
BT.2020 with studio range. Ultra-high-definition broadcast content. -
BT2020_FULL
BT.2020 with full range.
-
-
Method Details
-
of
- Returns:
- the interned instance for
matrixandrangewith aVideoColor.Transfer.SDRtransfer; never a new object - Throws:
NullPointerException- if either argument is null
-
of
public static VideoColor of(VideoColor.Matrix matrix, VideoColor.Range range, VideoColor.Transfer transfer) - Returns:
- the interned instance for
matrix,rangeandtransfer; never a new object - Throws:
NullPointerException- if any argument is null
-
withTransfer
- Returns:
- the interned instance with this matrix and range and
transfer's curve, orthiswhen the transfer already matches. An unsignalled interpretation given a transfer becomes a signalled one, because a stream that said PQ said something. - Throws:
NullPointerException- iftransferis null
-
unspecified
The interpretation for a stream that carried no colour information at all. Decodes exactly asBT709_LIMITEDunderVideoColor.Transfer.SDR, so a caller that ignores the distinction still gets the common case, but reportsfalsefromisSpecified()so a caller that can do better may. A distinct instance, so reference comparison separates "unsignalled" from "signalled as BT.709 limited"; a frame always has a decodable interpretation and this is never null.An unsignalled transfer means SDR, and that is a choice rather than an absence. Nearly every file that signals nothing is ordinary display-referred video, and a picture assumed SDR that is really PQ is visibly wrong the instant it is shown; the opposite assumption would make every untagged file wrong instead, all the time. So the fallback is the one that is right almost always and loud when it is not.
- Returns:
- the interned unspecified instance
-
matrix
- Returns:
- the encoding matrix;
VideoColor.Matrix.BT709forunspecified()
-
range
- Returns:
- the code range;
VideoColor.Range.LIMITEDforunspecified()
-
transfer
- Returns:
- the transfer function the decoded values carry;
VideoColor.Transfer.SDRunless signalled
-
isDisplayReferred
public boolean isDisplayReferred()Whether the decoded values are already numbers a display can show, which is the question that decides whether a consumer may composite them as an ordinary picture or owes them the inverse of a curve first.- Returns:
- true exactly when
transfer()isVideoColor.Transfer.SDR
-
isSpecified
public boolean isSpecified()- Returns:
- whether a stream actually signalled this interpretation, as opposed to it being the fallback a stream that signalled nothing decodes as
-
yOffset
public int yOffset(int bitDepth) - Parameters:
bitDepth- valid bits per component, in[8..16](PixelFormat.bitDepth())- Returns:
- the luma code that decodes to 0:
16 << (bitDepth - 8)for studio range, 0 for full range. Studio black is 16 at eight bits and 64 at ten, not 16 at both. - Throws:
IllegalArgumentException- ifbitDepthis outside[8..16]
-
chromaNeutral
public int chromaNeutral(int bitDepth) - Parameters:
bitDepth- valid bits per component, in[8..16]- Returns:
- the chroma code carrying no colour difference:
1 << (bitDepth - 1), so 128 at eight bits and 512 at ten, in every range - Throws:
IllegalArgumentException- ifbitDepthis outside[8..16]
-
yScale
public double yScale(int bitDepth) - Parameters:
bitDepth- valid bits per component, in[8..16]- Returns:
- luma gain, code to
[0..maxCode]: exactly 1 for full range, and for studio range the output span over the input span,255/219at eight bits and1023/876at ten, which are not the same number - Throws:
IllegalArgumentException- ifbitDepthis outside[8..16]
-
crToR
public double crToR(int bitDepth) - Parameters:
bitDepth- valid bits per component, in[8..16]- Returns:
- the Cr contribution to red, per unit of
Cr - chromaNeutral(bitDepth) - Throws:
IllegalArgumentException- ifbitDepthis outside[8..16]
-
cbToG
public double cbToG(int bitDepth) - Parameters:
bitDepth- valid bits per component, in[8..16]- Returns:
- the Cb contribution to green, per unit of
Cb - chromaNeutral(bitDepth); always negative - Throws:
IllegalArgumentException- ifbitDepthis outside[8..16]
-
crToG
public double crToG(int bitDepth) - Parameters:
bitDepth- valid bits per component, in[8..16]- Returns:
- the Cr contribution to green, per unit of
Cr - chromaNeutral(bitDepth); always negative - Throws:
IllegalArgumentException- ifbitDepthis outside[8..16]
-
cbToB
public double cbToB(int bitDepth) - Parameters:
bitDepth- valid bits per component, in[8..16]- Returns:
- the Cb contribution to blue, per unit of
Cb - chromaNeutral(bitDepth) - Throws:
IllegalArgumentException- ifbitDepthis outside[8..16]
-
toString
Never throws:VideoColor[BT709 LIMITED],VideoColor[BT2020 LIMITED PQ], orVideoColor[unspecified]. The transfer is printed only when it is not the SDR one, so the common case reads as it always has.
-