estimator

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 30, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package estimator implements the core estimation algorithms for F5 steganalysis.

This package provides three estimators used in the Fridrich et al. statistical attack:

  • Beta estimation: Calculates the modification rate (beta) of DCT coefficients using the least-squares formula from the Fridrich paper (Equation 3). Beta represents the fraction of coefficients modified by F5 steganography.

  • Histogram estimation: Implements the crop-and-recompress pipeline to estimate what the original cover-image DCT histograms would look like. The pipeline decodes, crops edges, applies blur, and recompresses with the same quality.

  • Message length estimation: Calculates the estimated hidden message size from the modification rate, matrix embedding efficiency, and shrinkage probability.

The estimator package is self-contained and does NOT import the root steganalysis package or the codec package. Instead, it defines its own local types (DCTHistogram, DCTCoefficient, BetaEstimationResult, etc.) and local interfaces for codec dependencies (JPEGCodec, ImageProcessor, CoefficientExtractor). Concrete codec implementations are injected at construction time from the root facade.

The root steganalysis package provides wrapper types that convert between root types and estimator-local types, similar to the pattern used for the codec package.

Reference: Fridrich, J., Goljan, M., & Hogea, D. (2002). "Steganalysis of JPEG Images: Breaking the F5 Algorithm." 5th Information Hiding Workshop, LNCS 2578, 310-323. See docs/Breaking-F5.pdf.

Index

Constants

View Source
const (
	// DefaultBetaThreshold is the default threshold for classifying images as clean.
	DefaultBetaThreshold = 0.125

	// DefaultCropPixels is the number of pixels to crop from each edge.
	DefaultCropPixels = 4

	// DefaultBlurEpsilon is the blur strength for the 3x3 low-pass filter.
	DefaultBlurEpsilon = 0.01

	// CoeffRangeMin is the minimum DCT coefficient value analyzed in histograms.
	CoeffRangeMin = -5

	// CoeffRangeMax is the maximum DCT coefficient value analyzed in histograms.
	CoeffRangeMax = 5

	// NumLowFrequencyModes is the number of DCT frequency modes used in analysis.
	NumLowFrequencyModes = 3

	// FrequencyBandMode12 is the frequency band index for the Fridrich paper's
	// mode (1,2): the first horizontal AC coefficient. Paper uses 1-indexed
	// (k,l) where (1,1)=DC, so (1,2) is the 0-indexed array position (0,1).
	// FrequencyBand = row*8 + col = 0*8 + 1 = 1.
	FrequencyBandMode12 = 1

	// FrequencyBandMode21 is the frequency band index for the Fridrich paper's
	// mode (2,1): the first vertical AC coefficient. 0-indexed position (1,0).
	// FrequencyBand = 1*8 + 0 = 8.
	FrequencyBandMode21 = 8

	// FrequencyBandMode22 is the frequency band index for the Fridrich paper's
	// mode (2,2): the first diagonal AC coefficient. 0-indexed position (1,1).
	// FrequencyBand = 1*8 + 1 = 9.
	FrequencyBandMode22 = 9
)
View Source
const (
	// ErrCodeEmptyHistograms is the error code returned when histogram input
	// contains no data, typically because the image has no DCT coefficients
	// in the analyzed range or coefficient extraction failed.
	ErrCodeEmptyHistograms = "empty_histograms"

	// ErrCodeMatrixKOutOfRange is the error code returned when the matrix
	// embedding parameter k is outside the valid range of 1 to 9, as defined
	// by the F5 specification.
	ErrCodeMatrixKOutOfRange = "matrix_k_out_of_range"
)
View Source
const (

	// HeaderBits is the size of the F5 message header (k + file size).
	//
	// Fridrich/Goljan/Hogea 2002 §2 says the embed side stream-cipher-XORs
	// the k parameter and the message length and emits them at the
	// beginning of the stream. The 32-bit width is NOT in either paper —
	// it's the F5 reference implementation's layout: bits 24-31 hold k
	// (8 bits), bits 0-22 hold the message length in bytes (23 bits,
	// so up to ~8 MB), bit 23 is unused. f5messageembed and the upstream
	// F5.jar / PixelKnot port all use this layout.
	//
	// CRITICAL: the header is embedded WITHOUT matrix encoding — it's
	// laid down as 32 raw LSB bits, one per usable (non-DC, non-zero)
	// coefficient in the permuted walk. See f5messageembed/header.go for
	// the embed-side implementation and f5messageextract/extractor.go
	// extractHeader for the receiver-side. The matrix-encoded payload
	// starts AFTER the 32 header coefficient walks complete.
	//
	// Implication for message-length estimation: Fridrich's
	//     M = W(k) · β · (P − h(1))
	// counts bits "embedded under matrix encoding" — the 32 raw header
	// bits are NOT included in M. So M is already the matrix-encoded
	// payload, not payload + header. BUT the Fridrich β estimator can't
	// tell header modifications apart from payload modifications — it
	// sees one global modification rate over the whole stream. The
	// header contributes ~32 modifications (plus shrinkage retries), all
	// of which β counts. That slightly inflates the M̂ formula's payload
	// estimate by roughly W(k)·HeaderBits bits.
	//
	// f5SelectOptimalK uses messageBits + HeaderBits when checking
	// capacity because the capacity must accommodate both the raw header
	// AND the matrix-encoded payload — 32 raw bits cost 32 coefficient
	// slots regardless of k.
	//
	// PayloadBits = max(0, MessageBits − HeaderBits) approximates the
	// user-visible plaintext size. The exact correction for the β-estimator
	// inflation caused by the 32-bit raw-LSB header would be W(k)·HeaderBits,
	// not HeaderBits alone — so for k≥2 the reported payload is slightly
	// larger than the true value (by ~(W(k)−1)·32 bits). This is acceptable
	// because the absolute error is small compared to the estimator's
	// intrinsic 5–10% noise floor. Do NOT describe this as a conservative
	// under-estimate; it is a slight over-estimate that simplifies the formula.
	HeaderBits = 32
)

