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
- func CountNonZeroACCoefficients(coeffs []DCTCoefficient) (totalNonZero, totalAbsH1 int)
- func EffectiveCapacityL(nonZeroAC, absH1 int) int
- func EmbeddingRatio(messageBits, nonZeroAC, absH1 int) float64
- func EstimateSimpleBeta(stegoHist, coverHist DCTHistogram) float64
- func EstimateSimpleBetaCorrected(stegoHist, coverHist DCTHistogram, totalUsable, totalAbsH1 int) float64
- func FoldedCount(hist DCTHistogram, d int) int
- func GetTranslator() translator
- func LiuMatrixK(r float64) int
- func MessageLengthErrorTolerance(length, w int) (lowerExclusiveBits, upperInclusiveBits int)
- func SetTranslator(t translator)
- type BetaEstimationResult
- type BetaEstimator
- type CoefficientExtractor
- type DCTCoefficient
- type DCTHistogram
- type Error
- type HistogramEstimator
- type ImageProcessor
- type JPEGCodec
- type MatrixKCandidate
- type MessageLengthEstimate
- type MessageLengthEstimator
- type PipelineError
- type TableAwareCodec
Constants ¶
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 )
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" )
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
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 ¶
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.