siyuan-note/siyuan · error

empty HEIF image

Error message

empty HEIF image

What it means

convert rejects a zero-length source byte slice with the inline error "empty HEIF image" before any other validation. It is a fast-fail guard so downstream goheic decoders never see empty input. Not a sentinel — callers must match on the message or check for empty input themselves.

Solutions

  1. Check len(source) == 0 at the call site and handle it as a missing/corrupt asset before invoking HEIF conversion
  2. Fix the upstream reader that produced empty bytes instead of swallowing its error
  3. Treat the empty asset as re-downloadable/re-importable and surface a user-facing message

Example fix

// before
out, err := heif.GetOrCreate(ctx, data, opts)
// after
if len(data) == 0 {
	return fmt.Errorf("asset data is empty, cannot convert HEIF")
}
out, err := heif.GetOrCreate(ctx, data, opts)
Defensive patterns

Strategy: validation

Validate before calling

if len(data) == 0 {
	return fmt.Errorf("empty HEIF asset")
}

Try / catch

if err != nil {
	if strings.Contains(err.Error(), "empty HEIF image") {
		// treat as missing/corrupt asset
	}
	return err
}

Prevention

When it happens

Trigger: convert (via GetOrCreate or ImageSize callers) is invoked with a nil or empty []byte, e.g. a failed asset read that returned no data and a nil error upstream.

Common situations: Asset file read raced with deletion, a truncated download stored as a 0-byte file, passing a slice carved with wrong offsets yielding length 0.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/8cd42c97ba2ead7b. Report an issue: GitHub.

Appendix: source

Thrown at kernel/heif/convert.go:103

func pixelsWithinBudget(workingBudget, outputReserve, hardLimit int) int {
	available := workingBudget - 2*MaxInputBytes - outputReserve
	if available <= 0 {
		return 0
	}
	return min(available/workingBytesPerPixel, hardLimit)
}

type Mode string

const (
	ModePreview   Mode = "preview"
	ModeThumbnail Mode = "thumb"
)

func convert(ctx context.Context, source []byte, mode Mode) ([]byte, error) {
	if len(source) == 0 {
		return nil, errors.New("empty HEIF image")
	}
	if len(source) > MaxInputBytes {
		return nil, ErrInputTooLarge
	}
	if mode != ModePreview && mode != ModeThumbnail {
		return nil, ErrInvalidMode
	}

	select {
	case conversionSlots <- struct{}{}:
		defer func() {
			<-conversionSlots
		}()
	case <-ctx.Done():
		return nil, ctx.Err()
	}
	conversionContext, cancel := context.WithTimeout(ctx, conversionTimeout)
	defer cancel()

View on GitHub (pinned to 9f775e8a12)