• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Eyevinn / hi264 / 22355158303

24 Feb 2026 02:28PM UTC coverage: 87.965% (+0.4%) from 87.558%
22355158303

Pull #12

github

tobbee
feat: 8x8 block granularity, double-res text glyphs, and PlaneGrid

Add 8x8 block resolution for the encoder. Each grid character maps to
an 8x8 block (4 per macroblock) with proper AC residual encoding at
quadrant boundaries using a forward 4x4 integer DCT.

Introduce PlaneGrid type for direct Y/Cb/Cr value planes with no
character-count limit, supporting both 16x16 and 8x8 block granularity.
All CLI output paths now use PlaneGrid as intermediate representation.

Add 6x10 double-resolution font (Glyph2x) for 8x8 block mode, giving
4x the glyph detail of the 3x5 font at the same pixel footprint.
Even text scales (2, 4, 6...) in 16x16 mode auto-select the 2x font
for sharper glyphs without requiring -8x8.

Text character set aligned across both fonts: A-Z 0-9 and
! # % + - . / : = ? [ ] _ ( ) plus space. Lowercase a-z and
rarely-used punctuation removed; lowercase input auto-uppercased.

Bug fixes:
- CAVLC level VLC encoding overflow for large coefficients (QP=0)
- Encoder reconstruction order (inverse Hadamard before dequant)
- SMPTE bars distributed at block-column granularity in -8x8 mode
- CLI -8x8 flag takes priority over .gridimg default block size
Pull Request #12: feat: 8x8 block granularity, double-res text glyphs, and PlaneGrid

1068 of 1182 new or added lines in 11 files covered. (90.36%)

5 existing lines in 3 files now uncovered.

7236 of 8226 relevant lines covered (87.96%)

1.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.26
/pkg/encode/frame_encode.go
1
package encode
2

3
import (
4
        "bytes"
5
        "fmt"
6

7
        "github.com/Eyevinn/mp4ff/avc"
8

9
        "github.com/Eyevinn/hi264/internal/cabac"
10
        "github.com/Eyevinn/hi264/internal/context"
11
        "github.com/Eyevinn/hi264/pkg/yuv"
12
)
13

14
// FrameEncoder encodes a grid-based image as an H.264 IDR frame.
15
type FrameEncoder struct {
16
        Grid            *yuv.Grid
17
        Colors          yuv.ColorMap
18
        Plane           *yuv.PlaneGrid // alternative to Grid+Colors; takes priority if set
19
        QP              int
20
        DisableDeblock  int            // 0=enable, 1=disable
21
        CABAC           bool           // use CABAC entropy coding (Main profile) instead of CAVLC (Baseline)
22
        MaxNumRefFrames int            // max_num_ref_frames in SPS (0 for IDR-only, 1+ for P-frames)
23
        Width           int            // actual pixel width for SPS (0 = Grid.Width*16)
24
        Height          int            // actual pixel height for SPS (0 = Grid.Height*16)
25
        ColorSpace      yuv.ColorSpace // YCbCr matrix standard (default BT601)
26
        Range           yuv.Range      // sample value range (default LimitedRange)
27
        FPS             int            // frame rate for level selection (0 = ignore MBPS)
28
        Kbps            int            // bitrate in kbit/s for level selection (0 = ignore)
29
}
30

31
// plane returns the PlaneGrid to use for encoding, converting Grid+Colors if needed.
32
func (e *FrameEncoder) plane() (*yuv.PlaneGrid, error) {
1✔
33
        if e.Plane != nil {
2✔
34
                return e.Plane, nil
1✔
35
        }
1✔
36
        return yuv.GridToPlaneGrid(e.Grid, e.Colors)
1✔
37
}
38

39
// frameDimensions returns the frame width and height in pixels.
40
func (e *FrameEncoder) frameDimensions() (width, height int) {
1✔
41
        if e.Plane != nil {
2✔
42
                width = e.Plane.PixelWidth()
1✔
43
                height = e.Plane.PixelHeight()
1✔
44
        } else {
2✔
45
                width = e.Grid.Width * 16
1✔
46
                height = e.Grid.Height * 16
1✔
47
        }
1✔
48
        if e.Width > 0 {
2✔
49
                width = e.Width
1✔
50
        }
1✔
51
        if e.Height > 0 {
2✔
52
                height = e.Height
1✔
53
        }
1✔
54
        return width, height
1✔
55
}
56

