Package limn.video

Enum Class PixelFormat

All Implemented Interfaces:
Serializable, Comparable<PixelFormat>, Constable

public enum PixelFormat extends Enum<PixelFormat>
The memory layout of a decoded VideoFrame: how many planes it has, what each one holds, and how each plane's sample grid is derived from the frame size. Every producer and every consumer computes plane geometry from here, so an odd frame size cannot be rounded one way by the code that fills a plane and another way by the code that reads it; that mismatch renders as a coloured stripe down one edge, nowhere near whichever side rounded wrong.

Layout only. Nothing here says what the sample values mean: matrix and range live on VideoColor, and the same I420 buffer is BT.601 or BT.709 depending on it.

Plane 0 is always full-resolution luma. Plane sizes are counted in samples of that plane's own grid; bytes are a separate question, because NV12 stores two components per chroma sample and a 10-bit sample occupies two bytes whatever the layout.

A code and its storage word are not the same number. Most layouts store a code right-justified, so the two coincide; P010 stores it shifted up to the top of a 16-bit word, which is what a hardware decoder produces. componentAt(java.nio.ByteBuffer, int) and putComponent(java.nio.ByteBuffer, int, int) are the only place that difference is spelled, so a consumer that reads samples through them is right for both and one that reads the bytes itself is right for one of them; see codeShift().

