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)
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")
}
}
reader.ZeroPadToByte()
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 h.DecodedICC != nil && 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")
}
itScale := 255.0 / intensityTarget
for y := 0; y < len(buffer[0]); y++ {
for x := 0; x < len(buffer[0][y]); x++ {
gammaL := buffer[1][y][x] + buffer[0][y][x] - oim.CbrtOpsinBias[0]
gammaM := buffer[1][y][x] - buffer[0][y][x] - oim.CbrtOpsinBias[1]
gammaS := buffer[2][y][x] - oim.CbrtOpsinBias[2]
mixL := gammaL*gammaL*gammaL + oim.OpsinBias[0]
mixM := gammaM*gammaM*gammaM + oim.OpsinBias[1]
mixS := gammaS*gammaS*gammaS + oim.OpsinBias[2]
for c := 0; c < 3; c++ {
buffer[c][y][x] = (mixL*oim.Matrix[c][0] + mixM*oim.Matrix[c][1] + mixS*oim.Matrix[c][2]) * 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/3, 1/3)
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/3, 1/3)
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"
"fmt"
"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"
)
// Box information (not sure what this is yet)
type BoxInfo struct {
boxSize uint32
posInBox uint32
container bool
}
// 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
}
for _, box := range jxl.boxHeaders {
_, 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
}
return nil, errors.New("unable to find image header")
}
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++
//showNextNBytes(jxl.bitReader, 4)
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 {
imgFrame.SkipFrameData()
continue
}
err = imgFrame.DecodeFrame(jxl.lfBuffer[header.LfLevel])
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
}
}
if found {
//FIXME(kpfaulkner)
// dumb copy of canvas?
canvas2 := make([]image2.ImageBuffer, len(jxl.canvas))
for _, ib := range jxl.canvas {
ib2 := image2.NewImageBufferFromImageBuffer(&ib)
canvas2 = append(canvas2, *ib2)
}
}
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
}
panic("make JXL image here?")
return nil, nil
}
func showNextNBytes(reader jxlio.BitReader, prefix string, n int) {
b, _ := reader.ShowBits(8 * n)
fmt.Printf(prefix + " ")
for i := 0; i < n; i++ {
fmt.Printf("%02x ", b&0xFF)
b >>= 8
}
fmt.Printf("\n")
}
// 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].CastToFloatIfInt(max)
// frameBuffer[d].CastToFloatIfInt(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)].CastToFloatIfInt(^(^0 << depth)); err != nil {
// return err
// }
// if err := refBuffer[colourChannels+int(info.AlphaChannel)].CastToFloatIfInt(^(^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].CastToFloatIfInt(^(^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
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)
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]
}
}
}
break
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]
}
}
break
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
}
}
}
break
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
}
}
break
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.CastToFloatIfInt(^(^0 << depthFrame)); err != nil {
return err
}
if err := canvas[canvasIdx].CastToFloatIfInt(^(^0 << depthCanvas)); err != nil {
return err
}
if err := ref.CastToFloatIfInt(^(^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() {
refBuffer[alphaIdx].CastToFloatIfInt(^(^0 << depth))
}
if !frameBuffers[alphaIdx].IsFloat() {
frameBuffers[alphaIdx].CastToFloatIfInt(^(^0 << depth))
}
}
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.CastToFloatIfInt(^(^0 << depthFrame)); err != nil {
return err
}
if err := canvas[channelNo].CastToFloatIfInt(^(^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
}
return image2.ImageBuffer{}, errors.New("unable to transpose buffer")
}
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")
}
return nil, nil
}
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")
}
return nil, nil
}
package core
import (
"io"
"github.com/kpfaulkner/jxl-go/bundle"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/options"
)
// 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,
}
br := jxlio.NewBitStreamReader(in)
// 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
}
// 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 jxl.iccProfile != nil && 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
err = jxl.transform(primaries, whitePoint, tf, PEAK_DETECT_AUTO)
if err != nil {
return nil, err
}
}
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].CastToFloatIfInt(^(^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].CastToIntIfFloat(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) error {
if primaries.Matches(jxl.primariesXY) && whitePoint.Matches(jxl.whiteXY) {
return jxl.transferImage(transfer, peakDetect)
}
if err := jxl.linearize(); err != nil {
return err
}
if err := jxl.toneMapLinear(); err != nil {
return err
}
if err := jxl.transferImage(transfer, peakDetect); err != nil {
return err
}
return 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) transferImage(transfer int32, peakDetect int32) error {
if transfer == jxl.transfer {
return nil
}
if err := jxl.linearize(); err != nil {
return err
}
if jxl.taggedTransfer == colour.TF_PQ &&
(peakDetect == PEAK_DETECT_AUTO || peakDetect == PEAK_DETECT_ON) {
panic("not implemented")
}
transferFunction, err := colour.GetTransferFunction(transfer)
if err != nil {
return err
}
if err := jxl.transferInPlace(transferFunction.FromLinear); err != nil {
return err
}
return nil
}
func (jxl *JXLImage) linearize() error {
if jxl.transfer == colour.TF_LINEAR {
return 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++ {
jxl.Buffer[c].CastToFloatIfInt(int32(jxl.bitDepths[c]))
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)
output.Write([]byte(header))
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
}
}
}
}
output.Write(buf.Bytes())
return nil
}
package core
import (
"bytes"
"compress/zlib"
"encoding/binary"
"hash/crc32"
"io"
"github.com/kpfaulkner/jxl-go/colour"
)
// 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 WritePNG(jxlImage *JXLImage, output io.Writer) error {
// PNG header
header := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
output.Write(header)
if err := writeIHDR(jxlImage, output); err != nil {
return err
}
if jxlImage.iccProfile != nil || len(jxlImage.iccProfile) != 0 {
if err := writeICCP(jxlImage, output); err != nil {
return err
}
} else {
// if we have an ICC profile then write it out
if err := writeSRGB(jxlImage, output); err != nil {
return err
}
}
if err := writeIDAT(jxlImage, output); err != nil {
return err
}
output.Write([]byte{0, 0, 0, 0})
output.Write([]byte{0x49, 0x45, 0x4E, 0x44})
output.Write([]byte{0xAE, 0x42, 0x60, 0x82})
return nil
}
func writeICCP(image *JXLImage, output io.Writer) error {
var buf bytes.Buffer
//output.Write([]byte{0x69, 0x43, 0x43, 0x50})
buf.Write([]byte{0x69, 0x43, 0x43, 0x50})
buf.Write([]byte("jxlatte")) // using jxlatte just to compare files
buf.WriteByte(0x00)
buf.WriteByte(0x00)
var iccProfile []int8
for i := 0; i < len(image.iccProfile); i++ {
iccProfile = append(iccProfile, int8(i))
}
var compressedICC bytes.Buffer
w, err := zlib.NewWriterLevel(&compressedICC, 1)
if err != nil {
return err
}
w.Write(image.iccProfile)
w.Flush()
w.Close()
b := compressedICC.Bytes()
buf.Write(b)
rawBytes := buf.Bytes()
buf2 := make([]byte, 4)
binary.BigEndian.PutUint32(buf2, uint32(len(rawBytes))-4)
output.Write(buf2)
output.Write(rawBytes)
checksum := crc32.ChecksumIEEE(rawBytes)
binary.BigEndian.PutUint32(buf2, checksum)
output.Write(buf2)
return nil
}
func writeSRGB(image *JXLImage, output io.Writer) error {
var buf bytes.Buffer
//output.Write([]byte{0x69, 0x43, 0x43, 0x50})
buf.Write([]byte{0x00, 0x00, 0x00, 0x01})
buf.Write([]byte{0x73, 0x52, 0x47, 0x42}) // using jxlatte just to compare files
buf.WriteByte(0x01)
buf.Write([]byte{0xD9, 0xC9, 0x2C, 0x7F})
rawBytes := buf.Bytes()
//buf2 := make([]byte, 4)
//binary.BigEndian.PutUint32(buf2, uint32(len(rawBytes))-4)
//output.Write(buf2)
output.Write(rawBytes)
return nil
}
func writeIHDR(jxlImage *JXLImage, output io.Writer) error {
// colourmode is 6 if include alpha... otherwise 2
colourMode := byte(2)
if jxlImage.ColorEncoding == colour.CE_GRAY {
if jxlImage.alphaIndex >= 0 {
colourMode = 4
} else {
colourMode = 0
}
} else {
if jxlImage.alphaIndex >= 0 {
colourMode = 6
} else {
colourMode = 2
}
}
var bitDepth int32
if jxlImage.imageHeader.BitDepth.BitsPerSample > 8 {
bitDepth = 16
} else {
bitDepth = 8
}
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(bitDepth)
ihdr[13] = 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 writeIDAT(jxlImage *JXLImage, output io.Writer) error {
var buf bytes.Buffer
buf.Write([]byte("IDAT"))
var compressedBytes bytes.Buffer
w, err := zlib.NewWriterLevel(&compressedBytes, zlib.DefaultCompression)
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].CastToIntIfFloat(maxValue); err != nil {
return err
}
if len(jxlImage.Buffer) > 1 {
if err = jxlImage.Buffer[1].CastToIntIfFloat(maxValue); err != nil {
return err
}
}
if len(jxlImage.Buffer) > 2 {
if err = jxlImage.Buffer[2].CastToIntIfFloat(maxValue); err != nil {
return err
}
}
if jxlImage.HasAlpha() {
if err = jxlImage.Buffer[3].CastToIntIfFloat(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].CastToIntIfFloat(maxValue); err != nil {
return err
}
}
}
for y := uint32(0); y < jxlImage.Height; y++ {
w.Write([]byte{0})
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 {
w.Write([]byte{byte(dat)})
} else {
byte1 := dat & 0xFF
byte2 := dat & 0xFF00
byte2 >>= 8
w.Write([]byte{byte(byte2), byte(byte1)})
}
}
if jxlImage.HasAlpha() {
dat := jxlImage.Buffer[3].IntBuffer[y][x]
if jxlImage.bitDepths[3] == 8 {
w.Write([]byte{byte(dat)})
} else {
byte1 := dat & 0xFF
byte2 := dat & 0xFF00
byte2 >>= 8
w.Write([]byte{byte(byte2), byte(byte1)})
}
}
}
}
w.Close()
buf.Write(compressedBytes.Bytes())
bb := buf.Bytes()
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, uint32(len(bb))-4)
output.Write(b)
output.Write(bb)
checksum := crc32.ChecksumIEEE(bb)
binary.BigEndian.PutUint32(b, checksum)
output.Write(b)
return nil
}
package core
import (
"bytes"
"encoding/binary"
"errors"
"io"
"github.com/kpfaulkner/jxl-go/jxlio"
log "github.com/sirupsen/logrus"
)
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
}
if boxSize < 0 {
return nil, errors.New("invalid box size")
}
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?")
}
}
}
return boxHeaders, nil
}
func (br *BoxReader) readBox() error {
return nil
}
// 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
}
func fromBeToLe(le uint32) uint32 {
return uint32(binary.BigEndian.Uint32([]byte{byte(le), byte(le >> 8), byte(le >> 16), byte(le >> 24)}))
}
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}})
var (
// just for some debugging
lastStateObj int32
)
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, errors.New(fmt.Sprintf("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, errors.New(fmt.Sprintf("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, errors.New(fmt.Sprintf("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
}
func NewEntropyStreamWithReaderAndNumDists(reader jxlio.BitReader, numDists int) (*EntropyStream, error) {
return NewEntropyStreamWithReader(reader, numDists, false)
}
func NewEntropyStreamWithStream(stream *EntropyStream) *EntropyStream {
es := &EntropyStream{}
es.usesLZ77 = stream.usesLZ77
es.lz77MinLength = stream.lz77MinLength
es.lz77MinSymbol = stream.lz77MinSymbol
es.lzLengthConfig = stream.lzLengthConfig
es.clusterMap = stream.clusterMap
es.dists = stream.dists
es.logAlphabetSize = stream.logAlphabetSize
if es.usesLZ77 {
es.window = make([]int32, 1<<20)
}
es.ansState = &ANSState{State: -1, HasState: false}
return es
}
func NewEntropyStreamWithReader(reader jxlio.BitReader, numDists int, disallowLZ77 bool) (*EntropyStream, error) {
es := &EntropyStream{}
var err error
if numDists <= 0 {
return nil, errors.New("Num Dists must be positive")
}
if es.usesLZ77, err = reader.ReadBool(); err != nil {
return nil, err
}
es.ansState = &ANSState{State: -1, HasState: false}
if es.usesLZ77 {
if disallowLZ77 {
return nil, errors.New("Nested distributions cannot use LZ77")
}
if lz77MinSymbol, err := reader.ReadU32(224, 0, 512, 0, 4096, 0, 8, 15); err != nil {
return nil, err
} else {
es.lz77MinSymbol = int32(lz77MinSymbol)
}
if lz77MinLength, err := reader.ReadU32(3, 0, 4, 0, 5, 2, 9, 8); err != nil {
return nil, err
} else {
es.lz77MinLength = int32(lz77MinLength)
}
numDists++
es.lzLengthConfig, err = NewHybridIntegerConfigWithReader(reader, 8)
if err != nil {
return nil, err
}
es.window = make([]int32, 1<<20)
}
es.clusterMap = make([]int, numDists)
numClusters, err := ReadClusterMap(reader, es.clusterMap, numDists)
if err != nil {
return nil, err
}
es.dists = make([]SymbolDistribution, numClusters)
var prefixCodes bool
if prefixCodes, err = reader.ReadBool(); err != nil {
return nil, err
}
if prefixCodes {
es.logAlphabetSize = 15
} else {
if logAlphabetSize, err := reader.ReadBits(2); err != nil {
return nil, 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 nil, 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 nil, err
}
if readBits {
var n uint64
if n, err = reader.ReadBits(4); err != nil {
return nil, err
}
if alphaSize, err := reader.ReadBits(uint32(n)); err != nil {
return nil, 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 nil, err
}
}
} else {
for i := 0; i < len(es.dists); i++ {
d, err := NewANSSymbolDistribution(reader, es.logAlphabetSize)
if err != nil {
return nil, err
}
es.dists[i] = d
}
}
for i := 0; i < len(es.dists); i++ {
es.dists[i].SetConfig(configs[i])
}
return es, nil
}
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)
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) 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 {
rcvr.populateSimplePrefix(reader)
} else {
rcvr.populateComplexPrefix(reader, int32(hskip))
}
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 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 Inp struct {
iPass int
iGroup int
}
type Frame struct {
tocPermutation []uint32
tocLengths []uint32
lfGroups []*LFGroup
buffers [][]uint8
Buffer []image.ImageBuffer
passes []Pass
bitreaders []jxlio.BitReader
GlobalMetadata *bundle.ImageHeader
options *options.JXLOptions
reader jxlio.BitReader
Header *FrameHeader
globalTree *MATree
hfGlobal *HFGlobal
LfGlobal *LFGlobal
width uint32
height uint32
groupRowStride uint32
lfGroupRowStride uint32
numGroups uint32
numLFGroups uint32
permutatedTOC bool
decoded bool
}
func (f *Frame) ReadFrameHeader() (FrameHeader, error) {
f.reader.ZeroPadToByte()
var err error
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)
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
//}
}
f.reader.ZeroPadToByte()
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
}
}
f.reader.ZeroPadToByte()
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))
}
func readPermutation(reader jxlio.BitReader, stream *entropy.EntropyStream, 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")
}
}
var temp []uint32
permutation := make([]uint32, size)
for i := 0; i < int(size); i++ {
temp = append(temp, uint32(i))
}
for i := 0; i < int(size); i++ {
index := lehmer[i]
val := temp[index]
temp = append(temp[:index], temp[index+1:]...)
permutation[i] = 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++ {
buffer := make([]byte, f.tocLengths[i])
err := f.reader.ReadBytesToBuffer(buffer, 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 {
if f.tocPermutation != nil {
i = f.tocPermutation[index]
}
i = uint32(index)
}
return f.bitreaders[i], nil
}
func (f *Frame) DecodeFrame(lfBuffer []image.ImageBuffer) error {
if f.decoded {
return nil
}
f.decoded = true
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
}
lfGlobalBitReader, err := f.getBitreader(0)
if err != nil {
return err
}
f.LfGlobal, err = NewLFGlobalWithReader(lfGlobalBitReader, f)
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])
}
}
}
if err := f.invertSubsampling(); err != nil {
return nil
}
if f.Header.restorationFilter.gab {
f.performGabConvolution()
}
if f.Header.restorationFilter.epfIterations > 0 {
f.performEdgePreservingFilter()
}
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.channels); i++ {
ch := f.LfGlobal.globalModular.channels[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)
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))
replaced := make([]ModularChannel, len(lfReplacementChannels))
for _, r := range lfReplacementChannels {
replaced = append(replaced, *NewModularChannelFromChannel(*r))
}
frameSize, err := f.GetPaddedFrameSize()
if err != nil {
return err
}
for i, info := range replaced {
lfHeight := frameSize.Height >> info.vshift
lfWidth := frameSize.Width >> info.hshift
info.origin.Y = lfGroupPos.Y * int32(info.size.Height)
info.origin.X = lfGroupPos.X * int32(info.size.Width)
info.size.Height = util.Min(info.size.Height, lfHeight-uint32(info.origin.Y))
info.size.Width = util.Min(info.size.Width, lfWidth-uint32(info.origin.X))
replaced[i] = info
}
f.lfGroups[lfGroupID], err = NewLFGroup(reader, f, int32(lfGroupID), replaced, lfBuffer)
if err != nil {
return err
}
}
for lfGroupID := uint32(0); lfGroupID < f.numLFGroups; lfGroupID++ {
for j := 0; j < len(lfReplacementChannelIndicies); j++ {
index := lfReplacementChannelIndicies[j]
channel := f.LfGlobal.globalModular.channels[index]
channel.allocate()
newChannelInfo := f.lfGroups[lfGroupID].modularLFGroup.channels[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)
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.channels[ii]
channel.allocate()
for group := 0; group < int(f.numGroups); group++ {
newChannelInfo := passGroups[pass][group].modularStream.channels[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.MakeMatrix3D[float32](3, 0, 0)
for c := 0; c < 3; c++ {
f.Buffer[c].CastToFloatIfInt(^(^0 << f.GlobalMetadata.BitDepth.BitsPerSample))
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
}
}
}
}
return nil
}
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])
}
}
}
}
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]
for xShift > 0 {
xShift--
oldBuffer := f.Buffer[c]
oldBuffer.CastToFloatIfInt(^(^0 << f.GlobalMetadata.BitDepth.BitsPerSample))
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
}
for yShift > 0 {
yShift--
oldBuffer := f.Buffer[c]
oldBuffer.CastToFloatIfInt(^(^0 << f.GlobalMetadata.BitDepth.BitsPerSample))
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
}
}
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++ {
f.Buffer[c].CastToFloatIfInt(^(^0 << f.GlobalMetadata.BitDepth.BitsPerSample))
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
for y := int32(0); y < height; 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] = normGabBase[c]*buffR[x] + normGabAdj[c]*adj + normGabDiag[c]*diag
}
}
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.MakeMatrix2D[float32](blockHeight, blockWidth)
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); 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")
}
for c := 0; c < 3; c++ {
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++ {
f.Buffer[c].CastToFloatIfInt(^(^0 << f.GlobalMetadata.BitDepth.BitsPerSample))
outBuf, err := image.NewImageBuffer(image.TYPE_FLOAT, int32(paddedSize.Height), int32(paddedSize.Width))
if err != nil {
return err
}
outputBuffer[c] = *outBuf
}
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)
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
}
var crossList []util.Point
if i == 0 {
crossList = epfDoubleCross
} else {
crossList = epfCross
}
sumChannels := make([]float32, colours)
for y := int32(0); y < int32(paddedSize.Height); y++ {
for x := int32(0); x < int32(paddedSize.Width); x++ {
s := inverseSigma[y>>3][x>>3]
if s > (1.0 / 0.3) {
for c := 0; c < len(outputBuffers); c++ {
outputBuffers[c][y][x] = inputBuffers[c][y][x]
}
continue
}
sumWeights := float32(0)
for ff, _ := range sumChannels {
sumChannels[ff] = 0
}
for _, cross := range crossList {
var dist float32
if i == 2 {
dist = f.epfDistance2(inputBuffers, colours, y, x, cross, paddedSize)
} else {
dist = f.epfDistance1(inputBuffers, colours, y, x, cross, paddedSize)
}
weight := f.epfWeight(sigmaScale, dist, s, y, x)
sumWeights += weight
mY := util.MirrorCoordinate(y+cross.Y, int32(paddedSize.Height))
mX := util.MirrorCoordinate(x+cross.X, int32(paddedSize.Width))
for c := int32(0); c < colours; c++ {
sumChannels[c] += inputBuffers[c][mY][mX] * weight
}
}
for c := 0; c < len(outputBuffers); c++ {
outputBuffers[c][y][x] = sumChannels[c] / sumWeights
}
}
}
for c := 0; c < int(colours); c++ {
//f.Buffer[c], outputBuffer[c] = outputBuffer[c], f.Buffer[c]
tmp := f.Buffer[c]
f.Buffer[c].FloatBuffer = outputBuffers[c]
outputBuffer[c] = tmp
}
}
return nil
}
func (f *Frame) epfWeight(sigmaScale float32, distance float32, inverseSigma float32, refY int32, refX int32) float32 {
modY := refY & 0b111
modX := refX & 0b111
if modY == 0 || modY == 7 || modX == 0 || modX == 7 {
distance *= f.Header.restorationFilter.epfBorderSadMul
}
v := 1.0 - distance*sigmaScale*inverseSigma
if v < 0 {
return 0
}
return v
}
func (f *Frame) epfDistance1(buffer [][][]float32, colours int32, basePosY int32, basePosX int32, dCross util.Point, frameSize util.Dimension) float32 {
dist := float32(0)
for c := int32(0); c < colours; c++ {
buffC := buffer[c]
scale := f.Header.restorationFilter.epfChannelScale[c]
for _, cross := range epfCross {
pY := util.MirrorCoordinate(basePosY+cross.Y, int32(frameSize.Height))
pX := util.MirrorCoordinate(basePosX+cross.X, int32(frameSize.Width))
dY := util.MirrorCoordinate(basePosY+dCross.Y+cross.Y, int32(frameSize.Height))
dX := util.MirrorCoordinate(basePosX+dCross.X+cross.X, int32(frameSize.Width))
dist += float32(math.Abs(float64(buffC[pY][pX]-buffC[dY][dX]))) * scale
}
}
return dist
}
func (f *Frame) epfDistance2(buffer [][][]float32, colours int32, basePosY int32, basePosX int32, cross util.Point, frameSize util.Dimension) float32 {
dist := float32(0)
for c := int32(0); c < colours; c++ {
buffC := buffer[c]
dY := util.MirrorCoordinate(basePosY+cross.Y, int32(frameSize.Height))
dX := util.MirrorCoordinate(basePosX+cross.X, int32(frameSize.Width))
dist += float32(math.Abs(float64(buffC[basePosY][basePosX]-buffC[dY][dX]))) * f.Header.restorationFilter.epfChannelScale[c]
}
return dist
}
func copyFloatBuffers(buffer []image.ImageBuffer, colours int32) [][][]float32 {
data := util.MakeMatrix3D[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) InitializeNoise(seed0 int64) error {
if f.LfGlobal.noiseParameters == nil || 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.CastToFloatIfInt(^(^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)
//idx1 := uint32(pos.Y) >> 3
//idx2 := uint32(f.lfGroupRowStride)
//idx3 := uint32(pos.X >> 3)
//idx := idx1*idx2 + idx3
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
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 (
"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
VARDCT = 0
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
break
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 {
// TODO(kpfaulkner) 20241026 getting 0's for xqmScale and bqmScale where as JXLatte gets 2 for both?!?
// REALLY confused how this is happening...
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
}
}
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
}
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
}
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 *Frame
hfctx *HFBlockContext
lfg *LFGroup
stream *entropy.EntropyStream
quantizedCoeffs [][][]int32
dequantHFCoeff [][][]float32
groupPos util.Point
blocks []*util.Point
}
func NewHFCoefficientsWithReader(reader jxlio.BitReader, frame *Frame, pass uint32, group uint32) (*HFCoefficients, error) {
hf := &HFCoefficients{}
hfPreset, err := reader.ReadBits(uint32(util.CeilLog1p(frame.hfGlobal.numHFPresets - 1)))
if err != nil {
return nil, err
}
hf.hfPreset = int32(hfPreset)
hf.groupID = group
hf.frame = frame
hf.hfctx = frame.LfGlobal.hfBlockCtx
hf.lfg = frame.getLFGroupForGroup(int32(group))
offset := 495 * hf.hfctx.numClusters * hf.hfPreset
header := frame.Header
shift := header.passes.shift[pass]
hfPass := hf.frame.passes[pass].hfPass
size, err := hf.frame.getGroupSize(int32(hf.groupID))
if err != nil {
return nil, err
}
nonZeros := util.MakeMatrix3D[int32](3, 32, 32)
hf.stream = entropy.NewEntropyStreamWithStream(hfPass.contextStream)
hf.quantizedCoeffs = util.MakeMatrix3D[int32](3, 0, 0)
hf.dequantHFCoeff = util.MakeMatrix3D[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.MakeMatrix2D[int32](sY, sX)
hf.dequantHFCoeff[c] = util.MakeMatrix2D[float32](sY, 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]))
ucoeff := make([]int32, orderSize-numBlocks)
histCtx := offset + 458*blockCtx + 37*hf.hfctx.numClusters
for k := int32(0); k < int32(len(ucoeff)); k++ {
var prev int32
if k == 0 {
if nonZero > orderSize/16 {
prev = 0
} else {
prev = 1
}
} else {
if ucoeff[k-1] != 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
}
ucoeff[k] = 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(ucoeff[k])) << shift
if ucoeff[k] != 0 {
nonZero--
if nonZero == 0 {
break
}
}
}
if nonZero != 0 {
return nil, errors.New("nonZero != 0")
}
}
}
if !hf.stream.ValidateFinalState() {
return nil, errors.New(fmt.Sprintf("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.GlobalMetadata.OpsinInverseMatrix
header := hf.frame.Header
globalScale := 65536.0 / float32(hf.frame.LfGlobal.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.hfGlobal.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.Header
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.LfGlobal.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]))
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 = lfc.baseCorrelationX + float32(hfX[fx])/float32(lfc.colorFactor)
kB = lfc.baseCorrelationB + float32(hfB[fx])/float32(lfc.colorFactor)
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.MakeMatrix3D[float32](2, 32, 32)
header := hf.frame.Header
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
}
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
}
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)
}
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()
}
}
}
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]
break
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}
break
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}
break
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}
break
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}
break
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}
break
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}
break
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])
break
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]
break
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
break
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
break
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]
break
case MODE_AFV:
afv, err := hfg.getAFVTransformWeights(index, c)
if err != nil {
return err
}
hfg.weights[index][c] = afv
break
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
}
}
break
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.EntropyStream
usedOrders uint32
}
func NewHFPassWithReader(reader jxlio.BitReader, frame *Frame, passIndex uint32) (*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.EntropyStream
if usedOrders != 0 {
if stream, err = entropy.NewEntropyStreamWithReaderAndNumDists(reader, 8); 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.hfGlobal.numHFPresets * frame.LfGlobal.hfBlockCtx.numClusters
contextStream, err := entropy.NewEntropyStreamWithReaderAndNumDists(reader, int(numContexts))
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"
)
type LFGlobal struct {
frame *Frame
Patches []Patch
splines []SplinesBundle
noiseParameters []NoiseParameters
lfDequant []float32
hfBlockCtx *HFBlockContext
lfChanCorr *LFChannelCorrelation
//gModular *GlobalModular
globalScale int32
quantLF int32
scaledDequant []float32
globalModular *ModularStream
}
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)
return lf
}
func NewLFGlobalWithReader(reader jxlio.BitReader, parent *Frame) (*LFGlobal, error) {
lf := NewLFGlobal()
lf.frame = parent
extra := len(lf.frame.GlobalMetadata.ExtraChannelInfo)
if lf.frame.Header.Flags&PATCHES != 0 {
return nil, errors.New("Patches not implemented yet")
stream, err := entropy.NewEntropyStreamWithReaderAndNumDists(reader, 10)
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.GlobalMetadata.ExtraChannelInfo), len(parent.GlobalMetadata.AlphaIndices))
if err != nil {
return nil, err
}
}
} else {
lf.Patches = []Patch{}
}
if lf.frame.Header.Flags&SPLINES != 0 {
return nil, errors.New("Splines not implemented yet")
} else {
lf.splines = nil
}
if lf.frame.Header.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.Header.Encoding == VARDCT {
//lf.quantizer, err = NewQuantizerWithReader(reader, lf.lfDequant)
//if err != nil {
// return nil, err
//}
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 = NewHFBlockContextWithReader(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 *MATree
if hasGlobalTree {
globalTree, err = NewMATreeWithReader(reader)
if err != nil {
return nil, err
}
} else {
globalTree = nil
}
lf.frame.globalTree = globalTree
subModularChannelCount := extra
ecStart := 0
if lf.frame.Header.Encoding == MODULAR {
if !lf.frame.Header.DoYCbCr && !lf.frame.GlobalMetadata.XybEncoded &&
lf.frame.GlobalMetadata.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 *Frame
size util.Dimension
modularLFGroup *ModularStream
}
func NewLFGroup(reader jxlio.BitReader, parent *Frame, index int32, replaced []ModularChannel, lfBuffer []image.ImageBuffer) (*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.Header.Encoding == VARDCT {
lfg.lfCoeff, err = NewLFCoefficientsWithReader(reader, lfg, parent, lfBuffer)
if err != nil {
return nil, err
}
} else {
lfg.lfCoeff = nil
}
stream, err := NewModularStreamWithStreamIndex(reader, parent, 1+int(parent.numLFGroups+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.Header.Encoding == VARDCT {
metadata, err := NewHFMetadataWithReader(reader, lfg, parent)
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 MATree struct {
parent *MATree
stream *entropy.EntropyStream
leftChildNode *MATree
rightChildNode *MATree
property int32
context int32
value int32
leftChildIndex int32
rightChildIndex int32
predictor int32
offset int32
multiplier int32
}
func NewMATreeWithReader(reader jxlio.BitReader) (*MATree, error) {
mt := &MATree{}
mt.parent = nil
var nodes []*MATree
stream, err := entropy.NewEntropyStreamWithReaderAndNumDists(reader, 6)
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 *MATree
if len(nodes) == 0 {
node = mt
} else {
node = &MATree{}
}
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 = entropy.NewEntropyStreamWithReader(reader, (len(nodes)+1)/2, false)
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 *MATree) isLeafNode() bool {
return t.property < 0
}
func (t *MATree) compactifyWithY(channelIndex int32, streamIndex int32, y int32) *MATree {
var prop int32
switch t.property {
case 0:
prop = channelIndex
break
case 1:
prop = streamIndex
break
case 2:
prop = y
break
default:
return t
}
var branch *MATree
if prop > t.value {
branch = t.leftChildNode
} else {
branch = t.rightChildNode
}
return branch.compactifyWithY(channelIndex, streamIndex, y)
}
func (t *MATree) compactify(channelIndex int32, streamIndex int32) *MATree {
var prop int32
switch t.property {
case 0:
prop = channelIndex
break
case 1:
prop = streamIndex
break
default:
return t
}
var branch *MATree
if prop > t.value {
branch = t.leftChildNode
} else {
branch = t.rightChildNode
}
return branch.compactify(channelIndex, streamIndex)
}
func (t *MATree) useWeightedPredictor() bool {
if t.isLeafNode() {
return t.predictor == 6
}
return t.property == 15 ||
t.leftChildNode.useWeightedPredictor() ||
t.rightChildNode.useWeightedPredictor()
}
func (t *MATree) walk(walkerFunc func(inp int32) (int32, error)) (*MATree, 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)
}
func (t *MATree) 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 *MATree, depth int) {
if !node.leftChildNode.isLeafNode() {
DisplayTree(node.leftChildNode, depth+1)
}
if !node.rightChildNode.isLeafNode() {
DisplayTree(node.rightChildNode, depth+1)
}
return
}
package frame
import (
"errors"
"math"
"github.com/kpfaulkner/jxl-go/entropy"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
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 mc.buffer != nil && 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)
}
maxError := tW
if util.Abs(tN) > util.Abs(maxError) {
maxError = tN
}
if util.Abs(tNW) > util.Abs(maxError) {
maxError = tNW
}
if util.Abs(tNE) > util.Abs(maxError) {
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) northWestOrig(x int32, y int32) int32 {
buf := mc.buffer
if x > 0 {
if y > 0 {
return buf[y-1][x-1]
}
return buf[y][x-1]
}
if y > 0 {
return buf[y-1][x]
}
return 0
}
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) errorNorthOrig(x int32, y int32, e int32) int32 {
if y > 0 {
return mc.err[e][y-1][x]
}
return 0
}
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) errorWestWestOrig(x int32, y int32, e int32) int32 {
if x > 1 {
return mc.err[e][y][x-2]
}
return 0
}
func (mc *ModularChannel) errorNorthWestOrig(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) 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) errorNorthEastOrig(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) walkerFunc(k int32) int32 {
return 0
}
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 {
k2++
return util.Abs(rC), nil
}
k2++
if k2 == k {
k2++
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 {
k2++
return util.Abs(rG), nil
}
k2++
if k2 == k {
k2++
return rG, nil
}
k2++
}
return 0, nil
}
}
}
func (mc *ModularChannel) getLeafNode(refinedTree *MATree, channelIndex int32, streamIndex int32, x int32, y int32, maxError int32, parent *ModularStream) (*MATree, 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.EntropyStream,
wpParams *WPParams, tree *MATree, 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] = int32(math.Abs(float64(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 ModularStream struct {
frame *Frame
streamIndex int
channelCount int
ecStart int
channels []*ModularChannel
tree *MATree
wpParams *WPParams
transforms []TransformInfo
distMultiplier int32
nbMetaChannels int
stream *entropy.EntropyStream
transformed bool
squeezeMap map[int][]SqueezeParam
}
func NewModularStreamWithStreamIndex(reader jxlio.BitReader, frame *Frame, streamIndex int, channelArray []ModularChannel) (*ModularStream, error) {
return NewModularStreamWithChannels(reader, frame, streamIndex, len(channelArray), 0, channelArray)
}
func NewModularStreamWithReader(reader jxlio.BitReader, frame *Frame, streamIndex int, channelCount int, ecStart int) (*ModularStream, error) {
return NewModularStreamWithChannels(reader, frame, streamIndex, channelCount, ecStart, nil)
}
func NewModularStreamWithChannels(reader jxlio.BitReader, frame *Frame, streamIndex int, channelCount int, ecStart int, channelArray []ModularChannel) (*ModularStream, 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 channelArray == nil || len(channelArray) == 0 {
for i := 0; i < channelCount; i++ {
size := frame.Header.Bounds.Size
var dimShift int32
if i < ecStart {
dimShift = 0
} else {
dimShift = frame.GlobalMetadata.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)
if err != nil {
return nil, err
}
ms.tree = tree
} else {
ms.tree = frame.globalTree
}
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) decodeChannels(reader jxlio.BitReader, partial bool) error {
groupDim := uint32(ms.frame.Header.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
}
break
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
}
break
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
}
break
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
}
break
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
}
break
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
}
break
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.GlobalMetadata.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/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
type Pass struct {
replacedChannels []*ModularChannel
hfPass *HFPass
minShift uint32
maxShift uint32
}
func NewPassWithReader(reader jxlio.BitReader, frame *Frame, passIndex uint32, prevMinShift uint32) (Pass, error) {
p := Pass{}
if passIndex > 0 {
p.maxShift = prevMinShift
} else {
p.maxShift = 3
}
n := -1
passes := frame.Header.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.LfGlobal.globalModular
p.replacedChannels = make([]*ModularChannel, len(stream.channels))
for i := 0; i < len(p.replacedChannels); i++ {
ch := stream.channels[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.Header.Encoding == VARDCT {
p.hfPass, err = NewHFPassWithReader(reader, frame, passIndex)
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 {
modularPassGroupBuffer [][][]int32
modularStream *ModularStream
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.MakeMatrix3D[float32](5, 256, 256)
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]),
}
lfs := make([]float32, 2)
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
}
break
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
}
}
break
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
}
}
break
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])
break
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)
break
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++ {
copy(buffer[y+outPos.Y][outPos.X:], block[y+inPos.Y][inPos.X:])
}
}
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{4, 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)])
}
}
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.EntropyStream, 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, 1 / 7, 2 / 7, 3 / 7, 4 / 7, 5 / 7, 6 / 7, 1}
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"
"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
}
func NewHFMetadataWithReader(reader jxlio.BitReader, parent *LFGroup, frame *Frame) (*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 := NewModularStreamWithStreamIndex(reader, frame, 1+2*int(frame.numLFGroups)+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, errors.New(fmt.Sprintf("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) {
x := lastBlock.X
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)) {
x = lastBlock.X
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"
"math"
"github.com/kpfaulkner/jxl-go/image"
"github.com/kpfaulkner/jxl-go/jxlio"
"github.com/kpfaulkner/jxl-go/util"
)
type LFCoefficients struct {
dequantLFCoeff [][][]float32
lfIndex [][]int32
frame *Frame
}
func NewLFCoefficientsWithReader(reader jxlio.BitReader, parent *LFGroup, frame *Frame, lfBuffer []image.ImageBuffer) (*LFCoefficients, error) {
lf := &LFCoefficients{}
lf.frame = frame
lf.lfIndex = util.MakeMatrix2D[int32](parent.size.Height, parent.size.Width)
header := frame.Header
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++ {
lfBuffer[c].CastToFloatIfInt(^(^0 << frame.GlobalMetadata.BitDepth.BitsPerSample))
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
}
lfQuantStream, err := NewModularStreamWithStreamIndex(reader, frame, int(1+parent.lfGroupID), info)
if err != nil {
return nil, err
}
err = lfQuantStream.decodeChannels(reader, false)
if err != nil {
return nil, err
}
lfQuant := lfQuantStream.getDecodedBuffer()
scaledDequant := frame.LfGlobal.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.LfGlobal.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)
return lf, nil
}
func (c *LFCoefficients) populatedLFIndex(parent *LFGroup, lfQuant [][][]int32) error {
hfctx := c.frame.LfGlobal.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.Header
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 {
weighted := make([][][]float32, 3)
gap := make([][]float32, len(coeff[0]))
dequantLFCoeff := make([][][]float32, 3)
for i := 0; i < 3; i++ {
co := coeff[i]
weighted[i] = make([][]float32, len(co))
sd := scaledDequant[i]
for y := 01; y < len(co)-1; y++ {
coy := co[y]
coym := co[y-1]
coyp := co[y+1]
if gap[y] == nil {
gap[y] = make([]float32, len(coy))
for x := 0; x < len(gap[y]); x++ {
gap[y][x] = 0.5
}
}
gy := gap[y]
weighted[i][y] = make([]float32, len(coy))
wy := weighted[i][y]
for x := 01; 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 := float32(math.Abs(float64(sample-wy[x])) * float64(sd))
if g > gy[x] {
gy[x] = g
}
}
}
}
for y := 0; y < len(gap); y++ {
if gap[y] == nil {
continue
}
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]
dequantLFCoeff[i] = make([][]float32, len(co))
dqi := dequantLFCoeff[i]
wi := weighted[i]
for y := 0; y < len(co); y++ {
coy := co[y]
dqi[y] = make([]float32, len(coy))
dqy := dqi[y]
gy := gap[y]
wiy := wi[y]
if y == 0 || y+1 == len(co) {
copy(dqy, coy)
continue
}
for x := 0; x < len(coy); x++ {
if x == 0 || x+1 == len(coy) {
dqy[x] = coy[x]
continue
}
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, errors.New(fmt.Sprintf("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) *ImageBuffer {
ib := &ImageBuffer{}
ib.IntBuffer = copyInt32Matrix2D(imageBuffer.IntBuffer)
ib.FloatBuffer = copyFloat32Matrix2D(imageBuffer.FloatBuffer)
ib.BufferType = imageBuffer.BufferType
ib.Height = imageBuffer.Height
ib.Width = imageBuffer.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
}
// 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) CastToFloatIfInt(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) CastToIntIfFloat(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)
if err != nil {
return true
}
return false
}
// 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 {
buffer := make([]byte, 1)
_, err := br.stream.Read(buffer)
if err != nil {
return 0, err
}
br.currentByte = 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 < 1 || bits > 64 {
return 0, errors.New("num bits must be between 1 and 64")
}
var v uint64
for i := uint32(0); i < bits; i++ {
bit, err := br.readBit()
if err != nil {
return 0, err
}
v |= uint64(bit) << i
}
return v, 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
}
}
if value > math.MaxInt32 {
return 0, errors.New("ICC varint overflow")
}
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 = lower
} else {
lower = c
}
if upper > c {
upper = upper
} else {
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 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++ {
for j := 0; j < len(right[0]); j++ {
for k := 0; k < len(right); k++ {
result[i][j] += left[i][k] * right[k][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 DeepCopy3[T comparable](a [][][]T) [][][]T {
if a == nil {
return nil
}
matrixCopy := make([][][]T, len(a))
for i := 0; i < len(a); i++ {
if a[i] == nil {
continue
}
matrixCopy[i] = make([][]T, len(a[i]))
for j := 0; j < len(a[i]); j++ {
if a[i][j] == nil {
continue
}
matrixCopy[i][j] = make([]T, len(a[i][j]))
for k := 0; k < len(a[i][j]); k++ {
matrixCopy[i][j][k] = a[i][j][k]
}
}
}
return matrixCopy
}
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
// 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
}