Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ because it turns other people's test suites red.
padding, which busybox reads, and the padding file inside the archive takes
the rest, as it did for larger archives. Those archives are not byte for
byte what 0.5.0 wrote. Every other tar.gz is unchanged.
- **A JPEG XL now opens in ffmpeg.** ffmpeg 7.1 never finished one, and
ffmpeg 9 took one below about 3.8 KB for a video and refused it, because of
the padding box behind the picture. The padding now stands in front of the
picture, in a box of the kind ffmpeg passes over, and nothing follows the
picture. Pillow, ImageMagick, Windows, Chrome and libjxl show the same
picture as before. Sizes are unchanged, but no JPEG XL is byte for byte what
0.5.0 wrote.

### Added

Expand Down Expand Up @@ -116,6 +123,14 @@ because it turns other people's test suites red.
manifest says so: a few percent of a small PDF, falling in the index, and
one keeping 10 billion bytes or more, which the index cannot reach. Files
nothing cuts are unchanged.
- **`truncate` cuts the picture of an AVIF or a JPEG XL by the percentage you
set.** A cut used to take only padding, leaving the whole picture. Now
`--damage truncate:missing=50` removes half of the coded picture, as an
interrupted download does. Chrome, Firefox, Windows, libavif and Pillow
refuse the cut AVIF. Half of a JPEG XL still shows the whole picture at
lower detail in Chrome and in libjxl with partial files allowed, because the
format sends a coarse picture first. A cut of 90% shows nothing. An AVIF
nothing cuts is unchanged. Every JPEG XL changes (see Breaking).

- **tfg is on npm, for tests in Node.**
`npm install --save-dev testing-files-generator` gives Playwright, Vitest
Expand Down
18 changes: 14 additions & 4 deletions internal/format/avif/avif.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
// sixty four bit box length that files above four gigabytes need. There is no
// dead zone above the minimum. Pillow reads every one of them and refuses a
// truncated or corrupted file, which is what makes it a witness. ffprobe reads
// the broken ones too, so it is not.
// the broken ones too, so it is not. A file a damage will cut has part of its
// free boxes in front of the media data instead (cut.go).
package avif

import (
Expand Down Expand Up @@ -166,6 +167,9 @@ type memo struct {
// which is what makes every size above the minimum reachable a byte at a
// time.
total int64
// cutAt is where a damage will cut the file, zero when nothing does
// (format.Request.CutAt).
cutAt int64
}

func (generator) Plan(r format.Request) (format.Plan, error) {
Expand All @@ -187,7 +191,7 @@ func (generator) Plan(r format.Request) (format.Plan, error) {
if err := checkFits(r.Bytes, m); err != nil {
return format.Plan{}, err
}
m.total = r.Bytes
m.total, m.cutAt = r.Bytes, r.CutAt

p := format.Plan{
Bytes: r.Bytes,
Expand Down Expand Up @@ -244,7 +248,7 @@ const holdsPerPixel = 8
func checkFits(want int64, m memo) error {
floor := int64(minimumBytes)
reason := core.Says("avif.MinimumReason",
"the smallest picture this format draws codes to %d B at worst, and the file always carries a free box, which costs %d B even when it holds nothing",
"the smallest picture this format draws codes to %d B at worst, and the file always carries a padding box, which costs %d B even when it holds nothing",
core.A("Coded", minimumBytes-boxHeader), core.A("Box", boxHeader))

// A picture named by hand can be larger than the smallest one, and then its
Expand All @@ -254,7 +258,7 @@ func checkFits(want int64, m memo) error {
if own := int64(len(m.coded)) + boxHeader; own > floor {
floor = own
reason = core.Says("avif.MinimumReasonOwn",
"a %dx%d picture at quality %d codes to %d B, and the file always carries a free box, which costs %d B even when it holds nothing",
"a %dx%d picture at quality %d codes to %d B, and the file always carries a padding box, which costs %d B even when it holds nothing",
core.A("Width", m.width), core.A("Height", m.height), core.A("Quality", m.quality), core.A("Coded", len(m.coded)), core.A("Box", boxHeader))
}
}
Expand Down Expand Up @@ -491,6 +495,12 @@ func (generator) Write(ctx context.Context, w io.Writer, p format.Plan) error {
m.width, m.height, len(coded), m.total))
}

if m.cutAt > 0 {
if done, err := writeForACut(ctx, w, m, coded); done || err != nil {
return err
}
}

// The map: the coder's boxes, its media data the content, and the free
// boxes after them that make up the size.
markBoxes(w, coded)
Expand Down
Loading
Loading