Variables

This section is empty.

Functions

func CountNonZeroACCoefficients

func CountNonZeroACCoefficients(coeffs []DCTCoefficient) (totalNonZero, totalAbsH1 int)

CountNonZeroACCoefficients counts all non-zero AC coefficients across ALL JPEG components (Y, Cb, Cr) in raw coefficient data. This provides the global P (total non-zero) and global h(1) (total |value|=1) counts needed for message length estimation.

Why all channels (not just luminance)

The Fridrich/Goljan/Hogea (2002) paper formulated its experiments on grayscale images, so the paper's text reads as if AC counts come from a single-channel histogram. But the REAL-world F5 implementations (F5.jar, the Android PixelKnot port) embed across all three channels — the embedder walks every non-zero AC coefficient regardless of component. Liu/Yang/Wang/Shi (2020) Eq. (1) likewise defines L over h_DCT, the total DCT count for the image, not per-channel.

Pre-2026 versions of this function only counted Y. That under-counted P by ~3× on typical 4:2:0 color JPEGs and made Liu Eq. (8) k-inference incorrectly diverge from F5's actual k choice on color stego artifacts. The fix here aligns the global capacity count with what F5 actually used at embed time. Per-mode histograms used by Eq. (3) β estimation remain luminance-only (BuildModeHistogram, BuildHistograms) — those follow the paper's literal formulation and don't share this concern.

func EffectiveCapacityL

func EffectiveCapacityL(nonZeroAC, absH1 int) int

EffectiveCapacityL implements Liu et al. Eq. (1) — the F5-acknowledged embedding capacity in coefficient slots, accounting for shrinkage loss at |c|=1 coefficients:

L = P − 0.51·h(1)

where P is the count of non-zero AC coefficients and h(1) is the count of AC coefficients with |value| = 1. This is the same capacity F5 uses on the embed side to decide which k value will fit a given message.

