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

Eyevinn / hi264 / 22354891696

24 Feb 2026 02:18PM UTC coverage: 86.968% (-0.6%) from 87.558%
22354891696

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

999 of 1182 new or added lines in 11 files covered. (84.52%)

6 existing lines in 4 files now uncovered.

7154 of 8226 relevant lines covered (86.97%)

1.0 hits per line

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

62.5
/pkg/encode/api.go
1
package encode
2

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

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

9
        "github.com/Eyevinn/hi264/pkg/yuv"
10
)
11

12
// EncodeParams holds H.264 encoding parameters.
13
type EncodeParams struct {
14
        Width          int            // frame width in pixels (must be even)
15
        Height         int            // frame height in pixels (must be even)
16
        QP             int            // quantization parameter (0-51, default 26)
17
        CABAC          bool           // true=Main profile CABAC, false=Baseline CAVLC
18
        DisableDeblock int            // 0=enable, 1=disable
19
        MaxRefFrames   int            // max_num_ref_frames (0=IDR-only, 1+=P-frames)
20
        ColorSpace     yuv.ColorSpace // YCbCr matrix standard (default BT601)
21
        Range          yuv.Range      // sample value range (default LimitedRange)
22
        FPS            int            // frame rate (used for level selection, 0 = ignore MBPS)
23
        Kbps           int            // bitrate in kbit/s (used for level selection, 0 = ignore bitrate)
24
}
25

26
func (p *EncodeParams) validate() error {
1✔
27
        if p.Width <= 0 || p.Width%2 != 0 {
1✔
28
                return fmt.Errorf("width must be positive and even, got %d", p.Width)
×
29
        }
×
30
        if p.Height <= 0 || p.Height%2 != 0 {
1✔
31
                return fmt.Errorf("height must be positive and even, got %d", p.Height)
×
32
        }
×
33
        if p.QP < 0 || p.QP > 51 {
1✔
34
                return fmt.Errorf("QP must be 0-51, got %d", p.QP)
×
35
        }
×
36
        return nil
1✔
37
}
38

39
func (p *EncodeParams) qp() int {
1✔
40
        if p.QP == 0 {
1✔
41
                return 26
×
42
        }
×
43
        return p.QP
1✔
44
}
45

46
// GenerateSPS returns SPS NALU bytes in Annex-B format.
47
func GenerateSPS(p EncodeParams) ([]byte, error) {
1✔
48
        if err := p.validate(); err != nil {
1✔
49
                return nil, err
×
50
        }
×
51
        level := ChooseLevel(p.Width, p.Height, p.FPS, p.Kbps, p.CABAC)
1✔
52
        var rbsp []byte
1✔
53
        if p.CABAC {
2✔
54
                rbsp = EncodeSPSMain(p.Width, p.Height, p.MaxRefFrames, level, p.ColorSpace, p.Range)
1✔
55
        } else {
2✔
56
                rbsp = EncodeSPS(p.Width, p.Height, p.MaxRefFrames, level, p.ColorSpace, p.Range)
1✔
57
        }
1✔
58
        var buf bytes.Buffer
1✔
59
        if err := WriteNALU(&buf, 7, 3, rbsp); err != nil {
1✔
60
                return nil, fmt.Errorf("write SPS: %w", err)
×
61
        }
×
62
        return buf.Bytes(), nil
1✔
63
}
64

65
// GeneratePPS returns PPS NALU bytes in Annex-B format.
66
func GeneratePPS(p EncodeParams) ([]byte, error) {
1✔
67
        if err := p.validate(); err != nil {
1✔
68
                return nil, err
×
69
        }
×
70
        var rbsp []byte
1✔
71
        if p.CABAC {
2✔
72
                rbsp = EncodePPSCABAC(p.DisableDeblock)
1✔
73
        } else {
2✔
74
                rbsp = EncodePPS(p.DisableDeblock)
1✔
75
        }
1✔
76
        var buf bytes.Buffer
1✔
77
        if err := WriteNALU(&buf, 8, 3, rbsp); err != nil {
1✔
78
                return nil, fmt.Errorf("write PPS: %w", err)
×
79
        }
×
80
        return buf.Bytes(), nil
1✔
81
}
82

83
// GenerateIDR encodes a flat-color IDR frame from a Grid/ColorMap.
84
// Returns the IDR slice NALU in Annex-B format (no SPS/PPS).
85
func GenerateIDR(p EncodeParams, grid *yuv.Grid, colors yuv.ColorMap, idrPicID uint32) ([]byte, error) {
1✔
86
        if err := p.validate(); err != nil {
1✔
87
                return nil, err
×
88
        }
×
89
        enc := &FrameEncoder{
1✔
90
                Grid:            grid,
1✔
91
                Colors:          colors,
1✔
92
                QP:              p.qp(),
1✔
93
                DisableDeblock:  p.DisableDeblock,
1✔
94
                CABAC:           p.CABAC,
1✔
95
                MaxNumRefFrames: p.MaxRefFrames,
1✔
96
                Width:           p.Width,
1✔
97
                Height:          p.Height,
1✔
98
                ColorSpace:      p.ColorSpace,
1✔
99
                Range:           p.Range,
1✔
100
        }
1✔
101
        return enc.EncodeSlice(idrPicID)
1✔
102
}
103

104
// GenerateIDRFromPlane encodes an IDR frame from a PlaneGrid.
105
// Returns the IDR slice NALU in Annex-B format (no SPS/PPS).
NEW
106
func GenerateIDRFromPlane(p EncodeParams, plane *yuv.PlaneGrid, idrPicID uint32) ([]byte, error) {
×
NEW
107
        if err := p.validate(); err != nil {
×
NEW
108
                return nil, err
×
NEW
109
        }
×
NEW
110
        enc := &FrameEncoder{
×
NEW
111
                Plane:           plane,
×
NEW
112
                QP:              p.qp(),
×
NEW
113
                DisableDeblock:  p.DisableDeblock,
×
NEW
114
                CABAC:           p.CABAC,
×
NEW
115
                MaxNumRefFrames: p.MaxRefFrames,
×
NEW
116
                Width:           p.Width,
×
NEW
117
                Height:          p.Height,
×
NEW
118
                ColorSpace:      p.ColorSpace,
×
NEW
119
                Range:           p.Range,
×
NEW
120
        }
×
NEW
121
        return enc.EncodeSlice(idrPicID)
×
122
}
123

124
// GeneratePSkip returns a P_Skip slice NALU in Annex-B format.
125
func GeneratePSkip(p EncodeParams, frameNum uint32) ([]byte, error) {
1✔
126
        if err := p.validate(); err != nil {
1✔
127
                return nil, err
×
128
        }
×
129
        sps := &avc.SPS{
1✔
130
                Width:            uint(p.Width),
1✔
131
                Height:           uint(p.Height),
1✔
132
                PicOrderCntType:  0,
1✔
133
                FrameMbsOnlyFlag: true,
1✔
134
        }
1✔
135
        pps := &avc.PPS{
1✔
136
                DeblockingFilterControlPresentFlag: true,
1✔
137
                EntropyCodingModeFlag:              p.CABAC,
1✔
138
                PicInitQpMinus26:                   p.qp() - 26,
1✔
139
        }
1✔
140
        return EncodePSkipSlice(sps, pps, frameNum, p.DisableDeblock)
1✔
141
}
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