57
// Encode produces an Annex-B bitstream containing SPS, PPS, and one IDR slice.
58
func (e *FrameEncoder) Encode() ([]byte, error) {
1✔
59
        var buf bytes.Buffer
1✔
60
        if err := e.EncodeSPSPPS(&buf); err != nil {
1✔
61
                return nil, err
×
62
        }
×
63
        slice, err := e.EncodeSlice(0)
1✔
64
        if err != nil {
1✔
65
                return nil, err
×
66
        }
×
67
        buf.Write(slice)
1✔
68
        return buf.Bytes(), nil
1✔
69
}
70

71
// EncodeSPSPPS writes SPS and PPS NALUs to buf (profile-aware).
72
func (e *FrameEncoder) EncodeSPSPPS(buf *bytes.Buffer) error {
1✔
73
        width, height := e.frameDimensions()
1✔
74

1✔
75
        level := ChooseLevel(width, height, e.FPS, e.Kbps, e.CABAC)
1✔
76
        if e.CABAC {
2✔
77
                spsRBSP := EncodeSPSMain(width, height, e.MaxNumRefFrames, level, e.ColorSpace, e.Range)
1✔
78
                if err := WriteNALU(buf, 7, 3, spsRBSP); err != nil {
1✔
79
                        return fmt.Errorf("write SPS: %w", err)
×
80
                }
×
81
                ppsRBSP := EncodePPSCABAC(e.DisableDeblock)
1✔
82
                if err := WriteNALU(buf, 8, 3, ppsRBSP); err != nil {
1✔
83
                        return fmt.Errorf("write PPS: %w", err)
×
84
                }
×
85
        } else {
1✔
86
                spsRBSP := EncodeSPS(width, height, e.MaxNumRefFrames, level, e.ColorSpace, e.Range)
1✔
87
                if err := WriteNALU(buf, 7, 3, spsRBSP); err != nil {
1✔
88
                        return fmt.Errorf("write SPS: %w", err)
×
89
                }
×
90
                ppsRBSP := EncodePPS(e.DisableDeblock)
1✔
91
                if err := WriteNALU(buf, 8, 3, ppsRBSP); err != nil {
1✔
92
                        return fmt.Errorf("write PPS: %w", err)
×
93
                }
×
94
        }
95
        return nil
1✔
96
}
97

98
// EncodeSlice encodes a single IDR slice NALU and returns it as Annex-B framed bytes.
99
// idrPicID is the idr_pic_id value (alternating 0/1 for consecutive IDR pictures).
100
func (e *FrameEncoder) EncodeSlice(idrPicID uint32) ([]byte, error) {
1✔
101
        if e.CABAC {
2✔
102
                return e.encodeSliceCABAC(idrPicID)
1✔
103
        }
1✔
104
        return e.encodeSliceCAVLC(idrPicID)
1✔
105
}
106

107
// EncodePSkipSlice encodes a P_Skip slice (all MBs skip, copying from reference).
108
// frameNum is the frame_num value for this slice.
109
// Returns an Annex-B framed non-IDR NALU (type=1, ref_idc=2).
110
func (e *FrameEncoder) EncodePSkipSlice(frameNum uint32) ([]byte, error) {
1✔
111
        width, height := e.frameDimensions()
1✔
112
        sps := &avc.SPS{
1✔
113
                Width:            uint(width),
1✔
114
                Height:           uint(height),
1✔
115
                PicOrderCntType:  0,
1✔
116
                FrameMbsOnlyFlag: true,
1✔
117
        }
1✔
118
        pps := &avc.PPS{
1✔
119
                DeblockingFilterControlPresentFlag: true,
1✔
120
                EntropyCodingModeFlag:              e.CABAC,
1✔
121
                PicInitQpMinus26:                   e.QP - 26,
1✔
122
        }
1✔
123
        return EncodePSkipSlice(sps, pps, frameNum, e.DisableDeblock)
1✔
124
}
1✔
125

126
func (e *FrameEncoder) encodeSliceCAVLC(idrPicID uint32) ([]byte, error) {
1✔
127
        pg, err := e.plane()
1✔
128
        if err != nil {
1✔
NEW
129
                return nil, err
×
NEW
130
        }
×
131
        mbWidth := pg.MBWidth()
1✔
132
        mbHeight := pg.MBHeight()
1✔
133
        width := mbWidth * 16
1✔
134
        height := mbHeight * 16
1✔
135

1✔
136
        qpDelta := int32(e.QP - 26) // pic_init_qp = 26, so delta = QP - 26
1✔
137

1✔
138
        // Build slice (header + MB data) in a single BitWriter to maintain bit alignment
1✔
139
        sliceW := NewBitWriter()
1✔
140
        WriteSliceHeader(sliceW, qpDelta, e.DisableDeblock, idrPicID)
1✔
141

1✔
142
        // Encode macroblocks
1✔
143
        // Track nC (number of non-zero coefficients) per 4x4 block for context
1✔
144
        // nC is computed from neighbors A (left) and B (above)
1✔
145
        nCLuma := make([][]int, mbHeight*4)
1✔
146
        for i := range nCLuma {
2✔
147
                nCLuma[i] = make([]int, mbWidth*4)
1✔
148
        }
1✔
149
        nCCb := make([][]int, mbHeight*2)
1✔
150
        for i := range nCCb {
2✔
151
                nCCb[i] = make([]int, mbWidth*2)
1✔
152
        }
1✔
153
        nCCr := make([][]int, mbHeight*2)
1✔
154
        for i := range nCCr {
2✔
155
                nCCr[i] = make([]int, mbWidth*2)
1✔
156
        }
1✔
157

158
        // Reconstructed pixel values (needed for DC prediction of neighbors)
159
        // Use flat slices with stride to minimize allocations.
160
        strideY := width
1✔
161
        strideC := width / 2
1✔
162
        reconY := make([]uint8, height*strideY)
1✔
163
        reconCb := make([]uint8, (height/2)*strideC)
1✔
164
        reconCr := make([]uint8, (height/2)*strideC)
1✔
165

1✔
166
        for mbY := range mbHeight {
2✔
167
                for mbX := range mbWidth {
2✔
168
                        lumaVals := pg.MBLumaValues(mbX, mbY)
1✔
169
                        cbVals, crVals := pg.MBChromaSub(mbX, mbY)
1✔
170

1✔
171
                        err := e.encodeMBPlane(sliceW, mbX, mbY, lumaVals, cbVals, crVals,
1✔
172
                                nCLuma, nCCb, nCCr, reconY, strideY, reconCb, reconCr, strideC)
1✔
173
                        if err != nil {
1✔
174
                                return nil, fmt.Errorf("encode MB (%d,%d): %w", mbX, mbY, err)
×
175
                        }
×
176
                }
177
        }
178

179
        // RBSP trailing bits
180
        sliceW.WriteBit(1) // rbsp_stop_one_bit
1✔
181
        sliceW.AlignToByte()
1✔
182

1✔
183
        var buf bytes.Buffer
1✔
184
        sliceRBSP := sliceW.Bytes()
1✔
185
        if err := WriteNALU(&buf, 5, 3, sliceRBSP); err != nil {
1✔
UNCOV
186
                return nil, fmt.Errorf("write IDR: %w", err)
×
187
        }
×
188

189
        return buf.Bytes(), nil
1✔
190
}
191

192
// encMBState tracks encoder-side MB state for CABAC context derivation.
193
type encMBState struct {
194
        mbType              int
195
        intraChromaPredMode int
196
        cbpChroma           int
197
        codedBlockFlag      [6][16]uint8
198
}
199