Use this in place of plain P whenever you compute embedding ratio r = l/L or feed capacity into F5-style k selection. Plain P over-counts capacity because it ignores that ~half of |c|=1 coefs will shrink to 0 during embed.

Returns 0 if either input is non-positive.

func EmbeddingRatio

func EmbeddingRatio(messageBits, nonZeroAC, absH1 int) float64

EmbeddingRatio computes Liu's r = l/L given a message-bit estimate and non-zero AC / h(1) counts. Useful for callers that want to inspect r directly to decide whether to trust an inference (Liu shows r → 1 breaks the underlying β estimator regardless of which k-rule is used).

func EstimateSimpleBeta

func EstimateSimpleBeta(stegoHist, coverHist DCTHistogram) float64

EstimateSimpleBeta returns the H(0)-only β estimate for one DCT mode. This is the dominant term in Fridrich Eq. 3 and is typically a tiny bit MORE accurate than the full LSQ on natural JPEGs (the v₁ term injects noise more than it removes it on real cover-histogram estimates).

Returns 0 when h(1) is 0 (the mode has no |c|=1 coefs to use as reference; the simple formula degenerates).

The result is unclamped — values outside [0, 1] indicate the cover histogram estimate has diverged from reality (small image, very high Q, or double JPEG compression). Apply clampBeta if you need the clamped form.

func EstimateSimpleBetaCorrected

func EstimateSimpleBetaCorrected(stegoHist, coverHist DCTHistogram, totalUsable, totalAbsH1 int) float64

EstimateSimpleBetaCorrected returns the H(0)-only β estimate adjusted for shrinkage via Fridrich §3.3's m = n·(1-P_S) factor where P_S = h(1)/P.

The plain β estimator counts every coefficient modification as a "successful embed", but matrix-encoded F5 with shrinkage actually produces n total modifications of which only n·(1-P_S) carry message bits — the rest are shrinkage retries. Multiplying by (1-P_S) brings the β estimate into agreement with the "useful modifications" interpretation the message-length formula already uses downstream.

On our boop test embed (truth β = 0.000495, P=440768 usable, h(1)=67770):

plain Eq.3:                +100% over truth
simple H(0) only:           +70% over truth
simple H(0) corrected:      +63% over truth   ← this function

totalUsable is the global P (sum of |c|≥1 across all AC modes, all channels). totalAbsH1 is the global h(1). Both are produced by CountNonZeroACCoefficients alongside the histograms.