Bit depth is not a detail of the samples, it is a property of the arithmetic. Every size here is derived through bytesPerSample(int), so a consumer that comes through these methods is depth-correct and one that multiplies a width by a component count is not. The decode matrix is depth-dependent too, which is why VideoColor's coefficient accessors take a bit depth rather than answering for eight bits and hoping.

  • Nested Class Summary

    Nested classes/interfaces inherited from class java.lang.Enum

    Enum.EnumDesc<E extends Enum<E>>
  • Enum Constant Summary

    Enum Constants
    Enum Constant
    Description
    8-bit 4:2:0 in three planes: Y, then Cb, then Cr, each planar, each chroma plane half resolution in both directions.
    10-bit 4:2:0 in three planes, laid out exactly as I420 but with a sample occupying two bytes.
    8-bit 4:4:4 in three planes: Y, Cb, Cr, all full resolution.
    10-bit 4:4:4 in three planes: I444's geometry with I420_10LE's samples.
    8-bit 4:2:0 in two planes: Y, then Cb and Cr interleaved in a single plane whose sample n is the byte pair (Cb, Cr), in that order.
    10-bit 4:2:0 in two planes: NV12's geometry with ten bits per component.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final int
    Largest frame width or height any geometry method accepts, in pixels.
  • Method Summary

    Modifier and Type
    Method
    Description
    int
    alignedStride(int plane, int frameWidth, int alignBytes)
    The plane's byte width rounded up to a multiple of alignBytes: what a producer that allocates its own planes should use as its stride, so rows start where wide loads and GPU uploads want them.
    int
     
    int
    bytesPerSample(int plane)
    Bytes occupied by one sample of plane.
    int
     
    int
     
    int
    How far a code sits above the bottom of its storage word: 0 for every right-justified layout and 6 for P010, whose ten bits occupy the top of a 16-bit word.
    int
    componentAt(ByteBuffer plane, int byteIndex)
    Reads one component out of a plane's bytes, at an absolute byte index and without moving the buffer's position.
    int
     
    int
    The largest sample code this layout can carry: 255 at eight bits, 1023 at ten.
    long
    minPlaneBytes(int plane, int frameWidth, int frameHeight, int strideBytes)
    Smallest buffer that can hold plane at this size and stride: every row but the last occupies a full strideBytes, and the last row needs only its byte width.
    int
    planeByteWidth(int plane, int frameWidth)
    Bytes of picture in one row of plane: the part of a row a consumer may read.
    int
     
    int
    planeHeight(int plane, int frameHeight)
     
    int
    planeWidth(int plane, int frameWidth)
    Samples across one row of plane for a frame frameWidth pixels wide.
    void
    putComponent(ByteBuffer plane, int byteIndex, int code)
    Writes one component into a plane's bytes, at an absolute byte index and without moving the buffer's position: the inverse of componentAt(java.nio.ByteBuffer, int), and the only place a producer needs to know how wide a sample is.
    Returns the enum constant of this class with the specified name.
    static PixelFormat[]
    Returns an array containing the constants of this enum class, in the order they are declared.

    Methods inherited from class java.lang.Object

    getClass, notify, notifyAll, wait, wait, wait
  • Enum Constant Details

    • I420

      public static final PixelFormat I420
      8-bit 4:2:0 in three planes: Y, then Cb, then Cr, each planar, each chroma plane half resolution in both directions. What a software H.264 / VP9 / AV1 decode hands back.
    • NV12

      public static final PixelFormat NV12
      8-bit 4:2:0 in two planes: Y, then Cb and Cr interleaved in a single plane whose sample n is the byte pair (Cb, Cr), in that order. That plane is 2 bytes per sample, so its byte width is the frame width rounded up to an even number: for an odd width it is one byte wider than the luma plane's, which is the arithmetic that defeats code assuming "chroma rows are smaller". What hardware decoders and capture devices produce.
    • I444

      public static final PixelFormat I444
      8-bit 4:4:4 in three planes: Y, Cb, Cr, all full resolution. Every plane's geometry equals the frame's, so chroma subsampling never applies. Screen capture and lossless intermediates.
    • I420_10LE

      public static final PixelFormat I420_10LE
      10-bit 4:2:0 in three planes, laid out exactly as I420 but with a sample occupying two bytes. What HEVC Main 10, VP9 Profile 2 and AV1 Main 10 hand back, and by a wide margin the commonest way a file carries more than eight bits.

      A sample is a code in [0..1023] stored right-justified in a little-endian 16-bit word: low byte first, and the top six bits zero. It is not a normalized 16-bit value, so reading one as though the plane were 16-bit content gives a picture 64 times too dark, and normalizing by 65535 instead of by maxCode() gives one 64 times too bright. Both look like a broken shader rather than like an off-by-a-factor.

    • I444_10LE

      public static final PixelFormat I444_10LE
      10-bit 4:4:4 in three planes: I444's geometry with I420_10LE's samples. High-end intermediates and screen capture that keep both the chroma resolution and the depth.
    • P010

      public static final PixelFormat P010
      10-bit 4:2:0 in two planes: NV12's geometry with ten bits per component. What every hardware decoder produces for 10-bit content, and the only layout here whose samples a producer other than this repository's own decoders creates.

      Its codes are left-justified, which is the whole reason it is a layout of its own rather than a wider NV12: the ten bits live in the top ten of the 16-bit word and the bottom six are zero, where I420_10LE and I444_10LE put them in the bottom ten. So the same 16-bit word means two different codes depending on the layout, and reading a P010 word as though it were an I420_10LE one is a picture 64 times too bright. Nothing in the geometry says so (the sizes, the strides and the plane count are NV12's doubled), which is why the justification is carried on codeShift() and applied by componentAt(java.nio.ByteBuffer, int) and putComponent(java.nio.ByteBuffer, int, int) rather than left to each reader.

  • Field Details

    • MAX_DIMENSION

      public static final int MAX_DIMENSION
      Largest frame width or height any geometry method accepts, in pixels. Above every coded size in use, and low enough that the rounding-up arithmetic inside those methods cannot wrap an int, which is the reason this is enforced rather than assumed.
      See Also:
  • Method Details

    • values

      public static PixelFormat[] values()
      Returns an array containing the constants of this enum class, in the order they are declared.
      Returns:
      an array containing the constants of this enum class, in the order they are declared
    • valueOf

      public static PixelFormat valueOf(String name)
      Returns the enum constant of this class with the specified name. The string must match exactly an identifier used to declare an enum constant in this class. (Extraneous whitespace characters are not permitted.)
      Parameters:
      name - the name of the enum constant to be returned.
      Returns:
      the enum constant with the specified name
      Throws:
      IllegalArgumentException - if this enum class has no constant with the specified name
      NullPointerException - if the argument is null
    • planeCount

      public int planeCount()
      Returns:
      separately addressed planes: 3 for I420 and I444, 2 for NV12
    • bitDepth

      public int bitDepth()
      Returns:
      valid bits per component: 8 or 10
    • maxCode

      public int maxCode()
      The largest sample code this layout can carry: 255 at eight bits, 1023 at ten. It is the divisor that turns a code into a normalized value, and it is not the largest value the sample's storage can hold: a 10-bit sample lives in a 16-bit word whose upper six bits are always zero, so normalizing by 65535 is the mistake this accessor exists to prevent.
      Returns:
      (1 << bitDepth()) - 1
    • codeShift

      public int codeShift()
      How far a code sits above the bottom of its storage word: 0 for every right-justified layout and 6 for P010, whose ten bits occupy the top of a 16-bit word.

      Two consumers need it and no third one should. componentAt(java.nio.ByteBuffer, int) and putComponent(java.nio.ByteBuffer, int, int) apply it, so every reader that comes through them works in codes and never meets the shift at all. A device sampler does not come through them: it normalizes by the texel's width, so a sampled value must be scaled by ((1 << storageBits) - 1) >> codeShift() rather than by the storage maximum, and using the storage maximum is a picture 64 times too dark.

      Returns:
      bits the code is shifted left inside its storage word; 0 unless the layout says otherwise
    • chromaShiftX

      public int chromaShiftX()
      Returns:
      log2 of horizontal chroma subsampling: 1 for 4:2:0, 0 for 4:4:4; luma is never subsampled
    • chromaShiftY

      public int chromaShiftY()
      Returns:
      log2 of vertical chroma subsampling: 1 for 4:2:0, 0 for 4:4:4
    • componentsPerSample

      public int componentsPerSample(int plane)
      Returns:
      components stored per sample of plane: 1 everywhere except NV12's plane 1, which is 2 (Cb then Cr, adjacent)
      Throws:
      IndexOutOfBoundsException - if plane is negative or at least planeCount()
    • bytesPerSample

      public int bytesPerSample(int plane)
      Bytes occupied by one sample of plane. The only place bit depth turns into bytes, and the reason to come through it is that the two things it multiplies together stopped agreeing once a sample could be ten bits wide: a 10-bit luma sample is two bytes and one component, where NV12's chroma sample is two bytes and two components. Anything that derives a channel count from this number is right for one of those and wrong for the other.
      Returns:
      componentsPerSample(int) times the bytes one component occupies
      Throws:
      IndexOutOfBoundsException - if plane is not a plane of this format
    • componentAt

      public int componentAt(ByteBuffer plane, int byteIndex)
      Reads one component out of a plane's bytes, at an absolute byte index and without moving the buffer's position. The only place this project spells the byte order of a wide sample: a 10-bit component is little-endian, so a reader that assembles it the other way round turns a dark grey into a bright noise pattern, everywhere at once, in a way that looks like the plane pointer being wrong. It is also the only place codeShift() is applied, so what comes back is a code and never a storage word.
      Parameters:
      plane - the plane's bytes, at least byteIndex + bytesPerSample long
      byteIndex - byte offset of the component within plane
      Returns:
      the code, in [0..maxCode()]
      Throws:
      IndexOutOfBoundsException - if the component does not lie inside the buffer
      NullPointerException - if plane is null
    • putComponent

      public void putComponent(ByteBuffer plane, int byteIndex, int code)
      Writes one component into a plane's bytes, at an absolute byte index and without moving the buffer's position: the inverse of componentAt(java.nio.ByteBuffer, int), and the only place a producer needs to know how wide a sample is.
      Parameters:
      code - a value in [0..maxCode()]; higher bits are dropped rather than checked, because this runs once per sample of every picture
      Throws:
      IndexOutOfBoundsException - if the component does not lie inside the buffer
      ReadOnlyBufferException - if plane is a read-only view
      NullPointerException - if plane is null
    • planeWidth

      public int planeWidth(int plane, int frameWidth)
      Samples across one row of plane for a frame frameWidth pixels wide. Chroma rounds up: a 5-pixel-wide 4:2:0 frame has 3 chroma samples per row, not 2, because the last luma column still needs a chroma sample to pair with. This is a sample count, not a byte count.
      Throws:
      IllegalArgumentException - if frameWidth is outside [1..MAX_DIMENSION]
      IndexOutOfBoundsException - if plane is not a plane of this format
    • planeHeight

      public int planeHeight(int plane, int frameHeight)
      Returns:
      rows in plane for a frame frameHeight pixels tall, chroma rounding up
      Throws:
      IllegalArgumentException - if frameHeight is outside [1..MAX_DIMENSION]
      IndexOutOfBoundsException - if plane is not a plane of this format
    • planeByteWidth

      public int planeByteWidth(int plane, int frameWidth)
      Bytes of picture in one row of plane: the part of a row a consumer may read. A row occupies a stride of bytes, of which only these carry samples; the rest is padding whose contents are undefined and which must never be uploaded, compared or hashed.
      Returns:
      planeWidth(int, int) times bytesPerSample(int)
      Throws:
      IllegalArgumentException - if frameWidth is outside [1..MAX_DIMENSION]
      IndexOutOfBoundsException - if plane is not a plane of this format
    • minPlaneBytes

      public long minPlaneBytes(int plane, int frameWidth, int frameHeight, int strideBytes)
      Smallest buffer that can hold plane at this size and stride: every row but the last occupies a full strideBytes, and the last row needs only its byte width. Trailing padding after the final sample is deliberately not required; a producer whose plane ends exactly at the end of its last row is handing over a valid buffer, and demanding stride × rows would force a copy on the one path that exists to avoid copies.

      The consequence, which is the wrong edit this text exists to prevent: a plane may not be bulk-copied as one stride × rows block. Copy row by row.

      Returned as a long because at the maximum dimension a single plane exceeds the range of an int, and a size check that overflowed to a negative number would accept every buffer handed to it.

      Parameters:
      strideBytes - distance in bytes between the starts of consecutive rows
      Throws:
      IllegalArgumentException - if strideBytes is below the plane's byte width, or a dimension is outside [1..MAX_DIMENSION]
      IndexOutOfBoundsException - if plane is not a plane of this format
    • alignedStride

      public int alignedStride(int plane, int frameWidth, int alignBytes)
      The plane's byte width rounded up to a multiple of alignBytes: what a producer that allocates its own planes should use as its stride, so rows start where wide loads and GPU uploads want them.
      Parameters:
      alignBytes - a power of two in [1..4096]
      Throws:
      IllegalArgumentException - if alignBytes is not a power of two in that range, or frameWidth is outside [1..MAX_DIMENSION]
      IndexOutOfBoundsException - if plane is not a plane of this format