200
func (e *FrameEncoder) encodeSliceCABAC(idrPicID uint32) ([]byte, error) {
1✔
201
        pg, err := e.plane()
1✔
202
        if err != nil {
1✔
NEW
203
                return nil, err
×
NEW
204
        }
×
205
        mbWidth := pg.MBWidth()
1✔
206
        mbHeight := pg.MBHeight()
1✔
207
        width := mbWidth * 16
1✔
208
        height := mbHeight * 16
1✔
209

1✔
210
        // Build slice header with CABAC alignment
1✔
211
        qpDelta := int32(e.QP - 26)
1✔
212
        headerW := NewBitWriter()
1✔
213
        WriteSliceHeaderCABAC(headerW, qpDelta, e.DisableDeblock, idrPicID)
1✔
214
        headerBytes := headerW.Bytes()
1✔
215

1✔
216
        // Initialize CABAC encoder and context models
1✔
217
        enc := cabac.NewEncoder()
1✔
218
        models := context.InitModels(e.QP, 2, 0) // sliceType=2 (I-slice)
1✔
219
        ctx := models[:]
1✔
220

1✔
221
        // MB state for neighbor context derivation
1✔
222
        mbStates := make([]encMBState, mbWidth*mbHeight)
1✔
223

1✔
224
        // Reconstructed pixel values (needed for DC prediction of neighbors)
1✔
225
        // Use flat slices with stride to minimize allocations.
1✔
226
        strideY := width
1✔
227
        strideC := width / 2
1✔
228
        reconY := make([]uint8, height*strideY)
1✔
229
        reconCb := make([]uint8, (height/2)*strideC)
1✔
230
        reconCr := make([]uint8, (height/2)*strideC)
1✔
231

1✔
232
        for mbY := range mbHeight {
2✔
233
                for mbX := range mbWidth {
2✔
234
                        lumaVals := pg.MBLumaValues(mbX, mbY)
1✔
235
                        cbVals, crVals := pg.MBChromaSub(mbX, mbY)
1✔
236

1✔
237
                        mbIdx := mbY*mbWidth + mbX
1✔
238
                        err := e.encodeMBCABACPlane(enc, ctx, mbStates, mbIdx, mbX, mbY,
1✔
239
                                lumaVals, cbVals, crVals,
1✔
240
                                mbWidth, mbHeight, reconY, strideY, reconCb, reconCr, strideC)
1✔
241
                        if err != nil {
1✔
242
                                return nil, fmt.Errorf("encode MB (%d,%d): %w", mbX, mbY, err)
×
243
                        }
×
244

245
                        // end_of_slice: terminate(1) for last MB, terminate(0) otherwise
246
                        isLast := mbY == mbHeight-1 && mbX == mbWidth-1
1✔
247
                        if isLast {
2✔
248
                                enc.EncodeTerminate(1)
1✔
249
                        } else {
2✔
250
                                enc.EncodeTerminate(0)
1✔
251
                        }
1✔
252
                }
253
        }
254

255
        cabacBytes := enc.Flush()
1✔
256

1✔
257
        // Concatenate header + CABAC data as RBSP
1✔
258
        sliceRBSP := make([]byte, 0, len(headerBytes)+len(cabacBytes))
1✔
259
        sliceRBSP = append(sliceRBSP, headerBytes...)
1✔
260
        sliceRBSP = append(sliceRBSP, cabacBytes...)
1✔
261

1✔
262
        var buf bytes.Buffer
1✔
263
        if err := WriteNALU(&buf, 5, 3, sliceRBSP); err != nil {
1✔
UNCOV
264
                return nil, fmt.Errorf("write IDR: %w", err)
×
265
        }
×
266

267
        return buf.Bytes(), nil
1✔
268
}
269

270
// deriveDCCBFCtx derives the coded_block_flag context for Intra16x16DC.
271
// condTermFlagA/B default to 1 when neighbor is not available.
272
func deriveDCCBFCtx(left, top *encMBState) int {
1✔
273
        condA := 1
1✔
274
        condB := 1
1✔
275
        if left != nil {
2✔
276
                condA = int(left.codedBlockFlag[encCtxBlockCatIntra16x16DC][0])
1✔
277
        }
1✔
278
        if top != nil {
2✔
279
                condB = int(top.codedBlockFlag[encCtxBlockCatIntra16x16DC][0])
1✔
280
        }
1✔
281
        return condA + 2*condB
1✔
282
}
283

284
// deriveChromaDCCBFCtx derives the coded_block_flag context for ChromaDC.
285
// iCbCr: 0 for Cb, 1 for Cr.
286
func deriveChromaDCCBFCtx(left, top *encMBState, iCbCr int) int {
1✔
287
        condA := 1
1✔
288
        condB := 1
1✔
289
        if left != nil {
2✔
290
                condA = int(left.codedBlockFlag[encCtxBlockCatChromaDC][iCbCr])
1✔
291
        }
1✔
292
        if top != nil {
2✔
293
                condB = int(top.codedBlockFlag[encCtxBlockCatChromaDC][iCbCr])
1✔
294
        }
1✔
295
        return condA + 2*condB
1✔
296
}
297