When totalUsable is 0 the correction degenerates and the function returns the uncorrected simple β (the model can't say what fraction is shrinkage if there are no usable coefs).

func FoldedCount

func FoldedCount(hist DCTHistogram, d int) int

FoldedCount returns the absolute-value folded histogram count for distance d. For d == 0, it returns counts[0] (the zero-coefficient count). For d > 0, it returns counts[d] + counts[-d], folding positive and negative coefficients at the same distance from zero.

func GetTranslator

func GetTranslator() translator

GetTranslator returns the current translator, or nil if not set.

func LiuMatrixK

func LiuMatrixK(r float64) int

LiuMatrixK implements Liu et al. Eq. (8): given an estimated embedding ratio r = l/L (message bits over Liu capacity), return the matrix-encoding parameter w that F5's optimal-k rule would have selected at embed time.

The rule: F5 picks the largest w whose matrix-rate w/(2^w − 1) is still ≥ r. As r grows toward 1, w shrinks toward 1 (no matrix encoding); as r shrinks toward 0, w grows toward maxK.

Returns 0 when r is outside [0, 1] or when no w in [1, maxK] satisfies the rate inequality (which corresponds to "the message can't fit at any k" — a saturation signal the caller should propagate).

func MessageLengthErrorTolerance

func MessageLengthErrorTolerance(length, w int) (lowerExclusiveBits, upperInclusiveBits int)

MessageLengthErrorTolerance returns the message-length range that maps to a given matrix-encoding parameter w via Liu's Eq. (8) k-determinator.

Note on the paper's Eq. (8) typo

Liu et al. (2020) state Eq. (8) as

(w−1)/(2^(w−1) − 1) < r ≤ w/(2^w − 1)

but this is impossible for any w ≥ 2 because the left side exceeds the right (rate(w) is monotonically DECREASING in w, so rate(w-1) > rate(w)). The intended form, given that F5 picks the largest w whose matrix-encoding rate w/(2^w − 1) is still ≥ r, is

(w+1)/(2^(w+1) − 1) < r ≤ w/(2^w − 1)

i.e. rate(w+1) < r ≤ rate(w). Translating from r-bounds back to bit bounds via r = msgBits / L gives the tolerance band returned here. We use the corrected form. (If a future paper version switches to "smallest w" semantics the formula here would flip; the constants in any pinned validation suite would need updating accordingly.)

Returns (lowerExclusiveBits, upperInclusiveBits] — the exclusive lower bound is rate(w+1)·L (the boundary at which F5 would switch to w+1), and the inclusive upper bound is rate(w)·L (the boundary above which F5 would switch to w-1). If w is out of range or L is non-positive the function returns (0, 0).

Callers can use this to gate auto-k decisions: if a candidate's predicted message length sits well inside its tolerance band, the inference is robust to small β-estimation error; if it sits near a boundary, a tiny β shift would flip the inferred k and the result should be flagged low-confidence.

func SetTranslator

func SetTranslator(t translator)

SetTranslator sets the translator for this package. Pass nil to disable translations and use default English messages. The root steganalysis package calls this to propagate its translator.

Types

type BetaEstimationResult

type BetaEstimationResult struct {
	PerModeBetas map[string]float64
	// PerModeRawBetas mirrors PerModeBetas but holds the unclamped per-mode
	// values. Useful for diagnosing which DCT mode is driving saturation.
	PerModeRawBetas map[string]float64
	// Beta is the average across valid per-mode estimates clamped to [0, 1].
	// This is the value used by IsClean / detection-threshold logic and the
	// message-length formula. Backwards-compatible field.
	Beta float64
	// RawBeta is the unclamped average across per-mode estimates BEFORE the
	// [0,1] clamp. Values outside [0, 1] indicate the cover-image histogram
	// estimate has diverged from reality — typically caused by small images,
	// very high quality factors (Q > 91), or double JPEG compression. When
	// RawBeta differs substantially from Beta, the message-size estimate is
	// unreliable; treat Saturated as a "low confidence" signal regardless of
	// what the Beta or Confidence fields say.
	RawBeta    float64
	Confidence float64
	MinBeta    float64
	BetaCV     float64
	// Saturated reports whether the raw estimator output was outside [0,1]
	// before clamping. True is a strong signal that the cover-image
	// histogram reconstruction failed and downstream estimates derived from
	// Beta should be treated as unreliable.
	Saturated bool
	IsClean   bool
}

BetaEstimationResult contains the results of beta (modification rate) estimation, including per-mode breakdown and variance statistics. This is a local copy of the root type to avoid circular imports between the root and estimator packages.

type BetaEstimator

type BetaEstimator interface {
	// EstimateBeta calculates beta by analyzing multiple DCT frequency modes.
	EstimateBeta(stegoHistograms, coverHistograms []DCTHistogram) (*BetaEstimationResult, error)

	// EstimateBetaForMode implements the Fridrich least-squares formula for a single DCT mode.
	EstimateBetaForMode(stegoHist, coverHist DCTHistogram) (beta, confidence float64, err error)
}

BetaEstimator estimates the modification rate (beta) in a suspected stego-image.

The modification rate beta represents the fraction of DCT coefficients that have been modified by the F5 steganographic algorithm. A clean image has beta near 0, while an image with hidden data has beta > 0.

Implementations must be safe for concurrent use.

func NewBetaEstimator

func NewBetaEstimator() BetaEstimator

NewBetaEstimator creates a new beta estimator with the default threshold. The default threshold of 0.125 is derived from the Fridrich paper for a 10^-8 false positive rate.

func NewBetaEstimatorWithThreshold

func NewBetaEstimatorWithThreshold(threshold float64) BetaEstimator

NewBetaEstimatorWithThreshold creates a beta estimator with a custom threshold.

type CoefficientExtractor

type CoefficientExtractor interface {
	Extract(data []byte) ([]DCTCoefficient, error)
}

CoefficientExtractor extracts DCT coefficients from JPEG data.

type DCTCoefficient

type DCTCoefficient struct {
	Row       int
	Col       int
	Component int
	Value     int16
}

DCTCoefficient represents a single DCT coefficient with its position information in the 8x8 DCT block. This is a local copy of the root type to avoid circular imports between the root and estimator packages.

type DCTHistogram

type DCTHistogram struct {
	Counts        map[int]int
	Component     int
	FrequencyBand int
}

DCTHistogram represents a histogram of DCT coefficient values for a specific frequency band and color component. This is a local copy of the root type to avoid circular imports between the root and estimator packages.

func BuildHistograms

func BuildHistograms(coeffs []DCTCoefficient, _, _ int) []DCTHistogram

BuildHistograms creates per-frequency-mode DCT histograms from coefficient data. Returns three histograms for the Fridrich paper's low-frequency modes (1,2), (2,1), (2,2). The paper uses 1-indexed (k,l) notation where (1,1) is the DC coefficient, so these correspond to 0-indexed array positions (0,1), (1,0), (1,1) — the three lowest AC frequencies in the 8x8 DCT block.

func BuildModeHistogram

func BuildModeHistogram(coeffs []DCTCoefficient, row, col int) DCTHistogram

BuildModeHistogram creates a DCT histogram for a specific frequency mode (row, col).

type Error

type Error struct {
	Code    string
	Message string
}

Error represents an error from the estimator package.

func ErrEmptyHistograms

func ErrEmptyHistograms() *Error

ErrEmptyHistograms returns an error for empty histogram input.

func ErrMatrixKOutOfRange

func ErrMatrixKOutOfRange() *Error

ErrMatrixKOutOfRange returns an error for matrix k parameter out of range.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface.

type HistogramEstimator

type HistogramEstimator interface {
	// EstimateCoverHistogram implements the complete crop-and-recompress pipeline.
	//
	// The returned coverTotalP and coverTotalAbsH1 are the global non-zero
	// AC counts (P and h(1)) used by the message-length formula. They are
	// computed from the full uncropped stego image rather than the cropped
	// recompressed cover, because the crop area loss biases the message
	// length low by ~1.5–8% depending on image size. P_stego differs from
	// the true P_cover only by β·h_cover(1) which is centred and smaller.
	//nolint:gocritic // tooManyResultsChecker: 6 returns required by algorithm interface contract
	EstimateCoverHistogram(stegoImage []byte) (
		stegoHistograms []DCTHistogram,
		coverHistograms []DCTHistogram,
		quality int,
		coverTotalP int,
		coverTotalAbsH1 int,
		err error,
	)
}

HistogramEstimator estimates the cover-image DCT histogram from a suspected stego-image.

Implementations must be safe for concurrent use.

func NewHistogramEstimator

func NewHistogramEstimator(
	codec JPEGCodec,
	imgProc ImageProcessor,
	coeffExt CoefficientExtractor,
) HistogramEstimator

NewHistogramEstimator creates a new histogram estimator with the required dependencies using the standard Fridrich parameters (4-pixel crop, ε=0.01 blur).

The parameters accept any types that satisfy the local codec interfaces. In practice, the root facade passes codec wrapper instances that implement these interfaces implicitly.

func NewHistogramEstimatorWithCrop

func NewHistogramEstimatorWithCrop(
	codec JPEGCodec,
	imgProc ImageProcessor,
	coeffExt CoefficientExtractor,
	cropPixels int,
	blurEpsilon float64,
) HistogramEstimator

NewHistogramEstimatorWithCrop creates a histogram estimator with configurable crop size and blur strength. Use cropPixels=0 to preserve DCT block alignment (no edge crop) and blurEpsilon=0 to skip blurring entirely.

The standard Fridrich pipeline uses cropPixels=DefaultCropPixels (4) and blurEpsilon=DefaultBlurEpsilon (0.01). Passing crop=0, blur=0 is useful for retry-without-crop strategies on saturated-β images where the 4-pixel shift inflates h(1).

type ImageProcessor

type ImageProcessor interface {
	ConvertToYCbCr(rgbPixels []byte, width, height int) ([]byte, error)
	ConvertToRGB(ycbcrPixels []byte, width, height int) ([]byte, error)
	CropImage(pixels []byte, width, height, channels, cropSize int) ([]byte, int, int, error)
	ApplyBlur(pixels []byte, width, height, channels int, epsilon float64) ([]byte, error)
}

ImageProcessor provides image manipulation operations.

type JPEGCodec

type JPEGCodec interface {
	Decode(data []byte) (pixels []byte, width, height int, err error)
	Encode(pixels []byte, width, height, quality int) (data []byte, err error)
	QualityFactor() int
	IsBaseline() bool
}

JPEGCodec provides JPEG encoding, decoding, and quality detection.

type MatrixKCandidate

type MatrixKCandidate struct {
	K                    int
	MessageBits          int
	MessageBytes         int
	PayloadBits          int
	PayloadBytes         int
	ModifiedCoefficients int
	SelfConsistent       bool
	FitsCapacity         bool
}

MatrixKCandidate is a per-k message-size hypothesis. The estimator emits one candidate per k in [1, 8] when running in auto-k mode, ranking them by SelfConsistent (Fridrich §3.3: F5's own optimal-k formula picks this k for the implied message size, so the embed-side and the analysis side agree).

Fields:

  • K: matrix encoding parameter under test
  • MessageBits/Bytes: predicted total matrix-encoded size at this k (matches Fridrich's M — includes the 32-bit F5 header)
  • PayloadBits/Bytes: user-visible plaintext size (MessageBits − 32)
  • ModifiedCoefficients: predicted total coefficient modifications
  • SelfConsistent: F5.SelectOptimalK(usable, MessageBits) returns this K
  • FitsCapacity: cover image actually has enough usable coefficients for the predicted message at this k

func EstimateOptimalK

func EstimateOptimalK(beta float64, nonZeroAC, absH1 int) (bestK int, candidates []MatrixKCandidate)

EstimateOptimalK derives the most plausible matrix-encoding parameter k for an F5 embed given β, the count of non-zero AC coefficients P, and the count of |c|=1 coefficients h(1).

Internally it computes Liu's effective capacity L = P − 0.51·h(1) and uses BOTH determinators in parallel as a cross-check:

  • F5's iterative SelectOptimalK (matches the embed side bit-for-bit when fed the same capacity input)
  • Liu's Eq. (8) closed form (cleaner derivation from r = l/L)

Both rules should produce identical answers on valid inputs; if they disagree the returned candidate flags PickerAgreement = false so the caller can investigate.

Each candidate carries:

  • K, MessageBits, MessageBytes: per-k message-size prediction
  • ModifiedCoefficients: predicted total mods n = β·P
  • SelfConsistent: F5/Liu k-rule picks this k for the predicted size
  • FitsCapacity: Liu's L is large enough at this k

bestK is the largest self-consistent k, or 0 if no candidate is self-consistent (typical when β has saturated). Callers that want a best-effort answer in saturation use the highest FitsCapacity candidate; callers that want truth use bestK > 0.

type MessageLengthEstimate

type MessageLengthEstimate struct {
	// Candidates holds per-k message-size estimates produced when the
	// caller didn't pin a specific k. Each entry mirrors the headline
	// fields above for one candidate k value, plus a self-consistency
	// flag indicating whether F5's own optimal-k selection algorithm
	// would pick that k for the predicted message size. The slice is
	// nil when the caller forced a specific k via WithMatrixK.
	Candidates                []MatrixKCandidate
	MessageBits               int
	MessageBytes              int
	PayloadBits               int
	PayloadBytes              int
	UsableCoefficients        int
	ModifiedCoefficients      int
	MatrixEmbeddingK          int
	MatrixEmbeddingEfficiency float64
	ShrinkageProbability      float64
	ModificationRate          float64
}

MessageLengthEstimate contains the estimated hidden message size derived from the modification rate, matrix embedding efficiency, and shrinkage probability. This is a local copy of the root type to avoid circular imports between the root and estimator packages.

MessageBits/Bytes vs PayloadBits/Bytes

MessageBits/Bytes is M from Fridrich §3.3 — the estimated number of matrix- encoded bits. The F5 32-bit header (Westfeld 2001 §3) is embedded as raw LSB bits BEFORE matrix encoding and is NOT included in M. However, because the β estimator cannot distinguish header modifications from payload modifications, M̂ is slightly inflated by the header's contribution. PayloadBits/Bytes applies a conservative correction (subtracts HeaderBits) to give an approximate user-visible plaintext length. When comparing against a known input file size, use PayloadBytes; when comparing against the paper's M formula directly, use MessageBytes.

type MessageLengthEstimator

type MessageLengthEstimator interface {
	// EstimateMessageLength calculates the estimated hidden message size.
	EstimateMessageLength(
		beta float64,
		stegoHistograms, coverHistograms []DCTHistogram,
		matrixK int,
		coverTotalP int,
		coverTotalAbsH1 int,
	) (*MessageLengthEstimate, error)

	// CalculateMatrixEfficiency returns the matrix embedding efficiency W(k).
	CalculateMatrixEfficiency(k int) (float64, error)
}

MessageLengthEstimator estimates the hidden message length from beta and histograms.

Implementations must be safe for concurrent use.

func NewMessageLengthEstimator

func NewMessageLengthEstimator() MessageLengthEstimator

NewMessageLengthEstimator creates a new message length estimator.

type PipelineError

type PipelineError struct {
	Cause error
	Step  string
}

PipelineError represents an error from a specific step in the estimation pipeline.

func (*PipelineError) Error

func (e *PipelineError) Error() string

Error implements the error interface.

func (*PipelineError) Unwrap

func (e *PipelineError) Unwrap() error

Unwrap returns the underlying cause.

type TableAwareCodec

type TableAwareCodec interface {
	// QuantizationTables returns the luminance and chrominance quantization
	// tables (each 64 entries, zigzag order) of the last decoded image. Either
	// may be nil. Must be called after Decode.
	QuantizationTables() (luma, chroma []int)

	// EncodeWithTables compresses pixels using the supplied zigzag-order
	// quantization tables; a nil table falls back to the standard table scaled
	// by fallbackQuality for that channel.
	EncodeWithTables(
		pixels []byte, width, height, fallbackQuality int,
		lumaTable, chromaTable []int,
	) (data []byte, err error)
}

TableAwareCodec is an OPTIONAL capability a JPEGCodec may implement to expose the last-decoded image's actual quantization tables and to re-compress using explicit tables.

Fridrich/Goljan/Hogea (2002) §3.2 requires the cover-histogram estimate to be recompressed with the SAME quantization table as the stego image. The default path recompresses with a standard table scaled by a SCALAR estimated quality, which (a) is off-by-one for several quality factors and (b) never reproduces a non-standard source table — injecting a phantom β floor and a high false-positive rate on clean re-encoded covers. When the injected codec also implements TableAwareCodec, EstimateCoverHistogram reuses the stego's real tables instead, eliminating that bias.

Codecs that do not implement this interface fall back to the scalar-quality Encode path unchanged.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL