2022-11-11 06:30:58 +00:00
package color
2022-09-14 19:42:57 +00:00
import (
"encoding/binary"
"errors"
"fmt"
"strconv"
"strings"
)
// SubsamplingScheme The subsampling scheme is commonly expressed as a three-part ratio J : A : B (e.g. 4:2:2), that describe the number of luminance and chrominance samples in a conceptual region that is J pixels wide and 2 pixels high.
type SubsamplingScheme struct {
// J horizontal sampling reference (width of the conceptual region). Usually, 4.
J byte
// A number of chrominance samples (Cb, Cr) in the first row of J pixels.
A byte
// B number of changes of chrominance samples (Cb, Cr) between first and second row of J pixels. Note that B has to be either zero or equal to A
B byte
}
2022-11-10 10:38:55 +00:00
// TODO: add color primaries
2022-09-15 21:43:42 +00:00
type ColorFormat struct {
2022-09-14 19:42:57 +00:00
Subsampling SubsamplingScheme
BitDepth byte
}
2022-09-15 21:43:42 +00:00
func ( c ColorFormat ) ToInteger ( ) uint32 {
2022-09-14 19:42:57 +00:00
return binary . BigEndian . Uint32 ( [ ] byte { c . Subsampling . J , c . Subsampling . A , c . Subsampling . B , c . BitDepth } )
}
// ElementPixels returns the number of pixels for a full element
2022-09-15 21:43:42 +00:00
func ( c ColorFormat ) ElementPixels ( ) int {
2022-09-14 19:42:57 +00:00
return int ( c . Subsampling . J ) * 2
}
// ElementSamples returns the number of actual samples (total Y, Cb, Cr) for an encoded element
2022-09-15 21:43:42 +00:00
func ( c ColorFormat ) ElementSamples ( ) int {
2022-09-14 19:42:57 +00:00
return int ( c . Subsampling . J ) * 2 + c . ElementChromaSamples ( )
}
2022-09-15 21:43:42 +00:00
func ( c ColorFormat ) ElementChromaSamples ( ) int {
2022-09-14 19:42:57 +00:00
return int ( c . Subsampling . A ) * 2 + int ( c . Subsampling . B ) * 2
}
2022-09-15 21:43:42 +00:00
func ( c ColorFormat ) PlaneLumaSamples ( width , height int ) int {
2022-09-14 19:42:57 +00:00
return width * height
}
2022-09-15 21:43:42 +00:00
func ( c ColorFormat ) PlaneCrSamples ( width , height int ) int {
2022-09-14 19:42:57 +00:00
return int ( ( int64 ( width ) * int64 ( height ) * ( int64 ( c . Subsampling . A ) + int64 ( c . Subsampling . B ) ) ) / int64 ( c . ElementPixels ( ) ) )
}
2022-09-15 21:43:42 +00:00
func ( c ColorFormat ) PlaneCbSamples ( width , height int ) int {
2022-09-14 19:42:57 +00:00
return int ( ( int64 ( width ) * int64 ( height ) * ( int64 ( c . Subsampling . A ) + int64 ( c . Subsampling . B ) ) ) / int64 ( c . ElementPixels ( ) ) )
}
2022-09-15 21:43:42 +00:00
func ( c ColorFormat ) FrameSizePacked ( width , height int ) ( int , error ) {
2022-09-14 19:42:57 +00:00
a1 := int64 ( width ) * int64 ( height )
a2 := int64 ( c . ElementPixels ( ) )
if a1 % a2 != 0 {
return 0 , errors . New ( "not divisible pixels" )
}
a3 := ( a1 / a2 ) * int64 ( c . ElementSamples ( ) ) * int64 ( c . BitDepth )
if a3 % 8 != 0 {
return 0 , errors . New ( "not divisible size" )
}
return int ( a3 / 8 ) , nil
}
2022-09-15 21:43:42 +00:00
func ( c ColorFormat ) FrameSize ( width , height int ) ( int , error ) {
2022-09-14 19:42:57 +00:00
actualBitDepth := int64 ( c . BitDepth )
if actualBitDepth & 0b111 != 0 {
actualBitDepth = ( ( actualBitDepth + 8 ) >> 3 ) << 3
}
a1 := int64 ( width ) * int64 ( height )
a2 := int64 ( c . ElementPixels ( ) )
if a1 % a2 != 0 {
return 0 , errors . New ( "not divisible pixels" )
}
a3 := ( a1 / a2 ) * int64 ( c . ElementSamples ( ) ) * actualBitDepth
if a3 % 8 != 0 {
return 0 , errors . New ( "not divisible size" )
}
return int ( a3 / 8 ) , nil
}
2022-09-15 21:43:42 +00:00
func ( c ColorFormat ) check ( ) error {
2022-09-14 19:42:57 +00:00
if c . Subsampling . J <= 0 {
return fmt . Errorf ( "unsupported J %d" , c . Subsampling . J )
}
if c . Subsampling . A < 0 && c . Subsampling . A > c . Subsampling . J {
return fmt . Errorf ( "unsupported A %d" , c . Subsampling . A )
}
if c . Subsampling . B != 0 && c . Subsampling . B != c . Subsampling . A {
return fmt . Errorf ( "unsupported B %d" , c . Subsampling . B )
}
if c . BitDepth != 8 && c . BitDepth != 10 && c . BitDepth != 12 && c . BitDepth != 14 && c . BitDepth != 16 {
return fmt . Errorf ( "unsupported BitDepth %d" , c . BitDepth )
}
return nil
}
2022-09-15 21:43:42 +00:00
func NewColorFormatFromInteger ( colorFormat uint32 ) ( ColorFormat , error ) {
2022-09-14 19:42:57 +00:00
buf := make ( [ ] byte , 4 )
2022-09-15 21:43:42 +00:00
binary . BigEndian . PutUint32 ( buf , colorFormat )
2022-09-14 19:42:57 +00:00
2022-09-15 21:43:42 +00:00
space := ColorFormat {
2022-09-14 19:42:57 +00:00
Subsampling : SubsamplingScheme {
J : buf [ 0 ] ,
A : buf [ 1 ] ,
B : buf [ 2 ] ,
} ,
BitDepth : buf [ 3 ] ,
}
return space , space . check ( )
}
2022-09-15 21:43:42 +00:00
func NewColorFormatFromString ( colorFormat string ) ( ColorFormat , error ) {
colorFormat = strings . ToLower ( colorFormat )
2022-11-11 08:49:05 +00:00
if colorFormat == "420mpeg2" { //todo: chroma location, left
2022-09-15 21:43:42 +00:00
return NewColorFormatFromString ( "420p8" )
2022-11-11 08:49:05 +00:00
} else if colorFormat == "420j" || colorFormat == "420jpeg" { //todo: chroma location, center
return NewColorFormatFromString ( "420p8" )
} else if colorFormat == "mono" {
return NewColorFormatFromString ( "400p8" )
} else if colorFormat == "mono10" {
return NewColorFormatFromString ( "400p10" )
} else if colorFormat == "mono12" {
return NewColorFormatFromString ( "400p12" )
} else if colorFormat == "mono14" {
return NewColorFormatFromString ( "400p14" )
} else if colorFormat == "mono16" {
return NewColorFormatFromString ( "400p16" )
2022-09-14 19:42:57 +00:00
}
2022-11-11 08:49:05 +00:00
2022-09-15 21:43:42 +00:00
splits := strings . Split ( colorFormat , "p" )
2022-09-14 19:42:57 +00:00
if len ( splits ) == 1 { //default 8 bit
splits = append ( splits , "8" )
}
2022-09-15 21:43:42 +00:00
space := ColorFormat { }
2022-09-14 19:42:57 +00:00
switch strings . ReplaceAll ( splits [ 0 ] , ":" , "" ) {
2022-11-11 08:49:05 +00:00
case "400" :
space . Subsampling . J = 4
space . Subsampling . A = 0
space . Subsampling . B = 0
2022-09-14 19:42:57 +00:00
case "420" :
space . Subsampling . J = 4
space . Subsampling . A = 2
space . Subsampling . B = 0
case "422" :
space . Subsampling . J = 4
space . Subsampling . A = 2
space . Subsampling . B = 2
case "444" :
space . Subsampling . J = 4
space . Subsampling . A = 4
space . Subsampling . B = 4
default :
return space , fmt . Errorf ( "unsupported Chroma Subsampling %s" , splits [ 0 ] )
}
n , err := strconv . Atoi ( splits [ 1 ] )
if err != nil {
return space , err
}
space . BitDepth = byte ( n )
return space , space . check ( )
}
2022-09-15 21:43:42 +00:00
func newColorFormatFromStringInternal ( colorFormat string ) ColorFormat {
space , _ := NewColorFormatFromString ( colorFormat )
2022-09-14 19:42:57 +00:00
return space
}
var (
2022-09-15 21:43:42 +00:00
Space420 ColorFormat = newColorFormatFromStringInternal ( "420" )
Space422 ColorFormat = newColorFormatFromStringInternal ( "422" )
Space444 ColorFormat = newColorFormatFromStringInternal ( "444" )
Space420P10 ColorFormat = newColorFormatFromStringInternal ( "420p10" )
Space422P10 ColorFormat = newColorFormatFromStringInternal ( "422p10" )
Space444P10 ColorFormat = newColorFormatFromStringInternal ( "444p10" )
Space420P12 ColorFormat = newColorFormatFromStringInternal ( "420p12" )
Space422P12 ColorFormat = newColorFormatFromStringInternal ( "422p12" )
Space444P12 ColorFormat = newColorFormatFromStringInternal ( "444p12" )
Space420P16 ColorFormat = newColorFormatFromStringInternal ( "420p16" )
Space422P16 ColorFormat = newColorFormatFromStringInternal ( "422p16" )
Space444P16 ColorFormat = newColorFormatFromStringInternal ( "444p16" )
2022-09-14 19:42:57 +00:00
)