298
func computeLumaDCPred(reconY []uint8, strideY, mbX, mbY int) uint8 {
1✔
299
        hasTop := mbY > 0
1✔
300
        hasLeft := mbX > 0
1✔
301

1✔
302
        if !hasTop && !hasLeft {
2✔
303
                return 128
1✔
304
        }
1✔
305

306
        sum := 0
1✔
307
        count := 0
1✔
308

1✔
309
        if hasTop {
2✔
310
                off := (mbY*16-1)*strideY + mbX*16
1✔
311
                for x := range 16 {
2✔
312
                        sum += int(reconY[off+x])
1✔
313
                }
1✔
314
                count += 16
1✔
315
        }
316

317
        if hasLeft {
2✔
318
                off := mbY*16*strideY + mbX*16 - 1
1✔
319
                for y := range 16 {
2✔
320
                        sum += int(reconY[off+y*strideY])
1✔
321
                }
1✔
322
                count += 16
1✔
323
        }
324

325
        if count == 32 {
2✔
326
                return uint8((sum + 16) >> 5)
1✔
327
        }
1✔
328
        return uint8((sum + 8) >> 4)
1✔
329
}
330

331
// lumaPredict16x16 computes the per-pixel I_16x16 prediction array for the given mode.
332
// This matches the decoder's prediction: V mode uses the full top row, H mode uses
333
// the full left column, DC mode uses a uniform value.
334
func lumaPredict16x16(reconY []uint8, strideY, mbX, mbY, mode int) [256]uint8 {
1✔
335
        var pred [256]uint8
1✔
336
        switch mode {
1✔
337
        case 0: // Vertical — each column gets top row pixel
1✔
338
                off := (mbY*16-1)*strideY + mbX*16
1✔
339
                for x := range 16 {
2✔
340
                        topVal := reconY[off+x]
1✔
341
                        for y := range 16 {
2✔
342
                                pred[y*16+x] = topVal
1✔
343
                        }
1✔
344
                }
345
        case 1: // Horizontal — each row gets left column pixel
1✔
346
                for y := range 16 {
2✔
347
                        leftVal := reconY[(mbY*16+y)*strideY+mbX*16-1]
1✔
348
                        for x := range 16 {
2✔
349
                                pred[y*16+x] = leftVal
1✔
350
                        }
1✔
351
                }
352
        case 2: // DC — uniform value
1✔
353
                dcVal := computeLumaDCPred(reconY, strideY, mbX, mbY)
1✔
354
                for i := range pred {
2✔
355
                        pred[i] = dcVal
1✔
356
                }
1✔
357
        }
358
        return pred
1✔
359
}
360

361
// chromaPredict8x8 computes the per-pixel chroma prediction array for the given mode.
362
// This matches the decoder's chroma prediction: DC gives per-sub-block averages,
363
// V copies the top row, H copies the left column.
364
func chromaPredict8x8(recon []uint8, strideC, mbX, mbY, mode int) [64]uint8 {
1✔
365
        var pred [64]uint8
1✔
366
        switch mode {
1✔
367
        case 0: // DC — per-sub-block DC prediction
1✔
368
                dcPreds := computeChromaDCPreds4x4(recon, strideC, mbX, mbY)
1✔
369
                for blk := range 4 {
2✔
370
                        x0 := (blk % 2) * 4
1✔
371
                        y0 := (blk / 2) * 4
1✔
372
                        for y := range 4 {
2✔
373
                                for x := range 4 {
2✔
374
                                        pred[(y0+y)*8+x0+x] = dcPreds[blk]
1✔
375
                                }
1✔
376
                        }
377
                }
378
        case 1: // Horizontal — each row gets left column pixel
1✔
379
                for y := range 8 {
2✔
380
                        leftVal := recon[(mbY*8+y)*strideC+mbX*8-1]
1✔
381
                        for x := range 8 {
2✔
382
                                pred[y*8+x] = leftVal
1✔
383
                        }
1✔
384
                }
385
        case 2: // Vertical — each column gets top row pixel
1✔
386
                off := (mbY*8-1)*strideC + mbX*8
1✔
387
                for x := range 8 {
2✔
388
                        topVal := recon[off+x]
1✔
389
                        for y := range 8 {
2✔
390
                                pred[y*8+x] = topVal
1✔
391
                        }
1✔
392
                }
393
        }
394
        return pred
1✔
395
}
396

