package bundle
import (
"errors"
"github.com/kpfaulkner/jxl-go/jxlio"
)
type AnimationHeader struct {
HaveTimeCodes bool
}
func NewAnimationHeader(reader jxlio.BitReader) (*AnimationHeader, error) {
return nil, errors.New("Animation not implemented")
}
package bundle
import "github.com/kpfaulkner/jxl-go/jxlio"
type BitDepthHeader struct {
BitsPerSample uint32
ExpBits uint32
UsesFloatSamples bool
}
func NewBitDepthHeader() *BitDepthHeader {
bh := &BitDepthHeader{}
bh.UsesFloatSamples = false
bh.BitsPerSample = 8
bh.ExpBits = 0
return bh
}
func NewBitDepthHeaderWithReader(reader jxlio.BitReader) (*BitDepthHeader, error) {
bh := &BitDepthHeader{}
var err error
if bh.UsesFloatSamples, err = reader.ReadBool(); err != nil {
return nil, err
}
if bh.UsesFloatSamples {
if bh.BitsPerSample, err = reader.ReadU32(32, 0, 16, 0, 24, 0, 1, 6); err != nil {
return nil, err
}
if expBits, err := reader.ReadBits(4); err != nil {
return nil, err
} else {
bh.ExpBits = 1 + uint32(expBits)
}
} else {
if bh.BitsPerSample, err = reader.ReadU32(8, 0, 10, 0, 12, 0, 1, 6); err != nil {
return nil, err
}
bh.ExpBits = 0
}
return bh, nil
}
package bundle
import (
"errors"
"math"
"github.com/kpfaulkner/jxl-go/jxlio"
)
type Extensions struct {
Payloads [64][]byte
ExtensionsKey uint64
}
func NewExtensions() *Extensions {
ex := &Extensions{}
ex.ExtensionsKey = 0
return ex
}
func NewExtensionsWithReader(reader jxlio.BitReader) (*Extensions, error) {
ex := &Extensions{}
var err error
if ex.ExtensionsKey, err = reader.ReadU64(); err != nil {
return nil, err
}
var length uint64
for i := uint64(0); i < 64; i++ {
if (1<<i)&ex.ExtensionsKey != 0 {
if length, err = reader.ReadU64(); err != nil {
return nil, err
}
if length > math.MaxUint32 {
return nil, errors.New("Large Extensions unsupported")
}
ex.Payloads[i] = make([]byte, length)
}
}
for i := 0; i < 64; i++ {
if len(ex.Payloads[i]) > 0 {
for j := 0; j < len(ex.Payloads[i]); j++ {
if bits, err := reader.ReadBits(8); err != nil {
return nil, err
} else {
ex.Payloads[i][j] = byte(bits)
}
}
}
}
return ex, nil
}
package bundle
const (
ALPHA = 0
DEPTH = 1
SPOT_COLOR = 2
SELECTION_MASK = 3
CMYK_BLACK = 4
COLOR_FILTER_ARRAY = 5
THERMAL = 6
NON_OPTIONAL = 15
OPTIONAL = 16
)
func ValidateExtraChannel(ec int32) bool {
return ec >= 0 && ec <= 6 || ec == 15 || ec == 16
}
package bundle
import (
"errors"
"github.com/kpfaulkner/jxl-go/jxlio"
)
type ExtraChannelInfo struct {
Name string
EcType int32
CfaIndex int32
DimShift int32
Red, Green, Blue, Solidity float32
BitDepth BitDepthHeader
AlphaAssociated bool
}
func NewExtraChannelInfoWithReader(reader jxlio.BitReader) (*ExtraChannelInfo, error) {
eci := &ExtraChannelInfo{}
var err error
var dAlpha bool
if dAlpha, err = reader.ReadBool(); err != nil {
return nil, err
}
if !dAlpha {
if eci.EcType, err = reader.ReadEnum(); err != nil {
return nil, err
}
if !ValidateExtraChannel(eci.EcType) {
return nil, errors.New("Illegal extra channel type")
}
if bitDepth, err := NewBitDepthHeaderWithReader(reader); err != nil {
return nil, err
} else {
eci.BitDepth = *bitDepth
}
if dimShift, err := reader.ReadU32(0, 0, 3, 0, 4, 0, 1, 3); err != nil {
return nil, err
} else {
eci.DimShift = int32(dimShift)
}
var nameLen uint32
var err error
if nameLen, err = reader.ReadU32(0, 0, 0, 4, 16, 5, 48, 10); err != nil {
return nil, err
}
nameBuffer := make([]byte, nameLen)
for i := uint32(0); i < nameLen; i++ {
if nb, err := reader.ReadBits(8); err != nil {
return nil, err
} else {
nameBuffer[i] = byte(nb)
}
}
eci.Name = string(nameBuffer)
if eci.EcType == ALPHA {
var alphaBool bool
if alphaBool, err = reader.ReadBool(); err != nil {
return nil, err
}
eci.AlphaAssociated = alphaBool
}
} else {
eci.EcType = ALPHA
eci.BitDepth = *NewBitDepthHeader()
eci.DimShift = 0
eci.Name = ""
eci.AlphaAssociated = false
}
if eci.EcType == SPOT_COLOR {
var err error
if eci.Red, err = reader.ReadF16(); err != nil {
return nil, err
}
if eci.Green, err = reader.ReadF16(); err != nil {
return nil, err
}
if eci.Blue, err = reader.ReadF16(); err != nil {
return nil, err
}
if eci.Solidity, err = reader.ReadF16(); err != nil {
return nil, err
}
} else {
eci.Red = 0
eci.Green = 0
eci.Blue = 0
eci.Solidity = 0
}
if eci.EcType == COLOR_FILTER_ARRAY {
if cfaIndex, err := reader.ReadU32(1, 0, 0, 2, 3, 4, 19, 8); err != nil {
return nil, err
} else {
eci.CfaIndex = int32(cfaIndex)
}
} else {
eci.CfaIndex = 1
}
return eci, nil
}
package bundle
import (
"bytes"
"errors"
"fmt"
"math"
"slices"
"github.com/kpfaulkner/jxl-go/colour"
"github.com/kpfaulkner/jxl-go/entropy"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
log "github.com/sirupsen/logrus"
)
const (
CODESTREAM_HEADER uint32 = 0x0AFF
)
var (
DEFAULT_UP2 = []float32{
-0.01716200, -0.03452303, -0.04022174, -0.02921014, -0.00624645,
0.14111091, 0.28896755, 0.00278718, -0.01610267, 0.56661550,
0.03777607, -0.01986694, -0.03144731, -0.01185068, -0.00213539}
DEFAULT_UP4 = []float32{
-0.02419067, -0.03491987, -0.03693351, -0.03094285, -0.00529785,
-0.01663432, -0.03556863, -0.03888905, -0.03516850, -0.00989469,
0.23651958, 0.33392945, -0.01073543, -0.01313181, -0.03556694,
0.13048175, 0.40103025, 0.03951150, -0.02077584, 0.46914198,
-0.00209270, -0.01484589, -0.04064806, 0.18942530, 0.56279892,
0.06674400, -0.02335494, -0.03551682, -0.00754830, -0.02267919,
-0.02363578, 0.00315804, -0.03399098, -0.01359519, -0.00091653,
-0.00335467, -0.01163294, -0.01610294, -0.00974088, -0.00191622,
-0.01095446, -0.03198464, -0.04455121, -0.02799790, -0.00645912,
0.06390599, 0.22963888, 0.00630981, -0.01897349, 0.67537268,
0.08483369, -0.02534994, -0.02205197, -0.01667999, -0.00384443}
DEFAULT_UP8 = []float32{
-0.02928613, -0.03706353, -0.03783812, -0.03324558, -0.00447632,
-0.02519406, -0.03752601, -0.03901508, -0.03663285, -0.00646649,
-0.02066407, -0.03838633, -0.04002101, -0.03900035, -0.00901973,
-0.01626393, -0.03954148, -0.04046620, -0.03979621, -0.01224485,
0.29895328, 0.35757708, -0.02447552, -0.01081748, -0.04314594,
0.23903219, 0.41119301, -0.00573046, -0.01450239, -0.04246845,
0.17567618, 0.45220643, 0.02287757, -0.01936783, -0.03583255,
0.11572472, 0.47416733, 0.06284440, -0.02685066, 0.42720050,
-0.02248939, -0.01155273, -0.04562755, 0.28689496, 0.49093869,
-0.00007891, -0.01545926, -0.04562659, 0.21238920, 0.53980934,
0.03369474, -0.02070211, -0.03866988, 0.14229550, 0.56593398,
0.08045181, -0.02888298, -0.03680918, -0.00542229, -0.02920477,
-0.02788574, -0.02118180, -0.03942402, -0.00775547, -0.02433614,
-0.03193943, -0.02030828, -0.04044014, -0.01074016, -0.01930822,
-0.03620399, -0.01974125, -0.03919545, -0.01456093, -0.00045072,
-0.00360110, -0.01020207, -0.01231907, -0.00638988, -0.00071592,
-0.00279122, -0.00957115, -0.01288327, -0.00730937, -0.00107783,
-0.00210156, -0.00890705, -0.01317668, -0.00813895, -0.00153491,
-0.02128481, -0.04173044, -0.04831487, -0.03293190, -0.00525260,
-0.01720322, -0.04052736, -0.05045706, -0.03607317, -0.00738030,
-0.01341764, -0.03965629, -0.05151616, -0.03814886, -0.01005819,
0.18968273, 0.33063684, -0.01300105, -0.01372950, -0.04017465,
0.13727832, 0.36402234, 0.01027890, -0.01832107, -0.03365072,
0.08734506, 0.38194295, 0.04338228, -0.02525993, 0.56408126,
0.00458352, -0.01648227, -0.04887868, 0.24585519, 0.62026135,
0.04314807, -0.02213737, -0.04158014, 0.16637289, 0.65027023,
0.09621636, -0.03101388, -0.04082742, -0.00904519, -0.02790922,
-0.02117818, 0.00798662, -0.03995711, -0.01243427, -0.02231705,
-0.02946266, 0.00992055, -0.03600283, -0.01684920, -0.00111684,
-0.00411204, -0.01297130, -0.01723725, -0.01022545, -0.00165306,
-0.00313110, -0.01218016, -0.01763266, -0.01125620, -0.00231663,
-0.01374149, -0.03797620, -0.05142937, -0.03117307, -0.00581914,
-0.01064003, -0.03608089, -0.05272168, -0.03375670, -0.00795586,
0.09628104, 0.27129991, -0.00353779, -0.01734151, -0.03153981,
0.05686230, 0.28500998, 0.02230594, -0.02374955, 0.68214326,
0.05018048, -0.02320852, -0.04383616, 0.18459474, 0.71517975,
0.10805613, -0.03263677, -0.03637639, -0.01394373, -0.02511203,
-0.01728636, 0.05407331, -0.02867568, -0.01893131, -0.00240854,
-0.00446511, -0.01636187, -0.02377053, -0.01522848, -0.00333334,
-0.00819975, -0.02964169, -0.04499287, -0.02745350, -0.00612408,
0.02727416, 0.19446600, 0.00159832, -0.02232473, 0.74982506,
0.11452620, -0.03348048, -0.01605681, -0.02070339, -0.00458223}
iccTags = []string{
"cprt", "wtpt", "bkpt", "rXYZ", "gXYZ", "bXYZ",
"kXYZ", "rTRC", "gTRC", "bTRC", "kTRC", "chad",
"desc", "chrm", "dmnd", "dmdd", "lumi",
}
MNTRGB = []byte{'m', 'n', 't', 'r', 'R', 'G', 'B', ' ', 'X', 'Y', 'Z', ' '}
ACSP = []byte{'a', 'c', 's', 'p'}
)
type ImageHeader struct {
AlphaIndices []int32
Up2Weights []float32
Up4Weights []float32
Up8Weights []float32
UpWeights [][][][][]float32
ExtraChannelInfo []ExtraChannelInfo
EncodedICC []byte
DecodedICC []byte
AnimationHeader *AnimationHeader
PreviewSize *util.Dimension
BitDepth *BitDepthHeader
ColourEncoding *colour.ColourEncodingBundle
ToneMapping *colour.ToneMapping
Extensions *Extensions
OpsinInverseMatrix *colour.OpsinInverseMatrix
Level int32
Orientation uint32
OrientedWidth uint32
OrientedHeight uint32
Size util.Dimension
intrinsicSize util.Dimension
XybEncoded bool
Modular16BitBuffers bool
}
func NewImageHeader() *ImageHeader {
ih := &ImageHeader{
XybEncoded: true,
}
return ih
}
func ParseImageHeader(reader jxlio.BitReader, level int32) (*ImageHeader, error) {
header := NewImageHeader()
var headerBits uint64
var err error
if headerBits, err = reader.ReadBits(16); err != nil {
return nil, err
}
if uint32(headerBits) != CODESTREAM_HEADER {
return nil, errors.New("Not a JXL codestream: 0xFF0A magic mismatch")
}
err = header.setLevel(level)
if err != nil {
return nil, err
}
if header.Size, err = readSizeHeader(reader, level); err != nil {
return nil, err
}
var allDefault bool
if allDefault, err = reader.ReadBool(); err != nil {
return nil, err
}
extraFields := false
if !allDefault {
if extraFields, err = reader.ReadBool(); err != nil {
return nil, err
}
}
if extraFields {
if orientation, err := reader.ReadBits(3); err != nil {
return nil, err
} else {
header.Orientation = 1 + uint32(orientation)
}
if extraReadBool, err := reader.ReadBool(); err != nil {
return nil, err
} else if extraReadBool {
header.intrinsicSize, err = readSizeHeader(reader, level)
if err != nil {
return nil, err
}
}
if previewBool, err := reader.ReadBool(); err != nil {
return nil, err
} else if previewBool {
header.PreviewSize, err = readPreviewHeader(reader)
if err != nil {
return nil, err
}
}
if animationBool, err := reader.ReadBool(); err != nil {
return nil, err
} else if animationBool {
header.AnimationHeader, err = NewAnimationHeader(reader)
if err != nil {
return nil, err
}
}
} else {
header.Orientation = 1
}
if header.Orientation > 4 {
header.OrientedWidth = header.Size.Height
header.OrientedHeight = header.Size.Width
} else {
header.OrientedWidth = header.Size.Width
header.OrientedHeight = header.Size.Height
}
if allDefault {
header.BitDepth = NewBitDepthHeader()
header.Modular16BitBuffers = true
header.ExtraChannelInfo = []ExtraChannelInfo{}
header.XybEncoded = true
header.ColourEncoding, err = colour.NewColourEncodingBundle()
if err != nil {
return nil, err
}
} else {
if bitDepth, err := NewBitDepthHeaderWithReader(reader); err != nil {
return nil, err
} else {
header.BitDepth = bitDepth
}
if header.Modular16BitBuffers, err = reader.ReadBool(); err != nil {
return nil, err
}
var extraChannelCount uint32
if extraChannelCount, err = reader.ReadU32(0, 0, 1, 0, 2, 4, 1, 12); err != nil {
return nil, err
} else {
header.ExtraChannelInfo = make([]ExtraChannelInfo, extraChannelCount)
}
alphaIndicies := make([]int32, extraChannelCount)
numAlphaChannels := 0
for i := 0; i < int(extraChannelCount); i++ {
eci, err := NewExtraChannelInfoWithReader(reader)
if err != nil {
return nil, err
}
header.ExtraChannelInfo[i] = *eci
if header.ExtraChannelInfo[i].EcType == ALPHA {
alphaIndicies[numAlphaChannels] = int32(i)
numAlphaChannels++
}
}
header.AlphaIndices = make([]int32, numAlphaChannels)
copy(header.AlphaIndices, alphaIndicies[:numAlphaChannels])
if header.XybEncoded, err = reader.ReadBool(); err != nil {
return nil, err
}
header.ColourEncoding, err = colour.NewColourEncodingBundleWithReader(reader)
if err != nil {
return nil, err
}
}
if extraFields {
header.ToneMapping, err = colour.NewToneMappingWithReader(reader)
if err != nil {
return nil, err
}
} else {
header.ToneMapping = colour.NewToneMapping()
}
if allDefault {
header.Extensions = NewExtensions()
} else {
header.Extensions, err = NewExtensionsWithReader(reader)
if err != nil {
return nil, err
}
}
var defaultMatrix bool
if defaultMatrix, err = reader.ReadBool(); err != nil {
return nil, err
}
if !defaultMatrix && header.XybEncoded {
if header.OpsinInverseMatrix, err = colour.NewOpsinInverseMatrixWithReader(reader); err != nil {
return nil, err
}
} else {
header.OpsinInverseMatrix = colour.NewOpsinInverseMatrix()
}
var cwMask int32
if defaultMatrix {
cwMask = 0
} else {
if cw, err := reader.ReadBits(3); err != nil {
return nil, err
} else {
cwMask = int32(cw)
}
}
if cwMask&1 != 0 {
header.Up2Weights = make([]float32, 15)
for i := 0; i < len(header.Up2Weights); i++ {
if header.Up2Weights[i], err = reader.ReadF16(); err != nil {
return nil, err
}
}
} else {
header.Up2Weights = DEFAULT_UP2
}
if cwMask&2 != 0 {
header.Up4Weights = make([]float32, 55)
for i := 0; i < len(header.Up4Weights); i++ {
if header.Up4Weights[i], err = reader.ReadF16(); err != nil {
return nil, err
}
}
} else {
header.Up4Weights = DEFAULT_UP4
}
if cwMask&4 != 0 {
header.Up8Weights = make([]float32, 210)
for i := 0; i < len(header.Up8Weights); i++ {
if header.Up8Weights[i], err = reader.ReadF16(); err != nil {
return nil, err
}
}
} else {
header.Up8Weights = DEFAULT_UP8
}
if header.ColourEncoding.UseIccProfile {
var encodedSize uint64
if encodedSize, err = reader.ReadU64(); err != nil {
return nil, err
}
// check MaxUint32 or MaxInt32
if encodedSize > math.MaxUint32 {
return nil, errors.New("Invalid encoded Size")
}
header.EncodedICC = make([]byte, encodedSize)
iccDistribution, err := entropy.NewEntropyStreamWithReaderAndNumDists(reader, 41, entropy.ReadClusterMap)
if err != nil {
return nil, err
}
for i := 0; i < int(encodedSize); i++ {
cc, err := iccDistribution.ReadSymbol(reader, GetICCContext(header.EncodedICC, i))
if err != nil {
return nil, err
}
header.EncodedICC[i] = byte(cc)
}
if !iccDistribution.ValidateFinalState() {
return nil, errors.New("ICC Stream")
}
}
if err := reader.ZeroPadToByte(); err != nil {
return nil, fmt.Errorf("error zero padding to byte: %v", err)
}
return header, nil
}
func (h *ImageHeader) GetColourChannelCount() int {
if h.ColourEncoding.ColourEncoding == colour.CE_GRAY {
return 1
}
return 3
}
func (h *ImageHeader) GetSize() util.Dimension {
return h.Size
}
func (h *ImageHeader) GetColourModel() int32 {
return h.ColourEncoding.ColourEncoding
}
func (h *ImageHeader) setLevel(level int32) error {
if level != 5 && level != 10 {
return errors.New("invalid bitstream")
}
h.Level = level
return nil
}
func (h *ImageHeader) HasAlpha() bool {
return len(h.AlphaIndices) > 0
}
func (h *ImageHeader) GetTotalChannelCount() int {
return len(h.ExtraChannelInfo) + h.GetColourChannelCount()
}
func (h *ImageHeader) GetDecodedICC() ([]byte, error) {
if len(h.DecodedICC) > 0 {
return h.DecodedICC, nil
}
if h.EncodedICC == nil {
return nil, nil
}
commandReader := jxlio.NewBitStreamReader(bytes.NewReader(h.EncodedICC))
outputSize, err := commandReader.ReadICCVarint()
if err != nil {
return nil, err
}
commandSize, err := commandReader.ReadICCVarint()
if err != nil {
return nil, err
}
commandStart := int32(commandReader.GetBitsCount() >> 3)
dataStart := commandStart + commandSize
dataReader := jxlio.NewBitStreamReader(bytes.NewReader(h.EncodedICC[dataStart:]))
headerSize := util.Min(128, outputSize)
h.DecodedICC = make([]byte, outputSize)
resultPos := int32(0)
for i := int32(0); i < headerSize; i++ {
e, err := dataReader.ReadBits(8)
if err != nil {
return nil, err
}
p := h.GetICCPrediction(h.DecodedICC, i)
h.DecodedICC[resultPos] = byte(int32(e)+p) & 0xFF
resultPos++
}
if resultPos == outputSize {
return h.DecodedICC, nil
}
tagCount, err := commandReader.ReadICCVarint()
if err != nil {
return nil, err
}
tagCount--
if tagCount >= 0 {
for i := 24; i >= 0; i -= 8 {
h.DecodedICC[resultPos] = byte(tagCount>>i) & 0xFF
resultPos++
}
prevTagStart := 128 + tagCount*12
prevTagSize := int32(0)
for !commandReader.AtEnd() && (commandReader.GetBitsCount()>>3) < uint64(dataStart) {
command, err := commandReader.ReadBits(8)
if err != nil {
return nil, err
}
tagCode := command & 0x3F
if tagCode == 0 {
break
}
var tag string
var tcr []byte
if tagCode == 1 {
tcr = make([]byte, 4)
for i := 0; i < 4; i++ {
dat, err := dataReader.ReadBits(8)
if err != nil {
return nil, err
}
tcr[i] = byte(dat)
}
tag = string(tcr)
} else if tagCode == 2 {
tag = "rTRC"
} else if tagCode == 3 {
tag = "rXYZ"
} else if tagCode >= 4 && tagCode <= 21 {
tag = iccTags[tagCode-4]
} else {
return nil, errors.New("illegal ICC tag code")
}
var tagStart int32
var tagSize int32
if command&0x40 > 0 {
dat, err := commandReader.ReadICCVarint()
if err != nil {
return nil, err
}
tagStart = dat
} else {
tagStart = prevTagStart + prevTagSize
}
if command&0x80 > 0 {
dat, err := commandReader.ReadICCVarint()
if err != nil {
return nil, err
}
tagSize = dat
} else {
if slices.Contains([]string{"rXYZ", "gXYZ", "bXYZ", "kXYZ", "wtpt", "bkpt", "lumi"}, tag) {
tagSize = 20
} else {
tagSize = prevTagSize
}
}
prevTagSize = tagSize
prevTagStart = tagStart
var tags []string
if tagCode == 2 {
tags = []string{"rTRC", "gTRC", "bTRC"}
} else if tagCode == 3 {
tags = []string{"rXYZ", "gXYZ", "bXYZ"}
} else {
tags = []string{tag}
}
for _, wTag := range tags {
tcr = []byte(wTag)
for i := 0; i < 4; i++ {
h.DecodedICC[resultPos] = tcr[i] & 0xFF
resultPos++
}
for i := 24; i >= 0; i -= 8 {
h.DecodedICC[resultPos] = byte(tagStart>>i) & 0xFF
resultPos++
}
for i := 24; i >= 0; i -= 8 {
h.DecodedICC[resultPos] = byte(tagSize>>i) & 0xFF
resultPos++
}
if tagCode == 3 {
tagStart += tagSize
}
}
}
for !commandReader.AtEnd() && (commandReader.GetBitsCount()>>3) < uint64(dataStart) {
command, err := commandReader.ReadBits(8)
if err != nil {
return nil, err
}
if command == 1 {
num, err := commandReader.ReadICCVarint()
if err != nil {
return nil, err
}
for i := 0; i < int(num); i++ {
dat, err := dataReader.ReadBits(8)
if err != nil {
return nil, err
}
h.DecodedICC[resultPos] = byte(dat)
resultPos++
}
} else if command == 2 || command == 3 {
num, err := commandReader.ReadICCVarint()
if err != nil {
return nil, err
}
b := make([]byte, num)
for p := 0; p < int(num); p++ {
dat, err := dataReader.ReadBits(8)
if err != nil {
return nil, err
}
b[p] = byte(dat)
}
var width int32
if command == 2 {
width = 2
} else {
width = 4
}
b = shuffle(b, width)
copy(h.DecodedICC[resultPos:], b)
resultPos += int32(len(b))
} else if command == 4 {
flags, err := commandReader.ReadBits(8)
if err != nil {
return nil, err
}
width := int32((flags & 3) + 1)
if width == 3 {
return nil, errors.New("illegal width is 3")
}
order := int32(flags&12) >> 2
if order == 3 {
return nil, errors.New("illegal order is 3")
}
var stride int32
if flags&0x10 > 0 {
dat, err := commandReader.ReadICCVarint()
if err != nil {
return nil, err
}
stride = dat
} else {
stride = int32(width)
}
if stride*4 >= resultPos {
return nil, errors.New("stride too large")
}
if stride < int32(width) {
return nil, errors.New("stride too small")
}
num, err := commandReader.ReadICCVarint()
if err != nil {
return nil, err
}
b := make([]byte, num)
for p := 0; p < int(num); p++ {
dat, err := dataReader.ReadBits(8)
if err != nil {
return nil, err
}
b[p] = byte(dat)
}
if width == 2 || width == 4 {
b = shuffle(b, int32(width))
}
for i := int32(0); i < num; i += width {
n := order + 1
prev := make([]int32, n)
for j := int32(0); j < n; j++ {
for k := int32(0); k < width; k++ {
prev[j] = prev[j] << 8
prev[j] = prev[j] | int32(h.DecodedICC[resultPos-stride*(j+1)+k]&0xFF)
}
}
var p int32
if order == 0 {
p = prev[0]
} else if order == 1 {
p = 2*prev[0] - prev[1]
} else {
p = 3*prev[0] - 3*prev[1] + prev[2]
}
for j := int32(0); j < width && i+j < num; j++ {
h.DecodedICC[resultPos] = ((b[i+j] + byte(p>>(8*(width-1-j)))) & 0xFF)
resultPos++
}
}
} else if command == 10 {
h.DecodedICC[resultPos] = 'X'
resultPos++
h.DecodedICC[resultPos] = 'Y'
resultPos++
h.DecodedICC[resultPos] = 'Z'
resultPos++
h.DecodedICC[resultPos] = ' '
resultPos++
resultPos += 4
for i := 0; i < 12; i++ {
dat, err := dataReader.ReadBits(8)
if err != nil {
return nil, err
}
h.DecodedICC[resultPos] = byte(dat)
resultPos++
}
} else if command >= 16 && command < 24 {
s := []string{"XYZ ", "desc", "text", "mluc", "para", "curv", "sf32", "gbd "}
trc := []byte(s[command-16])
for i := 0; i < 4; i++ {
h.DecodedICC[resultPos] = trc[i]
resultPos++
}
resultPos += 4
} else {
return nil, errors.New("illegal data command")
}
}
}
return h.DecodedICC, nil
}
func shuffle(buffer []byte, width int32) []byte {
height := int32(util.CeilDiv(uint32(len(buffer)), uint32(width)))
result := make([]byte, len(buffer))
for i := int32(0); i < int32(len(buffer)); i++ {
result[(i%height)*width+(i/height)] = buffer[i]
}
return result
}
func GetICCContext(buffer []byte, index int) int {
if index <= 128 {
return 0
}
b1 := int(buffer[index-1]) & 0xFF
b2 := int(buffer[index-2]) & 0xFF
var p1 int
var p2 int
if b1 >= 'a' && b1 <= 'z' || b1 >= 'A' && b1 <= 'Z' {
p1 = 0
} else if b1 >= '0' && b1 <= '9' || b1 == '.' || b1 == ',' {
p1 = 1
} else if b1 <= 1 {
p1 = 2 + b1
} else if b1 > 1 && b1 < 16 {
p1 = 4
} else if b1 > 240 && b1 < 255 {
p1 = 5
} else if b1 == 255 {
p1 = 6
} else {
p1 = 7
}
if b2 >= 'a' && b2 <= 'z' || b2 >= 'A' && b2 <= 'Z' {
p2 = 0
} else if b2 >= '0' && b2 <= '9' || b2 == '.' || b2 == ',' {
p2 = 1
} else if b2 < 16 {
p2 = 2
} else if b2 > 240 {
p2 = 3
} else {
p2 = 4
}
return 1 + p1 + 8*p2
}
func readPreviewHeader(reader jxlio.BitReader) (*util.Dimension, error) {
var dim util.Dimension
var err error
var div8 bool
if div8, err = reader.ReadBool(); err != nil {
return nil, err
}
if div8 {
if height, err := reader.ReadU32(16, 0, 32, 0, 1, 5, 33, 9); err != nil {
return nil, err
} else {
dim.Height = height
}
} else {
if height, err := reader.ReadU32(1, 6, 65, 8, 321, 10, 1345, 12); err != nil {
return nil, err
} else {
dim.Height = height
}
}
var ratio uint64
if ratio, err = reader.ReadBits(3); err != nil {
return nil, err
}
if ratio != 0 {
dim.Width, err = getWidthFromRatio(uint32(ratio), dim.Height)
if err != nil {
log.Errorf("Error getting Width from ratio: %v\n", err)
return nil, err
}
} else {
if div8 {
if width, err := reader.ReadU32(16, 0, 32, 0, 1, 5, 33, 9); err != nil {
return nil, err
} else {
dim.Width = width
}
} else {
if width, err := reader.ReadU32(1, 6, 65, 8, 321, 10, 1345, 12); err != nil {
return nil, err
} else {
dim.Width = width
}
}
}
if dim.Width > 4096 || dim.Height > 4096 {
log.Errorf("preview Width or preview Height too large: %d, %d", dim.Width, dim.Height)
return nil, errors.New("preview Width or preview Height too large")
}
return &dim, nil
}
func readSizeHeader(reader jxlio.BitReader, level int32) (util.Dimension, error) {
dim := util.Dimension{}
var err error
var div8 bool
if div8, err = reader.ReadBool(); err != nil {
return util.Dimension{}, err
}
if div8 {
if height, err := reader.ReadBits(5); err != nil {
return util.Dimension{}, err
} else {
dim.Height = (1 + uint32(height)) << 3
}
} else {
if height, err := reader.ReadU32(1, 9, 1, 13, 1, 18, 1, 30); err != nil {
return util.Dimension{}, err
} else {
dim.Height = height
}
}
var ratio uint64
if ratio, err = reader.ReadBits(3); err != nil {
return util.Dimension{}, err
}
if ratio != 0 {
dim.Width, err = getWidthFromRatio(uint32(ratio), dim.Height)
if err != nil {
log.Errorf("Error getting Width from ratio: %v\n", err)
return util.Dimension{}, err
}
} else {
if div8 {
if width, err := reader.ReadBits(5); err != nil {
return util.Dimension{}, err
} else {
dim.Width = (1 + uint32(width)) << 3
}
} else {
if width, err := reader.ReadU32(1, 9, 1, 13, 1, 18, 1, 30); err != nil {
return util.Dimension{}, err
} else {
dim.Width = width
}
}
}
maxDim := util.IfThenElse[uint64](level <= 5, 1<<18, 1<<28)
maxTimes := util.IfThenElse[uint64](level <= 5, 1<<30, 1<<40)
if dim.Width > uint32(maxDim) || dim.Height > uint32(maxDim) {
log.Errorf("Invalid size header: %d x %d", dim.Width, dim.Height)
return util.Dimension{}, fmt.Errorf("Invalid size header: %d x %d", dim.Width, dim.Height)
}
if uint64(dim.Width*dim.Height) > maxTimes {
log.Errorf("Width times Height too large: %d %d", dim.Width, dim.Height)
return util.Dimension{}, fmt.Errorf("Width times Height too large: %d %d", dim.Width, dim.Height)
}
return dim, nil
}
func getWidthFromRatio(ratio uint32, height uint32) (uint32, error) {
switch ratio {
case 1:
return height, nil
case 2:
return height * 6 / 5, nil
case 3:
return height * 4 / 3, nil
case 4:
return height * 3 / 2, nil
case 5:
return height * 16 / 9, nil
case 6:
return height * 5 / 4, nil
case 7:
return height * 2, nil
default:
return 0, fmt.Errorf("invalid ratio: %d", ratio)
}
}
func (h *ImageHeader) GetUpWeights() ([][][][][]float32, error) {
if h.UpWeights != nil {
return h.UpWeights, nil
}
h.UpWeights = make([][][][][]float32, 3)
for l := 0; l < 3; l++ {
k := 1 << (l + 1)
var upKWeights []float32
if k == 8 {
upKWeights = h.Up8Weights
}
if k == 4 {
upKWeights = h.Up4Weights
}
if k == 2 {
upKWeights = h.Up2Weights
}
if upKWeights == nil {
return nil, errors.New("Invalid UpWeights")
}
h.UpWeights[l] = util.MakeMatrix4D[float32](k, k, 5, 5)
for ky := 0; ky < k; ky++ {
for kx := 0; kx < k; kx++ {
for iy := 0; iy < 5; iy++ {
for ix := 0; ix < 5; ix++ {
var j int
if ky < k/2 {
j = iy + 5*ky
} else {
j = (4 - iy) + 5*(k-1-ky)
}
var i int
if kx < k/2 {
i = ix + 5*kx
} else {
i = (4 - ix) + 5*(k-1-kx)
}
var x int
if i < j {
x = j
} else {
x = i
}
y := x ^ j ^ i
index := 5*k*y/2 - y*(y-1)/2 + x - y
h.UpWeights[l][ky][kx][iy][ix] = upKWeights[index]
}
}
}
}
}
return h.UpWeights, nil
}
func (h *ImageHeader) GetICCPrediction(buffer []byte, i int32) int32 {
if i <= 3 {
return int32(len(buffer)) >> (8 * (3 - i))
}
if i == 8 {
return 4
}
if i >= 12 && i <= 23 {
return int32(MNTRGB[i-12])
}
if i >= 36 && i <= 39 {
return int32(ACSP[i-36])
}
if buffer[40] == 'A' {
if i == 41 || i == 42 {
return 'P'
}
if i == 43 {
return 'L'
}
} else if buffer[40] == 'M' {
if i == 41 {
return 'S'
}
if i == 42 {
return 'F'
}
if i == 43 {
return 'T'
}
} else if buffer[40] == 'S' {
if buffer[41] == 'G' {
if i == 42 {
return 'I'
}
if i == 43 {
return 32
}
} else if buffer[41] == 'U' {
if i == 42 {
return 'N'
}
if i == 43 {
return 'W'
}
}
}
if i == 70 {
return 246
}
if i == 71 {
return 214
}
if i == 73 {
return 1
}
if i == 78 {
return 211
}
if i == 79 {
return 45
}
if i >= 80 && i < 84 {
return int32(buffer[i-76])
}
return 0
}
package colour
type CIEPrimaries struct {
Red *CIEXY
Green *CIEXY
Blue *CIEXY
}
func NewCIEPrimaries(red *CIEXY, green *CIEXY, blue *CIEXY) *CIEPrimaries {
cp := CIEPrimaries{}
cp.Red = red
cp.Green = green
cp.Blue = blue
return &cp
}
func (cp *CIEPrimaries) Matches(b *CIEPrimaries) bool {
if b == nil {
return false
}
return cp.Red.Matches(b.Red) && cp.Green.Matches(b.Green) && cp.Blue.Matches(b.Blue)
}
package colour
import (
"errors"
"math"
"github.com/kpfaulkner/jxl-go/util"
)
type CIEXY struct {
X float32
Y float32
}
func NewCIEXY(x float32, y float32) *CIEXY {
cxy := &CIEXY{}
cxy.X = x
cxy.Y = y
return cxy
}
func (cxy *CIEXY) Matches(b *CIEXY) bool {
if b == nil {
return false
}
return math.Abs(float64(cxy.X-b.X))+math.Abs(float64(cxy.Y-b.Y)) < 0.0001
}
func AdaptWhitePoint(targetWP *CIEXY, currentWP *CIEXY) ([][]float32, error) {
if targetWP == nil {
targetWP = CM_WP_D50
}
if currentWP == nil {
currentWP = CM_WP_D65
}
wCurrent, err := GetXYZ(*currentWP)
if err != nil {
return nil, err
}
lmsCurrent, err := util.MatrixVectorMultiply(BRADFORD, wCurrent)
if err != nil {
return nil, err
}
wTarget, err := GetXYZ(*targetWP)
if err != nil {
return nil, err
}
lmsTarget, err := util.MatrixVectorMultiply(BRADFORD, wTarget)
if err != nil {
return nil, err
}
if !isLMSValid(lmsCurrent) {
return nil, errors.New("Invalid LMS")
}
a := util.MakeMatrix2D[float32](3, 3)
for i := 0; i < 3; i++ {
a[i][i] = lmsTarget[i] / lmsCurrent[i]
}
return util.MatrixMultiply(BRADFORD_INVERSE, a, BRADFORD)
}
func isLMSValid(lms []float32) bool {
for i := 0; i < len(lms); i++ {
if math.Abs(float64(lms[i])) < 1e-8 {
return false
}
}
return true
}
package colour
import (
"github.com/kpfaulkner/jxl-go/jxlio"
)
type CustomXY struct {
CIEXY
}
func NewCustomXY(reader jxlio.BitReader) (*CustomXY, error) {
cxy := &CustomXY{}
ciexy, err := cxy.readCustom(reader)
if err != nil {
return nil, err
}
cxy.CIEXY = *ciexy
return cxy, nil
}
func (cxy *CustomXY) readCustom(reader jxlio.BitReader) (*CIEXY, error) {
var x float32
if ux, err := reader.ReadU32(0, 19, 524288, 19, 1048576, 20, 2097152, 21); err != nil {
return nil, err
} else {
x = float32(jxlio.UnpackSigned(ux)) * 1e-6
}
var y float32
if uy, err := reader.ReadU32(0, 19, 524288, 19, 1048576, 20, 2097152, 21); err != nil {
return nil, err
} else {
y = float32(jxlio.UnpackSigned(uy)) * 1e-6
}
return NewCIEXY(x, y), nil
}
package colour
import (
"errors"
"slices"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
var (
DEFAULT_MATRIX = [][]float32{
{11.031566901960783, -9.866943921568629, -0.16462299647058826},
{-3.254147380392157, 4.418770392156863, -0.16462299647058826},
{-3.6588512862745097, 2.7129230470588235, 1.9459282392156863}}
DEFAULT_OPSIN_BIAS = []float32{-0.0037930732552754493, -0.0037930732552754493, -0.0037930732552754493}
DEFAULT_QUANT_BIAS = []float32{1.0 - 0.05465007330715401, 1.0 - 0.07005449891748593, 1.0 - 0.049935103337343655}
DEFAULT_QBIAS_NUMERATOR float32 = 0.145
)
type OpsinInverseMatrix struct {
Matrix [][]float32
OpsinBias []float32
QuantBias []float32
CbrtOpsinBias []float32
Primaries CIEPrimaries
WhitePoint CIEXY
QuantBiasNumerator float32
}
func NewOpsinInverseMatrix() *OpsinInverseMatrix {
return NewOpsinInverseMatrixAllParams(*CM_PRI_SRGB, *CM_WP_D65, DEFAULT_MATRIX, DEFAULT_OPSIN_BIAS, DEFAULT_QUANT_BIAS, DEFAULT_QBIAS_NUMERATOR)
}
func NewOpsinInverseMatrixAllParams(
primaries CIEPrimaries,
whitePoint CIEXY,
matrix [][]float32,
opsinBias []float32,
quantBias []float32,
quantBiasNumerator float32) *OpsinInverseMatrix {
oim := &OpsinInverseMatrix{}
oim.Matrix = matrix
oim.OpsinBias = opsinBias
oim.QuantBias = quantBias
oim.QuantBiasNumerator = quantBiasNumerator
oim.Primaries = primaries
oim.WhitePoint = whitePoint
oim.bakeCbrtBias()
return oim
}
func NewOpsinInverseMatrixWithReader(reader jxlio.BitReader) (*OpsinInverseMatrix, error) {
oim := &OpsinInverseMatrix{}
var err error
var useMatrix bool
if useMatrix, err = reader.ReadBool(); err != nil {
return nil, err
}
if useMatrix {
oim.Matrix = DEFAULT_MATRIX
oim.OpsinBias = DEFAULT_OPSIN_BIAS
oim.QuantBias = DEFAULT_QUANT_BIAS
oim.QuantBiasNumerator = DEFAULT_QBIAS_NUMERATOR
} else {
oim.Matrix = util.MakeMatrix2D[float32](3, 3)
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if oim.Matrix[i][j], err = reader.ReadF16(); err != nil {
return nil, err
}
}
}
oim.OpsinBias = make([]float32, 3)
for i := 0; i < 3; i++ {
if oim.OpsinBias[i], err = reader.ReadF16(); err != nil {
return nil, err
}
}
oim.QuantBias = make([]float32, 3)
for i := 0; i < 3; i++ {
if oim.QuantBias[i], err = reader.ReadF16(); err != nil {
return nil, err
}
}
if oim.QuantBiasNumerator, err = reader.ReadF16(); err != nil {
return nil, err
}
}
oim.Primaries = *CM_PRI_SRGB
oim.WhitePoint = *CM_WP_D65
oim.bakeCbrtBias()
return oim, nil
}
func (oim *OpsinInverseMatrix) bakeCbrtBias() {
oim.CbrtOpsinBias = make([]float32, 3)
for c := 0; c < 3; c++ {
oim.CbrtOpsinBias[c] = util.SignedPow(oim.OpsinBias[c], 1.0/3.0)
}
}
func (oim *OpsinInverseMatrix) GetMatrix(prim *CIEPrimaries, white *CIEXY) (*OpsinInverseMatrix, error) {
conversion, err := GetConversionMatrix(*prim, *white, oim.Primaries, oim.WhitePoint)
if err != nil {
return nil, err
}
matrix, err := util.MatrixMultiply(conversion, oim.Matrix)
if err != nil {
return nil, err
}
return NewOpsinInverseMatrixAllParams(*prim, *white, matrix, oim.OpsinBias, oim.QuantBias, oim.QuantBiasNumerator), nil
}
func (oim *OpsinInverseMatrix) InvertXYB(buffer [][][]float32, intensityTarget float32) error {
if len(buffer) < 3 {
return errors.New("Can only XYB on 3 channels")
}
// Cache struct fields locally to avoid repeated lookups in hot loop
itScale := 255.0 / intensityTarget
cbrtBias0 := oim.CbrtOpsinBias[0]
cbrtBias1 := oim.CbrtOpsinBias[1]
cbrtBias2 := oim.CbrtOpsinBias[2]
opsinBias0 := oim.OpsinBias[0]
opsinBias1 := oim.OpsinBias[1]
opsinBias2 := oim.OpsinBias[2]
// Cache matrix coefficients (unrolled for 3 channels)
m00 := oim.Matrix[0][0]
m01 := oim.Matrix[0][1]
m02 := oim.Matrix[0][2]
m10 := oim.Matrix[1][0]
m11 := oim.Matrix[1][1]
m12 := oim.Matrix[1][2]
m20 := oim.Matrix[2][0]
m21 := oim.Matrix[2][1]
m22 := oim.Matrix[2][2]
buf0 := buffer[0]
buf1 := buffer[1]
buf2 := buffer[2]
for y := 0; y < len(buf0); y++ {
// Cache row pointers to avoid repeated slice lookups in inner loop
row0 := buf0[y]
row1 := buf1[y]
row2 := buf2[y]
rowLen := len(row0)
for x := 0; x < rowLen; x++ {
b0 := row0[x]
b1 := row1[x]
b2 := row2[x]
gammaL := b1 + b0 - cbrtBias0
gammaM := b1 - b0 - cbrtBias1
gammaS := b2 - cbrtBias2
mixL := gammaL*gammaL*gammaL + opsinBias0
mixM := gammaM*gammaM*gammaM + opsinBias1
mixS := gammaS*gammaS*gammaS + opsinBias2
// Unrolled loop for 3 channels
row0[x] = (mixL*m00 + mixM*m01 + mixS*m02) * itScale
row1[x] = (mixL*m10 + mixM*m11 + mixS*m12) * itScale
row2[x] = (mixL*m20 + mixM*m21 + mixS*m22) * itScale
}
}
return nil
}
// Matches determines if values are equal. Simplistic but will do for now.
func (oim *OpsinInverseMatrix) Matches(other OpsinInverseMatrix) bool {
if !util.CompareMatrix2D(oim.Matrix, other.Matrix, func(a float32, b float32) bool { return a == b }) {
return false
}
if slices.Compare(oim.OpsinBias, other.OpsinBias) != 0 {
return false
}
if slices.Compare(oim.QuantBias, other.QuantBias) != 0 {
return false
}
if oim.QuantBiasNumerator != other.QuantBiasNumerator {
return false
}
if !oim.Primaries.Red.Matches(other.Primaries.Red) {
return false
}
if !oim.Primaries.Green.Matches(other.Primaries.Green) {
return false
}
if !oim.Primaries.Blue.Matches(other.Primaries.Blue) {
return false
}
if !oim.WhitePoint.Matches(&other.WhitePoint) {
return false
}
return true
}
package colour
import (
"errors"
"github.com/kpfaulkner/jxl-go/jxlio"
)
type ToneMapping struct {
IntensityTarget float32
MinNits float32
LinearBelow float32
RelativeToMaxDisplay bool
}
func NewToneMapping() *ToneMapping {
tm := &ToneMapping{}
tm.IntensityTarget = 255.0
tm.MinNits = 0.0
tm.RelativeToMaxDisplay = false
tm.LinearBelow = 0
return tm
}
func NewToneMappingWithReader(reader jxlio.BitReader) (*ToneMapping, error) {
tm := &ToneMapping{}
var err error
var useToneMapping bool
if useToneMapping, err = reader.ReadBool(); err != nil {
return nil, err
}
if useToneMapping {
tm.IntensityTarget = 255.0
tm.MinNits = 0.0
tm.RelativeToMaxDisplay = false
tm.LinearBelow = 0
} else {
if tm.IntensityTarget, err = reader.ReadF16(); err != nil {
return nil, err
}
if tm.IntensityTarget <= 0 {
return nil, errors.New("Intensity Target must be positive")
}
if tm.MinNits, err = reader.ReadF16(); err != nil {
return nil, err
}
if tm.MinNits < 0 {
return nil, errors.New("Min Nits must be positive")
}
if tm.MinNits > tm.IntensityTarget {
return nil, errors.New("Min Nits must be at most the Intensity Target")
}
if tm.RelativeToMaxDisplay, err = reader.ReadBool(); err != nil {
return nil, err
}
if tm.LinearBelow, err = reader.ReadF16(); err != nil {
return nil, err
}
if tm.RelativeToMaxDisplay && (tm.LinearBelow < 0 || tm.LinearBelow > 1) {
return nil, errors.New("Linear Below out of relative range")
}
if !tm.RelativeToMaxDisplay && tm.LinearBelow < 0 {
return nil, errors.New("Linear Below must be nonnegative")
}
}
return tm, nil
}
func (tm *ToneMapping) GetIntensityTarget() float32 {
return tm.IntensityTarget
}
package colour
import (
"errors"
"github.com/kpfaulkner/jxl-go/jxlio"
)
type ColourEncodingBundle struct {
Prim *CIEPrimaries
White *CIEXY
ColourEncoding int32
WhitePoint int32
Primaries int32
Tf int32
RenderingIntent int32
UseIccProfile bool
}
func NewColourEncodingBundle() (*ColourEncodingBundle, error) {
ceb := &ColourEncodingBundle{}
ceb.UseIccProfile = false
ceb.ColourEncoding = CE_RGB
ceb.WhitePoint = WP_D65
ceb.White = getWhitePoint(ceb.WhitePoint)
ceb.Primaries = PRI_SRGB
ceb.Prim = GetPrimaries(ceb.Primaries)
ceb.Tf = TF_SRGB
ceb.RenderingIntent = RI_RELATIVE
return ceb, nil
}
func NewColourEncodingBundleWithReader(reader jxlio.BitReader) (*ColourEncodingBundle, error) {
ceb := &ColourEncodingBundle{}
var allDefault bool
var err error
if allDefault, err = reader.ReadBool(); err != nil {
return nil, err
}
if !allDefault {
if ceb.UseIccProfile, err = reader.ReadBool(); err != nil {
return nil, err
}
}
if !allDefault {
if ceb.ColourEncoding, err = reader.ReadEnum(); err != nil {
return nil, err
}
} else {
ceb.ColourEncoding = CE_RGB
}
if !ValidateColourEncoding(ceb.ColourEncoding) {
return nil, errors.New("Invalid ColorSpace enum")
}
if !allDefault && !ceb.UseIccProfile && ceb.ColourEncoding != CE_XYB {
if ceb.WhitePoint, err = reader.ReadEnum(); err != nil {
return nil, err
}
} else {
ceb.WhitePoint = WP_D65
}
if !ValidateWhitePoint(ceb.WhitePoint) {
return nil, errors.New("Invalid WhitePoint enum")
}
if ceb.WhitePoint == WP_CUSTOM {
white, err := NewCustomXY(reader)
if err != nil {
return nil, err
}
ceb.White = &white.CIEXY
} else {
ceb.White = getWhitePoint(ceb.WhitePoint)
}
if !allDefault && !ceb.UseIccProfile && ceb.ColourEncoding != CE_XYB && ceb.ColourEncoding != CE_GRAY {
if ceb.Primaries, err = reader.ReadEnum(); err != nil {
return nil, err
}
} else {
ceb.Primaries = PRI_SRGB
}
if !ValidatePrimaries(ceb.Primaries) {
return nil, errors.New("Invalid Primaries enum")
}
if ceb.Primaries == PRI_CUSTOM {
pRed, err := NewCustomXY(reader)
if err != nil {
return nil, err
}
pGreen, err := NewCustomXY(reader)
if err != nil {
return nil, err
}
pBlue, err := NewCustomXY(reader)
if err != nil {
return nil, err
}
ceb.Prim = NewCIEPrimaries(&pRed.CIEXY, &pGreen.CIEXY, &pBlue.CIEXY)
} else {
ceb.Prim = GetPrimaries(ceb.Primaries)
}
if !allDefault && !ceb.UseIccProfile {
var useGamma bool
if useGamma, err = reader.ReadBool(); err != nil {
return nil, err
}
if useGamma {
if tf, err := reader.ReadBits(24); err != nil {
return nil, err
} else {
ceb.Tf = int32(tf)
}
} else {
var tfEnum int32
if tfEnum, err = reader.ReadEnum(); err != nil {
return nil, err
}
ceb.Tf = (1 << 24) + tfEnum
}
if !ValidateTransfer(ceb.Tf) {
return nil, errors.New("Illegal transfer function")
}
if ceb.RenderingIntent, err = reader.ReadEnum(); err != nil {
return nil, err
}
if !ValidateRenderingIntent(ceb.RenderingIntent) {
return nil, errors.New("Invalid RenderingIntent enum")
}
} else {
ceb.Tf = TF_SRGB
ceb.RenderingIntent = RI_RELATIVE
}
return ceb, nil
}
func getWhitePoint(whitePoint int32) *CIEXY {
switch whitePoint {
case WP_D65:
return NewCIEXY(0.3127, 0.3290)
case WP_E:
return NewCIEXY(1.0/3.0, 1.0/3.0)
case WP_DCI:
return NewCIEXY(0.314, 0.351)
case WP_D50:
return NewCIEXY(0.34567, 0.34567)
}
return nil
}
package colour
const (
PRI_SRGB int32 = 1
PRI_CUSTOM int32 = 2
PRI_BT2100 int32 = 9
PRI_P3 int32 = 11
WP_D50 int32 = -1
WP_D65 int32 = 1
WP_CUSTOM int32 = 2
WP_E int32 = 10
WP_DCI int32 = 11
CE_RGB int32 = 0
CE_GRAY int32 = 1
CE_XYB int32 = 2
CE_UNKNOWN int32 = 3
RI_PERCEPTUAL int32 = 0
RI_RELATIVE int32 = 1
RI_SATURATION int32 = 2
RI_ABSOLUTE int32 = 3
TF_BT709 int32 = 1 + (1 << 24)
TF_UNKNOWN int32 = 2 + (1 << 24)
TF_LINEAR int32 = 8 + (1 << 24)
TF_SRGB int32 = 13 + (1 << 24)
TF_PQ int32 = 16 + (1 << 24)
TF_DCI int32 = 17 + (1 << 24)
TF_HLG int32 = 18 + (1 << 24)
)
func ValidateColourEncoding(colourEncoding int32) bool {
return colourEncoding >= 0 && colourEncoding <= 3
}
func ValidateWhitePoint(whitePoint int32) bool {
return whitePoint == WP_D65 || whitePoint == WP_CUSTOM || whitePoint == WP_E || whitePoint == WP_DCI
}
func ValidatePrimaries(primaries int32) bool {
return primaries == PRI_SRGB || primaries == PRI_CUSTOM || primaries == PRI_BT2100 || primaries == PRI_P3
}
func ValidateRenderingIntent(renderingIntent int32) bool {
return renderingIntent >= 0 && renderingIntent <= 3
}
func ValidateTransfer(transfer int32) bool {
if transfer < 0 {
return false
} else if transfer <= 10_000_000 {
return true
} else if transfer < (1 << 24) {
return false
} else {
return transfer == TF_BT709 ||
transfer == TF_UNKNOWN ||
transfer == TF_LINEAR ||
transfer == TF_SRGB ||
transfer == TF_PQ ||
transfer == TF_DCI ||
transfer == TF_HLG
}
}
func GetPrimaries(primaries int32) *CIEPrimaries {
switch primaries {
case PRI_SRGB:
return NewCIEPrimaries(
NewCIEXY(0.639998686, 0.330010138),
NewCIEXY(0.300003784, 0.600003357),
NewCIEXY(0.150002046, 0.059997204))
case PRI_BT2100:
return NewCIEPrimaries(
NewCIEXY(0.708, 0.292),
NewCIEXY(0.170, 0.797),
NewCIEXY(0.131, 0.046))
case PRI_P3:
return NewCIEPrimaries(
NewCIEXY(0.680, 0.320),
NewCIEXY(0.265, 0.690),
NewCIEXY(0.150, 0.060))
}
return nil
}
func GetWhitePoint(whitePoint int32) *CIEXY {
switch whitePoint {
case WP_D65:
return NewCIEXY(0.3127, 0.3290)
case WP_E:
return NewCIEXY(1.0/3.0, 1.0/3.0)
case WP_DCI:
return NewCIEXY(0.314, 0.351)
case WP_D50:
return NewCIEXY(0.34567, 0.34567)
}
return nil
}
package colour
import (
"errors"
"github.com/kpfaulkner/jxl-go/util"
)
var (
CM_PRI_SRGB = GetPrimaries(PRI_SRGB)
CM_PRI_BT2100 = GetPrimaries(PRI_BT2100)
CM_PRI_P3 = GetPrimaries(PRI_P3)
CM_WP_D65 = GetWhitePoint(WP_D65)
CM_WP_D50 = GetWhitePoint(WP_D50)
BRADFORD = [][]float32{
{0.8951, 0.2664, -0.1614},
{-0.7502, 1.7135, 0.0367},
{0.0389, -0.0685, 1.0296},
}
BRADFORD_INVERSE = util.InvertMatrix3x3(BRADFORD)
)
type TransferFunction interface {
ToLinear(input float64) float64
FromLinear(input float64) float64
}
func GetConversionMatrix(targetPrim CIEPrimaries, targetWP CIEXY, currentPrim CIEPrimaries, currentWP CIEXY) ([][]float32, error) {
if targetPrim.Matches(¤tPrim) && targetWP.Matches(¤tWP) {
return util.MatrixIdentity(3), nil
}
var whitePointConv [][]float32
var err error
if !targetWP.Matches(¤tWP) {
whitePointConv, err = AdaptWhitePoint(&targetWP, ¤tWP)
if err != nil {
return nil, err
}
}
forward, err := primariesToXYZ(¤tPrim, ¤tWP)
if err != nil {
return nil, err
}
t, err := primariesToXYZ(&targetPrim, &targetWP)
if err != nil {
return nil, err
}
reverse := util.InvertMatrix3x3(t)
res, err := util.MatrixMultiply(reverse, whitePointConv, forward)
if err != nil {
return nil, err
}
return res, nil
}
func primariesToXYZ(primaries *CIEPrimaries, wp *CIEXY) ([][]float32, error) {
if primaries == nil {
return nil, nil
}
if wp == nil {
wp = CM_WP_D50
}
if wp.X < 0 || wp.X > 1 || wp.Y <= 0 || wp.Y > 1 {
return nil, errors.New("invalid argument")
}
r, errR := GetXYZ(*primaries.Red)
g, errG := GetXYZ(*primaries.Green)
b, errB := GetXYZ(*primaries.Blue)
if errR != nil || errG != nil || errB != nil {
return nil, errors.New("invalid argument")
}
primariesTr := [][]float32{r, g, b}
primariesMatrix := util.TransposeMatrix(primariesTr, *util.NewPoint(3, 3))
inversePrimaries := util.InvertMatrix3x3(primariesMatrix)
w, err := GetXYZ(*wp)
if err != nil {
return nil, err
}
xyz, err := util.MatrixVectorMultiply(inversePrimaries, w)
if err != nil {
return nil, err
}
a := [][]float32{{xyz[0], 0, 0}, {0, xyz[1], 0}, {0, 0, xyz[2]}}
res, err := util.MatrixMatrixMultiply(primariesMatrix, a)
if err != nil {
return nil, err
}
return res, nil
}
func validateXY(xy CIEXY) error {
if xy.X < 0 || xy.X > 1 || xy.Y <= 0 || xy.Y > 1 {
return errors.New("Invalid argument")
}
return nil
}
func GetXYZ(xy CIEXY) ([]float32, error) {
if err := validateXY(xy); err != nil {
return nil, err
}
invY := 1.0 / xy.Y
return []float32{xy.X * invY, 1.0, (1.0 - xy.X - xy.Y) * invY}, nil
}
func GetTransferFunction(transfer int32) (TransferFunction, error) {
switch transfer {
case TF_LINEAR:
return LinearTransferFunction{}, nil
case TF_SRGB:
return SRGBTransferFunction{}, nil
case TF_PQ:
return PQTransferFunction{}, nil
case TF_BT709:
return BT709TransferFunction{}, nil
case TF_DCI:
return NewGammaTransferFunction(transfer), nil
case TF_HLG:
return nil, errors.New("Not implemented")
}
if transfer < (1 << 24) {
return NewGammaTransferFunction(transfer), nil
}
return nil, errors.New("Invalid transfer function")
}
package colour
import "math"
type LinearTransferFunction struct {
}
func (tf LinearTransferFunction) ToLinear(input float64) float64 {
return input
}
func (tf LinearTransferFunction) FromLinear(input float64) float64 {
return input
}
type SRGBTransferFunction struct {
}
func (tf SRGBTransferFunction) ToLinear(input float64) float64 {
if input < 0.0404482362771082 {
return input * 0.07739938080495357
}
return math.Pow((input+0.055)*0.9478672985781991, 2.4)
}
func (tf SRGBTransferFunction) FromLinear(input float64) float64 {
if input < 0.00313066844250063 {
return input * 12.92
}
return 1.055*math.Pow(input, 0.4166666666666667) - 0.055
}
type BT709TransferFunction struct {
}
func (tf BT709TransferFunction) ToLinear(input float64) float64 {
if input < 0.081242858298635133011 {
return input * 0.22222222222222222222
}
return math.Pow((input+0.0992968268094429403)*0.90967241568627260377, 2.2222222222222222222)
}
func (tf BT709TransferFunction) FromLinear(input float64) float64 {
if input < 0.018053968510807807336 {
return 4.5 * input
}
return 1.0992968268094429403*math.Pow(input, 0.45) - 0.0992968268094429403
}
type PQTransferFunction struct {
}
func (tf PQTransferFunction) ToLinear(input float64) float64 {
d := math.Pow(input, 0.012683313515655965121)
return math.Pow((d-0.8359375)/(18.8515625+18.6875*d), 6.2725880551301684533)
}
func (tf PQTransferFunction) FromLinear(input float64) float64 {
d := math.Pow(input, 0.159423828125)
return math.Pow((0.8359375+18.8515625*d)/(1.0+18.6875*d), 78.84375)
}
type GammaTransferFunction struct {
gamma float64
inverseGamma float64
}
func NewGammaTransferFunction(transfer int32) GammaTransferFunction {
gtf := GammaTransferFunction{}
gtf.gamma = 1e-7 * float64(transfer)
gtf.inverseGamma = 1e7 / float64(transfer)
return gtf
}
func (tf GammaTransferFunction) ToLinear(input float64) float64 {
return math.Pow(input, tf.inverseGamma)
}
func (tf GammaTransferFunction) FromLinear(input float64) float64 {
return math.Pow(input, tf.gamma)
}
package core
import (
"errors"
"io"
"github.com/kpfaulkner/jxl-go/bundle"
"github.com/kpfaulkner/jxl-go/colour"
"github.com/kpfaulkner/jxl-go/frame"
image2 "github.com/kpfaulkner/jxl-go/image"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/options"
"github.com/kpfaulkner/jxl-go/util"
)
// JXLCodestreamDecoder decodes the JXL image
type JXLCodestreamDecoder struct {
// bit reader... the actual thing that will read the bits/U16/U32/U64 etc.
reference [][]image2.ImageBuffer
lfBuffer [][]image2.ImageBuffer
canvas []image2.ImageBuffer
boxHeaders []ContainerBoxHeader
bitReader jxlio.BitReader
imageHeader *bundle.ImageHeader
options options.JXLOptions
level int
foundSignature bool
}
func NewJXLCodestreamDecoder(br jxlio.BitReader, opts *options.JXLOptions) *JXLCodestreamDecoder {
jxl := &JXLCodestreamDecoder{}
jxl.bitReader = br
jxl.foundSignature = false
jxl.lfBuffer = make([][]image2.ImageBuffer, 5)
if opts != nil {
jxl.options = *options.NewJXLOptions(opts)
}
jxl.reference = make([][]image2.ImageBuffer, 4)
return jxl
}
func (jxl *JXLCodestreamDecoder) atEnd() bool {
if jxl.bitReader != nil {
return jxl.bitReader.AtEnd()
}
return false
}
// GetImageHeader just duplicates the first chunk of code from decode. This is so we can get the image size
// and colour model.
func (jxl *JXLCodestreamDecoder) GetImageHeader() (*bundle.ImageHeader, error) {
// read header to get signature
err := jxl.ReadSignatureAndBoxes()
if err != nil {
return nil, err
}
if len(jxl.boxHeaders) > 1 {
return nil, errors.New("multiple boxes found, cannot get image header alone")
}
box := jxl.boxHeaders[0]
_, err = jxl.bitReader.Seek(box.Offset, io.SeekStart)
if err != nil {
return nil, err
}
if jxl.atEnd() {
return nil, nil
}
level := int32(jxl.level)
imageHeader, err := bundle.ParseImageHeader(jxl.bitReader, level)
if err != nil {
return nil, err
}
return imageHeader, nil
}
func (jxl *JXLCodestreamDecoder) decode() (*JXLImage, error) {
// read header to get signature
err := jxl.ReadSignatureAndBoxes()
if err != nil {
return nil, err
}
box := jxl.boxHeaders[0]
_, err = jxl.bitReader.Seek(box.Offset, io.SeekStart)
if err != nil {
return nil, err
}
if jxl.atEnd() {
return nil, nil
}
level := int32(jxl.level)
imageHeader, err := bundle.ParseImageHeader(jxl.bitReader, level)
if err != nil {
return nil, err
}
jxl.imageHeader = imageHeader
size := imageHeader.Size
jxl.canvas = make([]image2.ImageBuffer, imageHeader.GetColourChannelCount()+len(imageHeader.ExtraChannelInfo))
if imageHeader.AnimationHeader != nil {
return nil, errors.New("animation not implemented")
}
if imageHeader.PreviewSize != nil {
//previewOptions := options.NewJXLOptions(&jxl.options)
//previewOptions.ParseOnly = true
//frame := frame.NewFrameWithReader(jxl.bitReader, jxl.imageHeader, previewOptions)
//frame.ReadFrameHeader()
return nil, errors.New("not implemented preview yet")
}
var matrix *colour.OpsinInverseMatrix
if imageHeader.XybEncoded {
bundle := imageHeader.ColourEncoding
matrix, err = imageHeader.OpsinInverseMatrix.GetMatrix(bundle.Prim, bundle.White)
if err != nil {
return nil, err
}
}
frameCount := 0
invisibleFrames := int64(0)
visibleFrames := 0
header := frame.FrameHeader{}
// If we have multiple box headers... then we want to loop over from the second one.
// Need to also seek to that box offset.
ii := 0
shouldSeekBoxOffset := false
if len(jxl.boxHeaders) > 1 {
ii = 1
shouldSeekBoxOffset = true
}
for _, box := range jxl.boxHeaders[ii:] {
// only seek if we're not dealing with a single box.
if shouldSeekBoxOffset {
_, err = jxl.bitReader.Seek(box.Offset, io.SeekStart)
if err != nil {
return nil, err
}
}
if jxl.atEnd() {
return nil, nil
}
for {
imgFrame := frame.NewFrameWithReader(jxl.bitReader, jxl.imageHeader, &jxl.options)
header, err = imgFrame.ReadFrameHeader()
if err != nil {
return nil, err
}
frameCount++
if jxl.lfBuffer[header.LfLevel] == nil && header.Flags&frame.USE_LF_FRAME != 0 {
return nil, errors.New("LF level too large")
}
err := imgFrame.ReadTOC()
if err != nil {
return nil, err
}
if jxl.options.ParseOnly {
if err := imgFrame.SkipFrameData(); err != nil {
return nil, err
}
continue
}
err = imgFrame.DecodeFrame(jxl.lfBuffer[header.LfLevel], frame.NewLFGlobalWithReader)
if err != nil {
return nil, err
}
if header.LfLevel > 0 {
jxl.lfBuffer[header.LfLevel-1] = imgFrame.Buffer
}
if header.FrameType == frame.LF_FRAME {
continue
}
save := (header.SaveAsReference != 0 || header.Duration == 0) && !header.IsLast && header.FrameType != frame.LF_FRAME
if imgFrame.IsVisible() {
visibleFrames++
invisibleFrames = 0
} else {
invisibleFrames++
}
err = imgFrame.Upsample()
if err != nil {
return nil, err
}
err = imgFrame.InitializeNoise(int64(visibleFrames<<32) | invisibleFrames)
if err != nil {
return nil, err
}
if save && header.SaveBeforeCT {
jxl.reference[header.SaveAsReference] = imgFrame.Buffer
}
err = jxl.computePatches(imgFrame)
if err != nil {
return nil, err
}
err = imgFrame.RenderSplines()
if err != nil {
return nil, err
}
err = imgFrame.SynthesizeNoise()
if err != nil {
return nil, err
}
err = jxl.performColourTransforms(matrix, imgFrame)
if err != nil {
return nil, err
}
if header.Encoding == frame.VARDCT && jxl.options.RenderVarblocks {
panic("VARDCT not implemented yet")
}
if jxl.canvas[0].Height == 0 && jxl.canvas[0].Width == 0 {
for c := 0; c < len(jxl.canvas); c++ {
canvas, err := image2.NewImageBuffer(imgFrame.Buffer[0].BufferType, int32(size.Height), int32(size.Width))
if err != nil {
return nil, err
}
jxl.canvas[c] = *canvas
}
}
if header.FrameType == frame.REGULAR_FRAME || header.FrameType == frame.SKIP_PROGRESSIVE {
found := false
for i := uint32(0); i < 4; i++ {
if image2.ImageBufferSliceEquals(jxl.reference[i], jxl.canvas) && i != header.SaveAsReference {
found = true
break
}
}
// FIXME(kpfaulkner) need to figure out new copies of canvas... why?
// nolint
if found {
canvas2 := make([]image2.ImageBuffer, len(jxl.canvas))
for _, ib := range jxl.canvas {
ib2 := image2.NewImageBufferFromImageBuffer(&ib, true)
canvas2 = append(canvas2, *ib2)
}
//jxl.canvas = canvas2
}
err = jxl.blendFrame(jxl.canvas, imgFrame)
if err != nil {
return nil, err
}
}
if save && !header.SaveBeforeCT {
jxl.reference[header.SaveAsReference] = jxl.canvas
}
if header.IsLast && header.Duration == 0 {
break
}
}
err = jxl.bitReader.ZeroPadToByte()
if err != nil {
return nil, err
}
// TOOD(kpfaulkner) unsure if need to perform similar drain cache functionality here. Don't think we do.
if jxl.options.ParseOnly {
return nil, nil
}
orientation := imageHeader.Orientation
orientedCanvas := make([]image2.ImageBuffer, len(jxl.canvas))
for i := 0; i < len(orientedCanvas); i++ {
orientedCanvas[i], err = jxl.transposeBuffer(jxl.canvas[i], orientation)
if err != nil {
return nil, err
}
}
// generate image and return.
img, err := NewJXLImageWithBuffer(orientedCanvas, *imageHeader)
if err != nil {
return nil, err
}
return img, nil
}
return nil, nil
}
// Read signature
func (jxl *JXLCodestreamDecoder) ReadSignatureAndBoxes() error {
br := NewBoxReader(jxl.bitReader)
boxHeaders, err := br.ReadBoxHeader()
if err != nil {
return err
}
jxl.boxHeaders = boxHeaders
jxl.level = br.level
return nil
}
func (jxl *JXLCodestreamDecoder) computePatches(frame *frame.Frame) error {
// do not support patches yet.
return nil
//header := frame.Header
//frameBuffer := frame.Buffer
//colourChannels := jxl.imageHeader.GetColourChannelCount()
//extraChannels := len(jxl.imageHeader.ExtraChannelInfo)
//patches := frame.LfGlobal.Patches
//hasAlpha := jxl.imageHeader.HasAlpha()
//for i := 0; i < len(patches); i++ {
// patch := patches[i]
// if patch.Ref > 3 {
// return errors.New("patch out of range")
// }
// refBuffer := jxl.reference[patch.Ref]
// if refBuffer == nil || len(refBuffer) == 0 {
// continue
// }
// lowerCorner := patch.Bounds.ComputeLowerCorner()
// if lowerCorner.Y > refBuffer[0].Height || lowerCorner.X > refBuffer[0].Width {
// return errors.New("patch too large")
// }
// for j := 0; i < len(patch.Positions); j++ {
// x0 := patch.Positions[j].X
// y0 := patch.Positions[j].Y
// if x0 < 0 || y0 < 0 {
// return errors.New("patch size out of bounds")
// }
//
// if patch.Bounds.Size.Height+uint32(y0) > header.Bounds.Size.Height ||
// patch.Bounds.Size.Width+uint32(x0) > header.Bounds.Size.Width {
// return errors.New("patch size out of bounds")
// }
//
// for d := int32(0); d < int32(colourChannels)+int32(extraChannels); d++ {
// var c int32
// if d < int32(colourChannels) {
// c = 0
// } else {
// c = d - int32(colourChannels) + 1
// }
// info := patch.BlendingInfos[j][c]
// if info.Mode == 0 {
// continue
// }
// var premult bool
// if jxl.imageHeader.HasAlpha() {
// premult = jxl.imageHeader.ExtraChannelInfo[info.AlphaChannel].AlphaAssociated
// } else {
// premult = true
// }
// isAlpha := c > 0 && jxl.imageHeader.ExtraChannelInfo[c-1].EcType == bundle.ALPHA
// if info.Mode > 0 && header.Upsampling > 1 && c > 0 && header.EcUpsampling[c-1]<<jxl.imageHeader.ExtraChannelInfo[c-1].DimShift != header.Upsampling {
// return errors.New("Alpha channel upsampling mismatch during patches")
// }
//
// toFloat := true
// switch info.Mode {
// case 1:
// if refBuffer[0].IsInt() && frameBuffer[d].IsInt() {
// refBufferI := refBuffer[d].IntBuffer
// frameBufferI := frameBuffer[d].IntBuffer
// for y := uint32(0); y < patch.Bounds.Size.Height; y++ {
// copy(frameBufferI[y+uint32(patch.Bounds.Origin.Y)][patch.Bounds.Origin.X:], refBufferI[y0+int32(y)][x0:])
// }
// toFloat = false
// }
// break
// case 2:
// if refBuffer[0].IsInt() && frameBuffer[d].IsInt() {
// refBufferI := refBuffer[d].IntBuffer
// frameBufferI := frameBuffer[d].IntBuffer
// for y := int32(0); y < int32(patch.Bounds.Size.Height); y++ {
// for x := int32(0); x < int32(patch.Bounds.Size.Width); x++ {
// frameBufferI[int32(y0)+y][int32(x0)+x] += refBufferI[patch.Bounds.Origin.Y+y][patch.Bounds.Origin.X+x]
// }
// }
// toFloat = false
// }
// break
// }
//
// if toFloat {
// var depth uint32
// if c == 0 {
// depth = jxl.imageHeader.BitDepth.BitsPerSample
// } else {
// depth = jxl.imageHeader.ExtraChannelInfo[c-1].BitDepth.BitsPerSample
// }
// max := ^(^int32(0) << depth)
// refBuffer[d].CastToFloatIfMax(max)
// frameBuffer[d].CastToFloatIfMax(max)
// }
// var refBufferF [][]float32
// var frameBufferF [][]float32
// if toFloat {
// refBufferF = refBuffer[d].FloatBuffer
// frameBufferF = frameBuffer[d].FloatBuffer
// } else {
// refBufferF = nil
// frameBufferF = nil
// }
// var alphaBufferOld [][]float32
// var alphaBufferNew [][]float32
// if info.Mode > 3 && hasAlpha {
// depth := jxl.imageHeader.ExtraChannelInfo[info.AlphaChannel].BitDepth.BitsPerSample
// if err := frameBuffer[colourChannels+int(info.AlphaChannel)].CastToFloatIfMax(^(^0 << depth)); err != nil {
// return err
// }
// if err := refBuffer[colourChannels+int(info.AlphaChannel)].CastToFloatIfMax(^(^0 << depth)); err != nil {
// return err
// }
// alphaBufferOld = frameBuffer[colourChannels+int(info.AlphaChannel)].FloatBuffer
// alphaBufferNew = refBuffer[colourChannels+int(info.AlphaChannel)].FloatBuffer
// } else {
// alphaBufferOld = nil
// alphaBufferNew = nil
// }
//
// switch info.Mode {
// case 1:
// if !toFloat {
// break
// }
// for y := 0; y < int(patch.Bounds.Size.Height); y++ {
// copy(frameBufferF[y+int(patch.Bounds.Origin.Y)][int(patch.Bounds.Origin.X):], refBufferF[int(y0)+y][x0:])
// }
// break
// case 2:
// if !toFloat {
// break
// }
// for y := int32(0); y < int32(patch.Bounds.Size.Height); y++ {
// for x := int32(0); x < int32(patch.Bounds.Size.Width); x++ {
// frameBufferF[y0+y][x0+x] += refBufferF[patch.Bounds.Origin.Y+y][patch.Bounds.Origin.X+x]
// }
// }
// break
// case 3:
// for y := uint32(0); y < patch.Bounds.Size.Height; y++ {
// for x := uint32(0); x < patch.Bounds.Size.Width; x++ {
// frameBufferF[uint32(y0)+y][uint32(x0)+x] *= refBufferF[uint32(patch.Bounds.Origin.Y)+y][uint32(patch.Bounds.Origin.X)+x]
// }
// }
// break
// case 4:
// if isAlpha {
// for y := int32(0); y < int32(patch.Bounds.Size.Height); y++ {
// newY := y + patch.Bounds.Origin.Y
// oldY := y + int32(y0)
// for x := int32(0); x < int32(patch.Bounds.Size.Width); x++ {
// oldX := x + int32(x0)
// newX := x + patch.Bounds.Origin.X
// newAlpha := alphaBufferNew[newY][newX]
// if info.Clamp {
// if newAlpha < 0 {
// newAlpha = 0
// } else if newAlpha > 1 {
// newAlpha = 1
// }
// }
// frameBufferF[oldY][oldX] = alphaBufferOld[oldY][oldY] +
// newAlpha*(1-alphaBufferOld[oldY][oldX])
// }
// }
// } else if premult {
// for y := int32(0); y < int32(patch.Bounds.Size.Height); y++ {
// newY := y + patch.Bounds.Origin.Y
// oldY := y + int32(y0)
// for x := int32(0); x < int32(patch.Bounds.Size.Width); x++ {
// newX := x + patch.Bounds.Origin.X
// oldX := x + int32(x0)
// newAlpha := alphaBufferNew[newY][newX]
// if info.Clamp {
// if newAlpha < 0 {
// newAlpha = 0
// } else if newAlpha > 1 {
// newAlpha = 1
// }
// }
// frameBufferF[oldY][oldX] = refBufferF[newY][newX] + frameBufferF[oldY][oldX]*(1-newAlpha)
// }
// }
// } else {
// for y := int32(0); y < int32(patch.Bounds.Size.Height); y++ {
// newY := y + patch.Bounds.Origin.Y
// oldY := y + int32(y0)
// for x := int32(0); x < int32(patch.Bounds.Size.Width); x++ {
// newX := x + patch.Bounds.Origin.X
// oldX := x + int32(x0)
// var oldAlpha float32
// var newAlpha float32
// if hasAlpha {
// oldAlpha = alphaBufferOld[oldY][oldX]
// newAlpha = alphaBufferNew[newY][newX]
// } else {
// oldAlpha = 1
// newAlpha = 1
// }
// if info.Clamp {
// if newAlpha < 0 {
// newAlpha = 0
// } else {
// if newAlpha > 1 {
// newAlpha = 1
// }
// }
// }
// alpha := oldAlpha + newAlpha*(1-oldAlpha)
// frameBufferF[oldY][oldX] = (refBufferF[newY][newX]*newAlpha + frameBufferF[oldY][oldX]*oldAlpha*(1-newAlpha)) / alpha
// }
// }
// }
// break
// case 5:
// if isAlpha {
// for y := int32(0); y < int32(patch.Bounds.Size.Height); y++ {
// newY := y + patch.Bounds.Origin.Y
// oldY := y + int32(y0)
// for x := int32(0); x < int32(patch.Bounds.Size.Width); x++ {
// oldX := x + int32(x0)
// newX := x + patch.Bounds.Origin.X
// frameBufferF[oldY][oldX] = alphaBufferOld[oldY][oldY] +
// alphaBufferNew[newY][newX]*(1-alphaBufferOld[oldY][oldX])
// }
// }
// } else if premult {
// for y := int32(0); y < int32(patch.Bounds.Size.Height); y++ {
// newY := y + patch.Bounds.Origin.Y
// oldY := y + int32(y0)
// for x := int32(0); x < int32(patch.Bounds.Size.Width); x++ {
// newX := x + patch.Bounds.Origin.X
// oldX := x + int32(x0)
// newAlpha := alphaBufferNew[newY][newX]
// if info.Clamp {
// if newAlpha < 0 {
// newAlpha = 0
// } else if newAlpha > 1 {
// newAlpha = 1
// }
// }
// frameBufferF[oldY][oldX] = frameBufferF[oldY][oldX] + refBufferF[newY][newX]*(1-newAlpha)
// }
// }
// } else {
// for y := int32(0); y < int32(patch.Bounds.Size.Height); y++ {
// newY := y + patch.Bounds.Origin.Y
// oldY := y + int32(y0)
// for x := int32(0); x < int32(patch.Bounds.Size.Width); x++ {
// newX := x + patch.Bounds.Origin.X
// oldX := x + int32(x0)
// var oldAlpha float32
// var newAlpha float32
// if hasAlpha {
// oldAlpha = alphaBufferOld[oldY][oldX]
// newAlpha = alphaBufferNew[newY][newX]
// } else {
// oldAlpha = 1
// newAlpha = 1
// }
// alpha := oldAlpha + newAlpha*(1-oldAlpha)
// frameBufferF[oldY][oldX] = (frameBufferF[oldY][oldX]*newAlpha + refBufferF[newY][newX]*oldAlpha*(1-newAlpha)) / alpha
// }
// }
// }
// break
// case 6:
// if isAlpha {
// for y := int32(0); y < int32(patch.Bounds.Size.Height); y++ {
// newY := y + patch.Bounds.Origin.Y
// oldY := y + int32(y0)
// for x := int32(0); x < int32(patch.Bounds.Size.Width); x++ {
// oldX := x + int32(x0)
// newX := x + patch.Bounds.Origin.X
// newAlpha := alphaBufferNew[newY][newX]
// if info.Clamp {
// if newAlpha < 0 {
// newAlpha = 0
// } else if newAlpha > 1 {
// newAlpha = 1
// }
// }
// v := float32(1.0)
// if !hasAlpha {
// v = newAlpha
// }
// frameBufferF[oldY][oldX] = v
// }
// }
// } else {
// for y := int32(0); y < int32(patch.Bounds.Size.Height); y++ {
// newY := y + patch.Bounds.Origin.Y
// oldY := y + int32(y0)
// for x := int32(0); x < int32(patch.Bounds.Size.Width); x++ {
// newX := x + patch.Bounds.Origin.X
// oldX := x + int32(x0)
// newAlpha := alphaBufferNew[newY][newX]
// if info.Clamp {
// if newAlpha < 0 {
// newAlpha = 0
// } else if newAlpha > 1 {
// newAlpha = 1
// }
// }
// frameBufferF[oldY][oldX] += refBufferF[newY][newX]
// }
// }
// }
// break
// case 7:
// if isAlpha {
// for y := int32(0); y < int32(patch.Bounds.Size.Height); y++ {
// oldY := y + int32(y0)
// for x := int32(0); x < int32(patch.Bounds.Size.Width); x++ {
// oldX := x + int32(x0)
//
// v := float32(1.0)
// if !hasAlpha {
// v = alphaBufferOld[oldY][oldX]
// }
// frameBufferF[oldY][oldX] = v
// }
// }
// } else {
// for y := int32(0); y < int32(patch.Bounds.Size.Height); y++ {
// newY := y + patch.Bounds.Origin.Y
// oldY := y + int32(y0)
// for x := int32(0); x < int32(patch.Bounds.Size.Width); x++ {
// newX := x + patch.Bounds.Origin.X
// oldX := x + int32(x0)
// var oldAlpha float32
// var newAlpha float32
// if hasAlpha {
// oldAlpha = alphaBufferOld[oldY][oldX]
// newAlpha = alphaBufferNew[newY][newX]
// } else {
// oldAlpha = 1
// newAlpha = 1
// }
// if info.Clamp {
// if newAlpha < 0 {
// newAlpha = 0
// } else if newAlpha > 1 {
// newAlpha = 1
// }
// }
// alpha := oldAlpha + newAlpha*(1-oldAlpha)
// frameBufferF[oldY][oldX] = refBufferF[newY][newX] + alpha*frameBufferF[oldY][oldX]
// }
// }
// }
// break
// default:
// return errors.New("unknown blending mode")
// }
// }
// }
//}
//return nil
}
func (jxl *JXLCodestreamDecoder) performColourTransforms(matrix *colour.OpsinInverseMatrix, frame *frame.Frame) error {
if matrix == nil && !frame.Header.DoYCbCr {
return nil
}
buffer := frame.Buffer
buffers := util.MakeMatrix3D[float32](3, 0, 0)
depth := jxl.imageHeader.BitDepth.BitsPerSample
for c := 0; c < 3; c++ {
if buffer[c].IsInt() {
if err := buffer[c].CastToFloatIfMax(^(^0 << depth)); err != nil {
return err
}
}
buffers[c] = buffer[c].FloatBuffer
}
if matrix != nil {
err := matrix.InvertXYB(buffers, jxl.imageHeader.ToneMapping.GetIntensityTarget())
if err != nil {
return err
}
}
if frame.Header.DoYCbCr {
size, err := frame.GetPaddedFrameSize()
if err != nil {
return err
}
for y := uint32(0); y < size.Height; y++ {
for x := uint32(0); x < size.Width; x++ {
cb := buffers[0][y][x]
yh := buffers[1][y][x] + 0.50196078431372549019
cr := buffers[2][y][x]
buffers[0][y][x] = yh + 1.402*cr
buffers[1][y][x] = yh - 0.34413628620102214650*cb - 0.71413628620102214650*cr
buffers[2][y][x] = yh + 1.772*cb
}
}
}
return nil
}
func (jxl *JXLCodestreamDecoder) blendFrame(canvas []image2.ImageBuffer, imgFrame *frame.Frame) error {
imageSize := jxl.imageHeader.GetSize()
header := imgFrame.Header
frameStartY := int32(0)
if header.Bounds.Origin.X >= 0 {
frameStartY = header.Bounds.Origin.Y
}
frameStartX := int32(0)
if header.Bounds.Origin.X >= 0 {
frameStartX = header.Bounds.Origin.X
}
frameOffsetY := frameStartY - header.Bounds.Origin.Y
frameOffsetX := frameStartX - header.Bounds.Origin.X
lowerCorner := header.Bounds.ComputeLowerCorner()
frameHeight := util.Min(lowerCorner.Y, int32(imageSize.Height)) - frameStartY
frameWidth := util.Min(lowerCorner.X, int32(imageSize.Width)) - frameStartX
frameColours := imgFrame.GetColourChannelCount()
imageColours := jxl.imageHeader.GetColourChannelCount()
hasAlpha := jxl.imageHeader.HasAlpha()
frameBuffers := imgFrame.Buffer
for c := int32(0); c < int32(len(canvas)); c++ {
var frameC int32
if frameColours != imageColours {
if c == 0 {
frameC = 1
} else {
frameC = c + 2
}
} else {
frameC = c
}
frameBuffer := imgFrame.Buffer[frameC]
var info *frame.BlendingInfo
if frameC < int32(frameColours) {
info = imgFrame.Header.BlendingInfo
} else {
info = &imgFrame.Header.EcBlendingInfo[frameC-int32(frameColours)]
}
isAlpha := c >= int32(imageColours) && jxl.imageHeader.ExtraChannelInfo[c-int32(imageColours)].EcType == bundle.ALPHA
premult := hasAlpha && jxl.imageHeader.ExtraChannelInfo[info.AlphaChannel].AlphaAssociated
refBuffer := jxl.reference[info.Source]
err := jxl.convertCanvasWithDifferentBufferType(canvas, c, &frameBuffer, imageColours, frameC, frameColours)
if err != nil {
return err
}
if info.Mode == frame.BLEND_REPLACE || refBuffer == nil && info.Mode == frame.BLEND_ADD {
offY := frameStartY - header.Bounds.Origin.Y
offX := frameStartX - header.Bounds.Origin.X
if err := jxl.copyToCanvas(&canvas[c], util.Point{Y: frameStartY, X: frameStartX}, util.Point{X: offX, Y: offY},
util.Dimension{Width: uint32(frameWidth), Height: uint32(frameHeight)}, frameBuffer); err != nil {
return err
}
continue
}
// check if just the zero value. Think dimension being 0 should be ok.
if refBuffer[c].Width == 0 && refBuffer[c].Height == 0 {
refBuf, err := image2.NewImageBuffer(frameBuffer.BufferType, canvas[c].Height, canvas[c].Width)
if err != nil {
return err
}
refBuffer[c] = *refBuf
}
ref := refBuffer[c]
err2 := jxl.blendAlpha(canvas[c], hasAlpha, info, imageColours, refBuffer, frameBuffers)
if err2 != nil {
return err2
}
err3 := jxl.convertReferenceWithDifferentBufferType(canvas, &ref, &frameBuffer, info, c, imageColours, frameC, frameColours)
if err3 != nil {
return err3
}
err4 := jxl.performBlending(canvas, info, frameBuffer, c, ref, frameHeight, frameStartY, frameOffsetY, frameWidth, frameStartX, frameOffsetX, hasAlpha, refBuffer, imageColours, frameBuffers, frameColours, isAlpha, premult)
if err4 != nil {
return err4
}
}
return nil
}
// perform blending... was refactored to a separate function but the number of parameters
// is insane. Need to tidy this up... but will leave for now.
func (jxl *JXLCodestreamDecoder) performBlending(canvas []image2.ImageBuffer,
info *frame.BlendingInfo, frameBuffer image2.ImageBuffer,
canvasIdx int32, ref image2.ImageBuffer,
frameHeight int32, frameStartY int32,
frameOffsetY int32, frameWidth int32,
frameStartX int32, frameOffsetX int32,
hasAlpha bool,
refBuffer []image2.ImageBuffer,
imageColours int,
frameBuffers []image2.ImageBuffer,
frameColours int, isAlpha bool, premult bool) error {
var cf, rf, ff, oaf, naf [][]float32
if info.Mode != frame.BLEND_ADD || frameBuffer.IsFloat() {
cf = canvas[canvasIdx].FloatBuffer
rf = ref.FloatBuffer
ff = frameBuffer.FloatBuffer
} else {
cf = nil
rf = nil
ff = nil
}
switch info.Mode {
case frame.BLEND_ADD:
if frameBuffer.IsInt() {
ci := canvas[canvasIdx].IntBuffer
ri := ref.IntBuffer
fi := frameBuffer.IntBuffer
for y := int32(0); y < frameHeight; y++ {
cy := y + frameStartY
fy := y + frameOffsetY
for x := int32(0); x < frameWidth; x++ {
cx := x + frameStartX
fx := x + frameOffsetX
ci[cy][cx] = ri[cy][cx] + fi[fy][fx]
}
}
} else {
for y := int32(0); y < frameHeight; y++ {
cy := y + frameStartY
fy := y + frameOffsetY
for x := int32(0); x < frameWidth; x++ {
cx := x + frameStartX
fx := x + frameOffsetX
cf[cy][cx] = rf[cy][cx] + ff[fy][fx]
}
}
}
case frame.BLEND_MULT:
for y := int32(0); y < frameHeight; y++ {
cy := y + frameStartY
fy := y + frameOffsetY
for x := int32(0); x < frameWidth; x++ {
cx := x + frameStartX
fx := x + frameOffsetX
newSample := ff[fy][fx]
if info.Clamp {
if newSample < 0 {
newSample = 0
} else if newSample > 1 {
newSample = 1
}
}
cf[cy][cx] = newSample * rf[cy][cx]
}
}
case frame.BLEND_BLEND:
if hasAlpha {
oaf = refBuffer[imageColours+int(info.AlphaChannel)].FloatBuffer
naf = frameBuffers[frameColours+int(info.AlphaChannel)].FloatBuffer
} else {
oaf = nil
naf = nil
}
for y := int32(0); y < frameHeight; y++ {
cy := y + frameStartY
fy := y + frameOffsetY
for x := int32(0); x < frameWidth; x++ {
cx := x + frameStartX
fx := x + frameOffsetX
var oldAlpha float32
var newAlpha float32
if hasAlpha {
oldAlpha = oaf[cy][cx]
newAlpha = naf[fy][fx]
} else {
oldAlpha = 1.0
newAlpha = 1.0
}
if info.Clamp {
if newAlpha < 0 {
newAlpha = 0
} else if newAlpha > 1 {
newAlpha = 1
}
}
alpha := float32(1)
oldSample := rf[cy][cx]
newSample := ff[fy][fx]
if isAlpha || hasAlpha && !premult {
alpha = oldAlpha + newAlpha*(1-oldAlpha)
}
if isAlpha {
cf[cy][cx] = alpha
} else if !hasAlpha || premult {
cf[cy][cx] = newSample + oldSample*(1-newAlpha)
} else {
cf[cy][cx] = (newSample*newAlpha + oldSample*oldAlpha*(1-newAlpha)) / alpha
}
}
}
case frame.BLEND_MULADD:
if hasAlpha {
oaf = refBuffer[imageColours+int(info.AlphaChannel)].FloatBuffer
naf = frameBuffers[frameColours+int(info.AlphaChannel)].FloatBuffer
} else {
oaf = nil
naf = nil
}
for y := int32(0); y < frameHeight; y++ {
cy := y + frameStartY
fy := y + frameOffsetY
for x := int32(0); x < frameWidth; x++ {
cx := x + frameStartX
fx := x + frameOffsetX
var oldAlpha float32
var newAlpha float32
if hasAlpha {
oldAlpha = oaf[cy][cx]
newAlpha = naf[fy][fx]
} else {
oldAlpha = 1.0
newAlpha = 1.0
}
if info.Clamp {
if newAlpha < 0 {
newAlpha = 0
} else if newAlpha > 1 {
newAlpha = 1
}
}
oldSample := rf[cy][cx]
newSample := ff[fy][fx]
alpha := float32(0)
if isAlpha {
alpha = oldAlpha
} else {
alpha = oldSample + newAlpha*newSample
}
cf[cy][cx] = alpha
}
}
default:
return errors.New("Illegal blend Mode")
}
return nil
}
func (jxl *JXLCodestreamDecoder) convertReferenceWithDifferentBufferType(
canvas []image2.ImageBuffer,
ref *image2.ImageBuffer,
frameBuffer *image2.ImageBuffer,
info *frame.BlendingInfo,
canvasIdx int32,
imageColours int,
frameC int32,
frameColours int) error {
if ref.BufferType != frameBuffer.BufferType || info.Mode != frame.BLEND_ADD {
var depthCanvas int32
var depthFrame int32
if canvasIdx >= int32(imageColours) {
depthCanvas = int32(jxl.imageHeader.ExtraChannelInfo[canvasIdx-int32(imageColours)].BitDepth.BitsPerSample)
} else {
depthCanvas = int32(jxl.imageHeader.BitDepth.BitsPerSample)
}
if frameC >= int32(frameColours) {
depthFrame = int32(jxl.imageHeader.ExtraChannelInfo[frameC-int32(frameColours)].BitDepth.BitsPerSample)
} else {
depthFrame = int32(jxl.imageHeader.BitDepth.BitsPerSample)
}
if err := frameBuffer.CastToFloatIfMax(^(^0 << depthFrame)); err != nil {
return err
}
if err := canvas[canvasIdx].CastToFloatIfMax(^(^0 << depthCanvas)); err != nil {
return err
}
if err := ref.CastToFloatIfMax(^(^0 << depthCanvas)); err != nil {
return err
}
}
return nil
}
func (jxl *JXLCodestreamDecoder) blendAlpha(canvas image2.ImageBuffer, hasAlpha bool, info *frame.BlendingInfo, imageColours int, refBuffer []image2.ImageBuffer, frameBuffers []image2.ImageBuffer) error {
if hasAlpha && (info.Mode == frame.BLEND_BLEND || info.Mode == frame.BLEND_MULADD) {
depth := jxl.imageHeader.ExtraChannelInfo[info.AlphaChannel].BitDepth.BitsPerSample
alphaIdx := imageColours + int(info.AlphaChannel)
if refBuffer[alphaIdx].Width == 0 && refBuffer[alphaIdx].Height == 0 {
refBuf, err := image2.NewImageBuffer(image2.TYPE_FLOAT, canvas.Height, canvas.Width)
if err != nil {
return err
}
refBuffer[alphaIdx] = *refBuf
}
if !refBuffer[alphaIdx].IsFloat() {
if err := refBuffer[alphaIdx].CastToFloatIfMax(^(^0 << depth)); err != nil {
return err
}
}
if !frameBuffers[alphaIdx].IsFloat() {
if err := frameBuffers[alphaIdx].CastToFloatIfMax(^(^0 << depth)); err != nil {
return err
}
}
}
return nil
}
func (jxl *JXLCodestreamDecoder) convertCanvasWithDifferentBufferType(
canvas []image2.ImageBuffer,
channelNo int32,
frameBuffer *image2.ImageBuffer,
imageColours int,
frameChannelNo int32,
frameColours int) error {
if canvas[channelNo].BufferType != frameBuffer.BufferType {
var depthCanvas int32
if channelNo >= int32(imageColours) {
depthCanvas = int32(jxl.imageHeader.ExtraChannelInfo[channelNo-int32(imageColours)].BitDepth.BitsPerSample)
} else {
depthCanvas = int32(jxl.imageHeader.BitDepth.BitsPerSample)
}
var depthFrame int32
if frameChannelNo >= int32(frameColours) {
depthFrame = int32(jxl.imageHeader.ExtraChannelInfo[frameChannelNo-int32(frameColours)].BitDepth.BitsPerSample)
} else {
depthFrame = int32(jxl.imageHeader.BitDepth.BitsPerSample)
}
if err := frameBuffer.CastToFloatIfMax(^(^0 << depthFrame)); err != nil {
return err
}
if err := canvas[channelNo].CastToFloatIfMax(^(^0 << depthCanvas)); err != nil {
return err
}
}
return nil
}
// needs to handle int and float buffers...
func (jxl *JXLCodestreamDecoder) copyToCanvas(canvas *image2.ImageBuffer, start util.Point, off util.Point,
size util.Dimension, frameBuffer image2.ImageBuffer) error {
// if buffer type different for canvas and frame, then fail
if canvas.BufferType != frameBuffer.BufferType {
return errors.New("Buffer type mismatch")
}
if canvas.IsInt() {
for y := uint32(0); y < size.Height; y++ {
copy(canvas.IntBuffer[y+uint32(start.Y)][start.X:], frameBuffer.IntBuffer[y+uint32(off.Y)][off.X:uint32(off.X)+size.Width])
}
} else {
for y := uint32(0); y < size.Height; y++ {
copy(canvas.FloatBuffer[y+uint32(start.Y)][start.X:], frameBuffer.FloatBuffer[y+uint32(off.Y)][off.X:uint32(off.X)+size.Width])
}
}
return nil
}
func (jxl *JXLCodestreamDecoder) transposeBuffer(src image2.ImageBuffer, orientation uint32) (image2.ImageBuffer, error) {
if src.IsInt() {
ints, err := jxl.transposeBufferInt(src.IntBuffer, orientation)
if err != nil {
return image2.ImageBuffer{}, err
}
return *image2.NewImageBufferFromInts(ints), nil
} else {
floats, err := jxl.transposeBufferFloat(src.FloatBuffer, orientation)
if err != nil {
return image2.ImageBuffer{}, err
}
return *image2.NewImageBufferFromFloats(floats), nil
}
}
func (jxl *JXLCodestreamDecoder) transposeBufferInt(src [][]int32, orientation uint32) ([][]int32, error) {
srcHeight := len(src)
srcWidth := len(src[0])
srcH1 := srcHeight - 1
srcW1 := srcWidth - 1
var dest [][]int32
if orientation > 4 {
dest = util.MakeMatrix2D[int32](srcWidth, srcHeight)
} else if orientation > 1 {
dest = util.MakeMatrix2D[int32](srcHeight, srcWidth)
} else {
dest = nil
}
switch orientation {
case 1:
return src, nil
case 2:
for y := 0; y < srcHeight; y++ {
for x := 0; x < srcWidth; x++ {
dest[y][srcW1-x] = src[y][x]
}
}
return dest, nil
case 3:
for y := 0; y < srcHeight; y++ {
for x := 0; x < srcWidth; x++ {
dest[srcH1-y][srcW1-x] = src[y][x]
}
}
return dest, nil
case 4:
for y := 0; y < srcHeight; y++ {
copy(dest[srcH1-y], src[y])
}
return dest, nil
case 5:
for y := 0; y < srcHeight; y++ {
for x := 0; x < srcWidth; x++ {
dest[x][y] = src[y][x]
}
}
return dest, nil
case 6:
for y := 0; y < srcHeight; y++ {
for x := 0; x < srcWidth; x++ {
dest[x][srcH1-y] = src[y][x]
}
}
return dest, nil
case 7:
for y := 0; y < srcHeight; y++ {
for x := 0; x < srcWidth; x++ {
dest[srcW1-x][srcH1-y] = src[y][x]
}
}
return dest, nil
case 8:
for y := 0; y < srcHeight; y++ {
for x := 0; x < srcWidth; x++ {
dest[srcW1-x][y] = src[y][x]
}
}
return dest, nil
default:
return nil, errors.New("Invalid orientation")
}
}
func (jxl *JXLCodestreamDecoder) transposeBufferFloat(src [][]float32, orientation uint32) ([][]float32, error) {
srcHeight := len(src)
srcWidth := len(src[0])
srcH1 := srcHeight - 1
srcW1 := srcWidth - 1
var dest [][]float32
if orientation > 4 {
dest = util.MakeMatrix2D[float32](srcWidth, srcHeight)
} else if orientation > 1 {
dest = util.MakeMatrix2D[float32](srcHeight, srcWidth)
} else {
dest = nil
}
switch orientation {
case 1:
return src, nil
case 2:
for y := 0; y < srcHeight; y++ {
for x := 0; x < srcWidth; x++ {
dest[y][srcW1-x] = src[y][x]
}
}
return dest, nil
case 3:
for y := 0; y < srcHeight; y++ {
for x := 0; x < srcWidth; x++ {
dest[srcH1-y][srcW1-x] = src[y][x]
}
}
return dest, nil
case 4:
for y := 0; y < srcHeight; y++ {
copy(dest[srcH1-y], src[y])
}
return dest, nil
case 5:
for y := 0; y < srcHeight; y++ {
for x := 0; x < srcWidth; x++ {
dest[x][y] = src[y][x]
}
}
return dest, nil
case 6:
for y := 0; y < srcHeight; y++ {
for x := 0; x < srcWidth; x++ {
dest[x][srcH1-y] = src[y][x]
}
}
return dest, nil
case 7:
for y := 0; y < srcHeight; y++ {
for x := 0; x < srcWidth; x++ {
dest[srcW1-x][srcH1-y] = src[y][x]
}
}
return dest, nil
case 8:
for y := 0; y < srcHeight; y++ {
for x := 0; x < srcWidth; x++ {
dest[srcW1-x][y] = src[y][x]
}
}
return dest, nil
default:
return nil, errors.New("Invalid orientation")
}
}
package core
import (
"io"
"github.com/kpfaulkner/jxl-go/bundle"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/options"
"github.com/kpfaulkner/jxl-go/testcommon"
)
// JXLDecoder decodes the JXL image
type JXLDecoder struct {
// input Stream
in io.ReadSeeker
// decoder
decoder *JXLCodestreamDecoder
}
func NewJXLDecoder(in io.ReadSeeker, opts *options.JXLOptions) *JXLDecoder {
jxl := &JXLDecoder{
in: in,
}
realBR := jxlio.NewBitStreamReader(in)
br := testcommon.NewBitReaderRecorder(realBR)
// if nil options, then create one
if opts == nil {
opts = options.NewJXLOptions(nil)
}
jxl.decoder = NewJXLCodestreamDecoder(br, opts)
return jxl
}
func (jxl *JXLDecoder) Decode() (*JXLImage, error) {
jxlImage, err := jxl.decoder.decode()
if err != nil {
return nil, err
}
return jxlImage, nil
}
func (jxl *JXLDecoder) GetImageHeader() (*bundle.ImageHeader, error) {
header, err := jxl.decoder.GetImageHeader()
if err != nil {
return nil, err
}
return header, nil
}
package core
import (
"fmt"
"image"
"github.com/kpfaulkner/jxl-go/bundle"
"github.com/kpfaulkner/jxl-go/colour"
image2 "github.com/kpfaulkner/jxl-go/image"
"github.com/kpfaulkner/jxl-go/util"
)
const (
// unsure if will use these yet.
PEAK_DETECT_AUTO = -1
PEAK_DETECT_ON = 1
PEAK_DETECT_OFF = 2
)
// JXLImage contains the core information about the JXL image.
type JXLImage struct {
Buffer []image2.ImageBuffer
iccProfile []byte
bitDepths []uint32
primariesXY *colour.CIEPrimaries
whiteXY *colour.CIEXY
imageHeader bundle.ImageHeader
Width uint32
Height uint32
ColorEncoding int32
alphaIndex int32
primaries int32
whitePoint int32
transfer int32
taggedTransfer int32
alphaIsPremultiplied bool
}
// NewJXLImageWithBuffer creates a new JXLImage with the given buffer and header.
func NewJXLImageWithBuffer(buffer []image2.ImageBuffer, header bundle.ImageHeader) (*JXLImage, error) {
jxl := &JXLImage{}
jxl.imageHeader = header
jxl.Width = header.OrientedWidth
jxl.Height = header.OrientedHeight
jxl.Buffer = buffer
bundle := header.ColourEncoding
jxl.ColorEncoding = bundle.ColourEncoding
if header.HasAlpha() {
jxl.alphaIndex = header.AlphaIndices[0]
} else {
jxl.alphaIndex = -1
}
jxl.primaries = bundle.Primaries
jxl.whitePoint = bundle.WhitePoint
jxl.primariesXY = bundle.Prim
jxl.whiteXY = bundle.White
var err error
if jxl.imageHeader.XybEncoded {
jxl.transfer = colour.TF_LINEAR
jxl.iccProfile = nil
} else {
jxl.transfer = bundle.Tf
jxl.iccProfile, err = header.GetDecodedICC()
if err != nil {
return nil, err
}
}
jxl.taggedTransfer = bundle.Tf
jxl.alphaIsPremultiplied = jxl.imageHeader.HasAlpha() && jxl.imageHeader.ExtraChannelInfo[jxl.alphaIndex].AlphaAssociated
jxl.bitDepths = make([]uint32, len(buffer))
colours := util.IfThenElse(jxl.ColorEncoding == colour.CE_GRAY, 1, 3)
for c := 0; c < len(jxl.bitDepths); c++ {
if c < colours {
jxl.bitDepths[c] = header.BitDepth.BitsPerSample
} else {
jxl.bitDepths[c] = header.ExtraChannelInfo[c-colours].BitDepth.BitsPerSample
}
}
return jxl, nil
}
func NewJXLImageFromJXLImage(img *JXLImage, copyBuffer bool) (*JXLImage, error) {
jxl := &JXLImage{}
jxl.imageHeader = img.imageHeader
jxl.ColorEncoding = img.ColorEncoding
jxl.alphaIndex = img.alphaIndex
jxl.primaries = img.primaries
jxl.whitePoint = img.whitePoint
jxl.transfer = img.transfer
jxl.taggedTransfer = img.taggedTransfer
jxl.whiteXY = img.whiteXY
jxl.primariesXY = img.primariesXY
jxl.iccProfile = img.iccProfile
jxl.Width = img.Width
jxl.Height = img.Height
jxl.alphaIsPremultiplied = img.alphaIsPremultiplied
jxl.bitDepths = make([]uint32, len(img.bitDepths))
copy(jxl.bitDepths, img.bitDepths)
for _, ib := range jxl.Buffer {
buf := image2.NewImageBufferFromImageBuffer(&ib, copyBuffer)
jxl.Buffer = append(jxl.Buffer, *buf)
}
return jxl, nil
}
// GetFloatChannelData will return the floating point image data for a channel.
// The underlying image MAY not have any floating point data (this is all image dependant).
func (jxl *JXLImage) GetFloatChannelData(c int) ([][]float32, error) {
if c < 0 || c >= len(jxl.Buffer) {
return nil, fmt.Errorf("Invalid channel index %d", c)
}
return jxl.Buffer[c].FloatBuffer, nil
}
// SetFloatChannelData sets the floating point image data for a channel.
func (jxl *JXLImage) SetFloatChannelData(c int, data [][]float32) error {
if c < 0 || c >= len(jxl.Buffer) {
return fmt.Errorf("Invalid channel index %d", c)
}
jxl.Buffer[c].FloatBuffer = data
return nil
}
// GetIntChannelData will return the integer image data for a channel.
// The underlying image MAY not have any integer point data (this is all image dependant).
func (jxl *JXLImage) GetIntChannelData(c int) ([][]int32, error) {
if c < 0 || c >= len(jxl.Buffer) {
return nil, fmt.Errorf("Invalid channel index %d", c)
}
return jxl.Buffer[c].IntBuffer, nil
}
// SetIntChannelData sets the integer image data for a channel.
func (jxl *JXLImage) SetIntChannelData(c int, data [][]int32) error {
if c < 0 || c >= len(jxl.Buffer) {
return fmt.Errorf("Invalid channel index %d", c)
}
jxl.Buffer[c].IntBuffer = data
return nil
}
// GetExtraChannelType returns the type of the channel (Alpha, Depth, etc).
// Possible values are defined in ExtraChannelType.go
func (jxl *JXLImage) GetExtraChannelType(c int) (int32, error) {
if c < 0 || c >= len(jxl.imageHeader.ExtraChannelInfo) {
return -1, fmt.Errorf("Invalid channel index %d", c)
}
return jxl.imageHeader.ExtraChannelInfo[c].EcType, nil
}
// IsIntBased returns true if underlying data related to image is integer based.
func (jxl *JXLImage) IsIntBased() bool {
return jxl.Buffer[0].IsInt()
}
// IsFloatBased returns true if underlying data related to image is float based.
func (jxl *JXLImage) IsFloatBased() bool {
return jxl.Buffer[0].IsFloat()
}
func (jxl *JXLImage) HasAlpha() bool {
return jxl.imageHeader.HasAlpha()
}
func (jxl *JXLImage) HasICCProfile() bool {
return len(jxl.iccProfile) > 0
}
func (jxl *JXLImage) NumExtraChannels() int {
return len(jxl.imageHeader.ExtraChannelInfo)
}
// ChannelToImage converts a single channel to grayscale Go image.Image interface.
// Can be used for any channel (R,G,B, alpha, depth..... etc) but is really expected to be
// used for NON "regular" channels (ie depth etc)
func (jxl *JXLImage) ChannelToImage(channelNo int) (image.Image, error) {
buffer, err := jxl.getBuffer(true)
if err != nil {
return nil, err
}
if channelNo < 0 || channelNo >= len(buffer) {
return nil, fmt.Errorf("Invalid channel index %d", channelNo)
}
img := image.NewGray(image.Rect(0, 0, int(buffer[0].Width), int(buffer[0].Height)))
pix := img.Pix
dx := img.Bounds().Dx()
dy := img.Bounds().Dy()
pos := 0
if buffer[0].IsFloat() {
for y := 0; y < dy; y++ {
for x := 0; x < dx; x++ {
// Assumption of 8 bits per channel. Will do for now.
pix[pos] = uint8(buffer[channelNo].FloatBuffer[y][x] * 255)
pos++
}
}
} else {
for y := 0; y < dy; y++ {
for x := 0; x < dx; x++ {
pix[pos] = uint8(buffer[channelNo].IntBuffer[y][x])
pos++
}
}
}
return img, nil
}
// ToImage converts to standard Go image.Image NRGBA format for the R,G,B and alpha channels
func (jxl *JXLImage) ToImage() (image.Image, error) {
var bitDepth int32
if jxl.imageHeader.BitDepth.BitsPerSample > 8 {
bitDepth = 16
} else {
bitDepth = 8
}
//gray := jxlImage.ColourEncoding == color.CE_GRAY
primaries := colour.CM_PRI_SRGB
tf := colour.TF_SRGB
if jxl.isHDR() {
primaries = colour.CM_PRI_BT2100
tf = colour.TF_PQ
}
whitePoint := colour.CM_WP_D65
iccProfile := jxl.iccProfile
var err error
if iccProfile == nil {
// transforms in place
img, err := jxl.transform(primaries, whitePoint, tf, PEAK_DETECT_AUTO)
if err != nil {
return nil, err
}
jxl = img
}
maxValue := int32(^(^0 << bitDepth))
coerce := jxl.alphaIsPremultiplied
buffer, err := jxl.getBuffer(false)
if err != nil {
return nil, err
}
if !coerce {
for c := 0; c < len(buffer); c++ {
if buffer[c].IsInt() && jxl.bitDepths[c] != uint32(bitDepth) {
coerce = true
break
}
}
}
if coerce {
for c := 0; c < len(buffer); c++ {
if err := buffer[c].CastToFloatIfMax(^(^0 << jxl.bitDepths[c])); err != nil {
return nil, err
}
}
}
if jxl.alphaIsPremultiplied {
panic("not implemented")
}
for c := 0; c < len(buffer); c++ {
if buffer[c].IsInt() && jxl.bitDepths[c] == uint32(bitDepth) {
if err := buffer[c].Clamp(maxValue); err != nil {
return nil, err
}
} else {
if err := buffer[c].CastToIntIfMax(maxValue); err != nil {
return nil, err
}
}
}
var img image.Image
colourCount := jxl.imageHeader.GetColourChannelCount()
if colourCount == 1 {
img = jxl.createGrayScaleImage(buffer)
} else {
img = jxl.create24BitImage(buffer)
}
return img, nil
}
func (jxl *JXLImage) createGrayScaleImage(buffer []image2.ImageBuffer) image.Image {
img := image.NewGray(image.Rect(0, 0, int(buffer[0].Width), int(buffer[0].Height)))
pix := img.Pix
dx := img.Bounds().Dx()
dy := img.Bounds().Dy()
pos := 0
if buffer[0].IsFloat() {
for y := 0; y < dy; y++ {
for x := 0; x < dx; x++ {
// assumption of 8 bits per channel but only 1 channel (grayscale)
pix[pos] = uint8(buffer[0].FloatBuffer[y][x] * 255)
pos++
}
}
} else {
for y := 0; y < dy; y++ {
for x := 0; x < dx; x++ {
pix[pos] = uint8(buffer[0].IntBuffer[y][x])
pos++
}
}
}
return img
}
func (jxl *JXLImage) create24BitImage(buffer []image2.ImageBuffer) image.Image {
img := image.NewNRGBA(image.Rect(0, 0, int(buffer[0].Width), int(buffer[0].Height)))
pix := img.Pix
dx := img.Bounds().Dx()
dy := img.Bounds().Dy()
pos := 0
if buffer[0].IsFloat() {
for y := 0; y < dy; y++ {
for x := 0; x < dx; x++ {
// assumption of 8 bits per channel.
pix[pos] = uint8(buffer[0].FloatBuffer[y][x] * 255)
pos++
pix[pos] = uint8(buffer[1].FloatBuffer[y][x] * 255)
pos++
pix[pos] = uint8(buffer[2].FloatBuffer[y][x] * 255)
pos++
if jxl.imageHeader.HasAlpha() {
// FIXME(kpfaulkner) deal with alpha channels properly
pix[pos] = 255 // uint8(buffer[3].FloatBuffer[y][x] * 255)
pos++
} else {
pos++
}
}
}
} else {
for y := 0; y < dy; y++ {
for x := 0; x < dx; x++ {
pix[pos] = uint8(buffer[0].IntBuffer[y][x])
pos++
pix[pos] = uint8(buffer[1].IntBuffer[y][x])
pos++
pix[pos] = uint8(buffer[2].IntBuffer[y][x])
pos++
if jxl.imageHeader.HasAlpha() {
pix[pos] = uint8(buffer[3].IntBuffer[y][x])
pos++
} else {
pix[pos] = 255
pos++
}
}
}
}
return img
}
func (jxl *JXLImage) isHDR() bool {
if jxl.taggedTransfer == colour.TF_PQ || jxl.taggedTransfer == colour.TF_HLG ||
jxl.taggedTransfer == colour.TF_LINEAR {
return true
}
col := jxl.imageHeader.ColourEncoding
return !col.Prim.Matches(colour.CM_PRI_SRGB) &&
!col.Prim.Matches(colour.CM_PRI_P3)
}
func (jxl *JXLImage) transform(primaries *colour.CIEPrimaries, whitePoint *colour.CIEXY, transfer int32, peakDetect int32) (*JXLImage, error) {
if primaries.Matches(jxl.primariesXY) && whitePoint.Matches(jxl.whiteXY) {
return jxl.transferImage(transfer, peakDetect)
}
var img *JXLImage
var err error
if img, err = jxl.linearize(); err != nil {
return nil, err
}
if err := img.toneMapLinear(); err != nil {
return nil, err
}
if img, err = img.transferImage(transfer, peakDetect); err != nil {
return nil, err
}
return img, nil
}
// getBuffer gets a copy of the buffer... making a copy if required
// REALLY not optimised but will fix later.
func (jxl *JXLImage) getBuffer(makeCopy bool) ([]image2.ImageBuffer, error) {
if !makeCopy {
return jxl.Buffer, nil
}
buffer := make([]image2.ImageBuffer, len(jxl.Buffer))
for c := 0; c < len(jxl.Buffer); c++ {
buf, err := image2.NewImageBuffer(jxl.Buffer[c].BufferType, jxl.Buffer[c].Height, jxl.Buffer[c].Width)
if err != nil {
return nil, err
}
buffer[c] = *buf
// very stupid... will optimise later TODO(kpfaulkner)
if buffer[c].IsInt() {
for y := 0; y < int(jxl.Buffer[c].Height); y++ {
for x := 0; x < int(jxl.Buffer[c].Width); x++ {
buffer[c].IntBuffer[y][x] = jxl.Buffer[c].IntBuffer[y][x]
}
}
} else {
for y := 0; y < int(jxl.Buffer[c].Height); y++ {
for x := 0; x < int(jxl.Buffer[c].Width); x++ {
buffer[c].FloatBuffer[y][x] = jxl.Buffer[c].FloatBuffer[y][x]
}
}
}
}
return buffer, nil
}
func (jxl *JXLImage) determinePeak() (float32, error) {
if jxl.transfer != colour.TF_LINEAR {
lin, err := jxl.linearize()
if err != nil {
return 0, err
}
peak, err := lin.determinePeak()
return peak, err
}
c := 0
if jxl.ColorEncoding != colour.CE_GRAY {
c = 1
}
if jxl.Buffer[c].IsInt() {
maxValue := float32(jxl.Buffer[c].MaxInt())
return maxValue / float32(^(^int(0) << jxl.bitDepths[c])), nil
} else {
maxValue := jxl.Buffer[c].MaxFloat()
return maxValue, nil
}
}
func (jxl *JXLImage) transferWithOp(f func(v float32) float32) (*JXLImage, error) {
colours := util.IfThenElse(jxl.ColorEncoding == colour.CE_GRAY, 1, 3)
buffers := util.MakeMatrix3D[float32](colours, 0, 0)
for c := 0; c < colours; c++ {
if err := jxl.Buffer[c].CastToFloatIfMax(^(^0 << jxl.bitDepths[c])); err != nil {
return nil, err
}
buffers[c] = jxl.Buffer[c].FloatBuffer
}
img, err := NewJXLImageFromJXLImage(jxl, false)
if err != nil {
return nil, err
}
for c := 0; c < colours; c++ {
b := img.Buffer[c].FloatBuffer
for y := 0; y < int(jxl.Height); y++ {
for x := 0; x < int(jxl.Width); x++ {
b[y][x] = f(buffers[c][y][x])
}
}
}
return img, nil
}
func (jxl *JXLImage) transferImage(transfer int32, peakDetect int32) (*JXLImage, error) {
if transfer == jxl.transfer {
return jxl, nil
}
var err error
var img *JXLImage
if img, err = jxl.linearize(); err != nil {
return nil, err
}
if img.taggedTransfer == colour.TF_PQ &&
(peakDetect == PEAK_DETECT_AUTO || peakDetect == PEAK_DETECT_ON) {
toPQ := transfer == colour.TF_PQ || transfer == colour.TF_LINEAR
fromPQ := img.transfer == colour.TF_PQ || img.transfer == colour.TF_LINEAR
if fromPQ && !toPQ {
peak, err := img.determinePeak()
if err != nil {
return nil, err
}
scale := 1.0 / peak
if scale > 1.0 || peakDetect == PEAK_DETECT_ON {
img, err = img.transferWithOp(func(v float32) float32 { return v * scale })
if err != nil {
return nil, err
}
}
}
}
transferFunction, err := colour.GetTransferFunction(transfer)
if err != nil {
return nil, err
}
if err := img.transferInPlace(transferFunction.FromLinear); err != nil {
return nil, err
}
return img, nil
}
func (jxl *JXLImage) linearize() (*JXLImage, error) {
if jxl.transfer == colour.TF_LINEAR {
return jxl, nil
}
panic("not implemented")
}
func (jxl *JXLImage) toneMapLinear() error {
return nil
}
func (jxl *JXLImage) transferInPlace(transferFunction func(float64) float64) error {
colours := 3
if jxl.ColorEncoding == colour.CE_GRAY {
colours = 1
}
buffers := util.MakeMatrix3D[float32](colours, 0, 0)
for c := 0; c < colours; c++ {
if err := jxl.Buffer[c].CastToFloatIfMax(int32(jxl.bitDepths[c])); err != nil {
return err
}
buffers[c] = jxl.Buffer[c].FloatBuffer
}
for c := 0; c < colours; c++ {
for y := 0; y < int(jxl.Height); y++ {
for x := 0; x < int(jxl.Width); x++ {
buffers[c][y][x] = float32(transferFunction(float64(buffers[c][y][x])))
}
}
}
return nil
}
package core
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"github.com/kpfaulkner/jxl-go/colour"
image2 "github.com/kpfaulkner/jxl-go/image"
)
func WritePFM(jxlImage *JXLImage, output io.Writer) error {
gray := jxlImage.ColorEncoding == colour.CE_GRAY
width := jxlImage.Width
height := jxlImage.Height
pf := "Pf"
if !gray {
pf = "PF"
}
header := fmt.Sprintf("%s\n%d %d\n1.0\n", pf, width, height)
_, err := output.Write([]byte(header))
if err != nil {
return err
}
cCount := 1
if !gray {
cCount = 3
}
buffer2, err := jxlImage.getBuffer(false)
if err != nil {
return err
}
nb := make([]image2.ImageBuffer, len(buffer2))
for c := 0; c < len(nb); c++ {
if buffer2[c].IsInt() {
panic("not implemented")
} else {
nb[c] = buffer2[c]
}
}
if gray {
cCount = 1
}
var buf bytes.Buffer
for y := int32(height - 1); y >= 0; y-- {
for x := int32(0); x < int32(width); x++ {
for c := 0; c < cCount; c++ {
err := binary.Write(&buf, binary.BigEndian, nb[c].FloatBuffer[y][x])
if err != nil {
fmt.Println("binary.Write failed:", err)
return err
}
}
}
}
_, err = output.Write(buf.Bytes())
if err != nil {
return err
}
return nil
}
package core
import (
"bytes"
"compress/zlib"
"encoding/binary"
"hash/crc32"
"io"
"github.com/kpfaulkner/jxl-go/colour"
)
type PNGWriter struct {
bitDepth int32
colourMode byte
width uint32
height uint32
alphaIndex int32
hdr bool
writeSRGBICC bool
}
// WritePNG instead of using standard golang image/png package since we need
// to write out ICC Profile which doesn't seem to be supported by the standard package.
func (w *PNGWriter) WritePNG(jxlImage *JXLImage, output io.Writer) error {
w.hdr = jxlImage.isHDR()
var bitDepth int32
if w.hdr {
bitDepth = 16
} else {
if jxlImage.imageHeader.BitDepth.BitsPerSample > 8 {
bitDepth = 16
} else {
bitDepth = 8
}
}
w.bitDepth = bitDepth
gray := jxlImage.ColorEncoding == colour.CE_GRAY
primaries := colour.CM_PRI_SRGB
tf := colour.TF_SRGB
if jxlImage.isHDR() {
primaries = colour.CM_PRI_BT2100
tf = colour.TF_PQ
}
whitePoint := colour.CM_WP_D65
if jxlImage.iccProfile == nil {
// transforms in place
img, err := jxlImage.transform(primaries, whitePoint, tf, PEAK_DETECT_AUTO)
if err != nil {
return err
}
jxlImage = img
}
maxValue := int32(^(^0 << bitDepth))
w.width = jxlImage.Width
w.height = jxlImage.Height
w.alphaIndex = jxlImage.alphaIndex
var colourMode byte
if gray {
if jxlImage.alphaIndex >= 0 {
colourMode = 4
} else {
colourMode = 0
}
} else {
if jxlImage.alphaIndex >= 0 {
colourMode = 6
} else {
colourMode = 2
}
}
w.colourMode = colourMode
coerce := jxlImage.alphaIsPremultiplied
buffer, err := jxlImage.getBuffer(false)
if err != nil {
return err
}
if !coerce {
for c := 0; c < len(buffer); c++ {
if buffer[c].IsInt() && jxlImage.bitDepths[c] != uint32(bitDepth) {
coerce = true
break
}
}
}
if coerce {
for c := 0; c < len(buffer); c++ {
if err := buffer[c].CastToFloatIfMax(^(^0 << jxlImage.bitDepths[c])); err != nil {
return err
}
}
}
if jxlImage.alphaIsPremultiplied {
panic("not implemented")
}
for c := 0; c < len(buffer); c++ {
if buffer[c].IsInt() && jxlImage.bitDepths[c] == uint32(bitDepth) {
if err := buffer[c].Clamp(maxValue); err != nil {
return err
}
} else {
if err := buffer[c].CastToIntIfMax(maxValue); err != nil {
return err
}
}
}
//newImg := jxlImage.create24BitImage(buffer)
//buf := new(bytes.Buffer)
//if err := png.Encode(buf, newImg); err != nil {
// panic(err)
//}
//
//pngFileName := `c:\temp\test-image.png`
//err = os.WriteFile(pngFileName, buf.Bytes(), 0666)
//if err != nil {
// panic(err)
//}
//return nil
// PNG header
header := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
_, err = output.Write(header)
if err != nil {
return err
}
if err := w.writeIHDR(jxlImage, output); err != nil {
return err
}
if w.hdr || len(jxlImage.iccProfile) != 0 || w.writeSRGBICC {
if err := w.writeICCP(jxlImage, output); err != nil {
return err
}
} else {
// if we have an ICC profile then write it out
if err := w.writeSRGB(jxlImage, output); err != nil {
return err
}
}
if err := w.writeIDAT(jxlImage, output); err != nil {
return err
}
_, err = output.Write([]byte{0, 0, 0, 0})
if err != nil {
return err
}
_, err = output.Write([]byte{0x49, 0x45, 0x4E, 0x44})
if err != nil {
return err
}
_, err = output.Write([]byte{0xAE, 0x42, 0x60, 0x82})
if err != nil {
return err
}
return nil
}
func (w *PNGWriter) writeICCP(image *JXLImage, output io.Writer) error {
var buf bytes.Buffer
buf.Write([]byte{0x69, 0x43, 0x43, 0x50})
buf.Write([]byte("jxlatte"))
buf.WriteByte(0x00)
buf.WriteByte(0x00)
var compressedICC bytes.Buffer
wr, err := zlib.NewWriterLevel(&compressedICC, 1)
if err != nil {
return err
}
if _, err = wr.Write(image.iccProfile); err != nil {
return err
}
if err = wr.Flush(); err != nil {
return err
}
if err = wr.Close(); err != nil {
return err
}
b := compressedICC.Bytes()
buf.Write(b)
rawBytes := buf.Bytes()
buf2 := make([]byte, 4)
binary.BigEndian.PutUint32(buf2, uint32(len(rawBytes))-4)
if _, err = output.Write(buf2); err != nil {
return err
}
if _, err = output.Write(rawBytes); err != nil {
return err
}
checksum := crc32.ChecksumIEEE(rawBytes)
binary.BigEndian.PutUint32(buf2, checksum)
if _, err = output.Write(buf2); err != nil {
return err
}
return nil
}
func (w *PNGWriter) writeSRGB(image *JXLImage, output io.Writer) error {
if w.hdr {
return nil
}
var buf bytes.Buffer
//output.Write([]byte{0x69, 0x43, 0x43, 0x50})
if _, err := buf.Write([]byte{0x00, 0x00, 0x00, 0x01}); err != nil {
return err
}
// using jxlatte just to compare files
if _, err := buf.Write([]byte{0x73, 0x52, 0x47, 0x42}); err != nil {
return err
}
if err := buf.WriteByte(0x01); err != nil {
return err
}
if _, err := buf.Write([]byte{0xD9, 0xC9, 0x2C, 0x7F}); err != nil {
return err
}
rawBytes := buf.Bytes()
//buf2 := make([]byte, 4)
//binary.BigEndian.PutUint32(buf2, uint32(len(rawBytes))-4)
//output.Write(buf2)
if _, err := output.Write(rawBytes); err != nil {
return err
}
return nil
}
func (w *PNGWriter) writeIHDR(jxlImage *JXLImage, output io.Writer) error {
ihdr := make([]byte, 17)
copy(ihdr[:4], []byte{'I', 'H', 'D', 'R'})
binary.BigEndian.PutUint32(ihdr[4:], jxlImage.Width)
binary.BigEndian.PutUint32(ihdr[8:], jxlImage.Height)
ihdr[12] = byte(w.bitDepth)
ihdr[13] = w.colourMode
ihdr[14] = 0
ihdr[15] = 0
ihdr[16] = 0
sizeBytes := make([]byte, 4)
binary.BigEndian.PutUint32(sizeBytes, uint32(len(ihdr)-4))
if _, err := output.Write(sizeBytes); err != nil {
return err
}
if _, err := output.Write(ihdr); err != nil {
return err
}
checksum := crc32.ChecksumIEEE(ihdr)
checkSumBytes := make([]byte, 4)
binary.BigEndian.PutUint32(checkSumBytes, checksum)
if _, err := output.Write(checkSumBytes); err != nil {
return err
}
return nil
}
func (w *PNGWriter) writeIDAT(jxlImage *JXLImage, output io.Writer) error {
var buf bytes.Buffer
buf.Write([]byte("IDAT"))
var compressedBytes bytes.Buffer
wr, err := zlib.NewWriterLevel(&compressedBytes, zlib.NoCompression)
if err != nil {
return err
}
var bitDepth int32
if jxlImage.imageHeader.BitDepth.BitsPerSample > 8 {
bitDepth = 16
} else {
bitDepth = 8
}
maxValue := int32(^(^0 << bitDepth))
if err = jxlImage.Buffer[0].CastToIntIfMax(maxValue); err != nil {
return err
}
if len(jxlImage.Buffer) > 1 {
if err = jxlImage.Buffer[1].CastToIntIfMax(maxValue); err != nil {
return err
}
}
if len(jxlImage.Buffer) > 2 {
if err = jxlImage.Buffer[2].CastToIntIfMax(maxValue); err != nil {
return err
}
}
if jxlImage.HasAlpha() {
if err = jxlImage.Buffer[3].CastToIntIfMax(maxValue); err != nil {
return err
}
}
for c := 0; c < len(jxlImage.Buffer); c++ {
if jxlImage.Buffer[c].IsInt() && jxlImage.bitDepths[c] == uint32(bitDepth) {
if err := jxlImage.Buffer[c].Clamp(maxValue); err != nil {
return err
}
} else {
if err := jxlImage.Buffer[c].CastToIntIfMax(maxValue); err != nil {
return err
}
}
}
for y := uint32(0); y < jxlImage.Height; y++ {
if _, err := wr.Write([]byte{0}); err != nil {
return err
}
for x := uint32(0); x < jxlImage.Width; x++ {
// FIXME(kpfaulkner) remove 3 assumption
for c := 0; c < jxlImage.imageHeader.GetColourChannelCount(); c++ {
dat := jxlImage.Buffer[c].IntBuffer[y][x]
if jxlImage.bitDepths[c] == 8 {
if _, err := wr.Write([]byte{byte(dat)}); err != nil {
return err
}
} else {
byte1 := dat & 0xFF
byte2 := dat & 0xFF00
byte2 >>= 8
if _, err := wr.Write([]byte{byte(byte2), byte(byte1)}); err != nil {
return err
}
}
}
if jxlImage.HasAlpha() {
dat := jxlImage.Buffer[3].IntBuffer[y][x]
if jxlImage.bitDepths[3] == 8 {
if _, err := wr.Write([]byte{byte(dat)}); err != nil {
return err
}
} else {
byte1 := dat & 0xFF
byte2 := dat & 0xFF00
byte2 >>= 8
if _, err := wr.Write([]byte{byte(byte2), byte(byte1)}); err != nil {
return err
}
}
}
}
}
wr.Close()
if _, err := buf.Write(compressedBytes.Bytes()); err != nil {
return err
}
bb := buf.Bytes()
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, uint32(len(bb))-4)
if _, err := output.Write(b); err != nil {
return err
}
if _, err := output.Write(bb); err != nil {
return err
}
checksum := crc32.ChecksumIEEE(bb)
binary.BigEndian.PutUint32(b, checksum)
if _, err := output.Write(b); err != nil {
return err
}
return nil
}
package core
import (
"bytes"
"errors"
"io"
"github.com/kpfaulkner/jxl-go/jxlio"
)
var (
JPEGXL_CONTAINER_HEADER = [12]byte{0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A}
JXLL = makeTag([]byte{'j', 'x', 'l', 'l'}, 0, 4)
JXLP = makeTag([]byte{'j', 'x', 'l', 'p'}, 0, 4)
JXLC = makeTag([]byte{'j', 'x', 'l', 'c'}, 0, 4)
)
type ContainerBoxHeader struct {
BoxType uint64
BoxSize uint64
IsLast bool
Offset int64 // offset compared to very beginning of file.
Processed bool // indicated if finished with.
}
type BoxReader struct {
reader jxlio.BitReader
level int
}
func NewBoxReader(reader jxlio.BitReader) *BoxReader {
return &BoxReader{
reader: reader,
level: 5,
}
}
func (br *BoxReader) ReadBoxHeader() ([]ContainerBoxHeader, error) {
buffer := make([]byte, 12)
err := br.reader.ReadByteArrayWithOffsetAndLength(buffer, 0, 12)
if err != nil {
return nil, err
}
var containerBoxHeaders []ContainerBoxHeader
// Believe this header is used when performing lossless decoding. Need to verify
if !bytes.Equal(buffer, JPEGXL_CONTAINER_HEADER[:]) {
//log.Errorf("invalid magic number: %+v", buffer)
// setup fake box header (if we dont have a container...?)
bh := ContainerBoxHeader{
BoxType: JXLC,
BoxSize: 0,
IsLast: true,
Offset: 0,
}
containerBoxHeaders = append(containerBoxHeaders, bh)
// reset reader to beginning of data... as we've read the first 12 bytes.
//br.reader.Reset()
return containerBoxHeaders, nil
}
if containerBoxHeaders, err = br.readAllBoxes(); err != nil {
return nil, err
}
return containerBoxHeaders, nil
}
func (br *BoxReader) readAllBoxes() ([]ContainerBoxHeader, error) {
var boxHeaders []ContainerBoxHeader
//boxSizeArray := make([]byte, 4)
boxSizeArray := make([]byte, 8)
boxTag := make([]byte, 4)
for {
err := br.reader.ReadBytesToBuffer(boxSizeArray, 4)
if err != nil {
if err == io.EOF {
// simple end of file... return with boxHeaders
return boxHeaders, nil
}
return nil, err
}
boxSize := makeTag(boxSizeArray, 0, 4)
if boxSize == 1 {
err = br.reader.ReadBytesToBuffer(boxSizeArray, 8)
if err != nil {
return nil, err
}
boxSize = makeTag(boxSizeArray, 0, 8)
if boxSize > 0 {
boxSize -= 8
}
}
if boxSize > 0 {
boxSize -= 8
}
err = br.reader.ReadBytesToBuffer(boxTag, 4)
if err != nil {
return nil, err
}
tag := makeTag(boxTag, 0, 4)
// check boxType... if we dont know the box type, just skip over the bytes and keep reading.
switch tag {
case JXLP:
// reads next 4 bytes as additional tag?
err = br.reader.ReadBytesToBuffer(boxTag, 4)
if err != nil {
return nil, err
}
boxSize -= 4
// fileoffset... directly from ReadSeeker?
pos, err := br.reader.Seek(0, io.SeekCurrent)
if err != nil {
return nil, err
}
bh := ContainerBoxHeader{
BoxType: tag,
BoxSize: boxSize,
IsLast: false,
Offset: pos,
Processed: false,
}
boxHeaders = append(boxHeaders, bh)
// skip past this box.
_, err = br.SkipFully(int64(boxSize))
if err != nil {
return nil, err
}
case JXLL:
if boxSize != 1 {
return nil, errors.New("JXLL box size should be 1")
}
l, err := br.reader.ReadByte()
if err != nil {
return nil, err
}
if l != 5 && l != 10 {
return nil, errors.New("invalid level")
}
br.level = int(l)
case JXLC:
// fileoffset... directly from ReadSeeker?
pos, err := br.reader.Seek(0, io.SeekCurrent)
if err != nil {
return nil, err
}
bh := ContainerBoxHeader{
BoxType: tag,
BoxSize: boxSize,
IsLast: false,
Offset: pos,
Processed: false,
}
boxHeaders = append(boxHeaders, bh)
// skip past this box.
_, err = br.SkipFully(int64(boxSize))
if err != nil {
return nil, err
}
default:
// skip over the bytes
if boxSize > 0 {
s, err := br.SkipFully(int64(boxSize))
if err != nil {
return nil, err
}
if s != 0 {
return nil, errors.New("truncated extra box")
}
} else {
panic("java read supplyExceptionally... unsure why?")
}
}
}
}
// returns number of bytes that were NOT skipped.
func (br *BoxReader) SkipFully(i int64) (int64, error) {
n, err := br.reader.Skip(uint32(i))
return i - n, err
}
func makeTag(bytes []uint8, offset int, length int) uint64 {
tag := uint64(0)
for i := offset; i < offset+length; i++ {
tag = (tag << 8) | uint64(bytes[i])&0xFF
}
return tag
}
package entropy
type ANSState struct {
State int32
HasState bool
}
func NewANSState() *ANSState {
s := &ANSState{}
return s
}
func (s *ANSState) SetState(state int32) {
s.State = state
s.HasState = true
}
package entropy
import (
"errors"
"fmt"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
var distPrefixTable = NewVLCTable(7, [][]int32{{10, 3}, {12, 7}, {7, 3}, {3, 4}, {6, 3}, {8, 3}, {9, 3}, {5, 4}, {10, 3}, {4, 4}, {7, 3}, {1, 4}, {6, 3}, {8, 3}, {9, 3}, {2, 4}, {10, 3}, {0, 5}, {7, 3}, {3, 4}, {6, 3}, {8, 3}, {9, 3}, {5, 4}, {10, 3}, {4, 4}, {7, 3}, {1, 4}, {6, 3}, {8, 3}, {9, 3}, {2, 4}, {10, 3}, {11, 6}, {7, 3}, {3, 4}, {6, 3}, {8, 3}, {9, 3}, {5, 4}, {10, 3}, {4, 4}, {7, 3}, {1, 4}, {6, 3}, {8, 3}, {9, 3}, {2, 4}, {10, 3}, {0, 5}, {7, 3}, {3, 4}, {6, 3}, {8, 3}, {9, 3}, {5, 4}, {10, 3}, {4, 4}, {7, 3}, {1, 4}, {6, 3}, {8, 3}, {9, 3}, {2, 4}, {10, 3}, {13, 7}, {7, 3}, {3, 4}, {6, 3}, {8, 3}, {9, 3}, {5, 4}, {10, 3}, {4, 4}, {7, 3}, {1, 4}, {6, 3}, {8, 3}, {9, 3}, {2, 4}, {10, 3}, {0, 5}, {7, 3}, {3, 4}, {6, 3}, {8, 3}, {9, 3}, {5, 4}, {10, 3}, {4, 4}, {7, 3}, {1, 4}, {6, 3}, {8, 3}, {9, 3}, {2, 4}, {10, 3}, {11, 6}, {7, 3}, {3, 4}, {6, 3}, {8, 3}, {9, 3}, {5, 4}, {10, 3}, {4, 4}, {7, 3}, {1, 4}, {6, 3}, {8, 3}, {9, 3}, {2, 4}, {10, 3}, {0, 5}, {7, 3}, {3, 4}, {6, 3}, {8, 3}, {9, 3}, {5, 4}, {10, 3}, {4, 4}, {7, 3}, {1, 4}, {6, 3}, {8, 3}, {9, 3}, {2, 4}})
type ANSSymbolDistribution struct {
SymbolDistributionBase
frequencies []int32
cutoffs []int32
symbols []int32
offsets []int32
}
func NewANSSymbolDistribution(reader jxlio.BitReader, logAlphabetSize int32) (*ANSSymbolDistribution, error) {
asd := &ANSSymbolDistribution{}
asd.logAlphabetSize = logAlphabetSize
uniqPos := int32(-1)
var simpleDistribution bool
var err error
if simpleDistribution, err = reader.ReadBool(); err != nil {
return nil, err
}
if simpleDistribution {
var dist1 bool
var err error
if dist1, err = reader.ReadBool(); err != nil {
return nil, err
}
if dist1 {
v1, err := reader.ReadU8()
if err != nil {
return nil, err
}
v2, err := reader.ReadU8()
if err != nil {
return nil, err
}
if v1 == v2 {
return nil, errors.New("Overlapping dual peak distribution")
}
asd.alphabetSize = 1 + util.Max[int32](int32(v1), int32(v2))
if asd.alphabetSize > (1 << asd.logAlphabetSize) {
return nil, fmt.Errorf("Illegal Alphabet size : %d", asd.alphabetSize)
}
asd.frequencies = make([]int32, asd.alphabetSize)
if freq, err := reader.ReadBits(12); err != nil {
return nil, err
} else {
asd.frequencies[v1] = int32(freq)
}
asd.frequencies[v2] = 1<<12 - asd.frequencies[v1]
if asd.frequencies[v1] == 0 {
uniqPos = int32(v2)
}
} else {
x, err := reader.ReadU8()
if err != nil {
return nil, err
}
asd.alphabetSize = 1 + int32(x)
asd.frequencies = make([]int32, asd.alphabetSize)
asd.frequencies[x] = 1 << 12
uniqPos = int32(x)
}
} else {
// flat distribution
var flat bool
if flat, err = reader.ReadBool(); err != nil {
return nil, err
}
if flat {
r, err := reader.ReadU8()
if err != nil {
return nil, err
}
asd.alphabetSize = 1 + int32(r)
if asd.alphabetSize > (1 << asd.logAlphabetSize) {
return nil, fmt.Errorf("Illegal Alphabet size : %d", asd.alphabetSize)
}
if asd.alphabetSize == 1 {
uniqPos = 0
}
asd.frequencies = make([]int32, asd.alphabetSize)
for i := int32(0); i < asd.alphabetSize; i++ {
asd.frequencies[i] = (1 << 12) / asd.alphabetSize
}
for i := int32(0); i < (1<<12)%asd.alphabetSize; i++ {
asd.frequencies[i]++
}
} else {
var l int
var err error
var b bool
for l = 0; l < 3; l++ {
if b, err = reader.ReadBool(); err != nil {
return nil, err
}
if !b {
break
}
}
var shift uint64
if shift, err = reader.ReadBits(uint32(l)); err != nil {
return nil, err
} else {
shift = (shift | 1<<l) - 1
}
if shift > 13 {
return nil, errors.New("Shift > 13")
}
r, err := reader.ReadU8()
if err != nil {
return nil, err
}
asd.alphabetSize = 3 + int32(r)
if asd.alphabetSize > (1 << asd.logAlphabetSize) {
return nil, fmt.Errorf("Illegal Alphabet size : %d", asd.alphabetSize)
}
asd.frequencies = make([]int32, asd.alphabetSize)
logCounts := make([]int32, asd.alphabetSize)
same := make([]int, asd.alphabetSize)
omitLog := int32(-1)
omitPos := int32(-1)
for i := int32(0); i < asd.alphabetSize; i++ {
logCounts[i], err = distPrefixTable.GetVLC(reader)
if err != nil {
return nil, err
}
if logCounts[i] == 13 {
rle, err := reader.ReadU8()
if err != nil {
return nil, err
}
same[i] = rle + 5
i += int32(rle) + 3
continue
}
if logCounts[i] > omitLog {
omitLog = logCounts[i]
omitPos = i
}
}
if omitPos < 0 || omitPos+1 < asd.alphabetSize && logCounts[omitPos+1] == 13 {
return nil, errors.New("Invalid OmitPos")
}
totalCount := int32(0)
numSame := 0
prev := int32(0)
for i := int32(0); i < asd.alphabetSize; i++ {
if same[i] != 0 {
numSame = same[i] - 1
if i > 0 {
prev = asd.frequencies[i-1]
} else {
prev = 0
}
}
if numSame != 0 {
asd.frequencies[i] = prev
numSame--
} else {
if i == omitPos || logCounts[i] == 0 {
continue
}
if logCounts[i] == 1 {
asd.frequencies[i] = 1
} else {
bitcount := int32(shift) - int32(uint32(12-logCounts[i]+1)>>1)
if bitcount < 0 {
bitcount = 0
}
if bitcount > int32(logCounts[i])-1 {
bitcount = int32(logCounts[i] - 1)
}
if freq, err := reader.ReadBits(uint32(bitcount)); err != nil {
return nil, err
} else {
asd.frequencies[i] = int32(1<<(logCounts[i]-1) + int32(freq)<<(logCounts[i]-1-bitcount))
}
}
}
totalCount += asd.frequencies[i]
}
asd.frequencies[omitPos] = (1 << 12) - totalCount
}
}
asd.generateAliasMapping(uniqPos)
return asd, nil
}
func (asd *ANSSymbolDistribution) generateAliasMapping(uniqPos int32) {
asd.logBucketSize = 12 - asd.logAlphabetSize
bucketSize := int32(1 << asd.logBucketSize)
tableSize := int32(1 << asd.logAlphabetSize)
overfull := util.NewDeque[int32]()
underfull := util.NewDeque[int32]()
asd.symbols = make([]int32, tableSize)
asd.cutoffs = make([]int32, tableSize)
asd.offsets = make([]int32, tableSize)
if uniqPos >= 0 {
for i := int32(0); i < tableSize; i++ {
asd.symbols[i] = uniqPos
asd.offsets[i] = i * bucketSize
asd.cutoffs[i] = 0
}
return
}
for i := int32(0); i < asd.alphabetSize; i++ {
asd.cutoffs[i] = asd.frequencies[i]
if asd.cutoffs[i] > bucketSize {
overfull.AddFirst(i)
} else if asd.cutoffs[i] < bucketSize {
underfull.AddFirst(i)
}
}
for i := asd.alphabetSize; i < tableSize; i++ {
underfull.AddFirst(i)
}
for !overfull.IsEmpty() {
u := underfull.RemoveFirst()
o := overfull.RemoveFirst()
by := bucketSize - asd.cutoffs[*u]
asd.cutoffs[*o] -= by
asd.symbols[*u] = *o
asd.offsets[*u] = asd.cutoffs[*o]
if asd.cutoffs[*o] < bucketSize {
underfull.AddFirst(*o)
} else if asd.cutoffs[*o] > bucketSize {
overfull.AddFirst(*o)
}
}
for i := int32(0); i < tableSize; i++ {
if asd.cutoffs[i] == bucketSize {
asd.symbols[i] = i
asd.offsets[i] = 0
asd.cutoffs[i] = 0
} else {
asd.offsets[i] -= asd.cutoffs[i]
}
}
}
func (asd *ANSSymbolDistribution) ReadSymbol(reader jxlio.BitReader, stateObj *ANSState) (int32, error) {
var state int32
if !stateObj.HasState {
s, err := reader.ReadBits(32)
if err != nil {
return 0, err
}
state = int32(s)
} else {
state = stateObj.State
}
index := state & 0xFFF
i := uint32(index) >> asd.logBucketSize
pos := index & ((1 << asd.logBucketSize) - 1)
var symbol int32
var offset int32
if pos >= asd.cutoffs[i] {
symbol = asd.symbols[i]
offset = asd.offsets[i] + int32(pos)
} else {
symbol = int32(i)
offset = int32(pos)
}
state = asd.frequencies[symbol]*int32(uint32(state)>>12) + offset
if uint32(state)&0xFFFF0000 == 0 {
state = (state << 16)
data, err := reader.ReadBits(16)
if err != nil {
return 0, err
}
state = state | int32(data)
}
stateObj.SetState(state)
return symbol, nil
}
package entropy
import (
"errors"
"github.com/kpfaulkner/jxl-go/jxlio"
)
var (
SPECIAL_DISTANCES = [][]int32{
{0, 1}, {1, 0}, {1, 1}, {-1, 1}, {0, 2}, {2, 0}, {1, 2}, {-1, 2}, {2, 1}, {-2, 1}, {2, 2},
{-2, 2}, {0, 3}, {3, 0}, {1, 3}, {-1, 3}, {3, 1}, {-3, 1}, {2, 3}, {-2, 3}, {3, 2},
{-3, 2}, {0, 4}, {4, 0}, {1, 4}, {-1, 4}, {4, 1}, {-4, 1}, {3, 3}, {-3, 3}, {2, 4},
{-2, 4}, {4, 2}, {-4, 2}, {0, 5}, {3, 4}, {-3, 4}, {4, 3}, {-4, 3}, {5, 0}, {1, 5},
{-1, 5}, {5, 1}, {-5, 1}, {2, 5}, {-2, 5}, {5, 2}, {-5, 2}, {4, 4}, {-4, 4}, {3, 5},
{-3, 5}, {5, 3}, {-5, 3}, {0, 6}, {6, 0}, {1, 6}, {-1, 6}, {6, 1}, {-6, 1}, {2, 6},
{-2, 6}, {6, 2}, {-6, 2}, {4, 5}, {-4, 5}, {5, 4}, {-5, 4}, {3, 6}, {-3, 6}, {6, 3},
{-6, 3}, {0, 7}, {7, 0}, {1, 7}, {-1, 7}, {5, 5}, {-5, 5}, {7, 1}, {-7, 1}, {4, 6},
{-4, 6}, {6, 4}, {-6, 4}, {2, 7}, {-2, 7}, {7, 2}, {-7, 2}, {3, 7}, {-3, 7}, {7, 3},
{-7, 3}, {5, 6}, {-5, 6}, {6, 5}, {-6, 5}, {8, 0}, {4, 7}, {-4, 7}, {7, 4}, {-7, 4},
{8, 1}, {8, 2}, {6, 6}, {-6, 6}, {8, 3}, {5, 7}, {-5, 7}, {7, 5}, {-7, 5}, {8, 4}, {6, 7},
{-6, 7}, {7, 6}, {-7, 6}, {8, 5}, {7, 7}, {-7, 7}, {8, 6}, {8, 7}}
)
type EntropyStream struct {
window []int32
clusterMap []int
dists []SymbolDistribution
lzLengthConfig *HybridIntegerConfig
ansState *ANSState
logAlphabetSize int32
lz77MinSymbol int32
lz77MinLength int32
numToCopy77 int32
copyPos77 int32
numDecoded77 int32
usesLZ77 bool
}
type EntropyStreamer interface {
//LoadWithReaderAndNumDists(reader jxlio.BitReader, numDists int, readClusterMapFunc ReadClusterMapFunc) error
//LoadWithReader(reader jxlio.BitReader, numDists int, disallowLZ77 bool, readClusterMapFunc func(reader jxlio.BitReader, clusterMap []int, maxClusters int) (int, error)) error
//LoadWithStream(stream EntropyStreamer) error
GetDists() []SymbolDistribution
GetState() *ANSState
ReadSymbol(reader jxlio.BitReader, context int) (int32, error)
TryReadSymbol(reader jxlio.BitReader, context int) int32
ReadSymbolWithMultiplier(reader jxlio.BitReader, context int, distanceMultiplier int32) (int32, error)
ReadHybridInteger(reader jxlio.BitReader, config *HybridIntegerConfig, token int32) (int32, error)
ValidateFinalState() bool
}
// Creating function types to make easier to pass in functions to functions (for later mocking)
type ReadClusterMapFunc func(reader jxlio.BitReader, clusterMap []int, maxClusters int) (int, error)
type EntropyStreamWithReaderAndNumDistsFunc func(reader jxlio.BitReader, numDists int, readClusterMapFunc ReadClusterMapFunc) (EntropyStreamer, error)
type EntropyStreamWithReaderFunc func(reader jxlio.BitReader, numDists int, disallowLZ77 bool, readClusterMapFunc func(reader jxlio.BitReader, clusterMap []int, maxClusters int) (int, error)) (EntropyStreamer, error)
// "constructors" are wrappers to other functions that do the work.
// Ideally if we get into a situation where we need to mock these out we can switch from
// constructors to creating the struct and then calling the underlying function.
// This isn't just a "in-theory-we'll-need-it, but have hit it quite a bit to
// increase test coverage. Unsure if good or bad idea yet.
func NewEntropyStreamWithReaderAndNumDists(reader jxlio.BitReader, numDists int, readClusterMapFunc ReadClusterMapFunc) (EntropyStreamer, error) {
es := &EntropyStream{}
err := es.LoadWithReaderAndNumDists(reader, numDists, readClusterMapFunc)
if err != nil {
return nil, err
}
return es, nil
}
func NewEntropyStreamWithStream(stream EntropyStreamer) EntropyStreamer {
es := &EntropyStream{}
es.LoadWithStream(stream)
return es
}
func NewEntropyStreamWithReader(reader jxlio.BitReader, numDists int, disallowLZ77 bool, readClusterMapFunc func(reader jxlio.BitReader, clusterMap []int, maxClusters int) (int, error)) (EntropyStreamer, error) {
es := &EntropyStream{}
err := es.LoadWithReader(reader, numDists, disallowLZ77, readClusterMapFunc)
return es, err
}
func ReadClusterMap(reader jxlio.BitReader, clusterMap []int, maxClusters int) (int, error) {
numDists := len(clusterMap)
if numDists == 1 {
clusterMap[0] = 0
} else {
var simpleClustering bool
var err error
if simpleClustering, err = reader.ReadBool(); err != nil {
return 0, err
}
if simpleClustering {
var nbits uint64
if nbits, err = reader.ReadBits(2); err != nil {
return 0, err
}
for i := 0; i < numDists; i++ {
if cm, err := reader.ReadBits(uint32(nbits)); err != nil {
return 0, err
} else {
clusterMap[i] = int(cm)
}
}
} else {
var useMtf bool
var err error
if useMtf, err = reader.ReadBool(); err != nil {
return 0, err
}
nested, err := NewEntropyStreamWithReader(reader, 1, numDists <= 2, ReadClusterMap)
if err != nil {
return 0, err
}
for i := 0; i < numDists; i++ {
c, err := nested.ReadSymbol(reader, 0)
clusterMap[i] = int(c)
if err != nil {
return 0, err
}
}
if !nested.ValidateFinalState() {
return 0, errors.New("nested distribution")
}
if useMtf {
mtf := make([]int, 256)
for i := 0; i < 256; i++ {
mtf[i] = i
}
for i := 0; i < numDists; i++ {
index := clusterMap[i]
clusterMap[i] = mtf[index]
if index != 0 {
value := mtf[index]
for j := index; j > 0; j-- {
mtf[j] = mtf[j-1]
}
mtf[0] = value
}
}
}
}
}
numClusters := 0
for i := 0; i < numDists; i++ {
if clusterMap[i] >= numClusters {
numClusters = clusterMap[i] + 1
}
}
if numClusters > maxClusters {
return 0, errors.New("Too many clusters")
}
return numClusters, nil
}
func (es *EntropyStream) GetDists() []SymbolDistribution {
return es.dists
}
func (es *EntropyStream) LoadWithStream(stream EntropyStreamer) {
inputStream := stream.(*EntropyStream)
es.usesLZ77 = inputStream.usesLZ77
es.lz77MinLength = inputStream.lz77MinLength
es.lz77MinSymbol = inputStream.lz77MinSymbol
es.lzLengthConfig = inputStream.lzLengthConfig
es.clusterMap = inputStream.clusterMap
es.dists = inputStream.dists
es.logAlphabetSize = inputStream.logAlphabetSize
if es.usesLZ77 {
es.window = make([]int32, 1<<20)
}
es.ansState = &ANSState{State: -1, HasState: false}
}
func (es *EntropyStream) LoadWithReaderAndNumDists(reader jxlio.BitReader, numDists int, readClusterMapFunc ReadClusterMapFunc) error {
return es.LoadWithReader(reader, numDists, false, readClusterMapFunc)
}
func (es *EntropyStream) LoadWithReader(reader jxlio.BitReader, numDists int, disallowLZ77 bool, readClusterMapFunc func(reader jxlio.BitReader, clusterMap []int, maxClusters int) (int, error)) error {
var err error
if numDists <= 0 {
return errors.New("Num Dists must be positive")
}
if es.usesLZ77, err = reader.ReadBool(); err != nil {
return err
}
es.ansState = &ANSState{State: -1, HasState: false}
if es.usesLZ77 {
if disallowLZ77 {
return errors.New("Nested distributions cannot use LZ77")
}
if lz77MinSymbol, err := reader.ReadU32(224, 0, 512, 0, 4096, 0, 8, 15); err != nil {
return err
} else {
es.lz77MinSymbol = int32(lz77MinSymbol)
}
if lz77MinLength, err := reader.ReadU32(3, 0, 4, 0, 5, 2, 9, 8); err != nil {
return err
} else {
es.lz77MinLength = int32(lz77MinLength)
}
numDists++
es.lzLengthConfig, err = NewHybridIntegerConfigWithReader(reader, 8)
if err != nil {
return err
}
es.window = make([]int32, 1<<20)
}
es.clusterMap = make([]int, numDists)
numClusters, err := readClusterMapFunc(reader, es.clusterMap, numDists)
if err != nil {
return err
}
es.dists = make([]SymbolDistribution, numClusters)
var prefixCodes bool
if prefixCodes, err = reader.ReadBool(); err != nil {
return err
}
if prefixCodes {
es.logAlphabetSize = 15
} else {
if logAlphabetSize, err := reader.ReadBits(2); err != nil {
return err
} else {
es.logAlphabetSize = 5 + int32(logAlphabetSize)
}
}
configs := make([]*HybridIntegerConfig, len(es.dists))
for i := 0; i < len(configs); i++ {
configs[i], err = NewHybridIntegerConfigWithReader(reader, es.logAlphabetSize)
if err != nil {
return err
}
}
if prefixCodes {
alphabetSizes := make([]int32, len(es.dists))
for i := 0; i < len(es.dists); i++ {
var readBits bool
if readBits, err = reader.ReadBool(); err != nil {
return err
}
if readBits {
var n uint64
if n, err = reader.ReadBits(4); err != nil {
return err
}
if alphaSize, err := reader.ReadBits(uint32(n)); err != nil {
return err
} else {
alphabetSizes[i] = 1 + int32(1<<n+alphaSize)
}
} else {
alphabetSizes[i] = 1
}
}
for i := 0; i < len(es.dists); i++ {
es.dists[i], err = NewPrefixSymbolDistributionWithReader(reader, alphabetSizes[i])
if err != nil {
return err
}
}
} else {
for i := 0; i < len(es.dists); i++ {
d, err := NewANSSymbolDistribution(reader, es.logAlphabetSize)
if err != nil {
return err
}
es.dists[i] = d
}
}
for i := 0; i < len(es.dists); i++ {
es.dists[i].SetConfig(configs[i])
}
return nil
}
func (es *EntropyStream) GetState() *ANSState {
return es.ansState
}
func (es *EntropyStream) ReadSymbol(reader jxlio.BitReader, context int) (int32, error) {
return es.ReadSymbolWithMultiplier(reader, context, 0)
}
func (es *EntropyStream) TryReadSymbol(reader jxlio.BitReader, context int) int32 {
v, err := es.ReadSymbol(reader, context)
if err != nil {
panic(err)
}
return v
}
func (es *EntropyStream) ReadSymbolWithMultiplier(reader jxlio.BitReader, context int, distanceMultiplier int32) (int32, error) {
if es.numToCopy77 > 0 {
es.copyPos77++
hybridInt := es.window[es.copyPos77&0xFFFFF]
es.numToCopy77--
es.numDecoded77++
es.window[es.numDecoded77&0xFFFFF] = hybridInt
return hybridInt, nil
}
if context >= len(es.clusterMap) {
return 0, errors.New("Context cannot be bigger than bundle length")
}
if es.clusterMap[context] >= len(es.dists) {
return 0, errors.New("Cluster Map points to nonexisted distribution")
}
dist := es.dists[es.clusterMap[context]]
t, err := dist.ReadSymbol(reader, es.ansState)
token := int32(t)
if err != nil {
return 0, err
}
if es.usesLZ77 && token >= es.lz77MinSymbol {
lz77dist := es.dists[es.clusterMap[len(es.clusterMap)-1]]
hi, err := es.ReadHybridInteger(reader, es.lzLengthConfig, token-es.lz77MinSymbol)
if err != nil {
return 0, err
}
es.numToCopy77 = es.lz77MinLength + hi
token, err = lz77dist.ReadSymbol(reader, es.ansState)
if err != nil {
return 0, err
}
distance, err := es.ReadHybridInteger(reader, lz77dist.GetConfig(), token)
if err != nil {
return 0, err
}
if distanceMultiplier == 0 {
distance++
} else if distance < 120 {
distance = SPECIAL_DISTANCES[distance][0] + distanceMultiplier*SPECIAL_DISTANCES[distance][1]
} else {
distance -= 119
}
if distance > (1 << 20) {
distance = 1 << 20
}
if distance > es.numDecoded77 {
distance = es.numDecoded77
}
es.copyPos77 = es.numDecoded77 - distance
return es.ReadSymbolWithMultiplier(reader, context, distanceMultiplier)
}
hybridInt, err := es.ReadHybridInteger(reader, dist.GetConfig(), token)
if err != nil {
return 0, err
}
if es.usesLZ77 {
es.numDecoded77++
es.window[es.numDecoded77&0xFFFFF] = hybridInt
}
return hybridInt, nil
}
func (es *EntropyStream) ReadHybridInteger(reader jxlio.BitReader, config *HybridIntegerConfig, token int32) (int32, error) {
split := 1 << config.SplitExponent
if token < int32(split) {
return token, nil
}
n := config.SplitExponent - config.LsbInToken - config.MsbInToken + (token-int32(split))>>(config.MsbInToken+config.LsbInToken)
if n > 32 {
return 0, errors.New("n is too large")
}
low := token & ((1 << config.LsbInToken) - 1)
token = int32(uint32(token) >> config.LsbInToken)
token &= (1 << config.MsbInToken) - 1
token |= 1 << config.MsbInToken
if data, err := reader.ReadBits(uint32(n)); err != nil {
return 0, err
} else {
return ((int32(token<<n)|int32(data))<<int32(config.LsbInToken) | int32(low)), nil
}
}
func (es *EntropyStream) ValidateFinalState() bool {
return !es.ansState.HasState || es.ansState.State == 0x130000
}
package entropy
import (
"errors"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
type HybridIntegerConfig struct {
SplitExponent int32
MsbInToken int32
LsbInToken int32
}
func NewHybridIntegerConfig(splitExponent int32, msbInToken int32, lsbInToken int32) *HybridIntegerConfig {
hic := &HybridIntegerConfig{}
hic.SplitExponent = splitExponent
hic.MsbInToken = msbInToken
hic.LsbInToken = lsbInToken
return hic
}
func NewHybridIntegerConfigWithReader(reader jxlio.BitReader, logAlphabetSize int32) (*HybridIntegerConfig, error) {
hic := &HybridIntegerConfig{}
if splitExp, err := reader.ReadBits(uint32(util.CeilLog1p(int64(logAlphabetSize)))); err != nil {
return nil, err
} else {
hic.SplitExponent = int32(splitExp)
}
if hic.SplitExponent == logAlphabetSize {
hic.MsbInToken = 0
hic.LsbInToken = 0
return hic, nil
}
var bits uint64
var err error
if bits, err = reader.ReadBits(uint32(util.CeilLog1p(int64(hic.SplitExponent)))); err != nil {
return nil, err
} else {
hic.MsbInToken = int32(bits)
}
if hic.MsbInToken > hic.SplitExponent {
return nil, errors.New("msbInToken is too large")
}
if bits, err = reader.ReadBits(uint32(util.CeilLog1p(int64(hic.SplitExponent - hic.MsbInToken)))); err != nil {
return nil, err
} else {
hic.LsbInToken = int32(bits)
}
if hic.MsbInToken+hic.LsbInToken > hic.SplitExponent {
return nil, errors.New("msbInToken + lsbInToken is too large")
}
return hic, nil
}
package entropy
import (
"errors"
"slices"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
var level0Table = NewVLCTable(4, [][]int32{{0, 2}, {4, 2}, {3, 2}, {2, 3}, {0, 2}, {4, 2}, {3, 2}, {1, 4}, {0, 2}, {4, 2}, {3, 2}, {2, 3}, {0, 2}, {4, 2}, {3, 2}, {5, 4}})
var codelenMap = []int{1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15}
type PrefixSymbolDistribution struct {
*SymbolDistributionBase
table *VLCTable
defaultSymbol int32
}
func NewPrefixSymbolDistributionWithReader(reader jxlio.BitReader, alphabetSize int32) (*PrefixSymbolDistribution, error) {
rcvr := &PrefixSymbolDistribution{
SymbolDistributionBase: NewSymbolDistributionBase(),
}
rcvr.alphabetSize = alphabetSize
rcvr.logAlphabetSize = int32(util.CeilLog1p(int64(alphabetSize - 1)))
if rcvr.alphabetSize == 1 {
rcvr.table = nil
rcvr.defaultSymbol = 0
return rcvr, nil
}
var hskip uint64
var err error
if hskip, err = reader.ReadBits(2); err != nil {
return nil, err
}
if hskip == 1 {
if err := rcvr.populateSimplePrefix(reader); err != nil {
return nil, err
}
} else {
if err := rcvr.populateComplexPrefix(reader, int32(hskip)); err != nil {
return nil, err
}
}
return rcvr, nil
}
func (rcvr *PrefixSymbolDistribution) populateComplexPrefix(reader jxlio.BitReader, hskip int32) error {
level1Lengths := make([]int32, 18)
level1Codecounts := make([]int32, 19)
level1Codecounts[0] = hskip
totalCode := 0
numCodes := 0
for i := hskip; i < 18; i++ {
code, err := level0Table.GetVLC(reader)
if err != nil {
return err
}
level1Lengths[codelenMap[i]] = code
level1Codecounts[code]++
if code != 0 {
totalCode += 32 >> code
numCodes++
}
if totalCode >= 32 {
level1Codecounts[0] += 17 - i
break
}
}
if totalCode != 32 && numCodes >= 2 || numCodes < 1 {
return errors.New("Invalid Level 1 Prefix codes")
}
for i := 1; i < 19; i++ {
level1Codecounts[i] += level1Codecounts[i-1]
}
level1LengthsScrambled := make([]int32, 18)
level1Symbols := make([]int32, 18)
for i := int32(17); i >= 0; i-- {
level1Codecounts[level1Lengths[i]]--
index := level1Codecounts[level1Lengths[i]]
level1LengthsScrambled[index] = level1Lengths[i]
level1Symbols[index] = i
}
var level1Table *VLCTable
var err error
if numCodes == 1 {
level1Table = NewVLCTable(0, [][]int32{{level1Symbols[17], 0}})
} else {
level1Table, err = NewVLCTableWithSymbols(5, level1LengthsScrambled, level1Symbols)
if err != nil {
return err
}
}
totalCode = 0
var prevRepeatCount int32
var prevZeroCount int32
level2Lengths := make([]int32, rcvr.alphabetSize)
level2Symbols := make([]int32, rcvr.alphabetSize)
level2Counts := make([]int32, rcvr.alphabetSize+1)
prev := int32(8)
for i := int32(0); i < int32(rcvr.alphabetSize); i++ {
code, err := level1Table.GetVLC(reader)
if err != nil {
return err
}
if code == 16 {
e, err := reader.ReadBits(2)
if err != nil {
return err
}
extra := 3 + int32(e)
if prevRepeatCount > 0 {
extra = 4*(prevRepeatCount-2) - prevRepeatCount + extra
}
for j := int32(0); j < extra; j++ {
level2Lengths[i+j] = prev
}
totalCode += int(uint32(32768)>>prev) * int(extra)
i += extra - 1
prevRepeatCount += extra
prevZeroCount = 0
level2Counts[prev] += extra
} else if code == 17 {
e, err := reader.ReadBits(3)
if err != nil {
return err
}
extra := 3 + int32(e)
if prevZeroCount > 0 {
extra = 8*(prevZeroCount-2) - prevZeroCount + extra
}
i += extra - 1
prevRepeatCount = 0
prevZeroCount += extra
level2Counts[0] += extra
} else {
level2Lengths[i] = code
prevRepeatCount = 0
prevZeroCount = 0
if code != 0 {
// uint32 casting due to in Java its using unsigned shift, right? Zero fill? (>>>).
// Go recommendation is cast to uint32 first.
totalCode += int(uint32(32768) >> code)
prev = code
}
level2Counts[code]++
}
if totalCode >= 32768 {
level2Counts[0] += rcvr.alphabetSize - i - 1
break
}
}
if totalCode != 32768 && level2Counts[0] < rcvr.alphabetSize-1 {
return errors.New("Invalid Level 2 Prefix Codes")
}
for i := int32(1); i <= rcvr.alphabetSize; i++ {
level2Counts[i] += level2Counts[i-1]
}
level2LengthsScrambled := make([]int32, rcvr.alphabetSize)
for i := rcvr.alphabetSize - 1; i >= 0; i-- {
level2Counts[level2Lengths[i]]--
index := level2Counts[level2Lengths[i]]
level2LengthsScrambled[index] = level2Lengths[i]
level2Symbols[index] = i
}
rcvr.table, err = NewVLCTableWithSymbols(15, level2LengthsScrambled, level2Symbols)
if err != nil {
return err
}
return nil
}
func (rcvr *PrefixSymbolDistribution) populateSimplePrefix(reader jxlio.BitReader) error {
symbols := make([]int32, 4)
var lens []int32 = nil
n, err := reader.ReadBits(2)
if err != nil {
return err
}
nsym := int32(n) + 1
treeSelect := false
bits := int32(0)
for i := 0; i < int(nsym); i++ {
s, err := reader.ReadBits(uint32(rcvr.logAlphabetSize))
if err != nil {
return err
}
symbols[i] = int32(s)
}
if nsym == 4 {
if treeSelect, err = reader.ReadBool(); err != nil {
return err
}
}
switch nsym {
case 1:
rcvr.table = nil
rcvr.defaultSymbol = symbols[0]
return nil
case 2:
bits = 1
lens = []int32{1, 1, 0, 0}
if symbols[0] > symbols[1] {
symbols[0], symbols[1] = symbols[1], symbols[0]
}
case 3:
bits = 2
lens = []int32{1, 2, 2, 0}
if symbols[1] > symbols[2] {
symbols[1], symbols[2] = symbols[2], symbols[1]
}
case 4:
if treeSelect {
bits = 3
lens = []int32{1, 2, 3, 3}
if symbols[2] > symbols[3] {
symbols[2], symbols[3] = symbols[3], symbols[2]
}
} else {
bits = 2
lens = []int32{2, 2, 2, 2}
slices.Sort(symbols)
}
}
rcvr.table, err = NewVLCTableWithSymbols(bits, lens, symbols)
if err != nil {
return err
}
return nil
}
func (rcvr *PrefixSymbolDistribution) ReadSymbol(reader jxlio.BitReader, state *ANSState) (int32, error) {
if rcvr.table == nil {
return rcvr.defaultSymbol, nil
}
return rcvr.table.GetVLC(reader)
}
package entropy
import "github.com/kpfaulkner/jxl-go/jxlio"
type SymbolDistribution interface {
ReadSymbol(reader jxlio.BitReader, state *ANSState) (int32, error)
SetConfig(config *HybridIntegerConfig)
GetConfig() *HybridIntegerConfig
}
type SymbolDistributionBase struct {
config *HybridIntegerConfig
logBucketSize int32
alphabetSize int32
logAlphabetSize int32
}
func NewSymbolDistributionBase() *SymbolDistributionBase {
rcvr := &SymbolDistributionBase{}
return rcvr
}
func (rcvr *SymbolDistributionBase) ReadSymbol(reader jxlio.BitReader, state *ANSState) (int32, error) {
return 0, nil
}
func (rcvr *SymbolDistributionBase) SetConfig(config *HybridIntegerConfig) {
rcvr.config = config
}
func (rcvr *SymbolDistributionBase) GetConfig() *HybridIntegerConfig {
return rcvr.config
}
package entropy
import (
"errors"
bbits "math/bits"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
type VLCTable struct {
table [][]int32
bits int32
}
func NewVLCTable(bits int32, table [][]int32) (rcvr *VLCTable) {
rcvr = &VLCTable{}
rcvr.bits = bits
rcvr.table = table
return
}
func NewVLCTableWithSymbols(bits int32, lengths []int32, symbols []int32) (*VLCTable, error) {
rcvr := &VLCTable{}
rcvr.bits = bits
table := util.MakeMatrix2D[int32](1<<bits, 2)
codes := make([]int, len(lengths))
nLengths := make([]int32, len(lengths))
nSymbols := make([]int32, len(lengths))
count := 0
code := 0
for i := int32(0); i < int32(len(lengths)); i++ {
currentLen := lengths[i]
if currentLen > 0 {
nLengths[count] = currentLen
if len(symbols) > 0 {
nSymbols[count] = symbols[i]
} else {
nSymbols[count] = i
}
codes[count] = int(code)
count++
} else if currentLen < 0 {
currentLen = -currentLen
} else {
continue
}
code += 1 << (32 - currentLen)
if code > 1<<32 {
return nil, errors.New("Too many VLC codes")
}
}
if code != 1<<32 {
return nil, errors.New("Not enough VLC codes")
}
for i := 0; i < count; i++ {
if nLengths[i] <= bits {
index := bbits.Reverse32(uint32(codes[i]))
number := 1 << (bits - nLengths[i])
offset := 1 << nLengths[i]
for j := 0; j < number; j++ {
oldSymbol := table[index][0]
oldLen := table[index][1]
if (oldLen > 0 || oldSymbol > 0) && (oldLen != nLengths[i] || oldSymbol != nSymbols[i]) {
return nil, errors.New("Illegal VLC codes")
}
table[index][0] = nSymbols[i]
table[index][1] = nLengths[i]
index += uint32(offset)
}
} else {
return nil, errors.New("Table size too small")
}
}
for i := 0; i < len(table); i++ {
if table[i][1] == 0 {
table[i][0] = -1
}
}
rcvr.table = table
return rcvr, nil
}
func (rcvr *VLCTable) GetVLC(reader jxlio.BitReader) (int32, error) {
index, err := reader.ShowBits(int(rcvr.bits))
if err != nil {
return 0, err
}
symbol := rcvr.table[index][0]
length := rcvr.table[index][1]
err = reader.SkipBits(uint32(length))
if err != nil {
return 0, err
}
return symbol, nil
}
package entropy
import "github.com/kpfaulkner/jxl-go/jxlio"
// FakeSymbolDistribution implements SymbolDistribution for testing.
// It returns predefined symbols in sequence.
type FakeSymbolDistribution struct {
Symbols []int32
Cfg *HybridIntegerConfig
idx int
ActivateStateOnRead bool
}
func (f *FakeSymbolDistribution) ReadSymbol(reader jxlio.BitReader, state *ANSState) (int32, error) {
if f.ActivateStateOnRead {
state.HasState = true
state.State = 0
}
if f.idx >= len(f.Symbols) {
return 0, nil
}
sym := f.Symbols[f.idx]
f.idx++
return sym, nil
}
func (f *FakeSymbolDistribution) SetConfig(config *HybridIntegerConfig) {
f.Cfg = config
}
func (f *FakeSymbolDistribution) GetConfig() *HybridIntegerConfig {
return f.Cfg
}
// NewEntropyStreamForTest creates a controllable EntropyStream for testing.
// All contexts map to the single provided distribution.
func NewEntropyStreamForTest(numContexts int, dist SymbolDistribution) *EntropyStream {
es := &EntropyStream{}
es.clusterMap = make([]int, numContexts)
es.dists = []SymbolDistribution{dist}
es.ansState = &ANSState{State: -1, HasState: false}
return es
}
package frame
import (
"github.com/kpfaulkner/jxl-go/jxlio"
)
type BlendingInfo struct {
Mode uint32
AlphaChannel uint32
Clamp bool
Source uint32
}
func NewBlendingInfo() *BlendingInfo {
bi := &BlendingInfo{}
bi.Mode = BLEND_REPLACE
bi.AlphaChannel = 0
bi.Clamp = false
bi.Source = 0
return bi
}
func NewBlendingInfoWithReader(reader jxlio.BitReader, extra bool, fullFrame bool) (*BlendingInfo, error) {
bi := &BlendingInfo{}
if mode, err := reader.ReadU32(0, 0, 1, 0, 2, 0, 3, 2); err != nil {
return nil, err
} else {
bi.Mode = mode
}
if extra && (bi.Mode == BLEND_BLEND || bi.Mode == BLEND_MULADD) {
if alphaChannel, err := reader.ReadU32(0, 0, 1, 0, 2, 0, 3, 3); err != nil {
return nil, err
} else {
bi.AlphaChannel = alphaChannel
}
} else {
bi.AlphaChannel = 0
}
var err error
if extra && (bi.Mode == BLEND_BLEND ||
bi.Mode == BLEND_MULT ||
bi.Mode == BLEND_MULADD) {
if bi.Clamp, err = reader.ReadBool(); err != nil {
return nil, err
}
} else {
bi.Clamp = false
}
if bi.Mode != BLEND_REPLACE || !fullFrame {
if bits, err := reader.ReadBits(2); err != nil {
return nil, err
} else {
bi.Source = uint32(bits)
}
} else {
bi.Source = 0
}
return bi, nil
}
package frame
import (
"bytes"
"errors"
"fmt"
"math"
"sync"
log "github.com/sirupsen/logrus"
"github.com/kpfaulkner/jxl-go/bundle"
"github.com/kpfaulkner/jxl-go/entropy"
"github.com/kpfaulkner/jxl-go/image"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/options"
"github.com/kpfaulkner/jxl-go/util"
)
var (
cMap = []int{1, 0, 2}
SQRT_H = math.Sqrt(0.5)
epfCross = []util.Point{
{Y: 0, X: 0},
{Y: 0, X: -1}, {Y: 0, X: 1},
{Y: -1, X: 0}, {Y: 1, X: 0}}
epfDoubleCross = []util.Point{{X: 0, Y: 0},
{X: 0, Y: -1}, {X: 0, Y: 1}, {X: -1, Y: 0}, {X: 1, Y: 0},
{X: -1, Y: 1}, {X: 1, Y: 1}, {X: 1, Y: -1}, {X: -1, Y: -1},
{X: 0, Y: -2}, {X: 0, Y: 2}, {X: 2, Y: 0}, {X: -2, Y: 0}}
)
type NewLFGlobalWithReaderFunc func(reader jxlio.BitReader, parent Framer, hfBlockContextFunc NewHFBlockContextFunc) (*LFGlobal, error)
type ReadPermutationFunc func(reader jxlio.BitReader, stream entropy.EntropyStreamer, size uint32, skip uint32) ([]uint32, error)
type Inp struct {
iPass int
iGroup int
}
type Framer interface {
getLFGroupForGroup(groupID int32) *LFGroup
getHFGlobal() *HFGlobal
getLFGlobal() *LFGlobal
getFrameHeader() *FrameHeader
getPasses() []Pass
getGroupSize(groupID int32) (util.Dimension, error)
groupPosInLFGroup(lfGroupID int32, groupID uint32) util.Point
getGlobalMetadata() *bundle.ImageHeader
getLFGroupLocation(lfGroupID int32) *util.Point
getGlobalTree() *MATreeNode
setGlobalTree(tree *MATreeNode)
getLFGroupSize(lfGroupID int32) (util.Dimension, error)
getNumLFGroups() uint32
}
type Frame struct {
tocPermutation []uint32
tocLengths []uint32
lfGroups []*LFGroup
Buffer []image.ImageBuffer
passes []Pass
bitreaders []jxlio.BitReader
GlobalMetadata *bundle.ImageHeader
options *options.JXLOptions
reader jxlio.BitReader
Header *FrameHeader
globalTree *MATreeNode
hfGlobal *HFGlobal
LfGlobal *LFGlobal
groupRowStride uint32
lfGroupRowStride uint32
numGroups uint32
numLFGroups uint32
permutatedTOC bool
decoded bool
}
func (f *Frame) getGlobalTree() *MATreeNode {
return f.globalTree
}
func (f *Frame) setGlobalTree(tree *MATreeNode) {
f.globalTree = tree
}
func (f *Frame) getGlobalMetadata() *bundle.ImageHeader {
return f.GlobalMetadata
}
func (f *Frame) getPasses() []Pass {
return f.passes
}
func (f *Frame) getFrameHeader() *FrameHeader {
return f.Header
}
func (f *Frame) ReadFrameHeader() (FrameHeader, error) {
err := f.reader.ZeroPadToByte()
if err != nil {
return FrameHeader{}, fmt.Errorf("error zero padding to byte: %v", err)
}
f.Header, err = NewFrameHeaderWithReader(f.reader, f.GlobalMetadata)
if err != nil {
return FrameHeader{}, err
}
f.Header.Bounds = &util.Rectangle{
Origin: f.Header.Bounds.Origin,
Size: f.Header.Bounds.Size,
}
f.groupRowStride = util.CeilDiv(f.Header.Bounds.Size.Width, f.Header.groupDim)
f.lfGroupRowStride = util.CeilDiv(f.Header.Bounds.Size.Width, f.Header.groupDim<<3)
f.numGroups = f.groupRowStride * util.CeilDiv(f.Header.Bounds.Size.Height, f.Header.groupDim)
f.numLFGroups = f.lfGroupRowStride * util.CeilDiv(f.Header.Bounds.Size.Height, f.Header.groupDim<<3)
return *f.Header, nil
}
func (f *Frame) ReadTOC() error {
var tocEntries uint32
if f.numGroups == 1 && f.Header.passes.numPasses == 1 {
tocEntries = 1
} else {
tocEntries = 1 + f.numLFGroups + 1 + f.numGroups*f.Header.passes.numPasses
}
var err error
if f.permutatedTOC, err = f.reader.ReadBool(); err != nil {
return err
}
if f.permutatedTOC {
tocStream, err := entropy.NewEntropyStreamWithReaderAndNumDists(f.reader, 8, entropy.ReadClusterMap)
if err != nil {
return err
}
f.tocPermutation, err = readPermutation(f.reader, tocStream, tocEntries, 0)
if err != nil {
return err
}
if !tocStream.ValidateFinalState() {
return errors.New("invalid final ANS state decoding TOC")
}
} else {
f.tocPermutation = nil
//f.tocPermutation = make([]uint32, tocEntries)
//for i := uint32(0); i < tocEntries; i++ {
// a := i
// f.tocPermutation[i] = a
//}
}
if err = f.reader.ZeroPadToByte(); err != nil {
return err
}
f.tocLengths = make([]uint32, tocEntries)
for i := 0; i < int(tocEntries); i++ {
if tocLengths, err := f.reader.ReadU32(0, 10, 1024, 14, 17408, 22, 4211712, 30); err != nil {
return err
} else {
f.tocLengths[i] = tocLengths
}
}
if err = f.reader.ZeroPadToByte(); err != nil {
return err
}
return nil
}
func (f *Frame) readBuffer(index int) ([]uint8, error) {
length := f.tocLengths[index]
buffer := make([]uint8, length+4)
err := f.reader.ReadBytesToBuffer(buffer, length)
if err != nil {
return nil, err
}
if len(buffer) < int(length) {
return nil, errors.New("unable to read full TOC entry")
}
return buffer, nil
}
func ctxFunc(x int64) int {
return min(7, util.CeilLog1p(x))
}
// absFloat32 returns the absolute value of a float32 using bit manipulation.
// This is ~3x faster than float32(math.Abs(float64(x))) as it avoids:
// 1. float32 -> float64 conversion
// 2. float64 -> float32 conversion
// 3. The overhead of math.Abs function call
func absFloat32(x float32) float32 {
return math.Float32frombits(math.Float32bits(x) & 0x7FFFFFFF)
}
func readPermutation(reader jxlio.BitReader, stream entropy.EntropyStreamer, size uint32, skip uint32) ([]uint32, error) {
end, err := stream.ReadSymbol(reader, ctxFunc(int64(size)))
if err != nil {
return nil, err
}
if uint32(end) > size-skip {
return nil, errors.New("illegal end value in lehmer sequence")
}
lehmer := make([]uint32, size)
for i := skip; i < uint32(end)+skip; i++ {
ctxVal := uint32(0)
if i > skip {
ctxVal = lehmer[i-1]
}
ii, err := stream.ReadSymbol(reader, ctxFunc(int64(ctxVal)))
if err != nil {
return nil, err
}
lehmer[i] = uint32(ii)
if lehmer[i] >= size-i {
return nil, errors.New("illegal end value in lehmer sequence")
}
}
// Convert Lehmer code to permutation using Fenwick tree for O(n log n)
// instead of O(n²) slice splicing
permutation := make([]uint32, size)
// Fenwick tree: tree[i] stores count of available elements in its range
// Initialize with all 1s (all elements available)
tree := make([]int, size+1)
for i := 1; i <= int(size); i++ {
tree[i] = i & (-i) // lowbit(i) = count of 1s in range for all-1s initialization
}
// Find k-th available element (1-indexed) using binary search on Fenwick tree
findKth := func(k int) int {
pos := 0
mask := 1
for mask <= int(size) {
mask <<= 1
}
for mask >>= 1; mask > 0; mask >>= 1 {
if pos+mask <= int(size) && tree[pos+mask] < k {
k -= tree[pos+mask]
pos += mask
}
}
return pos + 1
}
// Mark element at pos as used (subtract 1 from all ranges containing pos)
markUsed := func(pos int) {
for pos <= int(size) {
tree[pos]--
pos += pos & (-pos)
}
}
for i := uint32(0); i < size; i++ {
// Find the (lehmer[i]+1)-th available element
val := findKth(int(lehmer[i]) + 1)
permutation[i] = uint32(val - 1) // Convert to 0-indexed
markUsed(val)
}
return permutation, nil
}
func NewFrameWithReader(reader jxlio.BitReader, imageHeader *bundle.ImageHeader, options *options.JXLOptions) *Frame {
frame := &Frame{
GlobalMetadata: imageHeader,
options: options,
reader: reader,
}
return frame
}
func (f *Frame) SkipFrameData() error {
for i := 0; i < len(f.tocLengths); i++ {
_, err := f.reader.Skip(f.tocLengths[i])
if err != nil {
return err
}
}
return nil
}
// gets a bit reader for each TOC entry???
func (f *Frame) getBitreader(index int) (jxlio.BitReader, error) {
var i uint32
if len(f.tocLengths) <= 1 {
i = 0
} else {
i = uint32(index)
}
return f.bitreaders[i], nil
}
func (f *Frame) getHFGlobal() *HFGlobal {
return f.hfGlobal
}
func (f *Frame) getLFGlobal() *LFGlobal {
return f.LfGlobal
}
func (f *Frame) DecodeFrame(lfBuffer []image.ImageBuffer, newLFGlobalWithReader NewLFGlobalWithReaderFunc) error {
if f.decoded {
return nil
}
f.decoded = true
err := f.setupBitReaders()
if err != nil {
return err
}
lfGlobalBitReader, err := f.getBitreader(0)
if err != nil {
return err
}
f.LfGlobal, err = newLFGlobalWithReader(lfGlobalBitReader, f, NewHFBlockContextWithReader)
if err != nil {
return err
}
paddedSize, err := f.GetPaddedFrameSize()
if err != nil {
return err
}
numColours := f.GetColourChannelCount()
f.Buffer = make([]image.ImageBuffer, numColours+len(f.GlobalMetadata.ExtraChannelInfo))
for c := 0; c < len(f.Buffer); c++ {
channelSize := util.Dimension{
Width: paddedSize.Width,
Height: paddedSize.Height,
}
if c < 3 && c < f.GetColourChannelCount() {
channelSize.Height >>= f.Header.jpegUpsamplingY[c]
channelSize.Width >>= f.Header.jpegUpsamplingX[c]
}
var isFloat bool
if c < f.GetColourChannelCount() {
isFloat = f.GlobalMetadata.XybEncoded || f.Header.Encoding == VARDCT ||
f.GlobalMetadata.BitDepth.ExpBits != 0
} else {
isFloat = f.GlobalMetadata.ExtraChannelInfo[c-numColours].BitDepth.ExpBits != 0
}
typeToUse := image.TYPE_INT
if isFloat {
typeToUse = image.TYPE_FLOAT
}
buf, err := image.NewImageBuffer(typeToUse, int32(channelSize.Height), int32(channelSize.Width))
if err != nil {
return err
}
f.Buffer[c] = *buf
}
err = f.decodeLFGroups(lfBuffer)
if err != nil {
log.Errorf("Error decoding LFGroups %v", err)
return err
}
hfGlobalReader, err := f.getBitreader(1 + int(f.numLFGroups))
if err != nil {
return err
}
if f.Header.Encoding == VARDCT {
f.hfGlobal, err = NewHFGlobalWithReader(hfGlobalReader, f)
if err != nil {
return err
}
} else {
f.hfGlobal = nil
}
err = f.decodePasses(hfGlobalReader)
if err != nil {
return err
}
// bench.jxl, after this Buffer[x].FloatBuffer is NOT zeroed out.. but jxlatte is
err = f.decodePassGroupsConcurrent()
if err != nil {
return err
}
err = f.LfGlobal.globalModular.applyTransforms()
if err != nil {
return err
}
modularBuffer := f.LfGlobal.globalModular.getDecodedBuffer()
for c := 0; c < len(modularBuffer); c++ {
cIn := c
isModularColour := f.Header.Encoding == MODULAR && c < f.GetColourChannelCount()
isModularXYB := f.GlobalMetadata.XybEncoded && isModularColour
var cOut int
if isModularXYB {
cOut = cMap[c]
} else {
cOut = c
}
cOut += len(f.Buffer) - len(modularBuffer)
var scaleFactor float32
if isModularXYB {
scaleFactor = f.LfGlobal.lfDequant[cOut]
} else {
scaleFactor = 1.0
}
if isModularXYB && cIn == 2 {
outBuffer := f.Buffer[cOut].FloatBuffer
for y := uint32(0); y < f.Header.Bounds.Size.Height; y++ {
for x := uint32(0); x < f.Header.Bounds.Size.Width; x++ {
outBuffer[y][x] = scaleFactor * float32(modularBuffer[0][y][x]+modularBuffer[2][y][x])
}
}
} else if f.Buffer[cOut].IsFloat() {
outBuffer := f.Buffer[cOut].FloatBuffer
for y := uint32(0); y < f.Header.Bounds.Size.Height; y++ {
for x := uint32(0); x < f.Header.Bounds.Size.Width; x++ {
outBuffer[y][x] = scaleFactor * float32(modularBuffer[cIn][y][x])
}
}
} else {
outBuffer := f.Buffer[cOut].IntBuffer
for y := uint32(0); y < f.Header.Bounds.Size.Height; y++ {
//copy(outBuffer[y], modularBuffer[cIn][y])
for x := uint32(0); x < f.Header.Bounds.Size.Width; x++ {
outBuffer[y][x] = int32(scaleFactor * float32(modularBuffer[cIn][y][x]))
}
}
}
}
if err := f.invertSubsampling(); err != nil {
return nil
}
if f.Header.restorationFilter.gab {
if err := f.performGabConvolution(); err != nil {
return err
}
}
if f.Header.restorationFilter.epfIterations > 0 {
if err = f.performEdgePreservingFilter(); err != nil {
return err
}
}
return nil
}
func (f *Frame) setupBitReaders() error {
f.bitreaders = make([]jxlio.BitReader, len(f.tocLengths))
if len(f.tocLengths) != 1 {
for i := 0; i < len(f.tocLengths); i++ {
buffer, err := f.readBuffer(i)
if err != nil {
return err
}
f.bitreaders[i] = jxlio.NewBitStreamReader(bytes.NewReader(buffer))
}
} else {
f.bitreaders[0] = f.reader
}
return nil
}
func (f *Frame) IsVisible() bool {
return f.Header.FrameType == REGULAR_FRAME || f.Header.FrameType == SKIP_PROGRESSIVE && (f.Header.Duration != 0 || f.Header.IsLast)
}
func (f *Frame) GetColourChannelCount() int {
if f.GlobalMetadata.XybEncoded || f.Header.Encoding == VARDCT {
return 3
}
return f.GlobalMetadata.GetColourChannelCount()
}
func (f *Frame) GetPaddedFrameSize() (util.Dimension, error) {
factorY := 1 << util.Max(f.Header.jpegUpsamplingY...)
factorX := 1 << util.Max(f.Header.jpegUpsamplingX...)
var width uint32
var height uint32
if f.Header.Encoding == VARDCT {
height = (f.Header.Bounds.Size.Height + 7) >> 3
width = (f.Header.Bounds.Size.Width + 7) >> 3
} else {
width = f.Header.Bounds.Size.Width
height = f.Header.Bounds.Size.Height
}
height = util.CeilDiv(height, uint32(factorY))
width = util.CeilDiv(width, uint32(factorX))
if f.Header.Encoding == VARDCT {
return util.Dimension{
Width: (width * uint32(factorX)) << 3,
Height: (height * uint32(factorY)) << 3,
}, nil
} else {
return util.Dimension{
Width: width * uint32(factorX),
Height: height * uint32(factorY),
}, nil
}
}
func (f *Frame) decodeLFGroups(lfBuffer []image.ImageBuffer) error {
lfReplacementChannels := []*ModularChannel{}
lfReplacementChannelIndicies := []int{}
for i := 0; i < len(f.LfGlobal.globalModular.getChannels()); i++ {
ch := f.LfGlobal.globalModular.getChannels()[i]
if !ch.decoded {
if ch.hshift >= 3 && ch.vshift >= 3 {
lfReplacementChannelIndicies = append(lfReplacementChannelIndicies, i)
height := f.Header.lfGroupDim >> ch.vshift
width := f.Header.lfGroupDim >> ch.hshift
lfReplacementChannels = append(lfReplacementChannels, NewModularChannelWithAllParams(int32(height), int32(width), ch.vshift, ch.hshift, false))
}
}
}
f.lfGroups = make([]*LFGroup, f.numLFGroups)
// Hoist invariant computation out of the loop
frameSize, err := f.GetPaddedFrameSize()
if err != nil {
return err
}
// Pre-compute lfHeight/lfWidth for each replacement channel (invariant per channel)
numReplacements := len(lfReplacementChannels)
lfHeights := make([]uint32, numReplacements)
lfWidths := make([]uint32, numReplacements)
templateHeights := make([]uint32, numReplacements)
templateWidths := make([]uint32, numReplacements)
for i, r := range lfReplacementChannels {
lfHeights[i] = frameSize.Height >> r.vshift
lfWidths[i] = frameSize.Width >> r.hshift
templateHeights[i] = r.size.Height
templateWidths[i] = r.size.Width
}
for lfGroupID := uint32(0); lfGroupID < f.numLFGroups; lfGroupID++ {
reader, err := f.getBitreader(1 + int(lfGroupID))
if err != nil {
return err
}
lfGroupPos := f.getLFGroupLocation(int32(lfGroupID))
// Pre-allocate with correct capacity, use index assignment instead of append
replaced := make([]ModularChannel, numReplacements)
for i, r := range lfReplacementChannels {
// Copy the template channel directly instead of calling NewModularChannelFromChannel
replaced[i] = ModularChannel{
size: r.size,
origin: r.origin,
hshift: r.hshift,
vshift: r.vshift,
decoded: r.decoded,
// buffer is nil - will be allocated by NewLFGroupWithReader if needed
}
// Update origin and size for this specific LF group
replaced[i].origin.Y = lfGroupPos.Y * int32(templateHeights[i])
replaced[i].origin.X = lfGroupPos.X * int32(templateWidths[i])
replaced[i].size.Height = util.Min(templateHeights[i], lfHeights[i]-uint32(replaced[i].origin.Y))
replaced[i].size.Width = util.Min(templateWidths[i], lfWidths[i]-uint32(replaced[i].origin.X))
}
f.lfGroups[lfGroupID], err = NewLFGroupWithReader(reader, f, int32(lfGroupID), replaced, lfBuffer, NewLFCoefficientsWithReader, NewHFMetadataWithReader)
if err != nil {
return err
}
}
// Allocate all replacement channels once BEFORE copying data from LF groups
// (previously was allocating redundantly for each LF group)
channels := f.LfGlobal.globalModular.getChannels()
for j := 0; j < len(lfReplacementChannelIndicies); j++ {
channels[lfReplacementChannelIndicies[j]].allocate()
}
// Copy data from each LF group into the pre-allocated channels
for lfGroupID := uint32(0); lfGroupID < f.numLFGroups; lfGroupID++ {
for j := 0; j < len(lfReplacementChannelIndicies); j++ {
index := lfReplacementChannelIndicies[j]
channel := channels[index]
newChannelInfo := f.lfGroups[lfGroupID].modularLFGroup.getChannels()[index]
newChannel := newChannelInfo.buffer
for y := 0; y < len(newChannel); y++ {
copy(channel.buffer[int32(y)+newChannelInfo.origin.Y], newChannel[y])
}
}
}
return nil
}
func (f *Frame) decodePasses(reader jxlio.BitReader) error {
var err error
f.passes = make([]Pass, f.Header.passes.numPasses)
for pass := 0; pass < len(f.passes); pass++ {
prevMinShift := uint32(0)
if pass > 0 {
prevMinShift = f.passes[pass-1].minShift
}
f.passes[pass], err = NewPassWithReader(reader, f, uint32(pass), prevMinShift, NewHFPassWithReader)
if err != nil {
return err
}
}
return nil
}
func (f *Frame) startWorker(inputChan chan Inp, passGroups [][]PassGroup) {
for inp := range inputChan {
if err := f.doProcessing(inp.iPass, inp.iGroup, passGroups); err != nil {
log.Errorf("Error processing %v %v %v", inp.iPass, inp.iGroup, err)
}
}
}
func (f *Frame) doProcessing(iPass int, iGroup int, passGroups [][]PassGroup) error {
br, err := f.getBitreader(2 + int(f.numLFGroups) + iPass*int(f.numGroups) + iGroup)
if err != nil {
return err
}
replaced := []ModularChannel{}
for _, r := range f.passes[iPass].replacedChannels {
// remove any replacedChannels that are nil/empty
if r != nil {
mc := NewModularChannelFromChannel(*r)
replaced = append(replaced, *mc)
}
}
for i := 0; i < len(replaced); i++ {
info := replaced[i]
groupHeight := f.Header.groupDim >> info.vshift
groupWidth := f.Header.groupDim >> info.hshift
rowStride := util.CeilDiv(info.size.Width, groupWidth)
info.origin.Y = int32((uint32(iGroup) / rowStride) * groupHeight)
info.origin.X = int32((uint32(iGroup) % rowStride) * groupWidth)
info.size.Height = util.Min[uint32](info.size.Height-uint32(info.origin.Y), uint32(groupHeight))
info.size.Width = util.Min[uint32](info.size.Width-uint32(info.origin.X), uint32(groupWidth))
replaced[i] = info
}
pg, err := NewPassGroupWithReader(br, f, uint32(iPass), uint32(iGroup), replaced)
if err != nil {
return err
}
passGroups[iPass][iGroup] = *pg
return nil
}
func (f *Frame) decodePassGroupsConcurrent() error {
numPasses := len(f.passes)
numGroups := int(f.numGroups)
passGroups := util.MakeMatrix2D[PassGroup](numPasses, numGroups)
inputChan := make(chan Inp, numPasses*numGroups)
for pass0 := 0; pass0 < numPasses; pass0++ {
pass := pass0
for group0 := 0; group0 < numGroups; group0++ {
inputChan <- Inp{
iPass: pass,
iGroup: group0,
}
}
}
close(inputChan)
wg := sync.WaitGroup{}
for i := 0; i < f.options.MaxGoroutines; i++ {
wg.Add(1)
go func() {
f.startWorker(inputChan, passGroups)
wg.Done()
}()
}
wg.Wait()
for pass := 0; pass < numPasses; pass++ {
j := 0
for i := 0; i < len(f.passes[pass].replacedChannels); i++ {
if f.passes[pass].replacedChannels[i] == nil {
continue
}
ii := i
jj := j
channel := f.LfGlobal.globalModular.getChannels()[ii]
channel.allocate()
for group := 0; group < int(f.numGroups); group++ {
newChannelInfo := passGroups[pass][group].modularStream.getChannels()[jj]
buff := newChannelInfo.buffer
for y := 0; y < len(buff); y++ {
idx := y + int(newChannelInfo.origin.Y)
copy(channel.buffer[idx][newChannelInfo.origin.X:], buff[y][:len(buff[y])])
}
}
j++
}
}
if f.Header.Encoding == VARDCT {
// get floating point version of frame buffer
buffers := util.MakeMatrix3DPooled[float32](3, 0, 0)
defer util.ReturnMatrix3DToPool(buffers)
for c := 0; c < 3; c++ {
if err := f.Buffer[c].CastToFloatIfMax(^(^0 << f.GlobalMetadata.BitDepth.BitsPerSample)); err != nil {
return err
}
buffers[c] = f.Buffer[c].FloatBuffer
}
for pass := 0; pass < numPasses; pass++ {
for group := 0; group < numGroups; group++ {
passGroup := &passGroups[pass][group]
var prev *PassGroup
if pass > 0 {
prev = &passGroups[pass-1][group]
} else {
prev = nil
}
if err := passGroup.invertVarDCT(buffers, prev); err != nil {
return err
}
}
}
// Release HFCoefficients buffers back to pool
for pass := 0; pass < numPasses; pass++ {
for group := 0; group < numGroups; group++ {
passGroups[pass][group].Release()
}
}
}
return nil
}
// nolint
func displayBuffers(text string, frameBuffer [][][]float32) {
total := 0.0
for c := 0; c < len(frameBuffer); c++ {
for y := 0; y < len(frameBuffer[c]); y++ {
for x := 0; x < len(frameBuffer[c][y]); x++ {
total += float64(frameBuffer[c][y][x])
}
}
}
}
// nolint
func displayBuffer(text string, frameBuffer [][]float32) {
total := 0.0
for y := 0; y < len(frameBuffer); y++ {
//fmt.Printf("Row %d: %v\n", y, frameBuffer[c][y])
//fmt.Printf("Row %d: ", y)
for x := 0; x < len(frameBuffer[y]); x++ {
//fmt.Printf("%f ", frameBuffer[c][y][x])
total += float64(frameBuffer[y][x])
}
}
}
func (f *Frame) invertSubsampling() error {
for c := 0; c < 3; c++ {
xShift := f.Header.jpegUpsamplingX[c]
yShift := f.Header.jpegUpsamplingY[c]
xShift--
for xShift+1 > 0 {
oldBuffer := f.Buffer[c]
if err := oldBuffer.CastToFloatIfMax(^(^0 << f.GlobalMetadata.BitDepth.BitsPerSample)); err != nil {
log.Errorf("Error casting buffer to float %v", err)
return err
}
oldChannel := oldBuffer.FloatBuffer
newBuffer, err := image.NewImageBuffer(image.TYPE_FLOAT, oldBuffer.Height, oldBuffer.Width*2)
if err != nil {
log.Errorf("Error creating new buffer %v", err)
return err
}
newChannel := newBuffer.FloatBuffer
for y := 0; y < len(oldChannel); y++ {
oldRow := oldChannel[y]
//newRow := make([]float32, len(oldRow)*2)
newRow := newChannel[y]
for x := 0; x < len(oldRow); x++ {
b75 := 0.75 * oldRow[x]
xx := 0
if x != 0 {
xx = x - 1
}
newRow[2*x] = b75 + 0.25*oldRow[xx]
xx = x + 1
if x+1 == len(oldRow) {
xx = len(oldRow) - 1
}
newRow[2*x+1] = b75 + 0.25*oldRow[xx]
}
newChannel[y] = newRow
}
f.Buffer[c] = *newBuffer
xShift--
}
yShift--
for yShift+1 > 0 {
oldBuffer := f.Buffer[c]
if err := oldBuffer.CastToFloatIfMax(^(^0 << f.GlobalMetadata.BitDepth.BitsPerSample)); err != nil {
log.Errorf("Error casting buffer to float %v", err)
return err
}
oldChannel := oldBuffer.FloatBuffer
newBuffer, err := image.NewImageBuffer(image.TYPE_FLOAT, oldBuffer.Height*2, oldBuffer.Width)
if err != nil {
log.Errorf("Error creating new buffer %v", err)
return err
}
newChannel := newBuffer.FloatBuffer
for y := 0; y < len(oldChannel); y++ {
oldRow := oldChannel[y]
yy := y - 1
if y == 0 {
yy = 0
}
oldRowPrev := oldChannel[yy]
yy = y + 1
if y+1 == len(oldChannel) {
yy = len(oldChannel) - 1
}
oldRowNext := oldChannel[yy]
firstNewRow := newChannel[2*y]
secondNewRow := newChannel[2*y+1]
for x := 0; x < len(oldRow); x++ {
b75 := 0.75 * oldRow[x]
firstNewRow[x] = b75 + 0.25*oldRowPrev[x]
secondNewRow[x] = b75 + 0.25*oldRowNext[x]
}
}
f.Buffer[c] = *newBuffer
yShift--
}
}
return nil
}
func (f *Frame) performGabConvolution() error {
// f.Buffer[c] is already wrong at this stage. We have -0.003936676 where as should be -0.000675
colours := f.getColourChannelCount()
normGabBase := make([]float32, colours)
normGabAdj := make([]float32, colours)
normGabDiag := make([]float32, colours)
for c := int32(0); c < colours; c++ {
gabW1 := f.Header.restorationFilter.gab1Weights[c]
gabW2 := f.Header.restorationFilter.gab2Weights[c]
mult := 1.0 / (1.0 + 4.0*(gabW1+gabW2))
normGabBase[c] = mult
normGabAdj[c] = gabW1 * mult
normGabDiag[c] = gabW2 * mult
}
for c := int32(0); c < colours; c++ {
if err := f.Buffer[c].CastToFloatIfMax(^(^0 << f.GlobalMetadata.BitDepth.BitsPerSample)); err != nil {
return err
}
height := f.Buffer[c].Height
width := f.Buffer[c].Width
buffC := f.Buffer[c].FloatBuffer
newBuffer, err := image.NewImageBuffer(image.TYPE_FLOAT, height, width)
if err != nil {
return err
}
newBufferF := newBuffer.FloatBuffer
// Capture values for closures
gabBase := normGabBase[c]
gabAdj := normGabAdj[c]
gabDiag := normGabDiag[c]
// Worker function to process a range of rows
processRows := func(startY, endY int32) {
for y := startY; y < endY; y++ {
var north int32
if y == 0 {
north = 0
} else {
north = y - 1
}
var south int32
if y+1 == height {
south = height - 1
} else {
south = y + 1
}
buffR := buffC[y]
buffN := buffC[north]
buffS := buffC[south]
newBuffR := newBufferF[y]
for x := int32(0); x < width; x++ {
var west int32
if x == 0 {
west = 0
} else {
west = x - 1
}
var east int32
if x+1 == width {
east = width - 1
} else {
east = x + 1
}
adj := buffR[west] + buffR[east] + buffN[x] + buffS[x]
diag := buffN[west] + buffN[east] + buffS[west] + buffS[east]
newBuffR[x] = gabBase*buffR[x] + gabAdj*adj + gabDiag*diag
}
}
}
// Divide work among goroutines
numWorkers := f.options.MaxGoroutines
if numWorkers < 1 {
numWorkers = 1
}
rowsPerWorker := (height + int32(numWorkers) - 1) / int32(numWorkers)
var wg sync.WaitGroup
for w := 0; w < numWorkers; w++ {
startY := int32(w) * rowsPerWorker
endY := startY + rowsPerWorker
if endY > height {
endY = height
}
if startY >= height {
break
}
wg.Add(1)
go func(sy, ey int32) {
defer wg.Done()
processRows(sy, ey)
}(startY, endY)
}
wg.Wait()
f.Buffer[c] = *newBuffer
}
return nil
}
func (f *Frame) performEdgePreservingFilter() error {
stepMultiplier := float32(1.65) * 4 * float32(1-SQRT_H)
paddedSize, err := f.GetPaddedFrameSize()
if err != nil {
return err
}
blockHeight := (paddedSize.Height + 7) >> 3
blockWidth := (paddedSize.Width + 7) >> 3
inverseSigma := util.MakeMatrix2DPooled[float32](int(blockHeight), int(blockWidth))
defer util.ReturnMatrix2DToPool(inverseSigma)
colours := f.getColourChannelCount()
if f.Header.Encoding == MODULAR {
inv := 1.0 / f.Header.restorationFilter.epfSigmaForModular
for y := 0; y < int(blockHeight); y++ {
for i := 0; i < len(inverseSigma[y]); i++ {
inverseSigma[y][i] = inv
}
}
} else {
globalScale := float32(65536.0) / float32(f.LfGlobal.globalScale)
for y := int32(0); y < int32(blockHeight); y++ {
lfY := y >> 8
bY := y - (lfY << 8)
lfR := lfY * int32(f.lfGroupRowStride)
for x := int32(0); x < int32(blockWidth); x++ {
lfX := x >> 8
bX := x - (lfX << 8)
lfg := f.lfGroups[lfR+lfX]
hf := lfg.hfMetadata.hfMultiplier[bY][bX]
sharpness := lfg.hfMetadata.hfStreamBuffer[3][bY][bX]
if sharpness < 0 || sharpness > 7 {
return errors.New("sharpness value out of range")
}
sigma := globalScale * float32(f.Header.restorationFilter.epfSharpLut[sharpness]) / float32(hf)
inverseSigma[y][x] = 1.0 / sigma
}
}
}
outputBuffer := make([]image.ImageBuffer, colours)
for c := int32(0); c < colours; c++ {
if err = f.Buffer[c].CastToFloatIfMax(^(^0 << f.GlobalMetadata.BitDepth.BitsPerSample)); err != nil {
return err
}
outBuf, err := image.NewImageBuffer(image.TYPE_FLOAT, int32(paddedSize.Height), int32(paddedSize.Width))
if err != nil {
return err
}
outputBuffer[c] = *outBuf
}
// Cache channel scales to avoid repeated field lookups
channelScales := f.Header.restorationFilter.epfChannelScale
borderSadMul := f.Header.restorationFilter.epfBorderSadMul
// Threshold for skipping EPF (1.0 / 0.3)
const skipThreshold = float32(1.0 / 0.3)
height := int32(paddedSize.Height)
width := int32(paddedSize.Width)
for i := 0; i < 3; i++ {
if i == 0 && f.Header.restorationFilter.epfIterations < 3 {
continue
}
if i == 2 && f.Header.restorationFilter.epfIterations < 2 {
break
}
// copy first 3 (well number of colours we have) buffers
inputBuffers := copyFloatBuffers(f.Buffer, colours)
outputBuffers := copyFloatBuffers(outputBuffer, colours)
defer util.ReturnMatrix3DToPool(inputBuffers)
// Note: Don't return outputBuffers to pool - they get swapped into f.Buffer
var sigmaScale float32
if i == 0 {
sigmaScale = stepMultiplier * f.Header.restorationFilter.epfPass0SigmaScale
} else if i == 2 {
sigmaScale = stepMultiplier * f.Header.restorationFilter.epfPass2SigmaScale
} else {
sigmaScale = stepMultiplier
}
useDoubleCross := i == 0
useDistance2 := i == 2
// Worker function to process a range of rows
processRows := func(startY, endY int32) {
// Pre-allocate per-worker buffers
var sumChannels [3]float32 // Max 3 colour channels
for y := startY; y < endY; y++ {
// Check if we're on a block boundary row for border SAD multiplier
modY := y & 0b111
isBorderY := modY == 0 || modY == 7
invSigmaRow := inverseSigma[y>>3]
// Get row pointers for all channels to avoid repeated indexing
var inRows, outRows [3][]float32
for c := int32(0); c < colours; c++ {
inRows[c] = inputBuffers[c][y]
outRows[c] = outputBuffers[c][y]
}
for x := int32(0); x < width; x++ {
s := invSigmaRow[x>>3]
if s > skipThreshold {
for c := int32(0); c < colours; c++ {
outRows[c][x] = inRows[c][x]
}
continue
}
// Check if we're on a block boundary for border SAD multiplier
modX := x & 0b111
isBorder := isBorderY || modX == 0 || modX == 7
sigmaScaleS := sigmaScale * s
sumWeights := float32(0)
for c := int32(0); c < colours; c++ {
sumChannels[c] = 0
}
if useDoubleCross {
// Process epfDoubleCross (13 points) with inlined distance1
for ci := 0; ci < 13; ci++ {
crossY := epfDoubleCross[ci].Y
crossX := epfDoubleCross[ci].X
// Inline epfDistance1: sum over epfCross (5 points)
dist := float32(0)
for c := int32(0); c < colours; c++ {
buffC := inputBuffers[c]
scale := channelScales[c]
// Unroll epfCross loop (5 iterations with known offsets)
// Point 0: (0, 0)
pY0 := y
pX0 := x
dY0 := mirrorCoord(y+crossY, height)
dX0 := mirrorCoord(x+crossX, width)
dist += absFloat32(buffC[pY0][pX0]-buffC[dY0][dX0]) * scale
// Point 1: (0, -1)
pY1 := y
pX1 := mirrorCoord(x-1, width)
dY1 := mirrorCoord(y+crossY, height)
dX1 := mirrorCoord(x+crossX-1, width)
dist += absFloat32(buffC[pY1][pX1]-buffC[dY1][dX1]) * scale
// Point 2: (0, 1)
pY2 := y
pX2 := mirrorCoord(x+1, width)
dY2 := mirrorCoord(y+crossY, height)
dX2 := mirrorCoord(x+crossX+1, width)
dist += absFloat32(buffC[pY2][pX2]-buffC[dY2][dX2]) * scale
// Point 3: (-1, 0)
pY3 := mirrorCoord(y-1, height)
pX3 := x
dY3 := mirrorCoord(y+crossY-1, height)
dX3 := mirrorCoord(x+crossX, width)
dist += absFloat32(buffC[pY3][pX3]-buffC[dY3][dX3]) * scale
// Point 4: (1, 0)
pY4 := mirrorCoord(y+1, height)
pX4 := x
dY4 := mirrorCoord(y+crossY+1, height)
dX4 := mirrorCoord(x+crossX, width)
dist += absFloat32(buffC[pY4][pX4]-buffC[dY4][dX4]) * scale
}
// Inline epfWeight
if isBorder {
dist *= borderSadMul
}
weight := 1.0 - dist*sigmaScaleS
if weight < 0 {
weight = 0
}
sumWeights += weight
mY := mirrorCoord(y+crossY, height)
mX := mirrorCoord(x+crossX, width)
for c := int32(0); c < colours; c++ {
sumChannels[c] += inputBuffers[c][mY][mX] * weight
}
}
} else if useDistance2 {
// Process epfCross (5 points) with inlined distance2
for ci := 0; ci < 5; ci++ {
crossY := epfCross[ci].Y
crossX := epfCross[ci].X
// Inline epfDistance2: simple single-point distance
dist := float32(0)
dY := mirrorCoord(y+crossY, height)
dX := mirrorCoord(x+crossX, width)
for c := int32(0); c < colours; c++ {
dist += absFloat32(inputBuffers[c][y][x]-inputBuffers[c][dY][dX]) * channelScales[c]
}
// Inline epfWeight
if isBorder {
dist *= borderSadMul
}
weight := 1.0 - dist*sigmaScaleS
if weight < 0 {
weight = 0
}
sumWeights += weight
for c := int32(0); c < colours; c++ {
sumChannels[c] += inputBuffers[c][dY][dX] * weight
}
}
} else {
// Process epfCross (5 points) with inlined distance1
for ci := 0; ci < 5; ci++ {
crossY := epfCross[ci].Y
crossX := epfCross[ci].X
// Inline epfDistance1
dist := float32(0)
for c := int32(0); c < colours; c++ {
buffC := inputBuffers[c]
scale := channelScales[c]
// Unrolled epfCross
pY0 := y
pX0 := x
dY0 := mirrorCoord(y+crossY, height)
dX0 := mirrorCoord(x+crossX, width)
dist += absFloat32(buffC[pY0][pX0]-buffC[dY0][dX0]) * scale
pY1 := y
pX1 := mirrorCoord(x-1, width)
dY1 := mirrorCoord(y+crossY, height)
dX1 := mirrorCoord(x+crossX-1, width)
dist += absFloat32(buffC[pY1][pX1]-buffC[dY1][dX1]) * scale
pY2 := y
pX2 := mirrorCoord(x+1, width)
dY2 := mirrorCoord(y+crossY, height)
dX2 := mirrorCoord(x+crossX+1, width)
dist += absFloat32(buffC[pY2][pX2]-buffC[dY2][dX2]) * scale
pY3 := mirrorCoord(y-1, height)
pX3 := x
dY3 := mirrorCoord(y+crossY-1, height)
dX3 := mirrorCoord(x+crossX, width)
dist += absFloat32(buffC[pY3][pX3]-buffC[dY3][dX3]) * scale
pY4 := mirrorCoord(y+1, height)
pX4 := x
dY4 := mirrorCoord(y+crossY+1, height)
dX4 := mirrorCoord(x+crossX, width)
dist += absFloat32(buffC[pY4][pX4]-buffC[dY4][dX4]) * scale
}
// Inline epfWeight
if isBorder {
dist *= borderSadMul
}
weight := 1.0 - dist*sigmaScaleS
if weight < 0 {
weight = 0
}
sumWeights += weight
mY := mirrorCoord(y+crossY, height)
mX := mirrorCoord(x+crossX, width)
for c := int32(0); c < colours; c++ {
sumChannels[c] += inputBuffers[c][mY][mX] * weight
}
}
}
invSumWeights := 1.0 / sumWeights
for c := int32(0); c < colours; c++ {
outRows[c][x] = sumChannels[c] * invSumWeights
}
}
}
}
// Divide work among goroutines
numWorkers := f.options.MaxGoroutines
if numWorkers < 1 {
numWorkers = 1
}
rowsPerWorker := (height + int32(numWorkers) - 1) / int32(numWorkers)
var wg sync.WaitGroup
for w := 0; w < numWorkers; w++ {
startY := int32(w) * rowsPerWorker
endY := startY + rowsPerWorker
if endY > height {
endY = height
}
if startY >= height {
break
}
wg.Add(1)
go func(sy, ey int32) {
defer wg.Done()
processRows(sy, ey)
}(startY, endY)
}
wg.Wait()
for c := 0; c < int(colours); c++ {
tmp := f.Buffer[c]
f.Buffer[c].FloatBuffer = outputBuffers[c]
outputBuffer[c] = tmp
}
}
return nil
}
// mirrorCoord is an optimized version of MirrorCoordinate for EPF hot path.
// For most pixels (not on edges), this is just a bounds check.
func mirrorCoord(coord int32, size int32) int32 {
if coord >= 0 && coord < size {
return coord
}
return util.MirrorCoordinate(coord, size)
}
func copyFloatBuffers(buffer []image.ImageBuffer, colours int32) [][][]float32 {
data := util.MakeMatrix3DPooled[float32](int(colours), int(buffer[0].Height), int(buffer[0].Width))
for c := int32(0); c < colours; c++ {
for y := int32(0); y < buffer[c].Height; y++ {
copy(data[c][y], buffer[c].FloatBuffer[y])
}
}
return data
}
func (f *Frame) getNumLFGroups() uint32 {
return f.numLFGroups
}
func (f *Frame) InitializeNoise(seed0 int64) error {
if len(f.LfGlobal.noiseParameters) == 0 {
return nil
}
return errors.New("noise not implemented")
//rowStride := util.CeilDiv(f.Header.Width, f.Header.groupDim)
//localNoiseBuffer := util.MakeMatrix3D[float32](3, int(f.Header.Height), int(f.Header.Width))
//numGroups := rowStride * util.CeilDiv(f.Header.Height, f.Header.groupDim)
//for group := uint32(0); group < numGroups; group++ {
// groupXYUp := util.Coordinates(group, rowStride).Times(f.Header.Upsampling)
// for iy := uint32(0); iy < f.Header.Upsampling; iy++ {
// for ix := uint32(0); ix < f.Header.Upsampling; ix++ {
// x0 := (groupXYUp.X + ix) * f.Header.groupDim
// y0 := (groupXYUp.Y + iy) * f.Header.groupDim
//
// }
// }
//}
}
func (f *Frame) Upsample() error {
for c := 0; c < len(f.Buffer); c++ {
if buf, err := f.performUpsampling(f.Buffer[c], c); err != nil {
return err
} else {
f.Buffer[c] = *buf
}
}
f.Header.Bounds.Size.Height *= f.Header.Upsampling
f.Header.Bounds.Size.Width *= f.Header.Upsampling
f.Header.Bounds.Origin.Y *= int32(f.Header.Upsampling)
f.Header.Bounds.Origin.X *= int32(f.Header.Upsampling)
f.groupRowStride = util.CeilDiv(f.Header.Bounds.Size.Width, f.Header.groupDim)
f.lfGroupRowStride = util.CeilDiv(f.Header.Bounds.Size.Width, f.Header.groupDim<<3)
f.numGroups = f.groupRowStride * util.CeilDiv(f.Header.Bounds.Size.Height, f.Header.groupDim)
f.numLFGroups = f.lfGroupRowStride * util.CeilDiv(f.Header.Bounds.Size.Height, f.Header.groupDim<<3)
return nil
}
func (f *Frame) performUpsampling(ib image.ImageBuffer, c int) (*image.ImageBuffer, error) {
colour := f.GetColourChannelCount()
var k uint32
if c < colour {
k = f.Header.Upsampling
} else {
k = f.Header.EcUpsampling[c-colour]
}
if k == 1 {
return &ib, nil
}
var depth uint32
if c < colour {
depth = f.GlobalMetadata.BitDepth.BitsPerSample
} else {
depth = f.GlobalMetadata.ExtraChannelInfo[c-colour].BitDepth.BitsPerSample
}
if err := ib.CastToFloatIfMax(^(^0 << depth)); err != nil {
return nil, err
}
buffer := ib.FloatBuffer
l := util.CeilLog1p(k-1) - 1
up, err := f.GlobalMetadata.GetUpWeights()
if err != nil {
return nil, err
}
upWeights := up[l]
newBuffer := util.MakeMatrix2D[float32](len(buffer)*int(k), 0)
for y := 0; y < len(buffer); y++ {
for ky := 0; ky < int(k); ky++ {
newBuffer[y*int(k)+ky] = make([]float32, len(buffer[y])*int(k))
for x := 0; x < len(buffer[y]); x++ {
for kx := 0; kx < int(k); kx++ {
weights := upWeights[ky][kx]
total := float32(0.0)
min := float32(math.MaxFloat32)
max := float32(math.SmallestNonzeroFloat32)
for iy := 0; iy < 5; iy++ {
for ix := 0; ix < 5; ix++ {
newY := util.MirrorCoordinate(int32(y)+int32(iy)-2, int32(len(buffer)))
newX := util.MirrorCoordinate(int32(x)+int32(ix)-2, int32(len(buffer[newY])))
sample := buffer[newY][newX]
if sample < min {
min = sample
}
if sample > max {
max = sample
}
total += weights[iy][ix] * sample
}
}
var val float32
if total < min {
val = min
} else if total > max {
val = max
} else {
val = total
}
newBuffer[y*int(k)+ky][x*int(k)+kx] = val
}
}
}
}
return image.NewImageBufferFromFloats(newBuffer), nil
}
func (f *Frame) RenderSplines() error {
if f.LfGlobal.splines == nil {
return nil
}
return errors.New("RenderSplines not implemented")
}
func (f *Frame) SynthesizeNoise() error {
if f.LfGlobal.noiseParameters == nil {
return nil
}
return errors.New("SynthesizeNoise not implemented")
}
func (f *Frame) getLFGroupSize(lfGroupID int32) (util.Dimension, error) {
pos := f.getLFGroupLocation(lfGroupID)
paddedSize, err := f.GetPaddedFrameSize()
if err != nil {
return util.Dimension{}, err
}
height := util.Min(f.Header.lfGroupDim, paddedSize.Height-uint32(pos.Y)*f.Header.lfGroupDim)
width := util.Min(f.Header.lfGroupDim, paddedSize.Width-uint32(pos.X)*f.Header.lfGroupDim)
return util.Dimension{
Height: height,
Width: width,
}, nil
}
func (f *Frame) getLFGroupLocation(lfGroupID int32) *util.Point {
return util.NewPoint(lfGroupID/int32(f.lfGroupRowStride), lfGroupID%int32(f.lfGroupRowStride))
}
func (f *Frame) getGroupLocation(groupID int32) *util.Point {
return util.NewPoint(groupID/int32(f.groupRowStride), groupID%int32(f.groupRowStride))
}
func (f *Frame) getLFGroupForGroup(groupID int32) *LFGroup {
pos := f.getGroupLocation(groupID)
idx := (pos.Y>>3)*int32(f.lfGroupRowStride) + (pos.X >> 3)
return f.lfGroups[idx]
}
func (f *Frame) groupPosInLFGroup(lfGroupID int32, groupID uint32) util.Point {
gr := f.getGroupLocation(int32(groupID))
lf := f.getLFGroupLocation(lfGroupID)
gr2 := *gr
gr2.Y = gr.Y - lf.Y<<3
gr2.X = gr.X - lf.X<<3
return gr2
}
func (f *Frame) getGroupSize(groupID int32) (util.Dimension, error) {
pos := f.getGroupLocation(groupID)
paddedSize, err := f.GetPaddedFrameSize()
if err != nil {
return util.Dimension{}, err
}
height := util.Min(f.Header.groupDim, paddedSize.Height-uint32(pos.Y)*f.Header.groupDim)
width := util.Min(f.Header.groupDim, paddedSize.Width-uint32(pos.X)*f.Header.groupDim)
return util.Dimension{
Height: height,
Width: width,
}, nil
}
func (f *Frame) getColourChannelCount() int32 {
if f.GlobalMetadata.XybEncoded || f.Header.Encoding == VARDCT {
return 3
}
return int32(f.GlobalMetadata.GetColourChannelCount())
}
// generate a total (signature?) for each row of each channel in the buffer.
// This is just to see if we can compare Go and Java
// Assume float buffer
// nolint (skip linting for now... will be used later)
func (f *Frame) generateSignaturesForBuffer(idx int) []string {
sigs := []string{}
var c = f.Buffer[idx]
for y := int32(0); y < int32(len(c.FloatBuffer)); y++ {
sig := float64(0)
xx := c.FloatBuffer[y]
if y == 288 {
var cc float32
for x := int32(0); x < int32(len(xx)); x++ {
cc += c.FloatBuffer[y][x]
nanCheck := fmt.Sprintf("%.4f", cc)
if nanCheck == "NaN" {
fmt.Print("NAN!\n")
checkVal := c.FloatBuffer[y][x]
fmt.Printf("Nan check value %f\n", checkVal)
//fmt.Printf("range %+v\n", c.FloatBuffer[y][x-10:x+10])
}
}
fmt.Printf("xx %f\n", cc)
}
for x := int32(0); x < int32(len(xx)); x++ {
sig += float64(c.FloatBuffer[y][x])
}
sigs = append(sigs, fmt.Sprintf("%.4f", sig))
}
return sigs
}
package frame
import (
"fmt"
"github.com/kpfaulkner/jxl-go/bundle"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
const (
REGULAR_FRAME = 0
LF_FRAME = 1
REFERENCE_ONLY = 2
SKIP_PROGRESSIVE = 3
// lossy
VARDCT = 0
// lossless
MODULAR = 1
NOISE = 1
PATCHES = 2
SPLINES = 16
USE_LF_FRAME = 32
SKIP_ADAPTIVE_LF_SMOOTHING = 128
BLEND_REPLACE = 0
BLEND_ADD = 1
BLEND_BLEND = 2
BLEND_MULADD = 3
BLEND_MULT = 4
)
type FrameHeader struct {
jpegUpsamplingX []int32
jpegUpsamplingY []int32
EcUpsampling []uint32
EcBlendingInfo []BlendingInfo
name string
Bounds *util.Rectangle
restorationFilter *RestorationFilter
extensions *bundle.Extensions
passes *PassesInfo
BlendingInfo *BlendingInfo
Flags uint64
FrameType uint32
Width uint32
Height uint32
Upsampling uint32
LfLevel uint32
groupDim uint32
Encoding uint32
groupSizeShift uint32
lfGroupDim uint32
logGroupDim uint32
logLFGroupDIM uint32
xqmScale uint32
bqmScale uint32
Duration uint32
timecode uint32
SaveAsReference uint32
SaveBeforeCT bool
DoYCbCr bool
haveCrop bool
IsLast bool
}
func NewFrameHeaderWithReader(reader jxlio.BitReader, parent *bundle.ImageHeader) (*FrameHeader, error) {
fh := &FrameHeader{}
var err error
var allDefault bool
if allDefault, err = reader.ReadBool(); err != nil {
return nil, err
}
if allDefault {
fh.FrameType = REGULAR_FRAME
fh.Encoding = VARDCT
fh.Flags = 0
} else {
if frameType, err := reader.ReadBits(2); err != nil {
return nil, err
} else {
fh.FrameType = uint32(frameType)
}
if encoding, err := reader.ReadBits(1); err != nil {
return nil, err
} else {
fh.Encoding = uint32(encoding)
}
if fh.Flags, err = reader.ReadU64(); err != nil {
return nil, err
}
}
if !allDefault && !parent.XybEncoded {
if fh.DoYCbCr, err = reader.ReadBool(); err != nil {
return nil, err
}
} else {
fh.DoYCbCr = false
}
//fh.jpegUpsampling = make([]util.IntPoint, 3)
fh.jpegUpsamplingX = make([]int32, 3)
fh.jpegUpsamplingY = make([]int32, 3)
if fh.DoYCbCr && (fh.Flags&USE_LF_FRAME) == 0 {
for i := 0; i < 3; i++ {
var mode uint64
if mode, err = reader.ReadBits(2); err != nil {
return nil, err
}
//y := reader.MustReadBits(1)
//x := reader.MustReadBits(1)
//fh.jpegUpsampling[i] = util.NewIntPointWithXY(uint32(x^y), uint32(y))
switch mode {
case 1:
fh.jpegUpsamplingY[i] = 1
fh.jpegUpsamplingX[i] = 1
case 2:
fh.jpegUpsamplingY[i] = 0
fh.jpegUpsamplingX[i] = 1
case 3:
fh.jpegUpsamplingY[i] = 1
fh.jpegUpsamplingX[i] = 0
default:
break
}
}
}
fh.EcUpsampling = make([]uint32, len(parent.ExtraChannelInfo))
if !allDefault && (fh.Flags&USE_LF_FRAME) == 0 {
if upsampling, err := reader.ReadBits(2); err != nil {
return nil, err
} else {
fh.Upsampling = 1 << upsampling
}
for i := 0; i < len(fh.EcUpsampling); i++ {
if ecUpsampling, err := reader.ReadBits(2); err != nil {
return nil, err
} else {
fh.EcUpsampling[i] = 1 << ecUpsampling
}
}
} else {
fh.Upsampling = 1
//fh.EcUpsampling = []uint32{1}
for i := 0; i < len(fh.EcUpsampling); i++ {
fh.EcUpsampling[i] = 1
}
}
if fh.Encoding == MODULAR {
if groupSizeShift, err := reader.ReadBits(2); err != nil {
return nil, err
} else {
fh.groupSizeShift = uint32(groupSizeShift)
}
} else {
fh.groupSizeShift = 1
}
fh.groupDim = 128 << fh.groupSizeShift
fh.lfGroupDim = fh.groupDim << 3
fh.logGroupDim = uint32(util.CeilLog2(int64(fh.groupDim)))
fh.logLFGroupDIM = uint32(util.CeilLog2(int64(fh.lfGroupDim)))
if parent.XybEncoded && fh.Encoding == VARDCT {
if !allDefault {
xqmScale, err := reader.ReadBits(3)
if err != nil {
return nil, err
}
fh.xqmScale = uint32(xqmScale)
bqmScale, err := reader.ReadBits(3)
if err != nil {
return nil, err
}
fh.bqmScale = uint32(bqmScale)
} else {
fh.xqmScale = 3
fh.bqmScale = 2
}
} else {
fh.xqmScale = 2
fh.bqmScale = 2
}
if !allDefault && fh.FrameType != REFERENCE_ONLY {
fh.passes, err = NewPassesInfoWithReader(reader)
if err != nil {
return nil, err
}
} else {
fh.passes = NewPassesInfo()
}
if fh.FrameType == LF_FRAME {
if lfLevel, err := reader.ReadBits(2); err != nil {
return nil, err
} else {
fh.LfLevel = uint32(lfLevel) + 1
}
} else {
fh.LfLevel = 0
}
if !allDefault && fh.FrameType != LF_FRAME {
if fh.haveCrop, err = reader.ReadBool(); err != nil {
return nil, err
}
} else {
fh.haveCrop = false
}
if fh.haveCrop && fh.FrameType != REFERENCE_ONLY {
var err error
var x0 uint32
if x0, err = reader.ReadU32(0, 8, 256, 11, 2304, 14, 18688, 30); err != nil {
return nil, err
}
var y0 uint32
if y0, err = reader.ReadU32(0, 8, 256, 11, 2304, 14, 18688, 30); err != nil {
return nil, err
}
x0Signed := jxlio.UnpackSigned(x0)
y0Signed := jxlio.UnpackSigned(y0)
if fh.Bounds == nil {
fh.Bounds = &util.Rectangle{
Origin: util.Point{},
Size: util.Dimension{},
}
}
fh.Bounds.Origin.X = x0Signed
fh.Bounds.Origin.Y = y0Signed
}
if fh.haveCrop {
if width, err := reader.ReadU32(0, 8, 256, 11, 2304, 14, 18688, 30); err != nil {
return nil, err
} else {
fh.Width = width
fh.Bounds.Size.Width = width
}
if height, err := reader.ReadU32(0, 8, 256, 11, 2304, 14, 18688, 30); err != nil {
return nil, err
} else {
fh.Height = height
fh.Bounds.Size.Height = height
}
} else {
if fh.Bounds == nil {
fh.Bounds = &util.Rectangle{
Origin: util.Point{X: 0, Y: 0},
Size: util.Dimension{},
}
}
fh.Bounds.Size = parent.Size
}
normalFrame := !allDefault && (fh.FrameType == REGULAR_FRAME || fh.FrameType == SKIP_PROGRESSIVE)
lowerCorner := fh.Bounds.ComputeLowerCorner()
//fullFrame := fh.Bounds.Origin.X <= 0 && fh.Bounds.Origin.Y <= 0 &&
// (fh.Width+uint32(fh.Bounds.Origin.X) >= parent.size.Width && (fh.Height+uint32(fh.Bounds.Origin.Y) >= parent.size.Height))
fullFrame := fh.Bounds.Origin.Y <= 0 && fh.Bounds.Origin.X <= 0 &&
lowerCorner.Y >= int32(parent.Size.Height) && lowerCorner.X >= int32(parent.Size.Width)
fh.Bounds.Size.Height = util.CeilDiv(fh.Bounds.Size.Height, fh.Upsampling)
fh.Bounds.Size.Width = util.CeilDiv(fh.Bounds.Size.Width, fh.Upsampling)
fh.Bounds.Size.Height = util.CeilDiv(fh.Bounds.Size.Height, 1<<(3*fh.LfLevel))
fh.Bounds.Size.Width = util.CeilDiv(fh.Bounds.Size.Width, 1<<(3*fh.LfLevel))
fh.EcBlendingInfo = make([]BlendingInfo, len(parent.ExtraChannelInfo))
if normalFrame {
fh.BlendingInfo, err = NewBlendingInfoWithReader(reader, len(fh.EcBlendingInfo) > 0, fullFrame)
if err != nil {
return nil, err
}
for i := 0; i < len(fh.EcBlendingInfo); i++ {
bi, err := NewBlendingInfoWithReader(reader, true, fullFrame)
if err != nil {
return nil, err
}
// store value not pointer. TODO(kpfaulkner) check this is fine.
fh.EcBlendingInfo[i] = *bi
}
} else {
fh.BlendingInfo = NewBlendingInfo()
for i := 0; i < len(fh.EcBlendingInfo); i++ {
fh.EcBlendingInfo[i] = *fh.BlendingInfo
}
}
// nolint
if normalFrame && parent.AnimationHeader != nil {
// dont care about animation
panic("animation")
dur, err := reader.ReadU32(0, 0, 1, 0, 0, 8, 0, 32)
if err != nil {
return nil, err
}
fh.Duration = dur
} else {
fh.Duration = 0
}
if normalFrame && parent.AnimationHeader != nil && parent.AnimationHeader.HaveTimeCodes {
// dont care about animation
tc, err := reader.ReadBits(32)
if err != nil {
return nil, err
}
fh.timecode = uint32(tc)
} else {
fh.timecode = 0
}
if normalFrame {
if fh.IsLast, err = reader.ReadBool(); err != nil {
return nil, err
}
} else {
fh.IsLast = fh.FrameType == REGULAR_FRAME
}
if !allDefault && fh.FrameType != LF_FRAME && !fh.IsLast {
if saveAsReference, err := reader.ReadBits(2); err != nil {
return nil, err
} else {
fh.SaveAsReference = uint32(saveAsReference)
}
} else {
fh.SaveAsReference = 0
}
if !allDefault && (fh.FrameType == REFERENCE_ONLY || fullFrame &&
(fh.FrameType == REGULAR_FRAME || fh.FrameType == SKIP_PROGRESSIVE) &&
(fh.Duration == 0 || fh.SaveAsReference != 0) &&
!fh.IsLast && fh.BlendingInfo.Mode == BLEND_REPLACE) {
if fh.SaveBeforeCT, err = reader.ReadBool(); err != nil {
return nil, err
}
} else {
fh.SaveBeforeCT = false
}
if allDefault {
fh.name = ""
} else {
var nameLen uint32
if nameLen, err = reader.ReadU32(0, 0, 0, 4, 16, 5, 48, 10); err != nil {
return nil, err
}
buffer := make([]byte, nameLen)
for i := 0; i < int(nameLen); i++ {
buffer[i], err = reader.ReadByte()
if err != nil {
return nil, err
}
}
fh.name = string(buffer)
}
if allDefault {
fh.restorationFilter = NewRestorationFilter()
} else {
fh.restorationFilter, err = NewRestorationFilterWithReader(reader, fh.Encoding)
if err != nil {
return nil, err
}
}
if allDefault {
fh.extensions = bundle.NewExtensions()
} else {
fh.extensions, err = bundle.NewExtensionsWithReader(reader)
if err != nil {
return nil, err
}
}
maxJPY := util.Max(fh.jpegUpsamplingY...)
maxJPX := util.Max(fh.jpegUpsamplingX...)
fh.Bounds.Size.Height = util.CeilDiv(fh.Bounds.Size.Height, 1<<maxJPY) << maxJPY
fh.Bounds.Size.Width = util.CeilDiv(fh.Bounds.Size.Width, 1<<maxJPX) << maxJPX
for i := 0; i < 3; i++ {
fh.jpegUpsamplingY[i] = maxJPY - fh.jpegUpsamplingY[i]
fh.jpegUpsamplingX[i] = maxJPX - fh.jpegUpsamplingX[i]
}
return fh, nil
}
// DisplayDebug will output header information to stdout for debugging purposes.
func (fh *FrameHeader) DisplayDebug() {
if fh == nil {
fmt.Println("FrameHeader: <nil>")
return
}
fmt.Printf("FrameHeader name=%q\n", fh.name)
fmt.Printf(" FrameType=%d Encoding=%d Flags=0x%x DoYCbCr=%v IsLast=%v\n", fh.FrameType, fh.Encoding, fh.Flags, fh.DoYCbCr, fh.IsLast)
fmt.Printf(" Width=%d Height=%d Upsampling=%d LfLevel=%d\n", fh.Width, fh.Height, fh.Upsampling, fh.LfLevel)
fmt.Printf(" groupDim=%d lfGroupDim=%d logGroupDim=%d logLFGroupDIM=%d\n", fh.groupDim, fh.lfGroupDim, fh.logGroupDim, fh.logLFGroupDIM)
if fh.Bounds != nil {
fmt.Printf(" Bounds Origin=(%d,%d) Size=(%d x %d)\n", fh.Bounds.Origin.X, fh.Bounds.Origin.Y, fh.Bounds.Size.Width, fh.Bounds.Size.Height)
} else {
fmt.Println(" Bounds: <nil>")
}
fmt.Printf(" jpegUpsamplingX=%v jpegUpsamplingY=%v\n", fh.jpegUpsamplingX, fh.jpegUpsamplingY)
fmt.Printf(" EcUpsampling=%v\n", fh.EcUpsampling)
fmt.Printf(" EcBlendingInfo count=%d\n", len(fh.EcBlendingInfo))
if fh.BlendingInfo != nil {
fmt.Printf(" BlendingInfo=%+v\n", *fh.BlendingInfo)
} else {
fmt.Println(" BlendingInfo: <nil>")
}
if fh.passes != nil {
fmt.Printf(" PassesInfo: %+v\n", *fh.passes)
} else {
fmt.Println(" PassesInfo: <nil>")
}
if fh.restorationFilter != nil {
fmt.Printf(" RestorationFilter: %+v\n", *fh.restorationFilter)
} else {
fmt.Println(" RestorationFilter: <nil>")
}
if fh.extensions != nil {
fmt.Printf(" Extensions: %+v\n", *fh.extensions)
} else {
fmt.Println(" Extensions: <nil>")
}
// Print a few useful flag tests
fmt.Printf(" Flags bits: USE_LF_FRAME=%v NOISE=%v PATCHES=%v SPLINES=%v\n",
fh.Flags&USE_LF_FRAME != 0, fh.Flags&NOISE != 0, fh.Flags&PATCHES != 0, fh.Flags&SPLINES != 0)
}
package frame
import (
"errors"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
type HFBlockContext struct {
lfThresholds [][]int32
clusterMap []int
numClusters int32
qfThresholds []int32
numLFContexts int32
}
type NewHFBlockContextFunc func(reader jxlio.BitReader, readClusterMap func(reader jxlio.BitReader, clusterMap []int, maxClusters int) (int, error)) (*HFBlockContext, error)
func NewHFBlockContextWithReader(reader jxlio.BitReader, readClusterMap func(reader jxlio.BitReader, clusterMap []int, maxClusters int) (int, error)) (*HFBlockContext, error) {
hf := &HFBlockContext{}
hf.lfThresholds = util.MakeMatrix2D[int32](3, 0)
useDefault, err := reader.ReadBool()
if err != nil {
return nil, err
}
if useDefault {
hf.clusterMap = []int{0, 1, 2, 2, 3, 3, 4, 5, 6, 6, 6, 6, 6,
7, 8, 9, 9, 10, 11, 12, 13, 14, 14, 14, 14, 14,
7, 8, 9, 9, 10, 11, 12, 13, 14, 14, 14, 14, 14}
hf.numClusters = 15
hf.qfThresholds = []int32{}
hf.lfThresholds = util.MakeMatrix2D[int32](3, 0)
hf.numLFContexts = 1
}
nbLFThresh := make([]int32, 3)
lfCtx := int32(1)
for i := 0; i < 3; i++ {
nb, err := reader.ReadBits(4)
if err != nil {
return nil, err
}
nbLFThresh[i] = int32(nb)
lfCtx *= nbLFThresh[i] + 1
hf.lfThresholds[i] = make([]int32, nbLFThresh[i])
for j := int32(0); j < nbLFThresh[i]; j++ {
t, err := reader.ReadU32(0, 4, 16, 8, 272, 16, 65808, 32)
if err != nil {
return nil, err
}
hf.lfThresholds[i][j] = jxlio.UnpackSigned(t)
}
}
hf.numLFContexts = lfCtx
nbQfThread, err := reader.ReadBits(4)
if err != nil {
return nil, err
}
hf.qfThresholds = make([]int32, nbQfThread)
for i := 0; i < int(nbQfThread); i++ {
t, err := reader.ReadU32(0, 2, 4, 3, 12, 5, 44, 8)
if err != nil {
return nil, err
}
hf.qfThresholds[i] = int32(t) + 1
}
bSize := 39 * (int32(nbQfThread) + 1)
for i := 0; i < 3; i++ {
bSize *= nbLFThresh[i] + 1
}
if bSize > 39*64 {
return nil, errors.New("HF block size too large")
}
hf.clusterMap = make([]int, bSize)
//nc, err := entropy.ReadClusterMap(reader, hf.clusterMap, 16)
nc, err := readClusterMap(reader, hf.clusterMap, 16)
if err != nil {
return nil, err
}
hf.numClusters = int32(nc)
return hf, nil
}
package frame
import (
"errors"
"fmt"
"math"
"slices"
"github.com/kpfaulkner/jxl-go/entropy"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
var (
coeffFreqCtx = []int32{
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26,
27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30}
coeffNumNonzeroCtx = []int32{
-1, 0, 31, 62, 62, 93, 93, 93, 93, 123, 123, 123, 123, 152, 152,
152, 152, 152, 152, 152, 152, 180, 180, 180, 180, 180, 180, 180,
180, 180, 180, 180, 180, 206, 206, 206, 206, 206, 206, 206, 206,
206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206,
206, 206, 206, 206, 206, 206, 206, 206, 206, 206}
)
type HFCoefficients struct {
hfPreset int32
groupID uint32
frame Framer
hfctx *HFBlockContext
lfg *LFGroup
stream entropy.EntropyStreamer
quantizedCoeffs [][][]int32
dequantHFCoeff [][][]float32
groupPos util.Point
blocks []*util.Point
}
func NewHFCoefficientsWithReader(reader jxlio.BitReader, frame Framer, pass uint32, group uint32) (*HFCoefficients, error) {
hf := &HFCoefficients{}
if frame == nil || reader == nil {
return nil, errors.New("frame or reader is nil")
}
hfPreset, err := reader.ReadBits(uint32(util.CeilLog1p(frame.getHFGlobal().numHFPresets - 1)))
if err != nil {
return nil, err
}
hf.hfPreset = int32(hfPreset)
hf.groupID = group
hf.frame = frame
hf.hfctx = frame.getLFGlobal().hfBlockCtx
hf.lfg = frame.getLFGroupForGroup(int32(group))
offset := 495 * hf.hfctx.numClusters * hf.hfPreset
header := frame.getFrameHeader()
shift := header.passes.shift[pass]
hfPass := hf.frame.getPasses()[pass].hfPass
size, err := hf.frame.getGroupSize(int32(hf.groupID))
if err != nil {
return nil, err
}
nonZeros := util.MakeMatrix3DPooled[int32](3, 32, 32)
defer util.ReturnMatrix3DToPool(nonZeros)
hf.stream = entropy.NewEntropyStreamWithStream(hfPass.contextStream)
hf.quantizedCoeffs = util.MakeMatrix3DPooled[int32](3, 0, 0)
hf.dequantHFCoeff = util.MakeMatrix3DPooled[float32](3, 0, 0)
for c := 0; c < 3; c++ {
sY := size.Height >> header.jpegUpsamplingY[c]
sX := size.Width >> header.jpegUpsamplingX[c]
hf.quantizedCoeffs[c] = util.MakeMatrix2DPooled[int32](int(sY), int(sX))
hf.dequantHFCoeff[c] = util.MakeMatrix2DPooled[float32](int(sY), int(sX))
}
hf.groupPos = hf.frame.groupPosInLFGroup(hf.lfg.lfGroupID, hf.groupID)
hf.groupPos.Y <<= 5
hf.groupPos.X <<= 5
hf.blocks = make([]*util.Point, len(hf.lfg.hfMetadata.blockList))
for i := 0; i < len(hf.lfg.hfMetadata.blockList); i++ {
posInLfg := hf.lfg.hfMetadata.blockList[i]
groupY := posInLfg.Y - hf.groupPos.Y
groupX := posInLfg.X - hf.groupPos.X
if groupY < 0 || groupX < 0 || groupY >= 32 || groupX >= 32 {
continue
}
hf.blocks[i] = &posInLfg
tt := hf.lfg.hfMetadata.dctSelect[posInLfg.Y][posInLfg.X]
flip := tt.flip()
hfMult := hf.lfg.hfMetadata.hfMultiplier[posInLfg.Y][posInLfg.X]
lfIndex := hf.lfg.lfCoeff.lfIndex[posInLfg.Y][posInLfg.X]
numBlocks := tt.dctSelectHeight * tt.dctSelectWidth
for _, c := range cMap {
sGroupY := groupY >> header.jpegUpsamplingY[c]
sGroupX := groupX >> header.jpegUpsamplingX[c]
if groupY != sGroupY<<header.jpegUpsamplingY[c] || groupX != sGroupX<<header.jpegUpsamplingX[c] {
continue
}
pixelGroupY := sGroupY << 3
pixelGroupX := sGroupX << 3
predicted := getPredictedNonZeros(nonZeros, c, sGroupY, sGroupX)
blockCtx := hf.getBlockContext(c, tt.orderID, hfMult, lfIndex)
nonZeroCtx := offset + hf.getNonZeroContext(predicted, blockCtx)
nonZero, err := hf.stream.ReadSymbol(reader, int(nonZeroCtx))
if err != nil {
return nil, err
}
nz := nonZeros[c]
for iy := int32(0); iy < tt.dctSelectHeight; iy++ {
for ix := int32(0); ix < tt.dctSelectWidth; ix++ {
nz[sGroupY+iy][sGroupX+ix] = (nonZero + numBlocks - 1) / numBlocks
}
}
// TODO(kpfaulkner) check this... taken from JXLatte
if nonZero <= 0 {
continue
}
orderSize := int32(len(hfPass.order[tt.orderID][c]))
ucoeffLen := orderSize - numBlocks
histCtx := offset + 458*blockCtx + 37*hf.hfctx.numClusters
// Track only previous coefficient value instead of allocating full slice
// This eliminates allocation of (orderSize-numBlocks) int32s per block/channel
var prevUcoeff int32
for k := int32(0); k < ucoeffLen; k++ {
var prev int32
if k == 0 {
if nonZero > orderSize/16 {
prev = 0
} else {
prev = 1
}
} else {
if prevUcoeff != 0 {
prev = 1
} else {
prev = 0
}
}
ctx := histCtx + hf.getCoefficientContext(k+numBlocks, nonZero, numBlocks, prev)
uc, err := hf.stream.ReadSymbol(reader, int(ctx))
if err != nil {
return nil, err
}
prevUcoeff = uc
order := hfPass.order[tt.orderID][c][k+numBlocks]
posY := pixelGroupY
posX := pixelGroupX
if flip {
posY += order.X
posX += order.Y
} else {
posY += order.Y
posX += order.X
}
hf.quantizedCoeffs[c][posY][posX] = jxlio.UnpackSigned(uint32(uc)) << shift
if uc != 0 {
nonZero--
if nonZero == 0 {
break
}
}
}
if nonZero != 0 {
return nil, errors.New("nonZero != 0")
}
}
}
if !hf.stream.ValidateFinalState() {
return nil, fmt.Errorf("Illegal final state in passgroup pass %d : group %d", pass, group)
}
return hf, nil
}
func (hf *HFCoefficients) DisplayHFCoefficients() {
hf.displayQuantizedCoeffs()
hf.displayDequantHFCoeff()
}
func (hf *HFCoefficients) displayQuantizedCoeffs() {
grandTotal := int32(0)
for c := 0; c < 3; c++ {
for y := 0; y < len(hf.quantizedCoeffs[c]); y++ {
total := int32(0)
for x := 0; x < len(hf.quantizedCoeffs[c][y]); x++ {
total += hf.quantizedCoeffs[c][y][x]
}
grandTotal += total
}
}
}
func (hf *HFCoefficients) displayDequantHFCoeff() {
for c := 0; c < 3; c++ {
for y := 0; y < len(hf.dequantHFCoeff[c]); y++ {
total := float32(0)
for x := 0; x < len(hf.dequantHFCoeff[c][y]); x++ {
total += hf.dequantHFCoeff[c][y][x]
}
}
}
}
func (hf *HFCoefficients) getBlockContext(c int, orderID int32, hfMult int32, lfIndex int32) int32 {
var idx int
if c < 2 {
idx = 1 - c
} else {
idx = c
}
idx = idx*13 + int(orderID)
idx *= len(hf.hfctx.qfThresholds) + 1
for _, t := range hf.hfctx.qfThresholds {
if hfMult > t {
idx++
}
}
idx *= int(hf.hfctx.numLFContexts)
return int32(hf.hfctx.clusterMap[int32(idx)+lfIndex])
}
func (hf *HFCoefficients) getNonZeroContext(predicted int32, ctx int32) int32 {
if predicted > 64 {
predicted = 64
}
if predicted < 8 {
return ctx + hf.hfctx.numClusters*predicted
}
return ctx + hf.hfctx.numClusters*(4+predicted/2)
}
func (hf *HFCoefficients) getCoefficientContext(k int32, nonZeros int32, numBlocks int32, prev int32) int32 {
nonZeros = (nonZeros + numBlocks - 1) / numBlocks
k /= numBlocks
return (coeffNumNonzeroCtx[nonZeros]+coeffFreqCtx[k])*2 + prev
}
func (hf *HFCoefficients) bakeDequantizedCoeffs() error {
if err := hf.dequantizeHFCoefficients(); err != nil {
return err
}
if err := hf.chromaFromLuma(); err != nil {
return err
}
if err := hf.finalizeLLF(); err != nil {
return err
}
return nil
}
func (hf *HFCoefficients) dequantizeHFCoefficients() error {
matrix := hf.frame.getGlobalMetadata().OpsinInverseMatrix
header := hf.frame.getFrameHeader()
globalScale := 65536.0 / float32(hf.frame.getLFGlobal().globalScale)
scaleFactor := [3]float32{
globalScale * float32(math.Pow(0.8, float64(header.xqmScale-2))),
globalScale,
globalScale * float32(math.Pow(0.8, float64(header.bqmScale-2))),
}
weights := hf.frame.getHFGlobal().weights
qbclut := [][]float32{
{-matrix.QuantBias[0], 0.0, matrix.QuantBias[0]},
{-matrix.QuantBias[1], 0.0, matrix.QuantBias[1]},
{-matrix.QuantBias[2], 0.0, matrix.QuantBias[2]},
}
for i := 0; i < len(hf.blocks); i++ {
pos := hf.blocks[i]
if pos == nil {
continue
}
tt := hf.lfg.hfMetadata.dctSelect[pos.Y][pos.X]
groupY := pos.Y - hf.groupPos.Y
groupX := pos.X - hf.groupPos.X
flip := tt.flip()
w2 := weights[tt.parameterIndex]
for c := 0; c < 3; c++ {
sGroupY := groupY >> header.jpegUpsamplingY[c]
sGroupX := groupX >> header.jpegUpsamplingX[c]
if groupY != sGroupY<<header.jpegUpsamplingY[c] ||
groupX != sGroupX<<header.jpegUpsamplingX[c] {
continue
}
w3 := w2[c]
sfc := scaleFactor[c] / float32(hf.lfg.hfMetadata.hfMultiplier[pos.Y][pos.X])
pixelGroupY := sGroupY << 3
pixelGroupX := sGroupX << 3
qbc := qbclut[c]
for y := int32(0); y < tt.pixelHeight; y++ {
for x := int32(0); x < tt.pixelWidth; x++ {
if y < tt.dctSelectHeight && x < tt.dctSelectWidth {
continue
}
pY := pixelGroupY + y
pX := pixelGroupX + x
coeff := hf.quantizedCoeffs[c][pY][pX]
var quant float32
if coeff > -2 && coeff < 2 {
quant = qbc[coeff+1]
} else {
quant = float32(coeff) - matrix.QuantBiasNumerator/float32(coeff)
}
var wy int32
if flip {
wy = x
} else {
wy = y
}
wx := x ^ y ^ wy
hf.dequantHFCoeff[c][pY][pX] = quant * sfc * w3[wy][wx]
}
}
}
}
return nil
}
func (hf *HFCoefficients) chromaFromLuma() error {
header := hf.frame.getFrameHeader()
xMatch := slices.ContainsFunc(header.jpegUpsamplingX, func(x int32) bool { return x != 0 })
yMatch := slices.ContainsFunc(header.jpegUpsamplingY, func(y int32) bool { return y != 0 })
if xMatch || yMatch {
return nil
}
lfc := hf.frame.getLFGlobal().lfChanCorr
xFactorHF := hf.lfg.hfMetadata.hfStreamBuffer[0]
bFactorHF := hf.lfg.hfMetadata.hfStreamBuffer[1]
xFactors := util.MakeMatrix2D[float32](len(xFactorHF), len(xFactorHF[0]))
bFactors := util.MakeMatrix2D[float32](len(bFactorHF), len(bFactorHF[0]))
// Cache the float32 conversion outside the loops to avoid per-pixel conversion
invColorFactor := 1.0 / float32(lfc.colorFactor)
baseCorrelationX := lfc.baseCorrelationX
baseCorrelationB := lfc.baseCorrelationB
for i := 0; i < len(hf.blocks); i++ {
pos := hf.blocks[i]
if pos == nil {
continue
}
tt := hf.lfg.hfMetadata.dctSelect[pos.Y][pos.X]
pPosY := pos.Y << 3
pPosX := pos.X << 3
for iy := int32(0); iy < tt.pixelHeight; iy++ {
y := pPosY + iy
fy := y >> 6
by := fy<<6 == y
xF := xFactors[fy]
bF := bFactors[fy]
hfX := xFactorHF[fy]
hfB := bFactorHF[fy]
for ix := int32(0); ix < tt.pixelWidth; ix++ {
x := pPosX + ix
fx := x >> 6
var kX float32
var kB float32
if by && fx<<6 == x {
kX = baseCorrelationX + float32(hfX[fx])*invColorFactor
kB = baseCorrelationB + float32(hfB[fx])*invColorFactor
xF[fx] = kX
bF[fx] = kB
} else {
kX = xF[fx]
kB = bF[fx]
}
dequantY := hf.dequantHFCoeff[1][y&0xFF][x&0xFF]
hf.dequantHFCoeff[0][y&0xFF][x&0xFF] += kX * dequantY
hf.dequantHFCoeff[2][y&0xFF][x&0xFF] += kB * dequantY
}
}
}
return nil
}
func (hf *HFCoefficients) finalizeLLF() error {
scratchBlock := util.MakeMatrix3DPooled[float32](2, 32, 32)
defer util.ReturnMatrix3DToPool(scratchBlock)
header := hf.frame.getFrameHeader()
for i := 0; i < len(hf.blocks); i++ {
posInLfg := hf.blocks[i]
if posInLfg == nil {
continue
}
tt := hf.lfg.hfMetadata.dctSelect[posInLfg.Y][posInLfg.X]
groupY := posInLfg.Y - hf.groupPos.Y
groupX := posInLfg.X - hf.groupPos.X
for c := 0; c < 3; c++ {
sGroupY := groupY >> header.jpegUpsamplingY[c]
sGroupX := groupX >> header.jpegUpsamplingX[c]
if groupY != sGroupY<<header.jpegUpsamplingY[c] ||
groupX != sGroupX<<header.jpegUpsamplingX[c] {
continue
}
pixelGroupY := sGroupY << 3
pixelGroupX := sGroupX << 3
sLfgY := posInLfg.Y >> header.jpegUpsamplingY[c]
sLfgX := posInLfg.X >> header.jpegUpsamplingX[c]
dqlf := hf.lfg.lfCoeff.dequantLFCoeff[c]
dq := hf.dequantHFCoeff[c]
if err := util.ForwardDCT2D(dqlf, dq, util.Point{X: sLfgX, Y: sLfgY},
util.Point{X: pixelGroupX, Y: pixelGroupY},
tt.getDctSelectSize(), scratchBlock[0], scratchBlock[1], false); err != nil {
return err
}
for y := int32(0); y < tt.dctSelectHeight; y++ {
dqy := dq[y+pixelGroupY]
llfy := tt.llfScale[y]
for x := int32(0); x < tt.dctSelectWidth; x++ {
dqy[x+pixelGroupX] *= llfy[x]
}
}
}
}
return nil
}
// Release returns pooled buffers to the pool
func (hf *HFCoefficients) Release() {
if hf.quantizedCoeffs != nil {
util.ReturnMatrix3DToPool(hf.quantizedCoeffs)
hf.quantizedCoeffs = nil
}
if hf.dequantHFCoeff != nil {
util.ReturnMatrix3DToPool(hf.dequantHFCoeff)
hf.dequantHFCoeff = nil
}
}
func getPredictedNonZeros(nonZeros [][][]int32, c int, y int32, x int32) int32 {
if x == 0 && y == 0 {
return 32
}
if x == 0 {
return nonZeros[c][y-1][0]
}
if y == 0 {
return nonZeros[c][0][x-1]
}
return (nonZeros[c][y-1][x] + nonZeros[c][y][x-1] + 1) >> 1
}
package frame
import (
"errors"
"fmt"
"math"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
var (
defaultParams []DCTParam
dct4x4params = [][]float64{
{2200, 0.0, 0.0, 0.0},
{392, 0.0, 0.0, 0.0},
{112, -0.25, -0.25, -0.5}}
dct4x8params = [][]float64{
{2198.050556016380522, -0.96269623020744692, -0.76194253026666783, -0.6551140670773547},
{764.3655248643528689, -0.92630200888366945, -0.9675229603596517, -0.27845290869168118},
{527.107573587542228, -1.4594385811273854, -1.450082094097871593, -1.5843722511996204}}
afvFreqs = []float64{0, 0, 0.8517778890324296, 5.37778436506804,
0, 0, 4.734747904497923, 5.449245381693219, 1.6598270267479331, 4, 7.275749096817861,
10.423227632456525, 2.662932286148962, 7.630657783650829, 8.962388608184032, 12.97166202570235}
seqA = []float64{-1.025, -0.78, -0.65012, -0.19041574084286472, -0.20819395464, -0.421064, -0.32733845535848671}
seqB = []float64{-0.3041958212306401, -0.3633036457487539, -0.35660379990111464, -0.3443074455424403, -0.33699592683512467, -0.30180866526242109, -0.27321683125358037}
seqC = []float64{-1.2, -1.2, -0.8, -0.7, -0.7, -0.4, -0.5}
)
type HFGlobal struct {
params []DCTParam
weights [][][][]float32
numHFPresets int32
}
func init() {
setupDefaultParams()
}
func setupDefaultParams() {
defaultParams = make([]DCTParam, 17)
defaultParams[0] = DCTParam{
dctParam: [][]float64{
{3150.0, 0.0, -0.4, -0.4, -0.4, -2.0},
{560.0, 0.0, -0.3, -0.3, -0.3, -0.3},
{512.0, -2.0, -1.0, 0.0, -1.0, -2.0}},
param: nil,
mode: MODE_DCT,
denominator: 1,
params4x4: nil,
}
defaultParams[1] = DCTParam{
dctParam: nil,
param: [][]float32{
{280.0, 3160.0, 3160.0},
{60.0, 864.0, 864.0},
{18.0, 200.0, 200.0}},
mode: MODE_HORNUSS,
denominator: 1,
params4x4: nil,
}
defaultParams[2] = DCTParam{
dctParam: nil,
param: [][]float32{
{3840.0, 2560.0, 1280.0, 640.0, 480.0, 300.0},
{960.0, 640.0, 320.0, 180.0, 140.0, 120.0},
{640.0, 320.0, 128.0, 64.0, 32.0, 16.0}},
mode: MODE_DCT2,
denominator: 1,
params4x4: nil,
}
defaultParams[3] = DCTParam{
dctParam: dct4x4params,
param: [][]float32{
{1.0, 1.0},
{1.0, 1.0},
{1.0, 1.0}},
mode: MODE_DCT4,
denominator: 1,
params4x4: dct4x4params,
}
defaultParams[4] = DCTParam{
dctParam: [][]float64{
{8996.8725711814115328, -1.3000777393353804, -0.49424529824571225, -0.439093774457103443, -0.6350101832695744, -0.90177264050827612, -1.6162099239887414},
{3191.48366296844234752, -0.67424582104194355, -0.80745813428471001, -0.44925837484843441, -0.35865440981033403, -0.31322389111877305, -0.37615025315725483},
{1157.50408145487200256, -2.0531423165804414, -1.4, -0.50687130033378396, -0.42708730624733904, -1.4856834539296244, -4.9209142884401604}},
param: nil,
mode: MODE_DCT,
denominator: 1,
params4x4: nil,
}
defaultParams[5] = DCTParam{
dctParam: [][]float64{
{15718.40830982518931456, -1.025, -0.98, -0.9012, -0.4, -0.48819395464, -0.421064, -0.27},
{7305.7636810695983104, -0.8041958212306401, -0.7633036457487539, -0.55660379990111464, -0.49785304658857626, -0.43699592683512467, -0.40180866526242109, -0.27321683125358037},
{3803.53173721215041536, -3.060733579805728, -2.0413270132490346, -2.0235650159727417, -0.5495389509954993, -0.4, -0.4, -0.3}},
param: nil,
mode: MODE_DCT,
denominator: 1,
params4x4: nil,
}
defaultParams[6] = DCTParam{
dctParam: [][]float64{
{7240.7734393502, -0.7, -0.7, -0.2, -0.2, -0.2, -0.5},
{1448.15468787004, -0.5, -0.5, -0.5, -0.2, -0.2, -0.2},
{506.854140754517, -1.4, -0.2, -0.5, -0.5, -1.5, -3.6}},
param: nil,
mode: MODE_DCT,
denominator: 1,
params4x4: nil,
}
defaultParams[7] = DCTParam{
dctParam: [][]float64{
{16283.2494710648897, -1.7812845336559429, -1.6309059012653515, -1.0382179034313539, -0.85, -0.7, -0.9, -1.2360638576849587},
{5089.15750884921511936, -0.320049391452786891, -0.35362849922161446, -0.30340000000000003, -0.61, -0.5, -0.5, -0.6},
{3397.77603275308720128, -0.321327362693153371, -0.34507619223117997, -0.70340000000000003, -0.9, -1.0, -1.0, -1.1754605576265209}},
param: nil,
mode: MODE_DCT,
denominator: 1,
params4x4: nil,
}
defaultParams[8] = DCTParam{
dctParam: [][]float64{
{13844.97076442300573, -0.97113799999999995, -0.658, -0.42026, -0.22712, -0.2206, -0.226, -0.6},
{4798.964084220744293, -0.61125308982767057, -0.83770786552491361, -0.79014862079498627, -0.2692727459704829, -0.38272769465388551, -0.22924222653091453, -0.20719098826199578},
{1807.236946760964614, -1.2, -1.2, -0.7, -0.7, -0.7, -0.4, -0.5}},
param: nil,
mode: MODE_DCT,
denominator: 1,
params4x4: nil,
}
defaultParams[9] = DCTParam{
dctParam: dct4x8params,
param: [][]float32{{1.0}, {1.0}, {1.0}},
mode: MODE_DCT4_8,
denominator: 1,
params4x4: nil,
}
defaultParams[10] = DCTParam{
dctParam: dct4x8params,
param: [][]float32{
{3072, 3072, 256, 256, 256, 414, 0.0, 0.0, 0.0},
{1024, 1024, 50.0, 50.0, 50.0, 58, 0.0, 0.0, 0.0},
{384, 384, 12.0, 12.0, 12.0, 22, -0.25, -0.25, -0.25}},
mode: MODE_AFV,
denominator: 1,
params4x4: dct4x4params,
}
defaultParams[11] = DCTParam{
dctParam: [][]float64{
append([]float64{23966.1665298448605}, seqA[:]...),
append([]float64{8380.19148390090414}, seqB[:]...),
append([]float64{4493.02378009847706}, seqC[:]...),
},
param: nil,
mode: MODE_DCT,
denominator: 1,
params4x4: nil,
}
defaultParams[12] = DCTParam{
dctParam: [][]float64{
append([]float64{15358.89804933239925}, seqA[:]...),
append([]float64{5597.360516150652990}, seqB[:]...),
append([]float64{2919.961618960011210}, seqC[:]...),
},
param: nil,
mode: MODE_DCT,
denominator: 1,
params4x4: nil,
}
defaultParams[13] = DCTParam{
dctParam: [][]float64{
append([]float64{47932.3330596897210}, seqA[:]...),
append([]float64{16760.38296780180828}, seqB[:]...),
append([]float64{8986.04756019695412}, seqC[:]...),
},
param: nil,
mode: MODE_DCT,
denominator: 1,
params4x4: nil,
}
defaultParams[14] = DCTParam{
dctParam: [][]float64{
append([]float64{30717.796098664792}, seqA[:]...),
append([]float64{11194.72103230130598}, seqB[:]...),
append([]float64{5839.92323792002242}, seqC[:]...),
},
param: nil,
mode: MODE_DCT,
denominator: 1,
params4x4: nil,
}
defaultParams[15] = DCTParam{
dctParam: [][]float64{
append([]float64{95864.6661193794420}, seqA[:]...),
append([]float64{33520.76593560361656}, seqB[:]...),
append([]float64{17972.09512039390824}, seqC[:]...),
},
param: nil,
mode: MODE_DCT,
denominator: 1,
params4x4: nil,
}
defaultParams[16] = DCTParam{
dctParam: [][]float64{
append([]float64{61435.5921973295970}, seqA[:]...),
append([]float64{24209.44206460261196}, seqB[:]...),
append([]float64{12979.84647584004484}, seqC[:]...),
},
param: nil,
mode: MODE_DCT,
denominator: 1,
params4x4: nil,
}
}
func NewHFGlobalWithReader(reader jxlio.BitReader, frame *Frame) (*HFGlobal, error) {
hf := &HFGlobal{}
quantAllDefault, err := reader.ReadBool()
if err != nil {
return nil, err
}
if quantAllDefault {
hf.params = defaultParams
} else {
hf.params = make([]DCTParam, 17)
for i := int32(0); i < 17; i++ {
if err := hf.setupDCTParam(reader, frame, i); err != nil {
return nil, err
}
}
}
hf.weights = util.MakeMatrix4D[float32](17, 3, 0, 0)
for i := 0; i < 17; i++ {
if err := hf.generateWeights(i); err != nil {
return nil, err
}
}
//hf.totalWeights()
numPresets, err := reader.ReadBits(uint32(util.CeilLog1p(frame.numGroups - 1)))
if err != nil {
return nil, err
}
hf.numHFPresets = 1 + int32(numPresets)
return hf, nil
}
// nolint
func (hfg *HFGlobal) getHFPresets() int32 {
return hfg.numHFPresets
}
// nolint
func (hfg *HFGlobal) totalWeights() {
var total float32 = 0
for index := 0; index < 17; index++ {
for c := 0; c < len(hfg.weights[index]); c++ {
for y := 0; y < len(hfg.weights[index][c]); y++ {
for x := 0; x < len(hfg.weights[index][c][y]); x++ {
total += hfg.weights[index][c][y][x]
}
}
}
}
fmt.Printf("total weight %f\n", total)
}
// nolint
func (hfg *HFGlobal) displayWeights() {
for index := 0; index < 17; index++ {
for c := 0; c < len(hfg.weights[index]); c++ {
for y := 0; y < len(hfg.weights[index][c]); y++ {
for x := 0; x < len(hfg.weights[index][c][y]); x++ {
print(hfg.weights[index][c][y][x], " ")
}
println()
}
println()
}
}
}
// nolint
func (hfg *HFGlobal) displaySpecificWeights(index int, c int, y int) {
for x := 0; x < len(hfg.weights[index][c][y]); x++ {
fmt.Printf("%f ", hfg.weights[index][c][y][x])
}
fmt.Printf("\n")
}
func (hfg *HFGlobal) setupDCTParam(reader jxlio.BitReader, frame *Frame, index int32) error {
encodingMode, err := reader.ReadBits(3)
if err != nil {
return err
}
_, err = validateIndex(index, int32(encodingMode))
if err != nil {
return err
}
switch encodingMode {
case MODE_LIBRARY:
hfg.params[index] = defaultParams[index]
case MODE_HORNUSS:
m := util.MakeMatrix2D[float32](3, 3)
for y := int32(0); y < 3; y++ {
for x := int32(0); x < 3; x++ {
mm, err := reader.ReadF16()
if err != nil {
return err
}
m[y][x] = 64.0 * mm
}
}
hfg.params[index] = DCTParam{dctParam: nil, param: m, mode: MODE_HORNUSS, denominator: 1, params4x4: nil}
case MODE_DCT2:
m := util.MakeMatrix2D[float32](3, 6)
for y := int32(0); y < 3; y++ {
for x := int32(0); x < 6; x++ {
mm, err := reader.ReadF16()
if err != nil {
return err
}
m[y][x] = 64.0 * mm
}
}
hfg.params[index] = DCTParam{dctParam: nil, param: m, mode: MODE_DCT2, denominator: 1, params4x4: nil}
case MODE_DCT4:
m := util.MakeMatrix2D[float32](3, 2)
for y := int32(0); y < 3; y++ {
for x := int32(0); x < 2; x++ {
mm, err := reader.ReadF16()
if err != nil {
return err
}
m[y][x] = 64.0 * mm
}
}
dctParam, err := hfg.readDCTParams(reader)
if err != nil {
return err
}
hfg.params[index] = DCTParam{dctParam: dctParam, param: m, mode: MODE_DCT4, denominator: 1, params4x4: nil}
case MODE_DCT:
dctParam, err := hfg.readDCTParams(reader)
if err != nil {
return err
}
hfg.params[index] = DCTParam{dctParam: dctParam, param: nil, mode: MODE_DCT, denominator: 1, params4x4: nil}
case MODE_RAW:
den, err := reader.ReadF16()
if err != nil {
return err
}
var tt *TransformType
if tt, err = getHorizontalTransformType(index); err != nil {
return err
}
info := make([]ModularChannel, 3)
info[0] = *NewModularChannelWithAllParams(tt.matrixHeight, tt.matrixWidth, 0, 0, false)
info[1] = *NewModularChannelWithAllParams(tt.matrixHeight, tt.matrixWidth, 0, 0, false)
info[2] = *NewModularChannelWithAllParams(tt.matrixHeight, tt.matrixWidth, 0, 0, false)
stream, err := NewModularStreamWithStreamIndex(reader, frame, int(1+3*int32(frame.numLFGroups)+index), info)
if err != nil {
return err
}
if err = stream.decodeChannels(reader, false); err != nil {
return err
}
m := util.MakeMatrix2D[float32](3, tt.matrixWidth*tt.matrixHeight)
b := stream.getDecodedBuffer()
for c := 0; c < 3; c++ {
for y := 0; y < len(b[c]); y++ {
for x := 0; x < len(b[c][y]); x++ {
m[c][y*int(tt.matrixWidth)+x] = float32(b[c][y][x])
}
}
}
hfg.params[index] = DCTParam{dctParam: nil, param: m, mode: MODE_RAW, denominator: den, params4x4: nil}
case MODE_AFV:
m := util.MakeMatrix2D[float32](3, 9)
for y := int32(0); y < 3; y++ {
for x := int32(0); x < 9; x++ {
mm, err := reader.ReadF16()
if err != nil {
return err
}
m[y][x] = mm
if x < 6 {
m[y][x] *= 64.0
}
}
}
var d [][]float64
if d, err = hfg.readDCTParams(reader); err != nil {
return err
}
var f [][]float64
if f, err = hfg.readDCTParams(reader); err != nil {
return err
}
hfg.params[index] = DCTParam{dctParam: d, param: m, mode: MODE_AFV, denominator: 1, params4x4: f}
default:
return errors.New("Invalid encoding mode")
}
return nil
}
func (hfg *HFGlobal) readDCTParams(reader jxlio.BitReader) ([][]float64, error) {
var numParams uint64
var err error
if numParams, err = reader.ReadBits(4); err != nil {
return nil, err
}
numParams++
vals := util.MakeMatrix2D[float64](3, numParams)
for c := 0; c < 3; c++ {
for i := 0; i < int(numParams); i++ {
var v float32
if v, err = reader.ReadF16(); err != nil {
return nil, err
}
vals[c][i] = float64(v)
}
vals[c][0] *= 64
}
return vals, nil
}
func (hfg *HFGlobal) generateWeights(index int) error {
var tt *TransformType
var err error
if tt, err = getHorizontalTransformType(int32(index)); err != nil {
return err
}
for c := 0; c < 3; c++ {
var w [][]float32
switch hfg.params[index].mode {
case MODE_DCT:
hfg.weights[index][c] = hfg.getDCTQuantWeights(tt.matrixHeight, tt.matrixWidth, hfg.params[index].dctParam[c])
case MODE_DCT4:
hfg.weights[index][c] = util.MakeMatrix2D[float32](8, 8)
w = hfg.getDCTQuantWeights(4, 4, hfg.params[index].dctParam[c])
for y := 0; y < 8; y++ {
for x := 0; x < 8; x++ {
hfg.weights[index][c][y][x] = w[y/2][x/2]
}
}
hfg.weights[index][c][1][0] /= hfg.params[index].param[c][0]
hfg.weights[index][c][0][1] /= hfg.params[index].param[c][0]
hfg.weights[index][c][1][1] /= hfg.params[index].param[c][1]
case MODE_DCT2:
w = util.MakeMatrix2D[float32](8, 8)
w[0][0] = 1
w[0][1] = hfg.params[index].param[c][0]
w[1][0] = hfg.params[index].param[c][0]
w[1][1] = hfg.params[index].param[c][1]
for y := 0; y < 2; y++ {
for x := 0; x < 2; x++ {
w[y][x+2] = hfg.params[index].param[c][2]
w[x+2][y] = hfg.params[index].param[c][2]
w[y+2][x+2] = hfg.params[index].param[c][3]
}
}
for y := 0; y < 4; y++ {
for x := 0; x < 4; x++ {
w[y][x+4] = hfg.params[index].param[c][4]
w[x+4][y] = hfg.params[index].param[c][4]
w[y+4][x+4] = hfg.params[index].param[c][5]
}
}
hfg.weights[index][c] = w
case MODE_HORNUSS:
w = util.MakeMatrix2D[float32](8, 8)
for y := 0; y < 8; y++ {
for x := 0; x < 8; x++ {
w[y][x] = hfg.params[index].param[c][0]
}
}
w[1][1] = hfg.params[index].param[c][2]
w[0][1] = hfg.params[index].param[c][1]
w[1][0] = hfg.params[index].param[c][1]
w[0][0] = 1.0
hfg.weights[index][c] = w
case MODE_DCT4_8:
hfg.weights[index][c] = util.MakeMatrix2D[float32](8, 8)
w = hfg.getDCTQuantWeights(4, 8, hfg.params[index].dctParam[c])
for y := 0; y < 8; y++ {
for x := 0; x < 8; x++ {
hfg.weights[index][c][y][x] = w[y/2][x]
}
}
hfg.weights[index][c][1][0] /= hfg.params[index].param[c][0]
case MODE_AFV:
afv, err := hfg.getAFVTransformWeights(index, c)
if err != nil {
return err
}
hfg.weights[index][c] = afv
case MODE_RAW:
hfg.weights[index][c] = util.MakeMatrix2D[float32](tt.matrixHeight, tt.matrixWidth)
for y := int32(0); y < tt.matrixHeight; y++ {
for x := int32(0); x < tt.matrixWidth; x++ {
hfg.weights[index][c][y][x] = hfg.params[index].param[c][y*tt.matrixWidth+x] * hfg.params[index].denominator
}
}
default:
return errors.New("Invalid mode")
}
}
if hfg.params[index].mode != MODE_RAW {
for c := 0; c < 3; c++ {
for y := int32(0); y < tt.matrixHeight; y++ {
for x := int32(0); x < tt.matrixWidth; x++ {
if hfg.weights[index][c][y][x] < 0 || math.IsInf(float64(hfg.weights[index][c][y][x]), 0) {
return errors.New("Invalid weight")
}
hfg.weights[index][c][y][x] = 1.0 / hfg.weights[index][c][y][x]
}
}
}
}
return nil
}
func quantMult(v float32) float32 {
if v >= 0 {
return 1 + v
}
return 1 / (1 - v)
}
func (hfg *HFGlobal) getDCTQuantWeights(height int32, width int32, params []float64) [][]float32 {
bands := make([]float32, len(params))
bands[0] = float32(params[0])
for i := 1; i < len(bands); i++ {
bands[i] = bands[i-1] * quantMult(float32(params[i]))
}
weights := util.MakeMatrix2D[float32](height, width)
scale := float32(len(bands)-1) / (math.Sqrt2 + 1e-6)
for y := int32(0); y < height; y++ {
dy := float32(y) * scale / float32(height-1)
dy2 := dy * dy
for x := int32(0); x < width; x++ {
dx := float32(x) * scale / float32(width-1)
dist := float32(math.Sqrt(float64(dx*dx + dy2)))
weights[y][x] = interpolate(dist, bands)
}
}
return weights
}
func interpolate(scaledPos float32, bands []float32) float32 {
l := len(bands) - 1
if l == 0 {
return bands[0]
}
scaledIndex := int(scaledPos)
if scaledIndex+1 > l {
return bands[l]
}
fracIndex := float64(scaledPos) - float64(scaledIndex)
a := bands[scaledIndex]
b := bands[scaledIndex+1]
first := float64(b / a)
second := fracIndex
//fmt.Printf("first %f second %f\n", first, second)
return float32(a) * float32(math.Pow(first, second))
}
func (hfg *HFGlobal) getAFVTransformWeights(index int, c int) ([][]float32, error) {
weights4x8 := hfg.getDCTQuantWeights(4, 8, hfg.params[index].dctParam[c])
weights4x4 := hfg.getDCTQuantWeights(4, 4, hfg.params[index].params4x4[c])
low := 0.8517778890324296
high := 12.97166202570235
bands := make([]float32, 4)
bands[0] = hfg.params[index].param[c][5]
if bands[0] < 0 {
return nil, errors.New("Invalid band")
}
for i := 1; i < 4; i++ {
bands[i] = bands[i-1] * quantMult(hfg.params[index].param[c][i+5])
if bands[i] < 0 {
return nil, errors.New("Negative band value")
}
}
weight := util.MakeMatrix2D[float32](8, 8)
weight[0][0] = 1
weight[1][0] = hfg.params[index].param[c][0]
weight[0][1] = hfg.params[index].param[c][1]
weight[2][0] = hfg.params[index].param[c][2]
weight[0][2] = hfg.params[index].param[c][3]
weight[2][2] = hfg.params[index].param[c][4]
for y := 0; y < 4; y++ {
for x := 0; x < 4; x++ {
if x < 2 && y < 2 {
continue
}
pos := (afvFreqs[y*4+x] - low) / (high - low)
weight[2*x][2*y] = interpolate(float32(pos), bands)
}
for x := 0; x < 8; x++ {
if x == 0 && y == 0 {
continue
}
weight[2*y+1][x] = weights4x8[y][x]
}
for x := 0; x < 4; x++ {
if x == 0 && y == 0 {
continue
}
weight[2*y][2*x+1] = weights4x4[y][x]
}
}
return weight, nil
}
package frame
import (
"errors"
"slices"
"github.com/kpfaulkner/jxl-go/entropy"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
var (
orderLookup = make([]TransformType, 13)
parameterLookup = make([]TransformType, 17)
typeLookup = make([]TransformType, 27)
)
// not crazy about init functions... but think this is probably the best way to handle this.
func init() {
for i := int32(0); i < 13; i++ {
tt, err := filterByOrderID(i)
if err != nil {
// intentionally panic. If we can't setup this data, fail immediately
panic(err)
}
orderLookup[i] = tt
}
for i := int32(0); i < 17; i++ {
tt, err := filterByParameterIndex(i)
if err != nil {
// intentionally panic. If we can't setup this data, fail immediately
panic(err)
}
parameterLookup[i] = tt
}
for i := int32(0); i < 27; i++ {
tt, err := filterByType(i)
if err != nil {
// intentionally panic. If we can't setup this data, fail immediately
panic(err)
}
typeLookup[i] = tt
}
}
func filterByType(typeIndex int32) (TransformType, error) {
for _, tt := range allDCT {
if tt.ttType == typeIndex {
return tt, nil
}
}
return TransformType{}, errors.New("Unable to find transform type for typeIndex")
}
func filterByParameterIndex(parameterIndex int32) (TransformType, error) {
for _, tt := range allDCT {
if tt.parameterIndex == parameterIndex && !tt.isVertical() {
return tt, nil
}
}
return TransformType{}, errors.New("Unable to find transform type for parameterIndex")
}
func filterByOrderID(orderID int32) (TransformType, error) {
for _, tt := range allDCT {
if tt.orderID == orderID && !tt.isVertical() {
return tt, nil
}
}
return TransformType{}, errors.New("Unable to find transform type for orderID")
}
type HFPass struct {
order [][][]util.Point
naturalOrder [][]util.Point
contextStream entropy.EntropyStreamer
usedOrders uint32
}
func NewHFPassWithReader(reader jxlio.BitReader, frame Framer, passIndex uint32,
readClusterMapFunc entropy.ReadClusterMapFunc,
newEntropyStreamWithReader entropy.EntropyStreamWithReaderAndNumDistsFunc,
readPermutation ReadPermutationFunc) (*HFPass, error) {
hfp := &HFPass{}
hfp.naturalOrder = util.MakeMatrix2D[util.Point](13, 0)
hfp.order = util.MakeMatrix3D[util.Point](13, 3, 0)
usedOrders, err := reader.ReadU32(0x5F, 0, 0x13, 0, 0, 0, 0, 13)
if err != nil {
return nil, err
}
hfp.usedOrders = usedOrders
var stream entropy.EntropyStreamer
if usedOrders != 0 {
if stream, err = newEntropyStreamWithReader(reader, 8, readClusterMapFunc); err != nil {
return nil, err
}
} else {
stream = nil
}
for b := int32(0); b < 13; b++ {
naturalOrder, err := hfp.getNaturalOrder(b)
if err != nil {
return nil, err
}
l := len(naturalOrder)
for c := 0; c < 3; c++ {
if usedOrders&(1<<uint32(b)) != 0 {
hfp.order[b][c] = make([]util.Point, l)
perm, err := readPermutation(reader, stream, uint32(l), uint32(l/64))
if err != nil {
return nil, err
}
for i := 0; i < len(hfp.order[b][c]); i++ {
hfp.order[b][c][i] = naturalOrder[perm[i]]
}
} else {
hfp.order[b][c] = naturalOrder
}
}
}
if stream != nil && !stream.ValidateFinalState() {
return nil, errors.New("ANS state decoding error")
}
numContexts := 495 * frame.getHFGlobal().numHFPresets * frame.getLFGlobal().hfBlockCtx.numClusters
contextStream, err := newEntropyStreamWithReader(reader, int(numContexts), readClusterMapFunc)
if err != nil {
return nil, err
}
hfp.contextStream = contextStream
return hfp, nil
}
func (hfp *HFPass) getNaturalOrder(i int32) ([]util.Point, error) {
if len(hfp.naturalOrder[i]) != 0 {
return hfp.naturalOrder[i], nil
}
tt := getByOrderID(i)
l := tt.pixelWidth * tt.pixelHeight
hfp.naturalOrder[i] = make([]util.Point, l)
for y := int32(0); y < tt.pixelHeight; y++ {
for x := int32(0); x < tt.pixelWidth; x++ {
hfp.naturalOrder[i][y*tt.pixelWidth+x] = util.Point{X: x, Y: y}
}
}
sorterFunc, err := getNaturalOrderFunc(i)
if err != nil {
return nil, err
}
slices.SortFunc(hfp.naturalOrder[i], sorterFunc)
return hfp.naturalOrder[i], nil
}
func getNaturalOrderFunc(i int32) (func(a util.Point, b util.Point) int, error) {
tt := getByOrderID(i)
return func(a util.Point, b util.Point) int {
maxDim := util.Max(tt.dctSelectHeight, tt.dctSelectWidth)
aLLF := a.Y < tt.dctSelectHeight && a.X < tt.dctSelectWidth
bLLF := b.Y < tt.dctSelectHeight && b.X < tt.dctSelectWidth
if aLLF && !bLLF {
return -1
}
if !aLLF && bLLF {
return 1
}
if aLLF && bLLF {
if b.Y != a.Y {
return int(a.Y - b.Y)
}
return int(a.X - b.X)
}
heightDivider := maxDim / tt.dctSelectHeight
widthDivider := maxDim / tt.dctSelectWidth
aSY := a.Y * heightDivider
aSX := a.X * widthDivider
bSY := b.Y * heightDivider
bSX := b.X * widthDivider
aKey1 := aSY + aSX
bKey1 := bSY + bSX
if aKey1 != bKey1 {
return int(aKey1 - bKey1)
}
aKey2 := aSX - aSY
bKey2 := bSX - bSY
if (aKey1 & 1) == 1 {
aKey2 = -aKey2
}
if (bKey1 & 1) == 1 {
bKey2 = -bKey2
}
return int(aKey2 - bKey2)
}, nil
}
package frame
import "github.com/kpfaulkner/jxl-go/jxlio"
type LFChannelCorrelation struct {
colorFactor uint32
baseCorrelationX float32
baseCorrelationB float32
xFactorLF uint32
bFactorLF uint32
}
func NewLFChannelCorrelation() (*LFChannelCorrelation, error) {
return NewLFChannelCorrelationWithReaderAndDefault(nil, true)
}
func NewLFChannelCorrelationWithReaderAndDefault(reader jxlio.BitReader, allDefault bool) (*LFChannelCorrelation, error) {
lf := &LFChannelCorrelation{}
if allDefault {
lf.colorFactor = 84
lf.baseCorrelationX = 0.0
lf.baseCorrelationB = 1.0
lf.xFactorLF = 128
lf.bFactorLF = 128
} else {
var err error
if lf.colorFactor, err = reader.ReadU32(84, 0, 256, 0, 2, 8, 258, 16); err != nil {
return nil, err
}
if lf.baseCorrelationX, err = reader.ReadF16(); err != nil {
return nil, err
}
if lf.baseCorrelationB, err = reader.ReadF16(); err != nil {
return nil, err
}
bits := uint64(0)
if bits, err = reader.ReadBits(8); err != nil {
return nil, err
}
lf.xFactorLF = uint32(bits)
if bits, err = reader.ReadBits(8); err != nil {
return nil, err
}
lf.bFactorLF = uint32(bits)
}
return lf, nil
}
func NewLFChannelCorrelationWithReader(reader jxlio.BitReader) (*LFChannelCorrelation, error) {
var allDefault bool
var err error
if allDefault, err = reader.ReadBool(); err != nil {
return nil, err
}
return NewLFChannelCorrelationWithReaderAndDefault(reader, allDefault)
}
package frame
import (
"errors"
"github.com/kpfaulkner/jxl-go/colour"
"github.com/kpfaulkner/jxl-go/entropy"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
type LFGlobal struct {
frame Framer
Patches []Patch
splines []SplinesBundle
noiseParameters []NoiseParameters
lfDequant []float32
hfBlockCtx *HFBlockContext
lfChanCorr *LFChannelCorrelation
globalScale int32
quantLF int32
scaledDequant []float32
globalModular ModularStreamer
}
func NewLFGlobal() *LFGlobal {
lf := &LFGlobal{}
lf.lfDequant = []float32{1.0 / 4096.0, 1.0 / 512.0, 1.0 / 256.0}
lf.scaledDequant = make([]float32, 3)
lf.lfChanCorr = &LFChannelCorrelation{
colorFactor: 0,
baseCorrelationX: 0,
baseCorrelationB: 0,
xFactorLF: 0,
bFactorLF: 0,
}
lf.hfBlockCtx = &HFBlockContext{
lfThresholds: util.MakeMatrix2D[int32](3, 3),
}
return lf
}
func NewLFGlobalWithReader(reader jxlio.BitReader, parent Framer, hfBlockContextFunc NewHFBlockContextFunc) (*LFGlobal, error) {
lf := NewLFGlobal()
lf.frame = parent
extra := len(lf.frame.getGlobalMetadata().ExtraChannelInfo)
// nolint
if lf.frame.getFrameHeader().Flags&PATCHES != 0 {
return nil, errors.New("Patches not implemented yet")
stream, err := entropy.NewEntropyStreamWithReaderAndNumDists(reader, 10, entropy.ReadClusterMap)
if err != nil {
return nil, err
}
numPatches, err := stream.ReadSymbol(reader, 0)
if err != nil {
return nil, err
}
lf.Patches = make([]Patch, numPatches)
for i := 0; i < int(numPatches); i++ {
lf.Patches[i], err = NewPatchWithStreamAndReader(stream, reader, len(parent.getGlobalMetadata().ExtraChannelInfo), len(parent.getGlobalMetadata().AlphaIndices))
if err != nil {
return nil, err
}
}
} else {
lf.Patches = []Patch{}
}
if lf.frame.getFrameHeader().Flags&SPLINES != 0 {
return nil, errors.New("Splines not implemented yet")
} else {
lf.splines = nil
}
if lf.frame.getFrameHeader().Flags&NOISE != 0 {
return nil, errors.New("Noise not implemented yet")
} else {
lf.noiseParameters = nil
}
var err error
var readDequant bool
if readDequant, err = reader.ReadBool(); err != nil {
return nil, err
}
if !readDequant {
for i := 0; i < 3; i++ {
if lf.lfDequant[i], err = reader.ReadF16(); err != nil {
return nil, err
}
lf.lfDequant[i] *= (1.0 / 128.0)
}
}
if lf.frame.getFrameHeader().Encoding == VARDCT {
globalScale, err := reader.ReadU32(1, 11, 2049, 11, 4097, 12, 8193, 16)
if err != nil {
return nil, err
}
lf.globalScale = int32(globalScale)
quantLF, err := reader.ReadU32(16, 0, 1, 5, 1, 8, 1, 16)
if err != nil {
return nil, err
}
lf.quantLF = int32(quantLF)
for i := 0; i < 3; i++ {
lf.scaledDequant[i] = (1 << 16) * lf.lfDequant[i] / float32(lf.globalScale*lf.quantLF)
}
lf.hfBlockCtx, err = hfBlockContextFunc(reader, entropy.ReadClusterMap)
if err != nil {
return nil, err
}
lf.lfChanCorr, err = NewLFChannelCorrelationWithReader(reader)
if err != nil {
return nil, err
}
} else {
lf.globalScale = 0
lf.quantLF = 0
lf.hfBlockCtx = nil
lf.lfChanCorr, err = NewLFChannelCorrelation()
if err != nil {
return nil, err
}
}
hasGlobalTree, err := reader.ReadBool()
if err != nil {
return nil, err
}
var globalTree *MATreeNode
if hasGlobalTree {
globalTree, err = NewMATreeWithReader(reader, entropy.NewEntropyStreamWithReaderAndNumDists, entropy.NewEntropyStreamWithReader)
if err != nil {
return nil, err
}
} else {
globalTree = nil
}
lf.frame.setGlobalTree(globalTree)
subModularChannelCount := extra
ecStart := 0
if lf.frame.getFrameHeader().Encoding == MODULAR {
if !lf.frame.getFrameHeader().DoYCbCr && !lf.frame.getGlobalMetadata().XybEncoded &&
lf.frame.getGlobalMetadata().ColourEncoding.ColourEncoding == colour.CE_GRAY {
ecStart = 1
} else {
ecStart = 3
}
}
subModularChannelCount += ecStart
globalModular, err := NewModularStreamWithReader(reader, parent, 0, subModularChannelCount, ecStart)
if err != nil {
return nil, err
}
lf.globalModular = globalModular
if err = lf.globalModular.decodeChannels(reader, true); err != nil {
return nil, err
}
return lf, nil
}
package frame
import (
"github.com/kpfaulkner/jxl-go/image"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
type LFGroup struct {
lfCoeff *LFCoefficients
hfMetadata *HFMetadata
lfGroupID int32
frame Framer
size util.Dimension
modularLFGroup ModularStreamer
}
func NewLFGroupWithReader(reader jxlio.BitReader, parent Framer, index int32, replaced []ModularChannel, lfBuffer []image.ImageBuffer, lfCoeffientsFunc NewLFCoefficientsWithReaderFunc, hfMetadataFunc NewHFMetadataWithReaderFunc) (*LFGroup, error) {
lfg := &LFGroup{
frame: parent,
lfGroupID: index,
}
pixelSize, err := lfg.frame.getLFGroupSize(lfg.lfGroupID)
if err != nil {
return nil, err
}
lfg.size = util.Dimension{
Height: pixelSize.Height >> 3,
Width: pixelSize.Width >> 3,
}
if parent.getFrameHeader().Encoding == VARDCT {
lfg.lfCoeff, err = lfCoeffientsFunc(reader, lfg, parent, lfBuffer, NewModularStreamWithChannels)
if err != nil {
return nil, err
}
} else {
lfg.lfCoeff = nil
}
stream, err := NewModularStreamWithStreamIndex(reader, parent, 1+int(parent.getNumLFGroups()+uint32(lfg.lfGroupID)), replaced)
if err != nil {
return nil, err
}
lfg.modularLFGroup = stream
err = stream.decodeChannels(reader, false)
if err != nil {
return nil, err
}
if parent.getFrameHeader().Encoding == VARDCT {
metadata, err := hfMetadataFunc(reader, lfg, parent, NewModularStreamWithStreamIndex)
if err != nil {
return nil, err
}
lfg.hfMetadata = metadata
} else {
lfg.hfMetadata = nil
}
return lfg, nil
}
package frame
import (
"errors"
"github.com/kpfaulkner/jxl-go/entropy"
"github.com/kpfaulkner/jxl-go/jxlio"
)
type MATreeNode struct {
parent *MATreeNode
stream entropy.EntropyStreamer
leftChildNode *MATreeNode
rightChildNode *MATreeNode
property int32
context int32
value int32
leftChildIndex int32
rightChildIndex int32
predictor int32
offset int32
multiplier int32
}
func NewMATreeWithReader(reader jxlio.BitReader, newEntropyStreamAndNumDistsFunc entropy.EntropyStreamWithReaderAndNumDistsFunc, newEntropyStreamFunc entropy.EntropyStreamWithReaderFunc) (*MATreeNode, error) {
mt := &MATreeNode{}
mt.parent = nil
var nodes []*MATreeNode
stream, err := newEntropyStreamAndNumDistsFunc(reader, 6, entropy.ReadClusterMap)
if err != nil {
return nil, err
}
contextId := int32(0)
nodesRemaining := 1
for nodesRemaining > 0 {
nodesRemaining--
if len(nodes) > (1 << 20) {
return nil, errors.New("Tree too large")
}
property, err := stream.ReadSymbol(reader, 1)
if err != nil {
return nil, err
}
property--
var node *MATreeNode
if len(nodes) == 0 {
node = mt
} else {
node = &MATreeNode{}
}
if property >= 0 {
value := jxlio.UnpackSigned(uint32(stream.TryReadSymbol(reader, 0)))
leftChild := len(nodes) + nodesRemaining + 1
node.property = property
node.predictor = -1
node.value = value
node.leftChildIndex = int32(leftChild)
node.rightChildIndex = int32(leftChild) + 1
nodes = append(nodes, node)
nodesRemaining += 2
} else {
context := contextId
contextId++
var predictor int32
var err error
if predictor, err = stream.ReadSymbol(reader, 2); err != nil {
return nil, err
}
if predictor > 13 {
return nil, errors.New("invalid predictor value")
}
offset := jxlio.UnpackSigned(uint32(stream.TryReadSymbol(reader, 3)))
var mulLog int32
if mulLog, err = stream.ReadSymbol(reader, 4); err != nil {
return nil, err
}
if mulLog > 30 {
return nil, errors.New("mulLog too large")
}
var mulBits int32
if mulBits, err = stream.ReadSymbol(reader, 5); err != nil {
return nil, err
}
if mulBits > (1<<(31-mulLog))-2 {
return nil, errors.New("mulBits too large")
}
multiplier := (mulBits + 1) << uint(mulLog)
node.context = context
node.predictor = predictor
node.multiplier = multiplier
node.offset = offset
node.property = -1
nodes = append(nodes, node)
}
}
if !stream.ValidateFinalState() {
return nil, errors.New("illegal MA Tree Entropy Stream")
}
mt.stream, err = newEntropyStreamFunc(reader, (len(nodes)+1)/2, false, entropy.ReadClusterMap)
if err != nil {
return nil, err
}
for n, node := range nodes {
nodes[n].stream = mt.stream
if !node.isLeafNode() {
node.leftChildNode = nodes[node.leftChildIndex]
node.rightChildNode = nodes[node.rightChildIndex]
node.leftChildNode.parent = node
node.rightChildNode.parent = node
}
}
return mt, nil
}
func (t *MATreeNode) isLeafNode() bool {
return t.property < 0
}
func (t *MATreeNode) compactifyWithY(channelIndex int32, streamIndex int32, y int32) *MATreeNode {
var prop int32
switch t.property {
case 0:
prop = channelIndex
case 1:
prop = streamIndex
case 2:
prop = y
default:
return t
}
var branch *MATreeNode
if prop > t.value {
branch = t.leftChildNode
} else {
branch = t.rightChildNode
}
return branch.compactifyWithY(channelIndex, streamIndex, y)
}
func (t *MATreeNode) compactify(channelIndex int32, streamIndex int32) *MATreeNode {
var prop int32
switch t.property {
case 0:
prop = channelIndex
case 1:
prop = streamIndex
default:
return t
}
var branch *MATreeNode
if prop > t.value {
branch = t.leftChildNode
} else {
branch = t.rightChildNode
}
return branch.compactify(channelIndex, streamIndex)
}
func (t *MATreeNode) useWeightedPredictor() bool {
if t.isLeafNode() {
return t.predictor == 6
}
return t.property == 15 ||
t.leftChildNode.useWeightedPredictor() ||
t.rightChildNode.useWeightedPredictor()
}
func (t *MATreeNode) walk(walkerFunc func(inp int32) (int32, error)) (*MATreeNode, error) {
if t.isLeafNode() {
return t, nil
}
value, err := walkerFunc(t.property)
if err != nil {
return nil, err
}
if value > t.value {
return t.leftChildNode.walk(walkerFunc)
}
return t.rightChildNode.walk(walkerFunc)
}
// nolint
func (t *MATreeNode) getSize() int {
size := 1
if !t.isLeafNode() {
size += t.leftChildNode.getSize()
size += t.rightChildNode.getSize()
}
return size
}
// Prints the tree to the console. Used for comparing implementations
func DisplayTree(node *MATreeNode, depth int) {
if !node.leftChildNode.isLeafNode() {
DisplayTree(node.leftChildNode, depth+1)
}
if !node.rightChildNode.isLeafNode() {
DisplayTree(node.rightChildNode, depth+1)
}
}
package frame
import (
"errors"
"github.com/kpfaulkner/jxl-go/entropy"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
// absInt32 returns the absolute value of an int32.
// This is significantly faster than int32(math.Abs(float64(x))) as it avoids:
// 1. int32 -> float64 conversion
// 2. float64 -> int32 conversion
// 3. The overhead of math.Abs function call
func absInt32(x int32) int32 {
if x < 0 {
return -x
}
return x
}
var (
oneL24OverKP1 = make([]int64, 64)
)
type ModularChannel struct {
hshift int32
vshift int32
origin util.Point
forceWP bool
size util.Dimension
buffer [][]int32
decoded bool
err [][][]int32
pred [][]int32
subpred []int32
weight []int32
}
func init() {
for i := int64(0); i < int64(len(oneL24OverKP1)); i++ {
oneL24OverKP1[i] = (1 << 24) / (i + 1)
}
}
func NewModularChannelFromChannel(ch ModularChannel) *ModularChannel {
mc := NewModularChannelWithAllParams(int32(ch.size.Height), int32(ch.size.Width), ch.vshift, ch.hshift, ch.forceWP)
mc.decoded = ch.decoded
if ch.buffer != nil {
mc.allocate()
for y := uint32(0); y < mc.size.Height; y++ {
copy(mc.buffer[y], ch.buffer[y])
}
}
return mc
}
func NewModularChannelWithAllParams(height int32, width int32, vshift int32, hshift int32, forceWP bool) *ModularChannel {
mc := &ModularChannel{
hshift: hshift,
vshift: vshift,
forceWP: forceWP,
size: util.Dimension{
Width: uint32(width),
Height: uint32(height),
},
}
return mc
}
func (mc *ModularChannel) allocate() {
if len(mc.buffer) != 0 {
return
}
if mc.size.Height == 0 || mc.size.Width == 0 {
mc.buffer = util.MakeMatrix2D[int32](0, 0)
} else {
mc.buffer = util.MakeMatrix2D[int32](int(mc.size.Height), int(mc.size.Width))
}
}
func (mc *ModularChannel) prediction(y int32, x int32, k int32) (int32, error) {
var n, v, nw, w int32
switch k {
case 0:
return 0, nil
case 1:
return mc.west(x, y), nil
case 2:
return mc.north(x, y), nil
case 3:
return (mc.west(x, y) + mc.north(x, y)) / 2, nil
case 4:
w = mc.west(x, y)
n = mc.north(x, y)
nw = mc.northWest(x, y)
if util.Abs(n-nw) < util.Abs(w-nw) {
return w, nil
}
return n, nil
case 5:
w = mc.west(x, y)
n = mc.north(x, y)
v = w + n - mc.northWest(x, y)
return util.Clamp3(v, n, w), nil
case 6:
return (mc.pred[y][x] + 3) >> 3, nil
case 7:
return mc.northEast(x, y), nil
case 8:
return mc.northWest(x, y), nil
case 9:
return mc.westWest(x, y), nil
case 10:
return (mc.west(x, y) + mc.northWest(x, y)) / 2, nil
case 11:
return (mc.north(x, y) + mc.northWest(x, y)) / 2, nil
case 12:
return (mc.north(x, y) + mc.northEast(x, y)) / 2, nil
case 13:
return (6*mc.north(x, y) - 2*mc.northNorth(x, y) + 7*mc.west(x, y) +
mc.westWest(x, y) + mc.northEastEast(x, y) + 3*mc.northEast(x, y) + 8) / 16, nil
default:
return 0, errors.New("Illegal predictor state")
}
}
func (mc *ModularChannel) prePredictWP(wpParams *WPParams, x int32, y int32) (int32, error) {
n3 := mc.north(x, y) << 3
nw3 := mc.northWest(x, y) << 3
ne3 := mc.northEast(x, y) << 3
w3 := mc.west(x, y) << 3
nn3 := mc.northNorth(x, y) << 3
tN := mc.errorNorth(x, y, 4)
tW := mc.errorWest(x, y, 4)
tNE := mc.errorNorthEast(x, y, 4)
tNW := mc.errorNorthWest(x, y, 4)
mc.subpred[0] = w3 + ne3 - n3
mc.subpred[1] = n3 - ((tW+tN+tNE)*(int32(wpParams.param1)))>>5
mc.subpred[2] = w3 - ((tW+tN+tNW)*(int32(wpParams.param2)))>>5
mc.subpred[3] = n3 - ((tNW*wpParams.param3a +
tN*wpParams.param3b +
tNE*wpParams.param3c +
(nn3-n3)*wpParams.param3d +
(nw3-w3)*wpParams.param3e) >> 5)
wSum := int32(0)
for e := int32(0); e < 4; e++ {
eSum := mc.errorNorth(x, y, e) + mc.errorWest(x, y, e) + mc.errorNorthWest(x, y, e) + mc.errorWestWest(x, y, e) + mc.errorNorthEast(x, y, e)
if x+1 == int32(mc.size.Width) {
eSum += mc.errorWest(x, y, e)
}
shift := util.FloorLog1pUint64(uint64(eSum)) - 5
if shift < 0 {
shift = 0
}
mc.weight[e] = int32(4 + (wpParams.weight[e]*oneL24OverKP1[eSum>>shift])>>shift)
wSum += mc.weight[e]
}
logWeight := util.FloorLog1p(int64(wSum)-1) - 4
wSum = 0
weight := mc.weight
s := int32(0)
for e := 0; e < 4; e++ {
weight[e] = weight[e] >> logWeight
wSum += weight[e]
s += mc.subpred[e] * mc.weight[e]
}
s += (wSum >> 1) - 1
mc.pred[y][x] = int32((int64(s) * oneL24OverKP1[wSum-1]) >> 24)
if (tN^tW)|(tN^tNW) <= 0 {
mc.pred[y][x] = util.Clamp(mc.pred[y][x], w3, n3, ne3)
}
// Cache absolute values to avoid repeated util.Abs() calls
maxError := tW
absMaxError := util.Abs(tW)
if absN := util.Abs(tN); absN > absMaxError {
maxError = tN
absMaxError = absN
}
if absNW := util.Abs(tNW); absNW > absMaxError {
maxError = tNW
absMaxError = absNW
}
if absNE := util.Abs(tNE); absNE > absMaxError {
maxError = tNE
}
return maxError, nil
}
// Could try and use IfThenElse but that gets messy quickly. Prefer some simple 'if' statements.
func (mc *ModularChannel) west(x int32, y int32) int32 {
if x > 0 {
return mc.buffer[y][x-1]
}
if y > 0 {
return mc.buffer[y-1][x]
}
return 0
}
func (mc *ModularChannel) north(x int32, y int32) int32 {
if y > 0 {
return mc.buffer[y-1][x]
}
if x > 0 {
return mc.buffer[y][x-1]
}
return 0
}
func (mc *ModularChannel) northWest(x int32, y int32) int32 {
buf := mc.buffer
if x <= 0 {
if y > 0 {
return buf[y-1][x]
}
return 0
}
if y > 0 {
return buf[y-1][x-1]
}
return buf[y][x-1]
}
func (mc *ModularChannel) northEast(x int32, y int32) int32 {
if y > 0 && x+1 < int32(mc.size.Width) {
return mc.buffer[y-1][x+1]
}
return mc.north(x, y)
}
func (mc *ModularChannel) northNorth(x int32, y int32) int32 {
if y > 1 {
return mc.buffer[y-2][x]
}
return mc.north(x, y)
}
func (mc *ModularChannel) northEastEast(x int32, y int32) int32 {
if x+2 < int32(mc.size.Width) && y > 0 {
return mc.buffer[y-1][x+2]
}
return mc.northEast(x, y)
}
func (mc *ModularChannel) westWest(x int32, y int32) int32 {
if x > 1 {
return mc.buffer[y][x-2]
}
return mc.west(x, y)
}
func (mc *ModularChannel) errorNorth(x int32, y int32, e int32) int32 {
if y <= 0 {
return 0
}
return mc.err[e][y-1][x]
}
func (mc *ModularChannel) errorWest(x int32, y int32, e int32) int32 {
if x <= 0 {
return 0
}
return mc.err[e][y][x-1]
}
func (mc *ModularChannel) errorWestWest(x int32, y int32, e int32) int32 {
if x <= 1 {
return 0
}
return mc.err[e][y][x-2]
}
func (mc *ModularChannel) errorNorthWest(x int32, y int32, e int32) int32 {
if x > 0 && y > 0 {
return mc.err[e][y-1][x-1]
}
return mc.errorNorth(x, y, e)
}
func (mc *ModularChannel) errorNorthEast(x int32, y int32, e int32) int32 {
if x+1 < int32(mc.size.Width) && y > 0 {
return mc.err[e][y-1][x+1]
}
return mc.errorNorth(x, y, e)
}
func (mc *ModularChannel) getWalkerFunc(channelIndex int32, streamIndex int32, x int32, y int32, maxError int32, parent *ModularStream) func(int32) (int32, error) {
return func(k int32) (int32, error) {
switch k {
case 0:
return channelIndex, nil
case 1:
return streamIndex, nil
case 2:
return y, nil
case 3:
return x, nil
case 4:
return util.Abs(mc.north(x, y)), nil
case 5:
return util.Abs(mc.west(x, y)), nil
case 6:
return mc.north(x, y), nil
case 7:
return mc.west(x, y), nil
case 8:
if x > 0 {
return mc.west(x, y) - (mc.west(x-1, y) + mc.north(x-1, y) - mc.northWest(x-1, y)), nil
}
return mc.west(x, y), nil
case 9:
return mc.west(x, y) + mc.north(x, y) - mc.northWest(x, y), nil
case 10:
return mc.west(x, y) - mc.northWest(x, y), nil
case 11:
return mc.northWest(x, y) - mc.north(x, y), nil
case 12:
return mc.north(x, y) - mc.northEast(x, y), nil
case 13:
return mc.north(x, y) - mc.northNorth(x, y), nil
case 14:
return mc.west(x, y) - mc.westWest(x, y), nil
case 15:
return maxError, nil
default:
if k-16 >= 4*channelIndex {
return 0, nil
}
k2 := int32(16)
for j := channelIndex - 1; j >= 0; j-- {
channel := parent.channels[j]
if channel.size.Width != mc.size.Width || channel.size.Height != mc.size.Height ||
channel.hshift != mc.hshift || channel.vshift != mc.vshift {
continue
}
if k2+4 <= k {
k2 += 4
continue
}
rC := channel.buffer[y][x]
if k2 == k {
return util.Abs(rC), nil
}
k2++
if k2 == k {
return rC, nil
}
k2++
var rW int32
var rN int32
var rNW int32
var rG int32
if x > 0 {
rW = channel.buffer[y][x-1]
} else {
rW = 0
}
if y > 0 {
rN = channel.buffer[y-1][x]
} else {
rN = rW
}
if x > 0 && y > 0 {
rNW = channel.buffer[y-1][x-1]
} else {
rNW = rW
}
rG = rC - util.Clamp3(rW+rN-rNW, rN, rW)
if k2 == k {
return util.Abs(rG), nil
}
k2++
if k2 == k {
return rG, nil
}
k2++
}
return 0, nil
}
}
}
func (mc *ModularChannel) getLeafNode(refinedTree *MATreeNode, channelIndex int32, streamIndex int32, x int32, y int32, maxError int32, parent *ModularStream) (*MATreeNode, error) {
walkerFunc := mc.getWalkerFunc(channelIndex, streamIndex, x, y, maxError, parent)
leafNode, err := refinedTree.walk(walkerFunc)
if err != nil {
return nil, err
}
return leafNode, nil
}
func (mc *ModularChannel) decode(reader jxlio.BitReader, stream entropy.EntropyStreamer,
wpParams *WPParams, tree *MATreeNode, parent *ModularStream, channelIndex int32, streamIndex int32, distMultiplier int32) error {
if mc.decoded {
return nil
}
mc.decoded = true
mc.allocate()
tree = tree.compactify(channelIndex, streamIndex)
useWP := mc.forceWP || tree.useWeightedPredictor()
if useWP {
mc.err = util.MakeMatrix3D[int32](5, int(mc.size.Height), int(mc.size.Width))
mc.pred = util.MakeMatrix2D[int32](int(mc.size.Height), int(mc.size.Width))
mc.subpred = make([]int32, 4)
mc.weight = make([]int32, 4)
} else {
wpParams = nil
}
for y0 := uint32(0); y0 < mc.size.Height; y0++ {
y := int32(y0)
refinedTree := tree.compactifyWithY(channelIndex, streamIndex, int32(y))
var err error
for x0 := uint32(0); x0 < mc.size.Width; x0++ {
x := int32(x0)
var maxError int32
if useWP {
maxError, err = mc.prePredictWP(wpParams, x, y)
if err != nil {
return err
}
} else {
maxError = 0
}
leafNode, err := mc.getLeafNode(refinedTree, channelIndex, streamIndex, x, y, maxError, parent)
if err != nil {
return err
}
diff, err := stream.ReadSymbolWithMultiplier(reader, int(leafNode.context), distMultiplier)
if err != nil {
return err
}
diff = jxlio.UnpackSigned(uint32(diff))*leafNode.multiplier + leafNode.offset
p, err := mc.prediction(y, x, leafNode.predictor)
if err != nil {
return err
}
trueValue := diff + p
mc.buffer[y][x] = trueValue
if useWP {
for e := 0; e < 4; e++ {
mc.err[e][y][x] = (absInt32(mc.subpred[e]-(trueValue<<3)) + 3) >> 3
}
mc.err[4][y][x] = mc.pred[y][x] - (trueValue << 3)
}
}
}
return nil
}
package frame
import (
"errors"
"fmt"
"github.com/kpfaulkner/jxl-go/entropy"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
const (
RCT = 0
PALETTE = 1
SQUEEZE = 2
)
var (
permutationLUT = [][]int{
{0, 1, 2}, {1, 2, 0}, {2, 0, 1},
{0, 2, 1}, {1, 0, 2}, {2, 1, 0}}
kDeltaPalette = [][]int32{
{0, 0, 0}, {4, 4, 4}, {11, 0, 0}, {0, 0, -13}, {0, -12, 0}, {-10, -10, -10},
{-18, -18, -18}, {-27, -27, -27}, {-18, -18, 0}, {0, 0, -32}, {-32, 0, 0}, {-37, -37, -37},
{0, -32, -32}, {24, 24, 45}, {50, 50, 50}, {-45, -24, -24}, {-24, -45, -45}, {0, -24, -24},
{-34, -34, 0}, {-24, 0, -24}, {-45, -45, -24}, {64, 64, 64}, {-32, 0, -32}, {0, -32, 0},
{-32, 0, 32}, {-24, -45, -24}, {45, 24, 45}, {24, -24, -45}, {-45, -24, 24}, {80, 80, 80},
{64, 0, 0}, {0, 0, -64}, {0, -64, -64}, {-24, -24, 45}, {96, 96, 96}, {64, 64, 0},
{45, -24, -24}, {34, -34, 0}, {112, 112, 112}, {24, -45, -45}, {45, 45, -24}, {0, -32, 32},
{24, -24, 45}, {0, 96, 96}, {45, -24, 24}, {24, -45, -24}, {-24, -45, 24}, {0, -64, 0},
{96, 0, 0}, {128, 128, 128}, {64, 0, 64}, {144, 144, 144}, {96, 96, 0}, {-36, -36, 36},
{45, -24, -45}, {45, -45, -24}, {0, 0, -96}, {0, 128, 128}, {0, 96, 0}, {45, 24, -45},
{-128, 0, 0}, {24, -45, 24}, {-45, 24, -45}, {64, 0, -64}, {64, -64, -64}, {96, 0, 96},
{45, -45, 24}, {24, 45, -45}, {64, 64, -64}, {128, 128, 0}, {0, 0, -128}, {-24, 45, -45},
}
)
type SqueezeParam struct {
horizontal bool
inPlace bool
beginC int
numC int
}
func NewSqueezeParam(reader jxlio.BitReader) (SqueezeParam, error) {
sp := SqueezeParam{}
var err error
if sp.horizontal, err = reader.ReadBool(); err != nil {
return sp, err
}
if sp.inPlace, err = reader.ReadBool(); err != nil {
return sp, err
}
if beginC, err := reader.ReadU32(3, 6, 10, 13, 0, 8, 72, 1096); err != nil {
return sp, err
} else {
sp.beginC = int(beginC)
}
if numC, err := reader.ReadU32(0, 0, 0, 4, 1, 2, 3, 4); err != nil {
return sp, err
} else {
sp.numC = int(numC)
}
return sp, nil
}
type TransformInfo struct {
tr int
beginC int
rctType int
numC int
nbColours int
nbDeltas int
dPred int
sp []SqueezeParam
}
func NewTransformInfo(reader jxlio.BitReader) (TransformInfo, error) {
ti := TransformInfo{}
var tr uint64
var err error
if tr, err = reader.ReadBits(2); err != nil {
return ti, err
}
if tr != SQUEEZE {
if beginC, err := reader.ReadU32(0, 3, 8, 6, 72, 10, 1096, 13); err != nil {
return ti, err
} else {
ti.beginC = int(beginC)
}
} else {
ti.beginC = 0
}
if tr == RCT {
if rctType, err := reader.ReadU32(6, 0, 0, 2, 2, 4, 10, 6); err != nil {
return ti, err
} else {
ti.rctType = int(rctType)
}
} else {
ti.rctType = 0
}
if tr == PALETTE {
if numC, err := reader.ReadU32(1, 0, 3, 0, 4, 0, 1, 13); err != nil {
return ti, err
} else {
ti.numC = int(numC)
}
if nbColours, err := reader.ReadU32(0, 8, 256, 10, 1280, 12, 5376, 16); err != nil {
return ti, err
} else {
ti.nbColours = int(nbColours)
}
if nbDeltas, err := reader.ReadU32(0, 0, 1, 8, 257, 10, 1281, 16); err != nil {
return ti, err
} else {
ti.nbDeltas = int(nbDeltas)
}
if dPred, err := reader.ReadBits(4); err != nil {
return ti, err
} else {
ti.dPred = int(dPred)
}
} else {
ti.numC = 0
ti.nbColours = 0
ti.nbDeltas = 0
ti.dPred = 0
}
if tr == SQUEEZE {
var numSq uint32
var err error
if numSq, err = reader.ReadU32(0, 0, 1, 4, 9, 6, 41, 8); err != nil {
return ti, err
}
ti.sp = make([]SqueezeParam, numSq)
for i := 0; i < int(numSq); i++ {
if ti.sp[i], err = NewSqueezeParam(reader); err != nil {
return ti, err
}
}
} else {
ti.sp = nil
}
ti.tr = int(tr)
return ti, nil
}
type ModularStreamer interface {
decodeChannels(reader jxlio.BitReader, partial bool) error
getDecodedBuffer() [][][]int32
applyTransforms() error
getChannels() []*ModularChannel
}
type ModularStream struct {
frame Framer
streamIndex int
channels []*ModularChannel
tree *MATreeNode
wpParams *WPParams
transforms []TransformInfo
distMultiplier int32
nbMetaChannels int
stream entropy.EntropyStreamer
transformed bool
squeezeMap map[int][]SqueezeParam
}
func NewModularStreamWithStreamIndex(reader jxlio.BitReader, frame Framer, streamIndex int, channelArray []ModularChannel) (ModularStreamer, error) {
return NewModularStreamWithChannels(reader, frame, streamIndex, len(channelArray), 0, channelArray)
}
func NewModularStreamWithReader(reader jxlio.BitReader, frame Framer, streamIndex int, channelCount int, ecStart int) (ModularStreamer, error) {
return NewModularStreamWithChannels(reader, frame, streamIndex, channelCount, ecStart, nil)
}
func NewModularStreamWithChannels(reader jxlio.BitReader, frame Framer, streamIndex int, channelCount int, ecStart int, channelArray []ModularChannel) (ModularStreamer, error) {
ms := &ModularStream{}
ms.streamIndex = streamIndex
ms.frame = frame
ms.squeezeMap = make(map[int][]SqueezeParam)
if channelCount == 0 {
ms.tree = nil
ms.wpParams = nil
ms.transforms = []TransformInfo{}
ms.distMultiplier = 1
return ms, nil
}
var useGlobalTree bool
var err error
if useGlobalTree, err = reader.ReadBool(); err != nil {
return nil, err
}
if ms.wpParams, err = NewWPParams(reader); err != nil {
return nil, err
}
nbTransforms, err := reader.ReadU32(0, 0, 1, 0, 2, 4, 18, 8)
if err != nil {
return nil, err
}
ms.transforms = make([]TransformInfo, nbTransforms)
for i := 0; i < int(nbTransforms); i++ {
if ms.transforms[i], err = NewTransformInfo(reader); err != nil {
return nil, err
}
}
if len(channelArray) == 0 {
for i := 0; i < channelCount; i++ {
size := frame.getFrameHeader().Bounds.Size
var dimShift int32
if i < ecStart {
dimShift = 0
} else {
dimShift = frame.getGlobalMetadata().ExtraChannelInfo[i-ecStart].DimShift
}
ms.channels = append(ms.channels, NewModularChannelWithAllParams(int32(size.Height), int32(size.Width), dimShift, dimShift, false))
}
} else {
//ms.channels = append(ms.channels, channelArray...)
for _, c := range channelArray {
ms.channels = append(ms.channels, &c)
}
}
for i := 0; i < int(nbTransforms); i++ {
if ms.transforms[i].tr == PALETTE {
if ms.transforms[i].beginC < ms.nbMetaChannels {
ms.nbMetaChannels += 2 - ms.transforms[i].numC
} else {
ms.nbMetaChannels++
}
start := ms.transforms[i].beginC + 1
for j := start; j < ms.transforms[i].beginC+ms.transforms[i].numC; j++ {
ms.channels = append(ms.channels[:start], ms.channels[start+1:]...)
}
if ms.transforms[i].nbDeltas > 0 && ms.transforms[i].dPred == 6 {
mc := ms.channels[ms.transforms[i].beginC]
mc.forceWP = true
ms.channels[ms.transforms[i].beginC] = mc
}
mc := NewModularChannelWithAllParams(int32(ms.transforms[i].numC), int32(ms.transforms[i].nbColours), -1, -1, false)
ms.channels = append([]*ModularChannel{mc}, ms.channels...)
} else if ms.transforms[i].tr == SQUEEZE {
// See JPEGXL specs, section I.3
squeezeList := []SqueezeParam{}
if len(ms.transforms[i].sp) == 0 {
first := ms.nbMetaChannels
count := len(ms.channels) - first
size := ms.channels[0].size
if count > 2 && size.Width == ms.channels[first+1].size.Width && size.Height == ms.channels[first+1].size.Height {
squeezeList = append(squeezeList, SqueezeParam{horizontal: true, inPlace: false, beginC: first + 1, numC: 2})
squeezeList = append(squeezeList, SqueezeParam{horizontal: false, inPlace: false, beginC: first + 1, numC: 2})
}
if size.Height >= size.Width && size.Height > 8 {
squeezeList = append(squeezeList, SqueezeParam{horizontal: false, inPlace: true, beginC: first, numC: count})
size.Height = (size.Height + 1) / 2
}
for size.Width > 8 || size.Height > 8 {
if size.Width > 8 {
squeezeList = append(squeezeList, SqueezeParam{horizontal: true, inPlace: true, beginC: first, numC: count})
size.Width = (size.Width + 1) / 2
}
if size.Height > 8 {
squeezeList = append(squeezeList, SqueezeParam{horizontal: false, inPlace: true, beginC: first, numC: count})
size.Height = (size.Height + 1) / 2
}
}
} else {
squeezeList = append(squeezeList, ms.transforms[i].sp...)
}
ms.squeezeMap[i] = squeezeList
spa := squeezeList
ii := 0
for j := 0; j < len(squeezeList); j++ {
begin := spa[j].beginC
end := begin + spa[j].numC - 1
var offset int
if spa[j].inPlace {
offset = end + 1
} else {
offset = len(ms.channels)
}
if begin < ms.nbMetaChannels {
if !spa[j].inPlace {
return nil, errors.New("squeeze meta must be in place")
}
if end >= ms.nbMetaChannels {
return nil, errors.New("squeeze meta must end in meta")
}
ms.nbMetaChannels += spa[j].numC
}
for c := begin; c <= end; c++ {
var residu *ModularChannel
ch := ms.channels[c]
r := offset + c - begin
if spa[j].horizontal {
w := ch.size.Width
ch.size.Width = (w + 1) / 2
ch.hshift++
residu = NewModularChannelFromChannel(*ch)
residu.size.Width = w / 2
} else {
h := ch.size.Height
ch.size.Height = (h + 1) / 2
ch.vshift++
residu = NewModularChannelFromChannel(*ch)
residu.size.Height = h / 2
}
ms.channels = util.AddToSlice(ms.channels, r, residu)
ii++
}
}
} else if ms.transforms[i].tr == RCT {
continue
} else {
return nil, fmt.Errorf("illegal transform type %d", ms.transforms[i].tr)
}
}
if !useGlobalTree {
tree, err := NewMATreeWithReader(reader, entropy.NewEntropyStreamWithReaderAndNumDists, entropy.NewEntropyStreamWithReader)
if err != nil {
return nil, err
}
ms.tree = tree
} else {
ms.tree = frame.getGlobalTree()
}
ms.stream = entropy.NewEntropyStreamWithStream(ms.tree.stream)
// get max Width from all channels.
maxWidth := uint32(0)
for _, c := range ms.channels {
if c.size.Width > maxWidth {
maxWidth = c.size.Width
}
}
ms.distMultiplier = int32(maxWidth)
return ms, nil
}
func (ms *ModularStream) getChannels() []*ModularChannel {
return ms.channels
}
func (ms *ModularStream) decodeChannels(reader jxlio.BitReader, partial bool) error {
groupDim := uint32(ms.frame.getFrameHeader().groupDim)
for i := 0; i < len(ms.channels); i++ {
channel := ms.channels[i]
if partial && i >= ms.nbMetaChannels &&
(channel.size.Width > groupDim || channel.size.Height > groupDim) {
break
}
err := channel.decode(reader, ms.stream, ms.wpParams, ms.tree, ms, int32(i), int32(ms.streamIndex), ms.distMultiplier)
if err != nil {
return err
}
}
if ms.stream != nil && !ms.stream.ValidateFinalState() {
return errors.New("illegal final modular state")
}
if !partial {
err := ms.applyTransforms()
if err != nil {
return err
}
}
return nil
}
func (ms *ModularStream) applyTransforms() error {
if ms.transformed {
return nil
}
ms.transformed = true
var err error
for i := len(ms.transforms) - 1; i >= 0; i-- {
if ms.transforms[i].tr == SQUEEZE {
spa := ms.squeezeMap[i]
for j := len(spa) - 1; j >= 0; j-- {
sp := spa[j]
begin := sp.beginC
end := begin + sp.numC - 1
var offset int
if sp.inPlace {
offset = end + 1
} else {
offset = len(ms.channels) + begin - end - 1
}
for c := begin; c <= end; c++ {
r := offset + c - begin
ch := ms.channels[c]
residu := ms.channels[r]
var output *ModularChannel
if sp.horizontal {
outputInfo := NewModularChannelWithAllParams(int32(ch.size.Height), int32(ch.size.Width+residu.size.Width), ch.vshift, ch.hshift-1, false)
output, err = inverseHorizontalSqueeze(outputInfo, ch, residu)
if err != nil {
return err
}
} else {
outputInfo := NewModularChannelWithAllParams(int32(ch.size.Height+residu.size.Height), int32(ch.size.Width), ch.vshift-1, ch.hshift, false)
output, err = inverseVerticalSqueeze(outputInfo, ch, residu)
if err != nil {
return err
}
}
ms.channels[c] = output
}
for c := 0; c < end-begin+1; c++ {
ms.channels = append(ms.channels[:offset], ms.channels[offset+1:]...)
}
}
} else if ms.transforms[i].tr == RCT {
// HERE... need to implement
permutation := ms.transforms[i].rctType / 7
transType := ms.transforms[i].rctType % 7
v := [3]*ModularChannel{}
start := ms.transforms[i].beginC
var err error
for j := 0; j < 3; j++ {
v[j] = ms.channels[start+j]
}
var rct func(int32, int32) error
switch transType {
case 0:
rct = func(x int32, y int32) error {
return nil
}
case 1:
rct = func(x int32, y int32) error {
v[2].buffer[y][x] += v[0].buffer[y][x]
return nil
}
case 2:
rct = func(x int32, y int32) error {
v[1].buffer[y][x] += v[0].buffer[y][x]
return nil
}
case 3:
rct = func(x int32, y int32) error {
a := v[0].buffer[y][x]
v[2].buffer[y][x] += a
v[1].buffer[y][x] += a
return nil
}
case 4:
rct = func(x int32, y int32) error {
v[1].buffer[y][x] += (v[0].buffer[y][x] + v[2].buffer[y][x]) >> 1
return nil
}
case 5:
rct = func(x int32, y int32) error {
a := v[0].buffer[y][x]
ac := a + v[2].buffer[y][x]
v[1].buffer[y][x] += (a + ac) >> 1
v[2].buffer[y][x] = ac
return nil
}
case 6:
rct = func(x int32, y int32) error {
b := v[1].buffer[y][x]
c := v[2].buffer[y][x]
tmp := v[0].buffer[y][x] - (c >> 1)
f := tmp - (b >> 1)
v[0].buffer[y][x] = f + b
v[1].buffer[y][x] = c + tmp
v[2].buffer[y][x] = f
return nil
}
default:
return errors.New("illegal RCT type")
}
for y := uint32(0); y < v[0].size.Height; y++ {
for x := uint32(0); x < v[0].size.Width; x++ {
err = rct(int32(x), int32(y))
if err != nil {
return err
}
}
}
for j := 0; j < 3; j++ {
ms.channels[start+permutationLUT[permutation][j]] = v[j]
}
} else if ms.transforms[i].tr == PALETTE {
first := ms.transforms[i].beginC + 1
endC := ms.transforms[i].beginC + ms.transforms[i].numC - 1
last := endC + 1
bitDepth := ms.frame.getGlobalMetadata().BitDepth.BitsPerSample
firstChannel := ms.channels[first]
c0 := ms.channels[0]
for j := first + 1; j <= last; j++ {
ms.channels = util.Add(ms.channels, j, NewModularChannelFromChannel(*firstChannel))
}
for c := 0; c < ms.transforms[i].numC; c++ {
ch := ms.channels[first+c]
for y := uint32(0); y < firstChannel.size.Height; y++ {
for x := uint32(0); x < firstChannel.size.Width; x++ {
index := ch.buffer[y][x]
isDelta := index < int32(ms.transforms[i].nbDeltas)
var value int32
if index >= 0 && index < int32(ms.transforms[i].nbColours) {
value = c0.buffer[c][index]
} else if index >= int32(ms.transforms[i].nbColours) {
index -= int32(ms.transforms[i].nbColours)
if index < 64 {
value = ((index>>(2*c)%4)*((1<<bitDepth)-1)/4 + (1 << util.Max(0, bitDepth-3)))
} else {
index -= 64
for k := 0; k < c; k++ {
index /= 5
}
value = (index % 5) * ((1 << bitDepth) - 1) / 4
}
} else if c < 3 {
index = (-index - 1) % 143
value = kDeltaPalette[(index+1)>>1][c]
if index&1 == 0 {
value = -value
}
if bitDepth > 8 {
value = value << (util.Min(bitDepth, 24) - 8)
}
} else {
value = 0
}
ch.buffer[y][x] = value
if isDelta {
pred, err := ch.prediction(int32(y), int32(x), int32(ms.transforms[i].dPred))
if err != nil {
return err
}
ch.buffer[y][x] += pred
}
}
}
}
ms.channels = ms.channels[1:]
if ms.transforms[i].beginC < ms.nbMetaChannels {
ms.nbMetaChannels -= 2 - ms.transforms[i].numC
} else {
ms.nbMetaChannels--
}
}
}
return nil
}
func inverseHorizontalSqueeze(channel *ModularChannel, orig *ModularChannel, res *ModularChannel) (*ModularChannel, error) {
//if channel.size.Height != orig.size.Height+res.size.Height ||
// (orig.size.Height != res.size.Height && orig.size.Height != 1+res.size.Height) ||
// channel.size.Width != orig.size.Width || res.size.Width != orig.size.Width {
// return nil, errors.New("Corrupted squeeze transform")
//}
if channel.size.Width != orig.size.Width+res.size.Width ||
(orig.size.Width != res.size.Width && orig.size.Width != 1+res.size.Width) ||
channel.size.Height != orig.size.Height || res.size.Height != orig.size.Height {
return nil, errors.New("Corrupted squeeze transform")
}
channel.allocate()
for y := uint32(0); y < channel.size.Height; y++ {
for x := uint32(0); x < res.size.Width; x++ {
avg := orig.buffer[y][x]
residu := res.buffer[y][x]
var nextAvg int32
if x+1 < uint32(orig.size.Width) {
nextAvg = orig.buffer[y][x+1]
} else {
nextAvg = avg
}
var left int32
if x > 0 {
left = channel.buffer[y][2*x-1]
} else {
nextAvg = avg
}
diff := residu + tendancy(left, avg, nextAvg)
first := avg + diff/2
channel.buffer[y][2*x] = first
channel.buffer[y][2*x+1] = first - diff
}
}
if orig.size.Width > res.size.Width {
xs := 2 * res.size.Width
for y := uint32(0); y < channel.size.Height; y++ {
channel.buffer[y][xs] = orig.buffer[y][res.size.Width]
}
}
return channel, nil
}
func inverseVerticalSqueeze(channel *ModularChannel, orig *ModularChannel, res *ModularChannel) (*ModularChannel, error) {
if channel.size.Height != orig.size.Height+res.size.Height ||
(orig.size.Height != res.size.Height && orig.size.Height != 1+res.size.Height) ||
channel.size.Width != orig.size.Width || res.size.Width != orig.size.Width {
return nil, errors.New("Corrupted squeeze transform")
}
channel.allocate()
for y := uint32(0); y < res.size.Height; y++ {
for x := uint32(0); x < channel.size.Width; x++ {
avg := orig.buffer[y][x]
residu := res.buffer[y][x]
var nextAvg int32
if y+1 < orig.size.Height {
nextAvg = orig.buffer[y+1][x]
} else {
nextAvg = avg
}
var top int32
if y > 0 {
top = channel.buffer[2*y-1][x]
} else {
nextAvg = avg
}
diff := residu + tendancy(top, avg, nextAvg)
first := avg + diff/2
channel.buffer[2*y][x] = first
channel.buffer[2*y+1][x] = first - diff
}
}
if orig.size.Height > res.size.Height {
// must be a quicker way surely?
for x := uint32(0); x < uint32(len(orig.buffer[res.size.Height])); x++ {
channel.buffer[2*res.size.Height][x] = orig.buffer[res.size.Height][x]
}
}
return channel, nil
}
func tendancy(a int32, b int32, c int32) int32 {
if a >= b && b >= c {
x := (4*a - 3*c - b + 6) / 12
d := 2 * (a - b)
e := 2 * (b - c)
if (x - (x & 1)) > d {
x = d + 1
}
if (x + (x & 1)) > e {
x = e
}
return x
}
if a <= b && b <= c {
x := (4*a - 3*c - b - 6) / 12
d := 2 * (a - b)
e := 2 * (b - c)
if (x + (x & 1)) < d {
x = d - 1
}
if (x - (x & 1)) < e {
x = e
}
return x
}
return 0
}
func (ms *ModularStream) getDecodedBuffer() [][][]int32 {
bands := make([][][]int32, len(ms.channels))
for i := 0; i < len(bands); i++ {
bands[i] = ms.channels[i].buffer
}
return bands
}
package frame
import (
"math"
"github.com/kpfaulkner/jxl-go/util"
)
type NoiseGroup struct {
rng *XorShiro
}
func NewNoiseGroupWithHeader(header *FrameHeader, seed0 int64, noiseBuffer [][][]float32, x0 int32, y0 int32) *NoiseGroup {
ng := &NoiseGroup{}
seed1 := (int64(x0) << 32) | int64(y0)
xSize := util.Min(header.groupDim, header.Width-uint32(x0))
ySize := util.Min(header.groupDim, header.Height-uint32(y0))
ng.rng = NewXorShiroWith2Seeds(seed0, seed1)
bits := make([]int64, 16)
for c := 0; c < 3; c++ {
for y := 0; y < int(ySize); y++ {
for x := 0; x < int(xSize); x++ {
ng.rng.fill(bits)
for i := 0; i < 16 && x+i < int(xSize); i++ {
f := (uint32(bits[i]) >> 9) | 0x3f_80_00_00
noiseBuffer[c][y0+int32(y)][x0+int32(x)+int32(i)] = math.Float32frombits(f)
}
}
}
}
return ng
}
package frame
import (
"github.com/kpfaulkner/jxl-go/entropy"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
type Pass struct {
replacedChannels []*ModularChannel
hfPass *HFPass
minShift uint32
maxShift uint32
}
type NewHFPassWithReaderFunc func(reader jxlio.BitReader, frame Framer, passIndex uint32,
readClusterMapFunc entropy.ReadClusterMapFunc,
newEntropyStreamWithReader entropy.EntropyStreamWithReaderAndNumDistsFunc,
readPermutation ReadPermutationFunc) (*HFPass, error)
func NewPassWithReader(reader jxlio.BitReader, frame Framer, passIndex uint32, prevMinShift uint32, hfPassFunc NewHFPassWithReaderFunc) (Pass, error) {
p := Pass{}
if passIndex > 0 {
p.maxShift = prevMinShift
} else {
p.maxShift = 3
}
n := -1
passes := frame.getFrameHeader().passes
for i := 0; i < len(passes.lastPass); i++ {
if passes.lastPass[i] == passIndex {
n = i
break
}
}
if n >= 0 {
p.minShift = uint32(util.CeilLog1p(int64(passes.downSample[n] - 1)))
} else {
p.minShift = p.maxShift
}
stream := frame.getLFGlobal().globalModular
p.replacedChannels = make([]*ModularChannel, len(stream.getChannels()))
for i := 0; i < len(p.replacedChannels); i++ {
ch := stream.getChannels()[i]
if !ch.decoded {
m := uint32(min(ch.hshift, ch.vshift))
if p.minShift <= m && m < p.maxShift {
p.replacedChannels[i] = NewModularChannelFromChannel(*ch)
}
}
}
var err error
if frame.getFrameHeader().Encoding == VARDCT {
p.hfPass, err = hfPassFunc(reader, frame, passIndex, entropy.ReadClusterMap, entropy.NewEntropyStreamWithReaderAndNumDists, readPermutation)
if err != nil {
return Pass{}, err
}
} else {
p.hfPass = nil
}
return p, nil
}
package frame
import (
"errors"
"fmt"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
var (
AFV_BASIS = [][]float32{
{0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25},
{0.876902929799142, 0.2206518106944235, -0.10140050393753763, -0.1014005039375375, 0.2206518106944236, -0.10140050393753777, -0.10140050393753772, -0.10140050393753763,
-0.10140050393753758, -0.10140050393753769, -0.1014005039375375, -0.10140050393753768, -0.10140050393753768,
-0.10140050393753759, -0.10140050393753763, -0.10140050393753741},
{0.0, 0.0, 0.40670075830260755, 0.44444816619734445, 0.0, 0.0, 0.19574399372042936, 0.2929100136981264, -0.40670075830260716,
-0.19574399372042872, 0.0, 0.11379074460448091, -0.44444816619734384, -0.29291001369812636,
-0.1137907446044814, 0.0},
{0.0, 0.0, -0.21255748058288748, 0.3085497062849767, 0.0, 0.4706702258572536, -0.1621205195722993,
0.0, -0.21255748058287047, -0.16212051957228327, -0.47067022585725277, -0.1464291867126764,
0.3085497062849487, 0.0, -0.14642918671266536, 0.4251149611657548},
{0.0, -0.7071067811865474, 0.0, 0.0, 0.7071067811865476, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{-0.4105377591765233, 0.6235485373547691, -0.06435071657946274, -0.06435071657946266, 0.6235485373547694,
-0.06435071657946284, -0.0643507165794628, -0.06435071657946274, -0.06435071657946272, -0.06435071657946279,
-0.06435071657946266, -0.06435071657946277, -0.06435071657946277, -0.06435071657946273, -0.06435071657946274,
-0.0643507165794626},
{0.0, 0.0, -0.4517556589999482, 0.15854503551840063, 0.0, -0.04038515160822202,
0.0074182263792423875, 0.39351034269210167, -0.45175565899994635, 0.007418226379244351, 0.1107416575309343,
0.08298163094882051, 0.15854503551839705, 0.3935103426921022, 0.0829816309488214, -0.45175565899994796},
{0.0, 0.0, -0.304684750724869, 0.5112616136591823, 0.0, 0.0, -0.290480129728998, -0.06578701549142804,
0.304684750724884, 0.2904801297290076, 0.0, -0.23889773523344604, -0.5112616136592012, 0.06578701549142545,
0.23889773523345467, 0.0},
{0.0, 0.0, 0.3017929516615495, 0.25792362796341184, 0.0, 0.16272340142866204,
0.09520022653475037, 0.0, 0.3017929516615503, 0.09520022653475055, -0.16272340142866173, -0.35312385449816297,
0.25792362796341295, 0.0, -0.3531238544981624, -0.6035859033230976},
{0.0, 0.0, 0.40824829046386274, 0.0, 0.0,
0.0, 0.0, -0.4082482904638628, -0.4082482904638635, 0.0, 0.0, -0.40824829046386296, 0.0, 0.4082482904638634,
0.408248290463863, 0.0},
{0.0, 0.0, 0.1747866975480809, 0.0812611176717539, 0.0, 0.0, -0.3675398009862027,
-0.307882213957909, -0.17478669754808135, 0.3675398009862011, 0.0, 0.4826689115059883, -0.08126111767175039,
0.30788221395790305, -0.48266891150598584, 0.0},
{0.0, 0.0, -0.21105601049335784, 0.18567180916109802, 0.0, 0.0,
0.49215859013738733, -0.38525013709251915, 0.21105601049335806, -0.49215859013738905, 0.0, 0.17419412659916217,
-0.18567180916109904, 0.3852501370925211, -0.1741941265991621, 0.0}, {0.0, 0.0, -0.14266084808807264,
-0.3416446842253372, 0.0, 0.7367497537172237, 0.24627107722075148, -0.08574019035519306, -0.14266084808807344,
0.24627107722075137, 0.14883399227113567, -0.04768680350229251, -0.3416446842253373, -0.08574019035519267,
-0.047686803502292804, -0.14266084808807242},
{0.0, 0.0, -0.13813540350758585, 0.3302282550303788, 0.0,
0.08755115000587084, -0.07946706605909573, -0.4613374887461511, -0.13813540350758294, -0.07946706605910261,
0.49724647109535086, 0.12538059448563663, 0.3302282550303805, -0.4613374887461554, 0.12538059448564315,
-0.13813540350758452},
{0.0, 0.0, -0.17437602599651067, 0.0702790691196284, 0.0, -0.2921026642334881,
0.3623817333531167, 0.0, -0.1743760259965108, 0.36238173335311646, 0.29210266423348785, -0.4326608024727445,
0.07027906911962818, 0.0, -0.4326608024727457, 0.34875205199302267},
{0.0, 0.0, 0.11354987314994337,
-0.07417504595810355, 0.0, 0.19402893032594343, -0.435190496523228, 0.21918684838857466, 0.11354987314994257,
-0.4351904965232251, 0.5550443808910661, -0.25468277124066463, -0.07417504595810233, 0.2191868483885728,
-0.25468277124066413, 0.1135498731499429}}
)
type PassGroup struct {
modularStream ModularStreamer
frame *Frame
groupID uint32
passID uint32
hfCoefficients *HFCoefficients
lfg *LFGroup
}
func NewPassGroupWithReader(reader jxlio.BitReader, frame *Frame, pass uint32, group uint32, replacedChannels []ModularChannel) (*PassGroup, error) {
pg := &PassGroup{}
pg.frame = frame
pg.groupID = group
pg.passID = pass
if frame.Header.Encoding == VARDCT {
coeff, err := NewHFCoefficientsWithReader(reader, frame, pass, group)
if err != nil {
return nil, err
}
// have confirmed that QuantizedCoeffs and DequantizedCoeffs are the same
// as JXLatte at least. Have not checked other members in HFCoefficients.
pg.hfCoefficients = coeff
} else {
pg.hfCoefficients = nil
}
stream, err := NewModularStreamWithStreamIndex(reader, frame, int(18+3*frame.numLFGroups+frame.numGroups*pass+group), replacedChannels)
if err != nil {
return nil, err
}
pg.modularStream = stream
err = stream.decodeChannels(reader, false)
if err != nil {
return nil, err
}
pg.lfg = frame.getLFGroupForGroup(int32(group))
return pg, nil
}
func (g *PassGroup) invertVarDCT(frameBuffer [][][]float32, prev *PassGroup) error {
header := g.frame.Header
if prev != nil {
panic("not implemented")
}
// differs from jxlatte
if err := g.hfCoefficients.bakeDequantizedCoeffs(); err != nil {
return err
}
groupLocation := g.frame.getGroupLocation(int32(g.groupID))
groupLocation.Y <<= 8
groupLocation.X <<= 8
coeffs := g.hfCoefficients.dequantHFCoeff
scratchBlock := util.MakeMatrix3DPooled[float32](5, 256, 256)
defer util.ReturnMatrix3DToPool(scratchBlock)
// Pre-allocate lfs outside the loop to avoid repeated allocations
lfs := make([]float32, 2)
for i := 0; i < len(g.hfCoefficients.blocks); i++ {
posInLFG := g.hfCoefficients.blocks[i]
if posInLFG == nil {
continue
}
tt := g.lfg.hfMetadata.dctSelect[posInLFG.Y][posInLFG.X]
groupY := posInLFG.Y - g.hfCoefficients.groupPos.Y
groupX := posInLFG.X - g.hfCoefficients.groupPos.X
for c := 0; c < 3; c++ {
sGroupY := groupY >> header.jpegUpsamplingY[c]
sGroupX := groupX >> header.jpegUpsamplingX[c]
if sGroupY<<header.jpegUpsamplingY[c] != groupY ||
sGroupX<<header.jpegUpsamplingX[c] != groupX {
continue
}
ppg := util.Point{
X: sGroupX << 3,
Y: sGroupY << 3,
}
ppf := util.Point{
X: ppg.X + (groupLocation.X >> header.jpegUpsamplingX[c]),
Y: ppg.Y + (groupLocation.Y >> header.jpegUpsamplingY[c]),
}
var coeff0 float32
var coeff1 float32
switch tt.transformMethod {
case METHOD_DCT:
if err := util.InverseDCT2D(coeffs[c], frameBuffer[c], ppg, ppf, tt.getPixelSize(), scratchBlock[0], scratchBlock[1], false); err != nil {
return err
}
case METHOD_DCT8_4:
coeff0 = coeffs[c][ppg.Y][ppg.X]
coeff1 = coeffs[c][ppg.Y+1][ppg.X]
lfs[0] = coeff0 + coeff1
lfs[1] = coeff0 - coeff1
for x := int32(0); x < 2; x++ {
scratchBlock[0][0][0] = lfs[x]
for iy := int32(0); iy < 4; iy++ {
startX := int32(0)
if iy == 0 {
startX = 1
}
for ix := startX; ix < 8; ix++ {
scratchBlock[0][iy][ix] = coeffs[c][ppg.Y+x+iy*2][ppg.X+ix]
}
}
ppf2 := util.Point{
X: ppf.X,
Y: ppf.Y,
}
ppf2.X += x << 2
if err := util.InverseDCT2D(scratchBlock[0], frameBuffer[c], util.ZERO, ppf2,
util.Dimension{Height: 4, Width: 8}, scratchBlock[1], scratchBlock[2], true); err != nil {
return err
}
}
case METHOD_DCT4_8:
coeff0 = coeffs[c][ppg.Y][ppg.X]
coeff1 = coeffs[c][ppg.Y+1][ppg.X]
lfs[0] = coeff0 + coeff1
lfs[1] = coeff0 - coeff1
for y := int32(0); y < 2; y++ {
scratchBlock[0][0][0] = lfs[y]
for iy := int32(0); iy < 4; iy++ {
startX := int32(0)
if iy == 0 {
startX = 1
}
for ix := startX; ix < 8; ix++ {
scratchBlock[0][iy][ix] = coeffs[c][ppg.Y+y+iy*2][ppg.X+ix]
}
}
ppf2 := util.Point{
X: ppf.X,
Y: ppf.Y,
}
ppf2.Y += y << 2
if err := util.InverseDCT2D(scratchBlock[0], frameBuffer[c], util.ZERO, ppf2,
util.Dimension{Height: 4, Width: 8}, scratchBlock[1], scratchBlock[2], false); err != nil {
return err
}
}
case METHOD_AFV:
//displayBuffer("before", frameBuffer[c])
// FIXME(kpfaulkner) there is some bug in here compared to JXLatte, but
// have yet to figure it out.
if err := g.invertAFV(coeffs[c], frameBuffer[c], tt, ppg, ppf, scratchBlock); err != nil {
return err
}
//displayBuffer("after", frameBuffer[c])
case METHOD_DCT2:
g.auxDCT2(coeffs[c], scratchBlock[0], ppg, util.ZERO, 2)
g.auxDCT2(scratchBlock[0], scratchBlock[1], util.ZERO, util.ZERO, 4)
g.auxDCT2(scratchBlock[1], frameBuffer[c], util.ZERO, ppf, 8)
case METHOD_HORNUSS:
g.auxDCT2(coeffs[c], scratchBlock[1], ppg, util.ZERO, 2)
for y := int32(0); y < 2; y++ {
for x := int32(0); x < 2; x++ {
blockLF := scratchBlock[1][y][x]
residual := float32(0.0)
for iy := int32(0); iy < 4; iy++ {
ixTemp := int32(0)
if iy == 0 {
ixTemp = 1
}
for ix := ixTemp; ix < 4; ix++ {
residual += coeffs[c][ppg.Y+y+iy*2][ppg.X+x+ix*2]
}
}
scratchBlock[0][4*y+1][4*x+1] = blockLF - residual*0.0625
for iy := int32(0); iy < 4; iy++ {
for ix := int32(0); ix < 4; ix++ {
if ix == 1 && iy == 1 {
continue
}
scratchBlock[0][4*y+iy][x*4+ix] = coeffs[c][ppg.Y+y+iy*2][ppg.X+x+ix*2] + scratchBlock[0][4*y+1][4*x+1]
}
}
scratchBlock[0][4*y][4*x] = coeffs[c][ppg.Y+y+2][ppg.X+x+2] + scratchBlock[0][4*y+1][4*x+1]
}
}
layBlock(scratchBlock[0], frameBuffer[c], util.ZERO, ppf, tt.getPixelSize())
case METHOD_DCT4:
panic("not implemented")
default:
return errors.New("transform not implemented")
}
}
}
return nil
}
func layBlock(block [][]float32, buffer [][]float32, inPos util.Point, outPos util.Point, inSize util.Dimension) {
for y := int32(0); y < int32(inSize.Height); y++ {
// Make sure to specify end position in X slice. ie the outPos.X + inSize.Width etc.
// otherwise some images end up with black unpopulated sections.
copy(buffer[y+outPos.Y][outPos.X:outPos.X+int32(inSize.Width)], block[y+inPos.Y][inPos.X:inPos.X+int32(inSize.Width)])
}
}
func (g *PassGroup) invertAFV(coeffs [][]float32, buffer [][]float32, tt *TransformType, ppg util.Point, ppf util.Point, scratchBlock [][][]float32) error {
// some debugging logic here... there's a bug in here somewhere :/
if false {
fmt.Printf("invertAFV coeffs:\n")
for y := 0; y < len(coeffs); y++ {
total := float32(0.0)
for x := 0; x < len(coeffs[y]); x++ {
//fmt.Printf("%0.10f ", coeffs[i][j])
total += coeffs[y][x]
}
if total != 0.0 {
// super inefficient... but dont care.
fmt.Printf("coord y=%d non zero %0.10f\n", y, total)
for x := 0; x < len(coeffs[y]); x++ {
//fmt.Printf("%0.10f ", coeffs[y][x])
}
//fmt.Printf("\n")
}
}
fmt.Printf("==========\n")
}
scratchBlock[0][0][0] = (coeffs[ppg.Y][ppg.X] + coeffs[ppg.Y+1][ppg.X] + coeffs[ppg.Y][ppg.X+1]) * 4.0
for iy := int32(0); iy < 4; iy++ {
startX := int32(0)
if iy == 0 {
startX = 1
}
for ix := startX; ix < 4; ix++ {
scratchBlock[0][iy][ix] = coeffs[ppg.Y+iy*2][ppg.X+ix*2]
}
}
var flipX int32
var flipY int32
if tt == AFV2 || tt == AFV3 {
flipY = 1
}
if tt == AFV1 || tt == AFV3 {
flipX = 1
}
totalSample := float32(0)
for iy := 0; iy < 4; iy++ {
for ix := 0; ix < 4; ix++ {
sample := float32(0.0)
for j := 0; j < 16; j++ {
jy := j >> 2
jx := j & 0b11
sample += scratchBlock[0][jy][jx] * AFV_BASIS[j][iy*4+ix]
}
scratchBlock[1][iy][ix] = sample
totalSample += sample
}
}
for iy := int32(0); iy < 4; iy++ {
for ix := int32(0); ix < 4; ix++ {
xpos := ix
ypos := iy
if flipY == 1 {
ypos = 3 - iy
}
if flipX == 1 {
xpos = 3 - ix
}
buffer[ppf.Y+flipY*4+iy][ppf.X+flipX*4+ix] = scratchBlock[1][ypos][xpos]
}
}
scratchBlock[0][0][0] = coeffs[ppg.Y][ppg.X] + coeffs[ppg.Y+1][ppg.X] - coeffs[ppg.Y][ppg.X+1]
for iy := int32(0); iy < 4; iy++ {
startX := int32(0)
if iy == 0 {
startX = 1
}
for ix := startX; ix < 4; ix++ {
scratchBlock[0][iy][ix] = coeffs[ppg.Y+iy*2][ppg.X+ix*2+1]
}
}
if err := util.InverseDCT2D(scratchBlock[0], scratchBlock[1], util.ZERO, util.ZERO,
util.Dimension{Width: 4, Height: 4}, scratchBlock[2], scratchBlock[3], false); err != nil {
return err
}
for iy := int32(0); iy < 4; iy++ {
for ix := int32(0); ix < 4; ix++ {
xx := int32(4)
if flipX == 1 {
xx = 0
}
buffer[ppf.Y+flipY*4+iy][ppf.X+xx+ix] = scratchBlock[1][ix][iy]
}
}
scratchBlock[0][0][0] = coeffs[ppg.Y][ppg.X] - coeffs[ppg.Y+1][ppg.X]
for iy := int32(0); iy < 4; iy++ {
startX := int32(0)
if iy == 0 {
startX = 1
}
for ix := startX; ix < 8; ix++ {
scratchBlock[0][iy][ix] = coeffs[ppg.Y+1+iy*2][ppg.X+ix]
}
}
if err := util.InverseDCT2D(scratchBlock[0], scratchBlock[1], util.ZERO, util.ZERO, util.Dimension{Height: 4, Width: 8},
scratchBlock[2], scratchBlock[3], false); err != nil {
return err
}
for iy := int32(0); iy < 4; iy++ {
for ix := int32(0); ix < 8; ix++ {
yy := int32(4)
if flipY == 1 {
yy = 0
}
buffer[ppf.Y+yy+iy][ppf.X+ix] = scratchBlock[1][iy][ix]
}
}
return nil
}
func (g *PassGroup) auxDCT2(coeffs [][]float32, result [][]float32, p util.Point, ps util.Point, s int32) {
g.layBlock(coeffs, result, p, ps, util.Dimension{Height: 8, Width: 8})
num := s / 2
for iy := int32(0); iy < num; iy++ {
for ix := int32(0); ix < num; ix++ {
c00 := coeffs[p.Y+iy][p.X+ix]
c01 := coeffs[p.Y+iy][p.X+ix+num]
c10 := coeffs[p.Y+iy+num][p.X+ix]
c11 := coeffs[p.Y+iy+num][p.X+ix+num]
r00 := c00 + c01 + c10 + c11
r01 := c00 + c01 - c10 - c11
r10 := c00 - c01 + c10 - c11
r11 := c00 - c01 - c10 + c11
result[ps.Y+iy*2][ps.X+ix*2] = r00
result[ps.Y+iy*2][ps.X+ix*2+1] = r01
result[ps.Y+iy*2+1][ps.X+ix*2] = r10
result[ps.Y+iy*2+1][ps.X+ix*2+1] = r11
}
}
}
func (g *PassGroup) layBlock(block [][]float32, buffer [][]float32, inPos util.Point, outPos util.Point, inSize util.Dimension) {
for y := int32(0); y < int32(inSize.Height); y++ {
copy(buffer[y+outPos.Y][outPos.X:outPos.X+int32(inSize.Width)], block[y+inPos.Y][inPos.X:inPos.X+int32(inSize.Width)])
}
}
// Release returns pooled buffers to the pool
func (g *PassGroup) Release() {
if g.hfCoefficients != nil {
g.hfCoefficients.Release()
g.hfCoefficients = nil
}
}
package frame
import "github.com/kpfaulkner/jxl-go/jxlio"
type PassesInfo struct {
shift []uint32
downSample []uint32
lastPass []uint32
numPasses uint32
numDS uint32
}
func NewPassesInfo() *PassesInfo {
pi := &PassesInfo{}
pi.numPasses = 1
pi.numDS = 0
pi.shift = []uint32{}
pi.downSample = []uint32{}
pi.lastPass = []uint32{}
return pi
}
func NewPassesInfoWithReader(reader jxlio.BitReader) (*PassesInfo, error) {
pi := &PassesInfo{}
if numPasses, err := reader.ReadU32(1, 0, 2, 0, 3, 0, 4, 3); err != nil {
return nil, err
} else {
pi.numPasses = numPasses
}
if pi.numPasses != 1 {
if numDS, err := reader.ReadU32(0, 0, 1, 0, 2, 0, 3, 1); err != nil {
return nil, err
} else {
pi.numDS = numDS
}
} else {
pi.numDS = 0
}
pi.shift = make([]uint32, pi.numPasses)
for i := uint32(0); i < pi.numPasses-1; i++ {
if shift, err := reader.ReadBits(2); err != nil {
return nil, err
} else {
pi.shift[i] = uint32(shift)
}
}
pi.shift[pi.numPasses-1] = 0
pi.downSample = make([]uint32, pi.numDS+1)
for i := 0; i < int(pi.numDS); i++ {
if downSample, err := reader.ReadBits(2); err != nil {
return nil, err
} else {
pi.downSample[i] = 1 << uint32(downSample)
}
}
pi.lastPass = make([]uint32, pi.numDS+1)
for i := 0; i < int(pi.numDS); i++ {
if lastPass, err := reader.ReadU32(0, 0, 1, 0, 2, 0, 0, 3); err != nil {
return nil, err
} else {
pi.lastPass[i] = lastPass
}
}
pi.downSample[pi.numDS] = 1
pi.lastPass[pi.numDS] = pi.numPasses - 1
return pi, nil
}
package frame
import (
"github.com/kpfaulkner/jxl-go/entropy"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
type Patch struct {
Width int32
Height int32
Bounds util.Rectangle
Ref int32
Origin util.Point
Positions []util.Point
BlendingInfos [][]BlendingInfo
}
func NewPatchWithStreamAndReader(stream entropy.EntropyStreamer, reader jxlio.BitReader, extraChannelCount int, alphaChannelCount int) (Patch, error) {
return Patch{}, nil
}
package frame
import "github.com/kpfaulkner/jxl-go/jxlio"
type Quantizer struct {
globalScale uint32
quantLF uint32
scaledDequant []float32
}
func NewQuantizerWithReader(reader jxlio.BitReader, lfDequant []float32) (*Quantizer, error) {
q := &Quantizer{}
q.scaledDequant = make([]float32, 3)
var err error
q.globalScale, err = reader.ReadU32(1, 11, 2049, 11, 4097, 12, 8193, 16)
if err != nil {
return nil, err
}
q.quantLF, err = reader.ReadU32(16, 0, 1, 5, 1, 8, 1, 16)
if err != nil {
return nil, err
}
for i := 0; i < 3; i++ {
q.scaledDequant[i] = (1 << 16) * lfDequant[i] / float32(q.globalScale*q.quantLF)
}
return q, nil
}
package frame
import (
"github.com/kpfaulkner/jxl-go/bundle"
"github.com/kpfaulkner/jxl-go/jxlio"
)
type RestorationFilter struct {
gab bool
customGab bool
gab1Weights []float32
gab2Weights []float32
epfIterations uint32
epfSharpCustom bool
epfSharpLut []float32
epfChannelScale []float32
epfSigmaCustom bool
epfQuantMul float32
epfPass0SigmaScale float32
epfPass2SigmaScale float32
epfBorderSadMul float32
epfSigmaForModular float32
extensions *bundle.Extensions
epfWeightCustom bool
}
func NewRestorationFilter() *RestorationFilter {
rf := &RestorationFilter{}
rf.epfSharpLut = []float32{0.0, 1.0 / 7.0, 2.0 / 7.0, 3.0 / 7.0, 4.0 / 7.0, 5.0 / 7.0, 6.0 / 7.0, 1.0}
rf.epfChannelScale = []float32{40.0, 5.0, 3.5}
rf.gab1Weights = []float32{0.115169525, 0.115169525, 0.115169525}
rf.gab2Weights = []float32{0.061248592, 0.061248592, 0.061248592}
rf.gab = true
rf.customGab = false
rf.epfIterations = 2
rf.epfSharpCustom = false
rf.epfWeightCustom = false
rf.epfSigmaCustom = false
rf.epfQuantMul = 0.46
rf.epfPass0SigmaScale = 0.9
rf.epfPass2SigmaScale = 6.5
rf.epfBorderSadMul = 2.0 / 3.0
rf.epfSigmaForModular = 1.0
rf.extensions = bundle.NewExtensions()
for i := 0; i < 8; i++ {
rf.epfSharpLut[i] *= rf.epfQuantMul
}
return rf
}
func NewRestorationFilterWithReader(reader jxlio.BitReader, encoding uint32) (*RestorationFilter, error) {
rf := &RestorationFilter{}
rf.epfSharpLut = []float32{0, 1.0 / 7.0, 2.0 / 7.0, 3.0 / 7.0, 4.0 / 7.0, 5.0 / 7.0, 6.0 / 7.0, 1.0}
rf.epfChannelScale = []float32{40.0, 5.0, 3.5}
rf.gab1Weights = []float32{0.115169525, 0.115169525, 0.115169525}
rf.gab2Weights = []float32{0.061248592, 0.061248592, 0.061248592}
var allDefault bool
var err error
if allDefault, err = reader.ReadBool(); err != nil {
return nil, err
}
if allDefault {
rf.gab = true
} else {
if rf.gab, err = reader.ReadBool(); err != nil {
return nil, err
}
}
if !allDefault && rf.gab {
if rf.customGab, err = reader.ReadBool(); err != nil {
return nil, err
}
} else {
rf.customGab = false
}
if rf.customGab {
for i := 0; i < 3; i++ {
if rf.gab1Weights[i], err = reader.ReadF16(); err != nil {
return nil, err
}
if rf.gab2Weights[i], err = reader.ReadF16(); err != nil {
return nil, err
}
}
}
if allDefault {
rf.epfIterations = 2
} else {
if epfItreations, err := reader.ReadBits(2); err != nil {
return nil, err
} else {
rf.epfIterations = uint32(epfItreations)
}
}
if !allDefault && rf.epfIterations > 0 && encoding == VARDCT {
if rf.epfSharpCustom, err = reader.ReadBool(); err != nil {
return nil, err
}
} else {
rf.epfSharpCustom = false
}
if rf.epfSharpCustom {
for i := 0; i < len(rf.epfSharpLut); i++ {
if rf.epfSharpLut[i], err = reader.ReadF16(); err != nil {
return nil, err
}
}
}
if !allDefault && rf.epfIterations > 9 {
if rf.epfWeightCustom, err = reader.ReadBool(); err != nil {
return nil, err
}
} else {
rf.epfWeightCustom = false
}
if rf.epfWeightCustom {
for i := 0; i < len(rf.epfChannelScale); i++ {
if rf.epfChannelScale[i], err = reader.ReadF16(); err != nil {
return nil, err
}
}
_, _ = reader.ReadBits(32) // ??? what do we do with this data?
}
if !allDefault && rf.epfIterations > 0 {
if rf.epfSigmaCustom, err = reader.ReadBool(); err != nil {
return nil, err
}
} else {
rf.epfSigmaCustom = false
}
if rf.epfSigmaCustom && encoding == VARDCT {
rf.epfQuantMul, err = reader.ReadF16()
if err != nil {
return nil, err
}
} else {
rf.epfQuantMul = 0.46
}
if rf.epfSigmaCustom {
if rf.epfPass0SigmaScale, err = reader.ReadF16(); err != nil {
return nil, err
}
if rf.epfPass2SigmaScale, err = reader.ReadF16(); err != nil {
return nil, err
}
if rf.epfBorderSadMul, err = reader.ReadF16(); err != nil {
return nil, err
}
} else {
rf.epfPass0SigmaScale = 0.9
rf.epfPass2SigmaScale = 6.5
rf.epfBorderSadMul = 2.0 / 3.0
}
if !allDefault && rf.epfIterations > 0 && encoding == MODULAR {
if rf.epfSigmaForModular, err = reader.ReadF16(); err != nil {
return nil, err
}
} else {
rf.epfSigmaForModular = 1.0
}
if allDefault {
rf.extensions = bundle.NewExtensions()
} else {
rf.extensions, err = bundle.NewExtensionsWithReader(reader)
if err != nil {
return nil, err
}
}
for i := 0; i < 8; i++ {
rf.epfSharpLut[i] *= rf.epfQuantMul
}
return rf, nil
}
package frame
import "github.com/kpfaulkner/jxl-go/jxlio"
type WPParams struct {
param1 int
param2 int
param3a int32
param3b int32
param3c int32
param3d int32
param3e int32
weight [4]int64
}
func NewWPParams(reader jxlio.BitReader) (*WPParams, error) {
wp := WPParams{}
var err error
var defaultParams bool
if defaultParams, err = reader.ReadBool(); err != nil {
return nil, err
}
if defaultParams {
wp.param1 = 16
wp.param2 = 10
wp.param3a = 7
wp.param3b = 7
wp.param3c = 7
wp.param3d = 0
wp.param3e = 0
wp.weight[0] = 13
wp.weight[1] = 12
wp.weight[2] = 12
wp.weight[3] = 12
} else {
if param, err := reader.ReadBits(5); err != nil {
return nil, err
} else {
wp.param1 = int(param)
}
if param, err := reader.ReadBits(5); err != nil {
return nil, err
} else {
wp.param2 = int(param)
}
if param, err := reader.ReadBits(5); err != nil {
return nil, err
} else {
wp.param3a = int32(param)
}
if param, err := reader.ReadBits(5); err != nil {
return nil, err
} else {
wp.param3b = int32(param)
}
if param, err := reader.ReadBits(5); err != nil {
return nil, err
} else {
wp.param3c = int32(param)
}
if param, err := reader.ReadBits(5); err != nil {
return nil, err
} else {
wp.param3d = int32(param)
}
if param, err := reader.ReadBits(5); err != nil {
return nil, err
} else {
wp.param3e = int32(param)
}
if data, err := reader.ReadBits(4); err != nil {
return nil, err
} else {
wp.weight[0] = int64(data)
}
if data, err := reader.ReadBits(4); err != nil {
return nil, err
} else {
wp.weight[1] = int64(data)
}
if data, err := reader.ReadBits(4); err != nil {
return nil, err
} else {
wp.weight[2] = int64(data)
}
if data, err := reader.ReadBits(4); err != nil {
return nil, err
} else {
wp.weight[3] = int64(data)
}
}
return &wp, nil
}
package frame
type XorShiro struct {
state0 []uint64
state1 []uint64
batch []uint64
batchPos int
}
func (s *XorShiro) fill(bits []int64) {
for i := 0; i < len(bits); i += 2 {
l := s.nextLong()
bits[i] = int64(l & uint64(0xFF_FF_FF_FF))
bits[i+1] = int64((l >> 32) & 0xFF_FF_FF_FF)
}
}
func (s *XorShiro) nextLong() uint64 {
s.fillBatch()
b := s.batch[s.batchPos]
s.batchPos++
return b
}
func (s *XorShiro) fillBatch() {
if s.batchPos < len(s.batch) {
return
}
for i := 0; i < len(s.batch); i++ {
a := s.state1[i]
b := s.state0[i]
s.batch[i] = a + b
s.state0[i] = a
b ^= b << 23
s.state1[i] = b ^ a ^ (b >> 18) ^ (a >> 5)
}
s.batchPos = 0
}
func NewXorShiroWith4Seeds(seed0 int32, seed1 int32, seed2 int32, seed3 int32) *XorShiro {
return NewXorShiroWith2Seeds(
int64(seed0)<<32|int64(seed1)&0xFF_FF_FF_FF,
int64(seed2)<<32|int64(seed3)&0xFF_FF_FF_FF)
}
func NewXorShiroWith2Seeds(seed0 int64, seed1 int64) *XorShiro {
xs := &XorShiro{}
xs.state0 = make([]uint64, 8)
xs.state1 = make([]uint64, 8)
xs.batch = make([]uint64, 8)
xs.batchPos = 0
xs.state0[0] = splitMix64(uint64(seed0) + 0x9e3779b97f4a7c15)
xs.state1[0] = splitMix64(uint64(seed1) + 0x9e3779b97f4a7c15)
for i := 1; i < 8; i++ {
xs.state0[i] = splitMix64(uint64(xs.state0[i-1]))
xs.state1[i] = splitMix64(uint64(xs.state1[i-1]))
}
return xs
}
func splitMix64(z uint64) uint64 {
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9
z = (z ^ (z >> 27)) * 0x94d049bb133111eb
return z ^ (z >> 31)
}
package frame
import "github.com/kpfaulkner/jxl-go/util"
type DCTParam struct {
dctParam [][]float64
param [][]float32
mode int32
denominator float32
params4x4 [][]float64
}
// Equals compares 2 DCTParams structs. Slightly concerned about having to
// compare all the multi-dimensional slices. Investigated slices.EqualFunc, Equal, compare etc.
// but unsure about getting those working for multi-dimensional. So will just do naively for now
// and measure later.
// TODO(kpfaulkner) do some measuring around performance here.
func (dct DCTParam) Equals(other DCTParam) bool {
if dct.mode != other.mode {
return false
}
if dct.denominator != other.denominator {
return false
}
if len(dct.dctParam) != len(other.dctParam) {
return false
}
if len(dct.param) != len(other.param) {
return false
}
// FIXME(kpfaulkner) not keen on == for float64... need to double check
if !util.CompareMatrix2D(dct.dctParam, other.dctParam, func(a float64, b float64) bool {
return a == b
}) {
return false
}
if !util.CompareMatrix2D(dct.param, other.param, func(a float32, b float32) bool {
return a == b
}) {
return false
}
if !util.CompareMatrix2D(dct.params4x4, other.params4x4, func(a float64, b float64) bool {
return a == b
}) {
return false
}
return true
}
func NewDCTParam() *DCTParam {
return &DCTParam{
dctParam: [][]float64{},
param: [][]float32{},
params4x4: [][]float64{},
mode: 0,
denominator: 0,
}
}
package frame
import (
"errors"
"github.com/kpfaulkner/jxl-go/bundle"
"github.com/kpfaulkner/jxl-go/entropy"
"github.com/kpfaulkner/jxl-go/image"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
type FakeFramer struct {
lfGroup *LFGroup
hfGlobal *HFGlobal
lfGlobal *LFGlobal
header *FrameHeader
passes []Pass
groupSize *util.Dimension
groupSizeError error
groupPosInLFGroupPoint *util.Point
imageHeader *bundle.ImageHeader
}
func (f *FakeFramer) getLFGroupSize(lfGroupID int32) (util.Dimension, error) {
//TODO implement me
return util.Dimension{
Width: 5,
Height: 5,
}, nil
}
func (f *FakeFramer) getNumLFGroups() uint32 {
return 0
}
func (f *FakeFramer) getLFGroupLocation(lfGroupID int32) *util.Point {
//TODO implement me
panic("implement me")
}
func (f *FakeFramer) getGlobalTree() *MATreeNode {
t := &MATreeNode{stream: &entropy.EntropyStream{}}
return t
}
func (f *FakeFramer) setGlobalTree(tree *MATreeNode) {}
func (f *FakeFramer) getLFGroupForGroup(groupID int32) *LFGroup {
return f.lfGroup
}
func (f *FakeFramer) getHFGlobal() *HFGlobal {
return f.hfGlobal
}
func (f *FakeFramer) getLFGlobal() *LFGlobal {
return f.lfGlobal
}
func (f *FakeFramer) getFrameHeader() *FrameHeader {
return f.header
}
func (f *FakeFramer) getPasses() []Pass {
return f.passes
}
func (f *FakeFramer) getGroupSize(groupID int32) (util.Dimension, error) {
if f.groupSizeError != nil {
return util.Dimension{}, f.groupSizeError
}
return *f.groupSize, nil
}
func (f *FakeFramer) groupPosInLFGroup(lfGroupID int32, groupID uint32) util.Point {
return *f.groupPosInLFGroupPoint
}
func (f *FakeFramer) getGlobalMetadata() *bundle.ImageHeader {
return f.imageHeader
}
func NewFakeFramer(encoding uint32) Framer {
ff := &FakeFramer{
header: &FrameHeader{
jpegUpsamplingX: []int32{0, 0, 0},
jpegUpsamplingY: []int32{0, 0, 0},
Bounds: &util.Rectangle{
Origin: util.Point{},
Size: util.Dimension{Width: 5, Height: 5},
},
passes: NewPassesInfo(),
Encoding: encoding,
},
lfGlobal: NewLFGlobal(),
hfGlobal: &HFGlobal{
numHFPresets: 1,
},
imageHeader: &bundle.ImageHeader{
ExtraChannelInfo: []bundle.ExtraChannelInfo{{
DimShift: 0,
}},
},
}
ff.lfGlobal.scaledDequant = []float32{1, 1, 1}
return ff
}
func NewFakeHFBlockContextFunc(reader jxlio.BitReader, readClusterMap func(reader jxlio.BitReader, clusterMap []int, maxClusters int) (int, error)) (*HFBlockContext, error) {
return nil, nil
}
func NewFakeHFMetadataFunc(reader jxlio.BitReader, parent *LFGroup, frame Framer, modularStreamFunc NewModularStreamWithStreamIndexFunc) (*HFMetadata, error) {
return &HFMetadata{}, nil
}
func NewFakeLFCoeffientsFunc(reader jxlio.BitReader, parent *LFGroup, frame Framer, lfBuffer []image.ImageBuffer, modularStreamFunc NewModularStreamFunc) (*LFCoefficients, error) {
return &LFCoefficients{}, nil
}
type FakeEntropyStreamer struct {
FakeSymbols []int32
}
func (f FakeEntropyStreamer) GetDists() []entropy.SymbolDistribution {
return nil
}
func (f FakeEntropyStreamer) GetState() *entropy.ANSState {
//TODO implement me
panic("implement me")
}
func (f *FakeEntropyStreamer) ReadSymbol(reader jxlio.BitReader, context int) (int32, error) {
if len(f.FakeSymbols) == 0 {
return 0, errors.New("no more symbols")
}
symbol := f.FakeSymbols[0]
f.FakeSymbols = f.FakeSymbols[1:]
return symbol, nil
}
func (f FakeEntropyStreamer) TryReadSymbol(reader jxlio.BitReader, context int) int32 {
return 0
}
func (f FakeEntropyStreamer) ReadSymbolWithMultiplier(reader jxlio.BitReader, context int, distanceMultiplier int32) (int32, error) {
return 0, nil
}
func (f FakeEntropyStreamer) ReadHybridInteger(reader jxlio.BitReader, config *entropy.HybridIntegerConfig, token int32) (int32, error) {
//TODO implement me
panic("implement me")
}
func (f FakeEntropyStreamer) ValidateFinalState() bool {
return true
}
func NewFakeEntropyStreamer() entropy.EntropyStreamer {
return &FakeEntropyStreamer{}
}
func NewFakeEntropyStreamerFunc(reader jxlio.BitReader, numDists int, readClusterMapFunc entropy.ReadClusterMapFunc) (entropy.EntropyStreamer, error) {
return &FakeEntropyStreamer{}, nil
}
func NewFakeEntropyWithReaderFunc(reader jxlio.BitReader, numDists int, disallowLZ77 bool, readClusterMapFunc func(reader jxlio.BitReader, clusterMap []int, maxClusters int) (int, error)) (entropy.EntropyStreamer, error) {
return &FakeEntropyStreamer{}, nil
}
package frame
import (
"errors"
"fmt"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
type HFMetadata struct {
nbBlocks uint64
dctSelect [][]*TransformType
hfMultiplier [][]int32
hfStreamBuffer [][][]int32
parent *LFGroup
blockList []util.Point
}
type NewModularStreamWithStreamIndexFunc func(reader jxlio.BitReader, frame Framer, streamIndex int, channelArray []ModularChannel) (ModularStreamer, error)
type NewHFMetadataWithReaderFunc func(reader jxlio.BitReader, parent *LFGroup, frame Framer, modularStreamFunc NewModularStreamWithStreamIndexFunc) (*HFMetadata, error)
func NewHFMetadataWithReader(reader jxlio.BitReader, parent *LFGroup, frame Framer, modularStreamFunc NewModularStreamWithStreamIndexFunc) (*HFMetadata, error) {
hf := &HFMetadata{
parent: parent,
}
n := util.CeilLog2(parent.size.Height * parent.size.Width)
nbBlocks, err := reader.ReadBits(uint32(n))
if err != nil {
return nil, err
}
hf.nbBlocks = nbBlocks + 1
correlationHeight := int32((parent.size.Height + 7) / 8)
correlationWidth := int32((parent.size.Width + 7) / 8)
xFromY := NewModularChannelWithAllParams(correlationHeight, correlationWidth, 0, 0, false)
bFromY := NewModularChannelWithAllParams(correlationHeight, correlationWidth, 0, 0, false)
blockInfo := NewModularChannelWithAllParams(2, int32(hf.nbBlocks), 0, 0, false)
sharpness := NewModularChannelWithAllParams(int32(parent.size.Height), int32(parent.size.Width), 0, 0, false)
hfStream, err := modularStreamFunc(reader, frame, 1+2*int(frame.getNumLFGroups())+int(parent.lfGroupID), []ModularChannel{*xFromY, *bFromY, *blockInfo, *sharpness})
if err != nil {
return nil, err
}
err = hfStream.decodeChannels(reader, false)
if err != nil {
return nil, err
}
hf.hfStreamBuffer = hfStream.getDecodedBuffer()
hf.dctSelect = util.MakeMatrix2D[*TransformType](parent.size.Height, parent.size.Width)
hf.hfMultiplier = util.MakeMatrix2D[int32](parent.size.Height, parent.size.Width)
blockInfoBuffer := hf.hfStreamBuffer[2]
lastBlock := util.Point{X: 0, Y: 0}
tta := allDCT
hf.blockList = make([]util.Point, hf.nbBlocks)
for i := uint64(0); i < hf.nbBlocks; i++ {
t := blockInfoBuffer[0][i]
if t > 26 || t < 0 {
return nil, fmt.Errorf("Invalid transform Type %d", t)
}
tt := tta[t]
pos, err := hf.placeBlock(lastBlock, tt, 1+blockInfoBuffer[1][i])
if err != nil {
return nil, err
}
lastBlock = util.Point{
X: pos.X,
Y: pos.Y,
}
hf.blockList[i] = pos
}
return hf, nil
}
// FIXME(kpfaulkner) 20241102 think somethings wrong in here... llfScale is bad
func (m *HFMetadata) placeBlock(lastBlock util.Point, block TransformType, mul int32) (util.Point, error) {
var x int32
outerY:
for y := lastBlock.Y; y < int32(len(m.dctSelect)); y++ {
x = 0
dctY := m.dctSelect[y]
outerX:
for ; x < int32(len(dctY)); x++ {
if block.dctSelectWidth+x > int32(len(dctY)) {
continue outerY
}
for ix := int32(0); ix < block.dctSelectWidth; ix++ {
tt := dctY[x+ix]
if tt != nil {
x += tt.dctSelectWidth - 1
continue outerX
}
}
pos := util.Point{
X: x,
Y: y,
}
//m.hfMultiplier[y][x] = mul
for iy := int32(0); iy < block.dctSelectHeight; iy++ {
for f := x; f < x+block.dctSelectWidth; f++ {
m.dctSelect[y+iy][f] = &block
m.hfMultiplier[y+iy][f] = mul
}
}
return pos, nil
}
}
return util.Point{}, errors.New("No space for block")
}
package frame
import (
"errors"
"github.com/kpfaulkner/jxl-go/image"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
type NewModularStreamFunc func(reader jxlio.BitReader, frame Framer, streamIndex int, channelCount int, ecStart int, channelArray []ModularChannel) (ModularStreamer, error)
type NewLFCoefficientsWithReaderFunc func(reader jxlio.BitReader, parent *LFGroup, frame Framer, lfBuffer []image.ImageBuffer, modularStreamFunc NewModularStreamFunc) (*LFCoefficients, error)
type LFCoefficients struct {
dequantLFCoeff [][][]float32
lfIndex [][]int32
frame Framer
}
func NewLFCoefficientsWithReader(reader jxlio.BitReader, parent *LFGroup, frame Framer, lfBuffer []image.ImageBuffer, modularStreamFunc NewModularStreamFunc) (*LFCoefficients, error) {
lf := &LFCoefficients{}
lf.frame = frame
lf.lfIndex = util.MakeMatrix2D[int32](parent.size.Height, parent.size.Width)
header := frame.getFrameHeader()
adapativeSmoothing := (header.Flags & (SKIP_ADAPTIVE_LF_SMOOTHING | USE_LF_FRAME)) == 0
info := make([]ModularChannel, 3)
dequantLFCoeff := util.MakeMatrix3D[float32](3, 0, 0)
subSampled := header.jpegUpsamplingY[0] != 0 || header.jpegUpsamplingY[1] != 0 || header.jpegUpsamplingY[2] != 0 ||
header.jpegUpsamplingX[0] != 0 || header.jpegUpsamplingX[1] != 0 || header.jpegUpsamplingX[2] != 0
if adapativeSmoothing && subSampled {
return nil, errors.New("Adaptive smoothing is incompatible with subsampling")
}
for i := 0; i < 3; i++ {
sizeY := parent.size.Height >> header.jpegUpsamplingY[i]
sizeX := parent.size.Width >> header.jpegUpsamplingX[i]
info[cMap[i]] = *NewModularChannelWithAllParams(int32(sizeY), int32(sizeX), header.jpegUpsamplingY[i], header.jpegUpsamplingX[i], false)
dequantLFCoeff[i] = util.MakeMatrix2D[float32, uint32](sizeY, sizeX)
}
if (header.Flags & USE_LF_FRAME) != 0 {
pos := frame.getLFGroupLocation(parent.lfGroupID)
pY := pos.Y << 8
pX := pos.X << 8
lf.dequantLFCoeff = dequantLFCoeff
for c := 0; c < 3; c++ {
if err := lfBuffer[c].CastToFloatIfMax(^(^0 << frame.getGlobalMetadata().BitDepth.BitsPerSample)); err != nil {
return nil, err
}
b := lfBuffer[c].FloatBuffer
for y := int32(0); y < int32(len(dequantLFCoeff[c])); y++ {
for x, d := pX, 0; x < pX+int32(len(dequantLFCoeff[c][y])); x, d = x+1, d+1 {
dequantLFCoeff[c][y][d] = b[pY+y][x]
}
}
}
}
extraPrecision, err := reader.ReadBits(2)
if err != nil {
return nil, err
}
// use function to pass in func to create ModularStream... just easier to test.
lfQuantStream, err := modularStreamFunc(reader, frame, int(1+parent.lfGroupID), len(info), 0, info)
if err != nil {
return nil, err
}
err = lfQuantStream.decodeChannels(reader, false)
if err != nil {
return nil, err
}
lfQuant := lfQuantStream.getDecodedBuffer()
scaledDequant := frame.getLFGlobal().scaledDequant
for i := 0; i < 3; i++ {
c := cMap[i]
xx := 1 << extraPrecision
sd := scaledDequant[i] / float32(xx)
for y := 0; y < len(lfQuant[c]); y++ {
dq := dequantLFCoeff[i][y]
q := lfQuant[c][y]
for x := 0; x < len(lfQuant[c][y]); x++ {
dq[x] = float32(q[x]) * sd
}
}
}
if !subSampled {
// TOOD(kpfaulkner) investigate what this really does.
lfc := frame.getLFGlobal().lfChanCorr
kX := lfc.baseCorrelationX + float32(lfc.xFactorLF-128)/float32(lfc.colorFactor)
kB := lfc.baseCorrelationB + float32(lfc.bFactorLF-128)/float32(lfc.colorFactor)
dqLFY := dequantLFCoeff[1]
dqLFX := dequantLFCoeff[0]
dqLFB := dequantLFCoeff[2]
for y := 0; y < len(dqLFY); y++ {
dqLFYy := dqLFY[y]
dqLFXy := dqLFX[y]
dqLFBY := dqLFB[y]
for x := 0; x < len(dqLFYy); x++ {
dqLFXy[x] += kX * dqLFYy[x]
dqLFBY[x] += kB * dqLFYy[x]
}
}
}
if adapativeSmoothing {
lf.dequantLFCoeff = adaptiveSmooth(dequantLFCoeff, scaledDequant)
} else {
lf.dequantLFCoeff = dequantLFCoeff
}
err = lf.populatedLFIndex(parent, lfQuant)
if err != nil {
return nil, err
}
return lf, nil
}
func (c *LFCoefficients) populatedLFIndex(parent *LFGroup, lfQuant [][][]int32) error {
hfctx := c.frame.getLFGlobal().hfBlockCtx
for y := uint32(0); y < parent.size.Height; y++ {
for x := uint32(0); x < parent.size.Width; x++ {
c.lfIndex[y][x] = c.getLFIndex(lfQuant, hfctx, y, x)
}
}
return nil
}
func (c *LFCoefficients) getLFIndex(lfQuant [][][]int32, hfctx *HFBlockContext, y uint32, x uint32) int32 {
index := make([]int, 3)
header := c.frame.getFrameHeader()
for i := 0; i < 3; i++ {
sy := y >> header.jpegUpsamplingY[i]
sx := x >> header.jpegUpsamplingX[i]
hft := hfctx.lfThresholds[i]
for j := 0; j < len(hft); j++ {
if lfQuant[cMap[i]][sy][sx] > hft[j] {
index[i]++
}
}
}
lfIndex := index[0]
lfIndex *= len(hfctx.lfThresholds[2]) + 1
lfIndex += index[2]
lfIndex *= len(hfctx.lfThresholds[1]) + 1
lfIndex += index[1]
return int32(lfIndex)
}
func adaptiveSmooth(coeff [][][]float32, scaledDequant []float32) [][][]float32 {
height := len(coeff[0])
width := len(coeff[0][0])
// Pre-allocate all matrices upfront to avoid per-row allocations
weighted := util.MakeMatrix3D[float32](3, height, width)
gap := util.MakeMatrix2D[float32](height, width)
dequantLFCoeff := util.MakeMatrix3D[float32](3, height, width)
// Initialize gap to 0.5 for interior rows
for y := 1; y < height-1; y++ {
gy := gap[y]
for x := 0; x < width; x++ {
gy[x] = 0.5
}
}
for i := 0; i < 3; i++ {
co := coeff[i]
sd := scaledDequant[i]
for y := 1; y < len(co)-1; y++ {
coy := co[y]
coym := co[y-1]
coyp := co[y+1]
gy := gap[y]
wy := weighted[i][y]
for x := 1; x < len(coy)-1; x++ {
sample := coy[x]
adjacent := coy[x-1] + coy[x+1] + coym[x] + coyp[x]
diag := coym[x-1] + coym[x+1] + coyp[x-1] + coyp[x+1]
wy[x] = 0.05226273532324128*sample + 0.20345139757231578*adjacent + 0.0334829185968739*diag
g := absFloat32(sample-wy[x]) * sd
if g > gy[x] {
gy[x] = g
}
}
}
}
// Process gap values for interior rows only
for y := 1; y < height-1; y++ {
gy := gap[y]
for x := 0; x < len(gy); x++ {
gy[x] = util.Max[float32](0.0, 3.0-4.0*gy[x])
}
}
for i := 0; i < 3; i++ {
co := coeff[i]
dqi := dequantLFCoeff[i]
wi := weighted[i]
for y := 0; y < len(co); y++ {
coy := co[y]
dqy := dqi[y]
if y == 0 || y+1 == len(co) {
copy(dqy, coy)
continue
}
gy := gap[y]
wiy := wi[y]
// Copy edge pixels
dqy[0] = coy[0]
dqy[len(coy)-1] = coy[len(coy)-1]
// Process interior pixels
for x := 1; x < len(coy)-1; x++ {
dqy[x] = (coy[x]-wiy[x])*gy[x] + wiy[x]
}
}
}
return dequantLFCoeff
}
package frame
import (
"errors"
"fmt"
"github.com/kpfaulkner/jxl-go/util"
)
const (
MODE_LIBRARY = 0
MODE_HORNUSS = 1
MODE_DCT2 = 2
MODE_DCT4 = 3
MODE_DCT4_8 = 4
MODE_AFV = 5
MODE_DCT = 6
MODE_RAW = 7
METHOD_DCT = 0
METHOD_DCT2 = 1
METHOD_DCT4 = 2
METHOD_HORNUSS = 3
METHOD_DCT8_4 = 4
METHOD_DCT4_8 = 5
METHOD_AFV = 6
)
var (
SCALE_F = []float64{1.0000000000000000000, 1.0003954307206444720, 1.0015830492063566798,
1.0035668445359847378, 1.0063534990068075448, 1.0099524393750471170,
1.0143759095929498827, 1.0196390660646908181, 1.0257600967811994622,
1.0327603660498609462, 1.0406645869479269795, 1.0495010240726261235,
1.0593017296818027804, 1.0701028169146909598, 1.0819447744633102634,
1.0948728278735071820, 1.1089373535928257701, 1.1241943530045446156,
1.1407059950032801390, 1.1585412372562662921, 1.1777765381971696030,
1.1984966740821024139, 1.2207956782314713353, 1.2447779229495839992,
1.2705593687655135089, 1.2982690107340108228, 1.3280505578212198723,
1.3600643892400108061, 1.3944898413648201160, 1.4315278911623840964,
1.4714043176060183528, 1.5143734423313919909}
)
type TransformType struct {
parameterIndex int32
dctSelectWidth int32
dctSelectHeight int32
name string
ttType int32
matrixWidth int32
matrixHeight int32
orderID int32
transformMethod int32
llfScale [][]float32
pixelHeight int32
pixelWidth int32
}
var (
DCT8 = NewTransformType("DCT 8x8", 0, 0, 0, 0, 8, 8)
HORNUSS = NewTransformType("Hornuss", 1, 1, 1, 3, 8, 8)
DCT2 = NewTransformType("DCT 2x2", 2, 2, 1, 1, 8, 8)
DCT4 = NewTransformType("DCT 4x4", 3, 3, 1, 2, 8, 8)
DCT16 = NewTransformType("DCT 16x16", 4, 4, 2, 0, 16, 16)
DCT32 = NewTransformType("DCT 32x32", 5, 5, 3, 0, 32, 32)
DCT16_8 = NewTransformType("DCT 16x8", 6, 6, 4, 0, 16, 8)
DCT8_16 = NewTransformType("DCT 8x16", 7, 6, 4, 0, 8, 16)
DCT32_8 = NewTransformType("DCT 32x8", 8, 7, 5, 0, 32, 8)
DCT8_32 = NewTransformType("DCT 8x32", 9, 7, 5, 0, 8, 32)
DCT32_16 = NewTransformType("DCT 32x16", 10, 8, 6, 0, 32, 16)
DCT16_32 = NewTransformType("DCT 16x32", 11, 8, 6, 0, 16, 32)
DCT4_8 = NewTransformType("DCT 4x8", 12, 9, 1, 5, 8, 8)
DCT8_4 = NewTransformType("DCT 8x4", 13, 9, 1, 4, 8, 8)
AFV0 = NewTransformType("AFV0", 14, 10, 1, 6, 8, 8)
AFV1 = NewTransformType("AFV1", 15, 10, 1, 6, 8, 8)
AFV2 = NewTransformType("AFV2", 16, 10, 1, 6, 8, 8)
AFV3 = NewTransformType("AFV3", 17, 10, 1, 6, 8, 8)
DCT64 = NewTransformType("DCT 64x64", 18, 11, 7, 0, 64, 64)
DCT64_32 = NewTransformType("DCT 64x32", 19, 12, 8, 0, 64, 32)
DCT32_64 = NewTransformType("DCT 32x64", 20, 12, 8, 0, 32, 64)
DCT128 = NewTransformType("DCT 128x128", 21, 13, 9, 0, 128, 128)
DCT128_64 = NewTransformType("DCT 128x64", 22, 14, 10, 0, 128, 64)
DCT64_128 = NewTransformType("DCT 64x128", 23, 14, 10, 0, 64, 128)
DCT256 = NewTransformType("DCT 256x256", 24, 15, 11, 0, 256, 256)
DCT256_128 = NewTransformType("DCT 256x128", 25, 16, 12, 0, 256, 128)
DCT128_256 = NewTransformType("DCT 128x256", 26, 16, 12, 0, 128, 256)
allDCT = []TransformType{*DCT8, *HORNUSS, *DCT2, *DCT4, *DCT16, *DCT32, *DCT16_8, *DCT8_16, *DCT32_8, *DCT8_32, *DCT32_16, *DCT16_32, *DCT4_8, *DCT8_4, *AFV0, *AFV1, *AFV2, *AFV3, *DCT64, *DCT64_32, *DCT32_64, *DCT128, *DCT128_64, *DCT64_128, *DCT256, *DCT256_128, *DCT128_256}
)
func NewTransformType(name string, transType int32, parameterIndex int32, orderID int32, transformMethod int32, pixelHeight int32, pixelWidth int32) *TransformType {
dctSelectWidth := pixelWidth >> 3
dctSelectHeight := pixelHeight >> 3
yll := util.CeilLog2(dctSelectHeight)
xll := util.CeilLog2(dctSelectWidth)
tt := &TransformType{
name: name,
ttType: transType,
parameterIndex: parameterIndex,
pixelHeight: pixelHeight,
pixelWidth: pixelWidth,
dctSelectWidth: dctSelectWidth,
dctSelectHeight: dctSelectHeight,
orderID: orderID,
matrixWidth: util.Max[int32](pixelHeight, pixelWidth),
matrixHeight: util.Min[int32](pixelHeight, pixelWidth),
transformMethod: transformMethod,
llfScale: util.MakeMatrix2D[float32](dctSelectHeight, dctSelectWidth),
}
for y := int32(0); y < dctSelectHeight; y++ {
for x := int32(0); x < dctSelectWidth; x++ {
tt.llfScale[y][x] = float32(scaleF(y, int32(yll)) * scaleF(x, int32(xll)))
}
}
return tt
}
func (tt TransformType) isVertical() bool {
return tt.pixelHeight > tt.pixelWidth
}
func (tt TransformType) flip() bool {
return tt.pixelHeight > tt.pixelWidth || tt.transformMethod == METHOD_DCT && tt.pixelHeight == tt.pixelWidth
}
func (tt TransformType) getPixelSize() util.Dimension {
return util.Dimension{
Width: uint32(tt.pixelWidth),
Height: uint32(tt.pixelHeight),
}
}
func (tt TransformType) getDctSelectSize() util.Dimension {
return util.Dimension{
Width: uint32(tt.dctSelectWidth),
Height: uint32(tt.dctSelectHeight),
}
}
func scaleF(c int32, b int32) float64 {
//piSize := math.Pi * c
//return (1.0 / math.Cos(piSize/(2*b)) * math.Cos(piSize/b) * math.Cos(2.0*piSize/(2.0*b)))
return SCALE_F[c<<(5-b)]
}
func validateIndex(index int32, mode int32) (bool, error) {
if mode < 0 || mode > 7 {
return false, errors.New("Invalid mode")
}
if mode == MODE_LIBRARY || mode == MODE_DCT || mode == MODE_RAW {
return true, nil
}
if index >= 0 && index <= 3 || index == 9 || index == 10 {
return true, nil
}
return false, fmt.Errorf("Invalid index %d for mode %d", index, mode)
}
func getHorizontalTransformType(index int32) (*TransformType, error) {
for _, tt := range allDCT {
if tt.parameterIndex == index && !tt.isVertical() {
return &tt, nil
}
}
return nil, errors.New("Unable to find horizontal transform type")
}
func getByOrderID(orderID int32) TransformType {
return orderLookup[orderID]
}
package image
import (
"errors"
"github.com/kpfaulkner/jxl-go/util"
)
const (
TYPE_INT = 0
TYPE_FLOAT = 1
)
type ImageBuffer struct {
Width int32
Height int32
BufferType int
// image data can be either float or int based. Keep separate buffers and just
// reference each one as required. If conversion will be required then that might get
// expensive, but will optimise/revisit later.
FloatBuffer [][]float32
IntBuffer [][]int32
}
func NewImageBuffer(bufferType int, height int32, width int32) (*ImageBuffer, error) {
if bufferType != TYPE_INT && bufferType != TYPE_FLOAT {
return nil, errors.New("Invalid buffer type")
}
if height < 0 || height > (1<<30) || width < 0 || width > (1<<30) {
return nil, errors.New("Invalid height/width")
}
ib := &ImageBuffer{
Width: width,
Height: height,
BufferType: bufferType,
}
if bufferType == TYPE_INT {
ib.IntBuffer = util.MakeMatrix2D[int32](height, width)
} else {
ib.FloatBuffer = util.MakeMatrix2D[float32](height, width)
}
return ib, nil
}
func NewImageBufferFromInts(buffer [][]int32) *ImageBuffer {
ib := &ImageBuffer{}
ib.IntBuffer = buffer
ib.BufferType = TYPE_INT
ib.Height = int32(len(buffer))
ib.Width = int32(len(buffer[0]))
return ib
}
func NewImageBufferFromFloats(buffer [][]float32) *ImageBuffer {
ib := &ImageBuffer{}
ib.FloatBuffer = buffer
ib.BufferType = TYPE_FLOAT
ib.Height = int32(len(buffer))
ib.Width = int32(len(buffer[0]))
return ib
}
func NewImageBufferFromImageBuffer(imageBuffer *ImageBuffer, copyBuffer bool) *ImageBuffer {
ib := &ImageBuffer{}
ib.BufferType = imageBuffer.BufferType
ib.Height = imageBuffer.Height
ib.Width = imageBuffer.Width
if copyBuffer {
ib.IntBuffer = copyInt32Matrix2D(imageBuffer.IntBuffer)
ib.FloatBuffer = copyFloat32Matrix2D(imageBuffer.FloatBuffer)
} else {
ib.IntBuffer = util.MakeMatrix2D[int32](ib.Height, ib.Width)
ib.FloatBuffer = util.MakeMatrix2D[float32](ib.Height, ib.Width)
}
return ib
}
func copyInt32Matrix2D(src [][]int32) [][]int32 {
duplicate := make([][]int32, len(src))
for i := range src {
duplicate[i] = make([]int32, len(src[i]))
copy(duplicate[i], src[i])
}
return duplicate
}
func copyFloat32Matrix2D(src [][]float32) [][]float32 {
duplicate := make([][]float32, len(src))
for i := range src {
duplicate[i] = make([]float32, len(src[i]))
copy(duplicate[i], src[i])
}
return duplicate
}
// return maximum int32 from int buffer.
func (ib *ImageBuffer) MaxInt() int32 {
m := int32(0)
for y := 0; y < len(ib.IntBuffer); y++ {
for x := 0; x < len(ib.IntBuffer[y]); x++ {
if ib.IntBuffer[y][x] > m {
m = ib.IntBuffer[y][x]
}
}
}
return m
}
func (ib *ImageBuffer) MaxFloat() float32 {
m := float32(0)
for y := 0; y < len(ib.FloatBuffer); y++ {
for x := 0; x < len(ib.FloatBuffer[y]); x++ {
if ib.FloatBuffer[y][x] > m {
m = ib.FloatBuffer[y][x]
}
}
}
return m
}
// Equals compares two ImageBuffers and returns true if they are equal.
func (ib *ImageBuffer) Equals(other ImageBuffer) bool {
if ib.Width != other.Width || ib.Height != other.Height {
return false
}
if ib.BufferType != other.BufferType {
return false
}
if ib.BufferType == TYPE_INT {
return util.CompareMatrix2D(ib.IntBuffer, other.IntBuffer, func(aa int32, bb int32) bool {
return aa == bb
})
}
if ib.BufferType == TYPE_FLOAT {
return util.CompareMatrix2D(ib.FloatBuffer, other.FloatBuffer, func(aa float32, bb float32) bool {
return aa == bb
})
}
return false
}
func (ib *ImageBuffer) IsFloat() bool {
return ib.BufferType == TYPE_FLOAT
}
func (ib *ImageBuffer) IsInt() bool {
return ib.BufferType == TYPE_INT
}
func (ib *ImageBuffer) CastToFloatIfMax(maxValue int32) error {
if ib.BufferType == TYPE_FLOAT {
return nil
}
return ib.castToFloatBuffer(maxValue)
}
func (ib *ImageBuffer) castToFloatBuffer(maxValue int32) error {
if ib.BufferType == TYPE_FLOAT {
return errors.New("Already a float buffer")
}
if maxValue < 1 {
return errors.New("Invalid maxValue")
}
oldBuffer := ib.IntBuffer
newBuffer := util.MakeMatrix2D[float32](ib.Height, ib.Width)
scaleFactor := 1.0 / float32(maxValue)
for y := 0; y < int(ib.Height); y++ {
for x := 0; x < int(ib.Width); x++ {
newBuffer[y][x] = float32(oldBuffer[y][x]) * scaleFactor
}
}
ib.BufferType = TYPE_FLOAT
ib.FloatBuffer = newBuffer
return nil
}
func (ib *ImageBuffer) CastToIntIfMax(maxValue int32) error {
if ib.BufferType == TYPE_INT {
return nil
}
err := ib.castToIntBuffer(maxValue)
ib.BufferType = TYPE_INT
return err
}
func (ib *ImageBuffer) castToIntBuffer(maxValue int32) error {
if ib.BufferType == TYPE_INT {
return errors.New("Already a int buffer")
}
if maxValue < 1 {
return errors.New("Invalid maxValue")
}
oldBuffer := ib.FloatBuffer
newBuffer := util.MakeMatrix2D[int32](ib.Height, ib.Width)
scaleFactor := float32(maxValue)
for y := 0; y < int(ib.Height); y++ {
for x := 0; x < int(ib.Width); x++ {
v := int32(oldBuffer[y][x]*scaleFactor + 0.5)
var vv int32
if v < 0 {
vv = 0
} else if v > maxValue {
vv = maxValue
} else {
vv = v
}
newBuffer[y][x] = vv
}
}
ib.IntBuffer = newBuffer
ib.BufferType = TYPE_INT
return nil
}
func (ib *ImageBuffer) Clamp(maxValue int32) error {
if ib.IsFloat() {
return errors.New("Clamp only supported for int buffers")
}
buf := util.MakeMatrix2D[int32](ib.Height, ib.Width)
for y := 0; y < int(ib.Height); y++ {
for x := 0; x < int(ib.Width); x++ {
v := ib.IntBuffer[y][x]
if v < 0 {
buf[y][x] = 0
} else if v > maxValue {
buf[y][x] = maxValue
} else {
buf[y][x] = v
}
}
}
ib.IntBuffer = buf
return nil
}
// Equals compares two ImageBuffer slices and returns true if they are equal.
// Need to have:
// - same size
// - expected that in same order.
// - each ImageBuffer has same buffer type
// - each ImageBuffer has same width/height
// - each ImageBuffer has same buffer values
func ImageBufferSliceEquals(a []ImageBuffer, b []ImageBuffer) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if !a[i].Equals(b[i]) {
return false
}
}
return true
}
package jxlio
import (
"errors"
"io"
)
const (
tempBufSize = 10000
)
// returns number of bytes NOT read (remaining) and error.
func ReadFullyWithOffset(in io.ReadSeeker, buffer []byte, offset int, len int) (int, error) {
remaining := len
_, err := in.Seek(int64(offset), io.SeekStart)
if err != nil {
return 0, err
}
// potentially stupidly large buffer... but will leave for now. TODO(kpfaulkner) revisit
if len > 2*1024*1024*1024 {
return 0, errors.New("length of read too large")
}
tempBuf := make([]byte, len)
var tempBuffer []byte
for remaining > 0 {
//count := in.Read(buffer, offset+len-remaining, remaining)
count, err := in.Read(tempBuf)
if err != nil {
return 0, err
}
if count <= 0 {
break
}
// copy tempBuf to buffer.
tempBuffer = append(tempBuffer, tempBuf[:count]...)
remaining -= count
}
copy(buffer, tempBuffer[:len])
return remaining, nil
}
func ReadFully(in io.ReadSeeker, buffer []byte) (int, error) {
return ReadFullyWithOffset(in, buffer, 0, len(buffer))
}
// FIXME(kpfaulkner) really unsure what this is supposed to do. Skip some content... then read more?
func SkipFully(in io.ReadSeeker, n int64) (int, error) {
remaining := n
var sz int64
if n < tempBufSize {
sz = n
} else {
sz = tempBufSize
}
tempBuf := make([]byte, sz)
for remaining > 0 {
skipped, err := in.Read(tempBuf)
if err != nil {
return 0, err
}
remaining -= int64(skipped)
if skipped == 0 {
break
}
}
if remaining == 0 {
return 0, nil
}
buffer := make([]byte, 4096)
for remaining > int64(len(buffer)) {
k, err := ReadFully(in, buffer)
if err != nil {
return 0, err
}
remaining = remaining - int64(len(buffer)) + int64(k)
if k != 0 {
return int(remaining), nil
}
}
return ReadFullyWithOffset(in, buffer, 0, int(remaining))
}
package jxlio
import (
"encoding/binary"
"errors"
"fmt"
"io"
"math"
)
// BitReader is interface to BitStreamReader... am concerned (without measurements)
// that interface calling might have impact. But will convert to interface and measure
// later.
type BitReader interface {
Seek(offset int64, whence int) (int64, error)
Reset() error
AtEnd() bool
ReadBytesToBuffer(buffer []uint8, numBytes uint32) error
ReadBits(bits uint32) (uint64, error)
ReadByteArrayWithOffsetAndLength(buffer []byte, offset int64, length uint32) error
ReadByte() (uint8, error)
ReadEnum() (int32, error)
ReadF16() (float32, error)
ReadICCVarint() (int32, error)
ReadU32(c0 int, u0 int, c1 int, u1 int, c2 int, u2 int, c3 int, u3 int) (uint32, error)
ReadBool() (bool, error)
ReadU64() (uint64, error)
ReadU8() (int, error)
GetBitsCount() uint64
ShowBits(bits int) (uint64, error)
SkipBits(bits uint32) error
Skip(bytes uint32) (int64, error)
ReadBytesUint64(noBytes int) (uint64, error)
ZeroPadToByte() error
BitsRead() uint64
}
// BitStreamReader is the key struct for reading bits from a byte "stream".
type BitStreamReader struct {
buffer []byte
// stream/reader we're using most of the time
stream io.ReadSeeker
bitsRead uint64
tempIndex int
index uint8
currentByte uint8
}
func NewBitStreamReaderWithIndex(in io.ReadSeeker, index int) *BitStreamReader {
br := NewBitStreamReader(in)
br.tempIndex = index
br.buffer = make([]byte, 1)
return br
}
func NewBitStreamReader(in io.ReadSeeker) *BitStreamReader {
br := &BitStreamReader{}
br.buffer = make([]byte, 1)
br.stream = in
return br
}
// utter hack to seek about the place. TODO(kpfaulkner) confirm this really works.
func (br *BitStreamReader) Seek(offset int64, whence int) (int64, error) {
n, err := br.stream.Seek(offset, whence)
if err != nil {
return 0, err
}
return n, err
}
func (br *BitStreamReader) Reset() error {
_, err := br.stream.Seek(0, io.SeekStart)
if err != nil {
return err
}
// reset tracking
br.index = 0
br.currentByte = 0
return nil
}
func (br *BitStreamReader) AtEnd() bool {
_, err := br.ShowBits(1)
return err != nil
}
// ReadBytesToBuffer
// If part way through a byte then fail. Need to be aligned for this to work.
func (br *BitStreamReader) ReadBytesToBuffer(buffer []uint8, numBytes uint32) error {
if br.index != 0 {
return errors.New("BitStreamReader cache not aligned")
}
n, err := br.stream.Read(buffer[:numBytes])
if err != nil {
return err
}
if n != int(numBytes) {
return errors.New("unable to read all bytes")
}
return nil
}
// read single bit and will cache the current byte we're working on.
func (br *BitStreamReader) readBit() (uint8, error) {
if br.index == 0 {
_, err := br.stream.Read(br.buffer)
if err != nil {
return 0, err
}
br.currentByte = br.buffer[0]
}
v := (br.currentByte & (1 << br.index)) != 0
br.index = (br.index + 1) % 8
br.bitsRead++
if v {
return 1, nil
} else {
return 0, nil
}
}
func (br *BitStreamReader) ReadBits(bits uint32) (uint64, error) {
if bits == 0 {
return 0, nil
}
if bits > 64 {
return 0, errors.New("num bits must be between 1 and 64")
}
var result uint64
bitsRead := uint32(0)
// If we have bits remaining in currentByte, consume them first
if br.index != 0 {
bitsAvailable := uint32(8 - br.index)
bitsToRead := bits
if bitsToRead > bitsAvailable {
bitsToRead = bitsAvailable
}
// Extract bits from currentByte
mask := uint8((1 << bitsToRead) - 1)
extracted := (br.currentByte >> br.index) & mask
result = uint64(extracted)
bitsRead = bitsToRead
br.bitsRead += uint64(bitsToRead)
br.index = (br.index + uint8(bitsToRead)) % 8
}
// Read full bytes directly while we need 8+ more bits
for bits-bitsRead >= 8 {
_, err := br.stream.Read(br.buffer)
if err != nil {
return 0, err
}
result |= uint64(br.buffer[0]) << bitsRead
bitsRead += 8
br.bitsRead += 8
}
// Read remaining bits (less than 8)
remaining := bits - bitsRead
if remaining > 0 {
_, err := br.stream.Read(br.buffer)
if err != nil {
return 0, err
}
br.currentByte = br.buffer[0]
mask := uint8((1 << remaining) - 1)
extracted := br.currentByte & mask
result |= uint64(extracted) << bitsRead
br.index = uint8(remaining)
br.bitsRead += uint64(remaining)
}
return result, nil
}
func (br *BitStreamReader) ReadByteArrayWithOffsetAndLength(buffer []byte, offset int64, length uint32) error {
if length == 0 {
return nil
}
_, err := br.Seek(offset, io.SeekStart)
if err != nil {
return err
}
err = br.ReadBytesToBuffer(buffer, length)
if err != nil {
return err
}
return nil
}
func (br *BitStreamReader) ReadByte() (uint8, error) {
v, err := br.ReadBits(8)
if err != nil {
return 0, err
}
return uint8(v), nil
}
func (br *BitStreamReader) ReadEnum() (int32, error) {
constant, err := br.ReadU32(0, 0, 1, 0, 2, 4, 18, 6)
if err != nil {
return 0, err
}
if constant > 63 {
return 0, errors.New("enum constant > 63")
}
return int32(constant), nil
}
func (br *BitStreamReader) ReadF16() (float32, error) {
bits16, err := br.ReadBits(16)
if err != nil {
return 0, err
}
mantissa := bits16 & 0x3FF
biased_exp := (uint32(bits16) >> 10) & 0x1F
sign := (bits16 >> 15) & 1
if biased_exp == 31 {
return 0, errors.New("illegal infinite/NaN float16")
}
if biased_exp == 0 {
return (1.0 - 2.0*float32(sign)) * float32(mantissa) / 16777216.0, nil
}
biased_exp += 127 - 15
mantissa = mantissa << 13
sign = sign << 31
total := uint32(sign) | biased_exp<<23 | uint32(mantissa)
return math.Float32frombits(total), nil
}
func (br *BitStreamReader) ReadICCVarint() (int32, error) {
value := int32(0)
for shift := 0; shift < 63; shift += 7 {
b, err := br.ReadBits(8)
if err != nil {
return 0, err
}
value |= int32(b) & 127 << shift
if b <= 127 {
break
}
}
return value, nil
}
func (br *BitStreamReader) ReadU32(c0 int, u0 int, c1 int, u1 int, c2 int, u2 int, c3 int, u3 int) (uint32, error) {
choice, err := br.ReadBits(2)
if err != nil {
return 0, err
}
c := []int{c0, c1, c2, c3}
u := []int{u0, u1, u2, u3}
b, err := br.ReadBits(uint32(u[choice]))
if err != nil {
return 0, err
}
return uint32(c[choice]) + uint32(b), nil
}
func (br *BitStreamReader) ReadBool() (bool, error) {
v, err := br.readBit()
if err != nil {
return false, err
}
return v == 1, nil
}
func (br *BitStreamReader) ReadU64() (uint64, error) {
index, err := br.ReadBits(2)
if err != nil {
return 0, err
}
if index == 0 {
return 0, nil
}
if index == 1 {
b, err := br.ReadBits(4)
if err != nil {
return 0, err
}
return 1 + uint64(b), nil
}
if index == 2 {
b, err := br.ReadBits(8)
if err != nil {
return 0, err
}
return 17 + uint64(b), nil
}
value2, err := br.ReadBits(12)
if err != nil {
return 0, err
}
value := uint64(value2)
shift := 12
var boolCheck bool
for {
if boolCheck, err = br.ReadBool(); err != nil {
return 0, err
}
if !boolCheck {
break
}
if shift == 60 {
if data, err := br.ReadBits(4); err != nil {
return 0, err
} else {
value |= data << shift
}
break
}
if data, err := br.ReadBits(8); err != nil {
return 0, err
} else {
value |= data << shift
}
shift += 8
}
return value, nil
}
func (br *BitStreamReader) ReadU8() (int, error) {
b, err := br.ReadBool()
if err != nil {
return 0, err
}
if !b {
return 0, nil
}
n, err := br.ReadBits(3)
if err != nil {
return 0, err
}
if n == 0 {
return 1, nil
}
nn, err := br.ReadBits(uint32(n))
if err != nil {
return 0, err
}
return int(nn + 1<<n), nil
}
func (br *BitStreamReader) GetBitsCount() uint64 {
return br.bitsRead
}
func (br *BitStreamReader) ShowBits(bits int) (uint64, error) {
curPos, err := br.Seek(0, io.SeekCurrent)
if err != nil {
return 0, err
}
oldCur := br.currentByte
oldIndex := br.index
oldBitsRead := br.bitsRead
b, err := br.ReadBits(uint32(bits))
if err != nil {
return 0, err
}
_, err = br.Seek(curPos, io.SeekStart)
if err != nil {
return 0, err
}
br.currentByte = oldCur
br.index = oldIndex
br.bitsRead = oldBitsRead
return b, nil
}
func (br *BitStreamReader) SkipBits(bits uint32) error {
numBytes := bits / 8
if numBytes > 0 {
buffer := make([]byte, numBytes)
_, err := br.stream.Read(buffer)
if err != nil {
return err
}
br.currentByte = buffer[numBytes-1]
}
// read bits so we can keep track of where we are.
for i := numBytes * 8; i < bits; i++ {
_, err := br.readBit()
if err != nil {
return err
}
}
return nil
}
func (br *BitStreamReader) Skip(bytes uint32) (int64, error) {
err := br.SkipBits(bytes << 3)
if err != nil {
return 0, err
}
return int64(bytes), nil
}
func (br *BitStreamReader) ReadBytesUint64(noBytes int) (uint64, error) {
if noBytes < 1 || noBytes > 8 {
return 0, fmt.Errorf("number of bytes number should be between 1 and 8.")
}
ba := make([]byte, 8)
err := br.ReadBytesToBuffer(ba, uint32(noBytes))
if err != nil {
return 0, err
}
return binary.LittleEndian.Uint64(ba), nil
}
func (br *BitStreamReader) ZeroPadToByte() error {
if br.index == 0 {
return nil
}
remaining := 8 - br.index
if remaining > 0 {
_, err := br.ReadBits(uint32(remaining))
if err != nil {
return err
}
}
return nil
}
func (br *BitStreamReader) BitsRead() uint64 {
return br.bitsRead
}
// JPEGXL spec states unpackedsigned is
// equivalent to u / 2 if u is even, and -(u + 1) / 2 if u is odd
func UnpackSigned(value uint32) int32 {
if value&1 == 0 {
return int32(value >> 1)
}
return -(int32(value) + 1) >> 1
}
func UnpackSigned64(value uint64) int64 {
if value&1 == 0 {
return int64(value >> 1)
}
return -(int64(value) + 1) >> 1
}
package util
import (
"cmp"
"errors"
"math"
"math/bits"
"golang.org/x/exp/constraints"
)
var (
cosineLUT = generateCosineLUT()
)
type signedInts interface {
int8 | int16 | int32 | int64
}
func generateCosineLUT() [][][]float32 {
tempCosineLUT := MakeMatrix3D[float32](9, 0, 0)
root2 := math.Sqrt(2.0)
for l := 0; l < len(tempCosineLUT); l++ {
s := 1 << l
tempCosineLUT[l] = MakeMatrix2D[float32](s-1, s)
for n := 0; n < len(tempCosineLUT[l]); n++ {
for k := 0; k < len(tempCosineLUT[l][n]); k++ {
tempCosineLUT[l][n][k] = float32(root2 * math.Cos(float64(math.Pi*(float32(n)+1.0)*(float32(k)+0.5)/float32(s))))
}
}
}
return tempCosineLUT
}
func SignedPow(base float32, exponent float32) float32 {
if base < 0 {
return -float32(math.Pow(float64(-base), float64(exponent)))
}
return float32(math.Pow(float64(base), float64(exponent)))
}
func CeilLog1p[T constraints.Integer](x T) int {
return 64 - bits.LeadingZeros64(uint64(x))
}
func CeilLog1pUint64(x uint64) int {
return 64 - bits.LeadingZeros64(x)
}
func FloorLog1p[T constraints.Integer](x T) int64 {
c := int64(CeilLog1p[T](x))
if (x+1)&x != 0 {
return c - 1
}
return c
}
func FloorLog1pUint64(x uint64) int64 {
c := int64(CeilLog1pUint64(x))
if (x+1)&x != 0 {
return c - 1
}
return c
}
func CeilLog2[T constraints.Integer](x T) int {
return CeilLog1p[T](x - 1)
}
func Max[T cmp.Ordered](args ...T) T {
if len(args) == 0 {
return *new(T)
}
if isNan(args[0]) {
return args[0]
}
max := args[0]
for _, arg := range args[1:] {
if isNan(arg) {
return arg
}
if arg > max {
max = arg
}
}
return max
}
func Min[T cmp.Ordered](args ...T) T {
if len(args) == 0 {
return *new(T)
}
if isNan(args[0]) {
return args[0]
}
min := args[0]
for _, arg := range args[1:] {
if isNan(arg) {
return arg
}
if arg < min {
min = arg
}
}
return min
}
func Clamp3(v int32, a int32, b int32) int32 {
var lower int32
if a < b {
lower = a
} else {
lower = b
}
upper := lower ^ a ^ b
if v < lower {
return lower
}
if v > upper {
return upper
}
return v
}
func Clamp3Float32(v float32, a float32, b float32) float32 {
var lower float32
if a < b {
lower = a
} else {
lower = b
}
var upper float32
if a < b {
upper = b
} else {
upper = a
}
if v < lower {
return lower
}
if v > upper {
return upper
}
return v
}
func Clamp(v int32, a int32, b int32, c int32) int32 {
var lower int32
if a < b {
lower = a
} else {
lower = b
}
upper := lower ^ a ^ b
if lower >= c {
lower = c
}
if upper <= c {
upper = c
}
if v < lower {
return lower
}
if v > upper {
return upper
}
return v
}
func isNan[T cmp.Ordered](arg T) bool {
return arg != arg
}
func Abs[T signedInts](a T) T {
if a < 0 {
return -a
}
return a
}
func MakeSliceWithDefault[T any](length int, defaultVal T) []T {
if length < 0 {
return nil
}
m := make([]T, length)
for i := 0; i < length; i++ {
m[i] = defaultVal
}
return m
}
func MatrixIdentity(i int) [][]float32 {
matrix := make([][]float32, i)
for j := 0; j < i; j++ {
matrix[j] = make([]float32, i)
matrix[j][j] = 1
}
return matrix
}
func MatrixVectorMultiply(matrix [][]float32, columnVector []float32) ([]float32, error) {
if len(matrix) == 0 {
return columnVector, nil
}
if len(matrix[0]) > len(columnVector) || len(columnVector) == 0 {
return nil, errors.New("Invalid argument")
}
extra := len(columnVector) - len(matrix[0])
total := make([]float32, len(matrix)+extra)
for y := 0; y < len(matrix); y++ {
row := matrix[y]
for x := 0; x < len(row); x++ {
total[y] += row[x] * columnVector[x]
}
}
if extra != 0 {
copy(total[len(matrix):], columnVector[len(matrix[0]):])
}
return total, nil
}
// multiply any number of matrices
func MatrixMultiply(matrix ...[][]float32) ([][]float32, error) {
var err error
left := matrix[0]
for i := 1; i < len(matrix); i++ {
right := matrix[i]
left, err = MatrixMatrixMultiply(left, right)
if err != nil {
return nil, err
}
}
return left, nil
}
func MatrixMatrixMultiply(left [][]float32, right [][]float32) ([][]float32, error) {
if left == nil {
return right, nil
}
if right == nil {
return left, nil
}
if len(left[0]) != len(right) {
return nil, errors.New("Invalid argument")
}
result := make([][]float32, len(left))
for i := 0; i < len(left); i++ {
result[i] = make([]float32, len(right[0]))
}
for i := 0; i < len(left); i++ {
resultI := result[i]
leftI := left[i]
for k := 0; k < len(right); k++ {
leftIK := leftI[k]
rightK := right[k]
for j := 0; j < len(rightK); j++ {
resultI[j] += leftIK * rightK[j]
}
}
}
return result, nil
}
func InvertMatrix3x3(matrix [][]float32) [][]float32 {
det := matrix[0][0]*matrix[1][1]*matrix[2][2] + matrix[0][1]*matrix[1][2]*matrix[2][0] + matrix[0][2]*matrix[1][0]*matrix[2][1] - matrix[0][2]*matrix[1][1]*matrix[2][0] - matrix[0][1]*matrix[1][0]*matrix[2][2] - matrix[0][0]*matrix[1][2]*matrix[2][1]
if det == 0 {
return nil
}
invDet := 1.0 / det
return [][]float32{
{(matrix[1][1]*matrix[2][2] - matrix[1][2]*matrix[2][1]) * invDet, (matrix[0][2]*matrix[2][1] - matrix[0][1]*matrix[2][2]) * invDet, (matrix[0][1]*matrix[1][2] - matrix[0][2]*matrix[1][1]) * invDet},
{(matrix[1][2]*matrix[2][0] - matrix[1][0]*matrix[2][2]) * invDet, (matrix[0][0]*matrix[2][2] - matrix[0][2]*matrix[2][0]) * invDet, (matrix[0][2]*matrix[1][0] - matrix[0][0]*matrix[1][2]) * invDet},
{(matrix[1][0]*matrix[2][1] - matrix[1][1]*matrix[2][0]) * invDet, (matrix[0][1]*matrix[2][0] - matrix[0][0]*matrix[2][1]) * invDet, (matrix[0][0]*matrix[1][1] - matrix[0][1]*matrix[1][0]) * invDet},
}
}
func CeilDiv(numerator uint32, denominator uint32) uint32 {
return ((numerator - 1) / denominator) + 1
}
func TransposeMatrix(matrix [][]float32, inSize Point) [][]float32 {
if inSize.X == 0 || inSize.Y == 0 {
return nil
}
dest := MakeMatrix2D[float32](inSize.X, inSize.Y)
TransposeMatrixInto(matrix, dest, ZERO, ZERO, inSize)
return dest
}
func TransposeMatrixInto(src [][]float32, dest [][]float32, srcStart Point, destStart Point, srcSize Point) {
for y := int32(0); y < srcSize.Y; y++ {
srcY := src[y+srcStart.Y]
for x := int32(0); x < srcSize.X; x++ {
dest[destStart.Y+x][destStart.X+y] = srcY[srcStart.X+x]
}
}
}
func Matrix3Equal[T comparable](a [][][]T, b [][][]T) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
for j := 0; j < len(a[i]); j++ {
for k := 0; k < len(a[i][j]); k++ {
if a[i][j][k] != b[i][j][k] {
return false
}
}
}
}
return true
}
func InverseDCT2D(src [][]float32, dest [][]float32, startIn Point, startOut Point, size Dimension, scratchSpace0 [][]float32, scratchSpace1 [][]float32, transposed bool) error {
logHeight := CeilLog2(size.Height)
logWidth := CeilLog2(size.Width)
if transposed {
for y := int32(0); y < int32(size.Height); y++ {
if err := inverseDCTHorizontal(src[startIn.Y+y], scratchSpace1[y], startIn.X, 0, logWidth, int32(size.Width)); err != nil {
return err
}
}
TransposeMatrixInto(scratchSpace1, scratchSpace0, ZERO, ZERO, Point{X: int32(size.Width), Y: int32(size.Height)})
for y := int32(0); y < int32(size.Width); y++ {
if err := inverseDCTHorizontal(scratchSpace0[y], dest[startOut.Y+y], 0, startOut.X, logHeight, int32(size.Height)); err != nil {
return err
}
}
} else {
TransposeMatrixInto(src, scratchSpace0, startIn, ZERO, Point{X: int32(size.Width), Y: int32(size.Height)})
for y := int32(0); y < int32(size.Width); y++ {
if err := inverseDCTHorizontal(scratchSpace0[y], scratchSpace1[y],
0, 0, logHeight, int32(size.Height)); err != nil {
return err
}
}
TransposeMatrixInto(scratchSpace1, scratchSpace0, ZERO, ZERO, Point{X: int32(size.Height), Y: int32(size.Width)})
for y := int32(0); y < int32(size.Height); y++ {
if err := inverseDCTHorizontal(scratchSpace0[y], dest[startOut.Y+y],
0, startOut.X, logWidth, int32(size.Width)); err != nil {
return err
}
}
}
return nil
}
func inverseDCTHorizontal(src []float32, dest []float32, xStartIn int32, xStartOut int32, xLogLength int,
xLength int32) error {
// fill dest with initial data
for i := xStartOut; i < xStartOut+xLength; i++ {
dest[i] = src[xStartIn]
}
lutX := cosineLUT[xLogLength]
for n := int32(1); n < xLength; n++ {
lut := lutX[n-1]
s2 := src[xStartIn+n]
for k := int32(0); k < xLength; k++ {
dest[xStartOut+k] += s2 * lut[k]
}
}
return nil
}
func ForwardDCT2D(src [][]float32, dest [][]float32, startIn Point, startOut Point, length Dimension,
scratchSpace0 [][]float32, scratchSpace1 [][]float32, b bool) error {
yLogLength := CeilLog2(length.Height)
xLogLength := CeilLog2(length.Width)
for y := int32(0); y < int32(length.Height); y++ {
if err := forwardDCTHorizontal(src[y+startIn.Y], scratchSpace0[y], startIn.X, 0, xLogLength, int32(length.Width)); err != nil {
return err
}
}
TransposeMatrixInto(scratchSpace0, scratchSpace1, ZERO, ZERO, Point{X: int32(length.Width), Y: int32(length.Height)})
for x := int32(0); x < int32(length.Width); x++ {
if err := forwardDCTHorizontal(scratchSpace1[x], scratchSpace0[x], 0, 0, yLogLength, int32(length.Height)); err != nil {
return err
}
}
TransposeMatrixInto(scratchSpace0, dest, ZERO, startOut, Point{X: int32(length.Height), Y: int32(length.Width)})
return nil
}
func forwardDCTHorizontal(src []float32, dest []float32, xStartIn int32, xStartOut int32, xLogLength int, xLength int32) error {
invLength := 1.0 / float32(xLength)
d2 := src[xStartIn]
for x := int32(1); x < xLength; x++ {
d2 += src[xStartIn+x]
}
dest[xStartOut] = d2 * invLength
for k := int32(1); k < xLength; k++ {
lut := cosineLUT[xLogLength][k-1]
d2 = src[xStartIn] * lut[0]
for n := int32(1); n < xLength; n++ {
d2 += src[xStartIn+n] * lut[n]
}
dest[xStartOut+k] = d2 * invLength
}
return nil
}
func MirrorCoordinate(coordinate int32, size int32) int32 {
for coordinate < 0 || coordinate >= size {
tc := ^coordinate
if tc >= 0 {
coordinate = tc
} else {
coordinate = (size << 1) + tc
}
}
return coordinate
}
package util
var (
ZERO = *NewPoint(0, 0)
)
type Point struct {
X int32
Y int32
}
func NewPoint(y int32, x int32) *Point {
return &Point{
X: x,
Y: y,
}
}
package util
import (
"io"
)
// COULD try out the new Go 1.23 iter package, but to keep backwards compatibility will
// just use something basic and simple.
func RangeIterator(startX uint32, startY uint32, endX uint32, endY uint32) func() (*Point, error) {
x := startX
y := startY
return func() (*Point, error) {
if x > endX {
x = startX
y++
}
if y > endY {
return nil, io.EOF
}
x++
return &Point{X: int32(x), Y: int32(y)}, nil
}
}
func RangeIteratorWithIntPoint(ip Point) func() (*Point, error) {
return RangeIterator(0, 0, uint32(ip.X), uint32(ip.Y))
}
package util
import (
"fmt"
"sync"
"sync/atomic"
)
// Matrix3DPool provides pooling for 3D matrices to reduce allocations
type Matrix3DPool[T any] struct {
pools map[string]*sync.Pool
mu sync.RWMutex
// Metrics
hits atomic.Int64
misses atomic.Int64
}
var (
float32Pool3D = &Matrix3DPool[float32]{pools: make(map[string]*sync.Pool)}
int32Pool3D = &Matrix3DPool[int32]{pools: make(map[string]*sync.Pool)}
float32Pool2D = &Matrix2DPool[float32]{pools: make(map[string]*sync.Pool)}
int32Pool2D = &Matrix2DPool[int32]{pools: make(map[string]*sync.Pool)}
)
// getPoolKey generates a key for the pool map
func getPoolKey(dims ...int) string {
switch len(dims) {
case 2:
return fmt.Sprintf("%d_%d", dims[0], dims[1])
case 3:
return fmt.Sprintf("%d_%d_%d", dims[0], dims[1], dims[2])
default:
return ""
}
}
// Get retrieves a matrix from the pool or creates a new one
func (p *Matrix3DPool[T]) Get(depth, height, width int) [][][]T {
if depth == 0 || height == 0 || width == 0 {
return MakeMatrix3D[T](depth, height, width)
}
key := getPoolKey(depth, height, width)
// Fast path: read lock
p.mu.RLock()
pool, exists := p.pools[key]
p.mu.RUnlock()
if exists {
if matrix := pool.Get(); matrix != nil {
p.hits.Add(1)
return matrix.([][][]T)
}
} else {
// Slow path: create new pool
p.mu.Lock()
// Double-check after acquiring write lock
pool, exists = p.pools[key]
if !exists {
pool = &sync.Pool{
New: func() interface{} {
return MakeMatrix3D[T](depth, height, width)
},
}
p.pools[key] = pool
}
p.mu.Unlock()
}
p.misses.Add(1)
return MakeMatrix3D[T](depth, height, width)
}
// Put returns a matrix to the pool after clearing it
func (p *Matrix3DPool[T]) Put(matrix [][][]T) {
if len(matrix) == 0 {
return
}
depth := len(matrix)
if depth == 0 {
return
}
height := len(matrix[0])
if height == 0 {
return
}
width := len(matrix[0][0])
key := getPoolKey(depth, height, width)
p.mu.RLock()
pool, exists := p.pools[key]
p.mu.RUnlock()
if exists {
// Clear the matrix before returning to pool
var zero T
for i := range matrix {
for j := range matrix[i] {
for k := range matrix[i][j] {
matrix[i][j][k] = zero
}
}
}
pool.Put(matrix)
}
}
// GetMetrics returns pool usage statistics
func (p *Matrix3DPool[T]) GetMetrics() (hits, misses int64) {
return p.hits.Load(), p.misses.Load()
}
// Matrix2DPool provides pooling for 2D matrices
type Matrix2DPool[T any] struct {
pools map[string]*sync.Pool
mu sync.RWMutex
hits atomic.Int64
misses atomic.Int64
}
// Get retrieves a 2D matrix from the pool or creates a new one
func (p *Matrix2DPool[T]) Get(height, width int) [][]T {
if height == 0 || width == 0 {
return MakeMatrix2D[T](height, width)
}
key := getPoolKey(height, width)
p.mu.RLock()
pool, exists := p.pools[key]
p.mu.RUnlock()
if exists {
if matrix := pool.Get(); matrix != nil {
p.hits.Add(1)
return matrix.([][]T)
}
} else {
p.mu.Lock()
pool, exists = p.pools[key]
if !exists {
pool = &sync.Pool{
New: func() interface{} {
return MakeMatrix2D[T](height, width)
},
}
p.pools[key] = pool
}
p.mu.Unlock()
}
p.misses.Add(1)
return MakeMatrix2D[T](height, width)
}
// Put returns a 2D matrix to the pool after clearing it
func (p *Matrix2DPool[T]) Put(matrix [][]T) {
if len(matrix) == 0 {
return
}
height := len(matrix)
if height == 0 {
return
}
width := len(matrix[0])
key := getPoolKey(height, width)
p.mu.RLock()
pool, exists := p.pools[key]
p.mu.RUnlock()
if exists {
var zero T
for i := range matrix {
for j := range matrix[i] {
matrix[i][j] = zero
}
}
pool.Put(matrix)
}
}
// GetMetrics returns pool usage statistics
func (p *Matrix2DPool[T]) GetMetrics() (hits, misses int64) {
return p.hits.Load(), p.misses.Load()
}
// Public API functions
// MakeMatrix3DPooled creates or retrieves a 3D matrix from the pool
func MakeMatrix3DPooled[T any](depth, height, width int) [][][]T {
var zero T
switch any(zero).(type) {
case float32:
return any(float32Pool3D.Get(depth, height, width)).([][][]T)
case int32:
return any(int32Pool3D.Get(depth, height, width)).([][][]T)
default:
// Fallback for unsupported types
return MakeMatrix3D[T](depth, height, width)
}
}
// ReturnMatrix3DToPool returns a 3D matrix to the pool
func ReturnMatrix3DToPool[T any](matrix [][][]T) {
if len(matrix) == 0 {
return
}
var zero T
switch any(zero).(type) {
case float32:
float32Pool3D.Put(any(matrix).([][][]float32))
case int32:
int32Pool3D.Put(any(matrix).([][][]int32))
}
}
// MakeMatrix2DPooled creates or retrieves a 2D matrix from the pool
func MakeMatrix2DPooled[T any](height, width int) [][]T {
var zero T
switch any(zero).(type) {
case float32:
return any(float32Pool2D.Get(height, width)).([][]T)
case int32:
return any(int32Pool2D.Get(height, width)).([][]T)
default:
return MakeMatrix2D[T](height, width)
}
}
// ReturnMatrix2DToPool returns a 2D matrix to the pool
func ReturnMatrix2DToPool[T any](matrix [][]T) {
if len(matrix) == 0 {
return
}
var zero T
switch any(zero).(type) {
case float32:
float32Pool2D.Put(any(matrix).([][]float32))
case int32:
int32Pool2D.Put(any(matrix).([][]int32))
}
}
// GetPoolMetrics returns metrics for all pools
func GetPoolMetrics() map[string]map[string]int64 {
f32_3d_hits, f32_3d_misses := float32Pool3D.GetMetrics()
i32_3d_hits, i32_3d_misses := int32Pool3D.GetMetrics()
f32_2d_hits, f32_2d_misses := float32Pool2D.GetMetrics()
i32_2d_hits, i32_2d_misses := int32Pool2D.GetMetrics()
return map[string]map[string]int64{
"float32_3d": {
"hits": f32_3d_hits,
"misses": f32_3d_misses,
},
"int32_3d": {
"hits": i32_3d_hits,
"misses": i32_3d_misses,
},
"float32_2d": {
"hits": f32_2d_hits,
"misses": f32_2d_misses,
},
"int32_2d": {
"hits": i32_2d_hits,
"misses": i32_2d_misses,
},
}
}
package util
// Deque double ended queue to mimic Java version (or at least the functionality I need).
type Deque[T any] struct {
data []T
}
func NewDeque[T any]() *Deque[T] {
dq := &Deque[T]{}
return dq
}
func (dq *Deque[T]) AddFirst(item T) {
dq.data = append([]T{item}, dq.data...)
}
func (dq *Deque[T]) AddLast(item T) {
dq.data = append(dq.data, item)
}
func (dq *Deque[T]) RemoveFirst() *T {
if len(dq.data) == 0 {
return nil
}
item := dq.data[0]
dq.data = dq.data[1:]
return &item
}
func (dq *Deque[T]) RemoveLast() *T {
if len(dq.data) == 0 {
return nil
}
item := dq.data[len(dq.data)-1]
dq.data = dq.data[:len(dq.data)-1]
return &item
}
func (dq *Deque[T]) IsEmpty() bool {
return len(dq.data) == 0
}
package util
import "golang.org/x/exp/constraints"
func IfThenElse[T any](condition bool, a T, b T) T {
if condition {
return a
}
return b
}
func MakeMatrix2D[T any, S constraints.Integer](a S, b S) [][]T {
matrix := make([][]T, a)
for i := range matrix {
matrix[i] = make([]T, b)
}
return matrix
}
func MakeMatrix3D[T any](a int, b int, c int) [][][]T {
matrix := make([][][]T, a)
for i := range matrix {
matrix[i] = make([][]T, b)
for j := range matrix[i] {
matrix[i][j] = make([]T, c)
}
}
return matrix
}
func MakeMatrix4D[T any](a int, b int, c int, d int) [][][][]T {
matrix := make([][][][]T, a)
for i := range matrix {
matrix[i] = make([][][]T, b)
for j := range matrix[i] {
matrix[i][j] = make([][]T, c)
for k := range matrix[i][j] {
matrix[i][j][k] = make([]T, d)
}
}
}
return matrix
}
func CompareMatrix2D[T any](a [][]T, b [][]T, compare func(T, T) bool) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if len(a[i]) != len(b[i]) {
return false
}
for j := 0; j < len(a[i]); j++ {
if !compare(a[i][j], b[i][j]) {
return false
}
}
}
return true
}
func CompareMatrix3D[T any](a [][][]T, b [][][]T, compare func(T, T) bool) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if len(a[i]) != len(b[i]) {
return false
}
for j := 0; j < len(a[i]); j++ {
if len(a[i][j]) != len(b[i][j]) {
return false
}
for k := 0; k < len(a[i][j]); k++ {
if !compare(a[i][j][k], b[i][j][k]) {
return false
}
}
}
}
return true
}
func FillFloat32(a []float32, fromIndex uint32, toIndex uint32, val float32) {
for i := fromIndex; i < toIndex; i++ {
a[i] = val
}
}
func Add[T any](slice []T, index int, elem T) []T {
newSlice := append(slice[:index], elem)
newSlice = append(newSlice, slice[index:]...)
return newSlice
}
type Dimension struct {
Width uint32
Height uint32
}
type Rectangle struct {
Origin Point
Size Dimension
}
func (r Rectangle) ComputeLowerCorner() Point {
return Point{
X: r.Origin.X + int32(r.Size.Width),
Y: r.Origin.Y + int32(r.Size.Height),
}
}
package util
func AddToSlice[T any](slice []*T, pos int, item *T) []*T {
if len(slice) == 0 {
return []*T{item}
}
// just add to end.
if pos == len(slice) {
slice = append(slice, item)
return slice
}
// having to screw around copying vs just cutting slices due to we have
// slices of pointers and we'd end up with duplicate entries no matter what I tried! :(
tempSlice := make([]*T, len(slice)+1)
copy(tempSlice[:pos], slice[:pos])
tempSlice[pos] = item
if pos < len(slice) {
copy(tempSlice[pos+1:], slice[pos:])
}
return tempSlice
}