397
func absInt(v int) int {
1✔
398
        if v < 0 {
2✔
399
                return -v
1✔
400
        }
1✔
401
        return v
1✔
402
}
403

404
// computeChromaDCPreds4x4 computes per-sub-block chroma DC predictions,
405
// matching the decoder's predictChromaDC8x8 (spec section 8.3.4.1).
406
// Returns [4]uint8 for sub-blocks: TL(0), TR(1), BL(2), BR(3).
407
func computeChromaDCPreds4x4(recon []uint8, strideC, mbX, mbY int) [4]uint8 {
1✔
408
        hasTop := mbY > 0
1✔
409
        hasLeft := mbX > 0
1✔
410

1✔
411
        var top [8]int
1✔
412
        var left [8]int
1✔
413
        if hasTop {
2✔
414
                off := (mbY*8-1)*strideC + mbX*8
1✔
415
                for x := range 8 {
2✔
416
                        top[x] = int(recon[off+x])
1✔
417
                }
1✔
418
        }
419
        if hasLeft {
2✔
420
                off := mbY*8*strideC + mbX*8 - 1
1✔
421
                for y := range 8 {
2✔
422
                        left[y] = int(recon[off+y*strideC])
1✔
423
                }
1✔
424
        }
425

426
        var preds [4]uint8
1✔
427
        for blk := range 4 {
2✔
428
                x0 := (blk % 2) * 4
1✔
429
                y0 := (blk / 2) * 4
1✔
430

1✔
431
                sum := 0
1✔
432
                count := 0
1✔
433

1✔
434
                isTopRow := blk < 2
1✔
435
                isLeftCol := blk%2 == 0
1✔
436

1✔
437
                useTop := false
1✔
438
                useLeft := false
1✔
439

1✔
440
                switch {
1✔
441
                case isTopRow && isLeftCol: // TL: use both if available
1✔
442
                        useTop = hasTop
1✔
443
                        useLeft = hasLeft
1✔
444
                case isTopRow && !isLeftCol: // TR: prefer top, fallback to left
1✔
445
                        if hasTop {
2✔
446
                                useTop = true
1✔
447
                        } else if hasLeft {
3✔
448
                                useLeft = true
1✔
449
                        }
1✔
450
                case !isTopRow && isLeftCol: // BL: prefer left, fallback to top
1✔
451
                        if hasLeft {
2✔
452
                                useLeft = true
1✔
453
                        } else if hasTop {
3✔
454
                                useTop = true
1✔
455
                        }
1✔
456
                default: // BR: use both if available
1✔
457
                        useTop = hasTop
1✔
458
                        useLeft = hasLeft
1✔
459
                }
460

461
                if useTop {
2✔
462
                        for x := x0; x < x0+4; x++ {
2✔
463
                                sum += top[x]
1✔
464
                                count++
1✔
465
                        }
1✔
466
                }
467
                if useLeft {
2✔
468
                        for y := y0; y < y0+4; y++ {
2✔
469
                                sum += left[y]
1✔
470
                                count++
1✔
471
                        }
1✔
472
                }
473

474
                if count > 0 {
2✔
475
                        preds[blk] = uint8((sum + count/2) / count)
1✔
476
                } else {
2✔
477
                        preds[blk] = 128
1✔
478
                }
1✔
479
        }
480
        return preds
1✔
481
}
482

483
func computeNC4x4(nCLuma [][]int, blkX, blkY int) int {
1✔
484
        hasA := blkX > 0
1✔
485
        hasB := blkY > 0
1✔
486

1✔
487
        nA, nB := 0, 0
1✔
488
        if hasA {
2✔
489
                nA = nCLuma[blkY][blkX-1]
1✔
490
        }
1✔
491
        if hasB {
2✔
492
                nB = nCLuma[blkY-1][blkX]
1✔
493
        }
1✔
494

495
        if hasA && hasB {
2✔
496
                return (nA + nB + 1) / 2
1✔
497
        }
1✔
498
        if hasA {
2✔
499
                return nA
1✔
500
        }
1✔
501
        if hasB {
2✔
502
                return nB
1✔
503
        }
1✔
504
        return 0
1✔
505
}
506

507
func clipU8(v int) uint8 {
1✔
508
        if v < 0 {
1✔
509
                return 0
×
510
        }
×
511
        if v > 255 {
1✔
512
                return 255
×
513
        }
×
514
        return uint8(v)
1✔
515
}
516

517
// Inlined inverse transforms to avoid import cycle with transform package
518
func dequantDC4x4(coeffs [16]int32, qp int, weightScaleDC int32) [16]int32 {
1✔
519
        var result [16]int32
1✔
520
        qpPer := qp / 6
1✔
521
        qpRem := qp % 6
1✔
522
        levelScale := levelScale4x4[qpRem][0] * weightScaleDC
1✔
523
        if qpPer >= 6 {
1✔
524
                for i := range 16 {
×
525
                        result[i] = coeffs[i] * levelScale << uint(qpPer-6)
×
526
                }
×
527
        } else {
1✔
528
                for i := range 16 {
2✔
529
                        result[i] = (coeffs[i]*levelScale + (1 << uint(5-qpPer))) >> uint(6-qpPer)
1✔
530
                }
1✔
531
        }
532
        return result
1✔
533
}
534

535
func dequantChromaDC2x2(coeffs [4]int32, qpc int, weightScaleDC int32) [4]int32 {
1✔
536
        var result [4]int32
1✔
537
        qpPer := qpc / 6
1✔
538
        qpRem := qpc % 6
1✔
539
        levelScale := levelScale4x4[qpRem][0] * weightScaleDC
1✔
540
        if qpPer >= 5 {
1✔
541
                for i := range 4 {
×
542
                        result[i] = coeffs[i] * levelScale << uint(qpPer-5)
×
543
                }
×
544
        } else {
1✔
545
                for i := range 4 {
2✔
546
                        result[i] = (coeffs[i] * levelScale) >> uint(5-qpPer)
1✔
547
                }
1✔
548
        }
549
        return result
1✔
550
}
551

552
func inverseHadamard4x4(coeffs [16]int32) [16]int32 {
1✔
553
        var temp [16]int32
1✔
554
        for i := range 4 {
2✔
555
                s0, s1, s2, s3 := coeffs[i*4], coeffs[i*4+1], coeffs[i*4+2], coeffs[i*4+3]
1✔
556
                temp[i*4+0] = s0 + s1 + s2 + s3
1✔
557
                temp[i*4+1] = s0 + s1 - s2 - s3
1✔
558
                temp[i*4+2] = s0 - s1 - s2 + s3
1✔
559
                temp[i*4+3] = s0 - s1 + s2 - s3
1✔
560
        }
1✔
561
        var result [16]int32
1✔
562
        for j := range 4 {
2✔
563
                f0, f1, f2, f3 := temp[j], temp[4+j], temp[8+j], temp[12+j]
1✔
564
                result[j] = f0 + f1 + f2 + f3
1✔
565
                result[4+j] = f0 + f1 - f2 - f3
1✔
566
                result[8+j] = f0 - f1 - f2 + f3
1✔
567
                result[12+j] = f0 - f1 + f2 - f3
1✔
568
        }
1✔
569
        return result
1✔
570
}
571

572
func inverseHadamard2x2(coeffs [4]int32) [4]int32 {
1✔
573
        return [4]int32{
1✔
574
                coeffs[0] + coeffs[1] + coeffs[2] + coeffs[3],
1✔
575
                coeffs[0] - coeffs[1] + coeffs[2] - coeffs[3],
1✔
576
                coeffs[0] + coeffs[1] - coeffs[2] - coeffs[3],
1✔
577
                coeffs[0] - coeffs[1] - coeffs[2] + coeffs[3],
1✔
578
        }
1✔
579
}
1✔
580

581
var levelScale4x4 = [6][3]int32{
582
        {10, 13, 16},
583
        {11, 14, 18},
584
        {13, 16, 20},
585
        {14, 18, 23},
586
        {16, 20, 25},
587
        {18, 23, 29},
588
}
589

590
// inverseRasterX4x4 maps 4x4 block index (0-15) to x position within MB.
591
var inverseRasterX4x4 = [16]int{
592
        0, 4, 0, 4, 8, 12, 8, 12,
593
        0, 4, 0, 4, 8, 12, 8, 12,
594
}
595

596
// inverseRasterY4x4 maps 4x4 block index (0-15) to y position within MB.
597
var inverseRasterY4x4 = [16]int{
598
        0, 0, 4, 4, 0, 0, 4, 4,
599
        8, 8, 12, 12, 8, 8, 12, 12,
600
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc