Documentation
¶
Overview ¶
Package unpixel reconstructs text hidden behind pixelation.
UnPixel attempts to recover the original text from a pixelated (mosaic-blurred) image region. The algorithm works by rendering each candidate string into an off-screen image, re-applying the same block-average pixelation with the same grid origin, and measuring the pixel-level distance against the redacted region. A guided depth-first search explores the candidate space character by character, pruning branches whose cumulative score exceeds the configured threshold.
Usage ¶
Obtain or synthesise an image.Image containing the pixelated region, construct an Engine with New, and call Run. Import the defaults package for its side-effect to wire the standard renderer, pixelator, metric, and search strategy; without it the components must be supplied explicitly in Config.
import _ "github.com/oioio-space/unpixel/defaults" // wire default components
eng, err := unpixel.New(img, unpixel.Config{})
if err != nil { ... }
progCh, resCh := eng.Run(ctx)
unpixel.OnProgress(progCh, func(p unpixel.Progress) {
fmt.Println(p.BestGuess)
})
for r := range resCh {
fmt.Println(r.BestGuess, r.BestScore)
}
UnPixel is a faithful Go port of Bishop Fox's unredacter (GPL-3.0).
Example ¶
Example demonstrates the quick-start usage of UnPixel: construct an Engine from a pixelated image, run the search, consume progress events, then collect results. The image here is a small synthetic white rectangle used purely to keep the example self-contained and fast; in practice, supply the actual pixelated screenshot region.
package main
import (
"context"
"fmt"
"image"
"image/color"
"github.com/oioio-space/unpixel"
_ "github.com/oioio-space/unpixel/defaults"
)
func main() {
// Build a tiny synthetic image to stand in for a real pixelated screenshot.
img := image.NewRGBA(image.Rect(0, 0, 64, 16))
for y := range img.Bounds().Dy() {
for x := range img.Bounds().Dx() {
img.SetRGBA(x, y, color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
}
}
eng, err := unpixel.New(img, unpixel.Config{
Charset: "ab ",
MaxLength: 2,
BlockSize: unpixel.DefaultBlockSize,
})
if err != nil {
fmt.Println("new:", err)
return
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
progCh, resCh := eng.Run(ctx)
// Consume progress events concurrently while collecting results below.
// OnProgress blocks until the progress channel is closed (EventDone).
done := make(chan struct{})
go func() {
defer close(done)
unpixel.OnProgress(progCh, func(p unpixel.Progress) {
if p.Kind == unpixel.EventNewBest {
fmt.Println("new best:", p.BestGuess)
}
})
}()
for r := range resCh {
if r.Err != nil {
fmt.Println("result error:", r.Err)
continue
}
fmt.Println("result:", r.BestGuess)
}
<-done
}
Output:
Index ¶
Examples ¶
Constants ¶
const ( // DefaultCharset is the candidate alphabet: lowercase ASCII letters and space. DefaultCharset = "abcdefghijklmnopqrstuvwxyz " // DefaultMaxLength is the maximum candidate string length the search explores. DefaultMaxLength = 20 // DefaultBlockSize is the pixelation block side length in pixels. DefaultBlockSize = 8 // DefaultThreshold is the per-block score gate for non-space characters; // candidates scoring above this value are pruned. DefaultThreshold = 0.25 // DefaultSpaceThreshold is the per-block score gate for the space character, // which is visually ambiguous and requires a more permissive threshold. DefaultSpaceThreshold = 0.5 // DefaultTopN is the default maximum number of ranked candidates retained // per grid offset and exposed on Result.TopN. DefaultTopN = 5 // DefaultBeamWidth is the number of candidates retained per depth level by // BeamStrategy. Larger values improve recall at the cost of more evaluations. DefaultBeamWidth = 16 // DefaultCacheSize is the maximum number of stageImage results held by // CachingScorer. Zero disables caching. DefaultCacheSize = 4096 )
Default configuration values, matching the original unredacter reference implementation.
Variables ¶
var DefaultComponents func(cfg *Config) error
DefaultComponents is a hook that wires the standard Renderer, Pixelator, Metric, and Strategy into cfg for any fields that are still nil. It is populated by importing the defaults package for its side-effect, which avoids an import cycle between the root package and its internal implementations. Applications that supply all component fields explicitly need not set this.
var ErrNilImage = errors.New("redacted image must not be nil")
ErrNilImage is returned by New when a nil image is passed.
Functions ¶
func InferBlockSize ¶ added in v0.2.0
InferBlockSize estimates the pixelation block size, in pixels, of a mosaic- redacted image by detecting the spacing of its block grid. It scans for the columns and rows where the colour changes — the boundaries between constant- colour blocks — and returns the greatest common divisor of the gaps between them, which equals the block side length even when some adjacent blocks happen to share a colour (their gap is then a multiple of the block size).
It returns 0 when the grid cannot be determined — a uniform image, an image smaller than 2×2, or a non-pixelated image whose gaps reduce to 1. New uses it to fill a non-positive Config.BlockSize, falling back to DefaultBlockSize when inference returns 0.
func OnProgress ¶
OnProgress drains the progress channel returned by Engine.Run, calling fn for every event in order. It blocks until the channel is closed (i.e. until EventDone has been delivered and consumed). It is a convenience wrapper around a range loop and does not start a goroutine.
Types ¶
type Config ¶
type Config struct {
// Charset is the ordered set of candidate characters tried at each search
// depth. Defaults to DefaultCharset (a–z plus space).
Charset string
// MaxLength is the maximum number of characters the search will attempt
// before backtracking. Defaults to DefaultMaxLength.
MaxLength int
// BlockSize is the side length in pixels of each pixelation block. It must
// match the block size used to produce the redacted image. When left
// non-positive, New auto-detects it from the image via InferBlockSize,
// falling back to DefaultBlockSize if the grid cannot be determined.
BlockSize int
// Threshold is the per-block score below which a non-space candidate is
// accepted and the search descends deeper. Lower values are stricter.
// Defaults to DefaultThreshold.
Threshold float64
// SpaceThreshold is the acceptance threshold applied specifically to the
// space character, which is harder to distinguish visually. Defaults to
// DefaultSpaceThreshold.
SpaceThreshold float64
// TopN is the maximum number of ranked candidates retained per grid offset
// in Result.TopN. Candidates are sorted ascending by score (lowest first);
// ties are broken by Guess string so results are deterministic. Zero or
// negative values are replaced by DefaultTopN in applyDefaults.
TopN int
// ThresholdFor returns the acceptance threshold for a given candidate
// character. It defaults to a closure that returns SpaceThreshold for ' '
// and Threshold for all other runes. Override to apply per-class thresholds
// without modifying search logic.
ThresholdFor func(rune) float64
// Style controls the font size, weight, and padding used when rendering
// candidates. Zero fields use the design defaults (32 pt, 8 px padding).
Style Style
// Renderer is the component that rasterises candidate strings to RGBA.
// Defaults to the XImage renderer (wired by the defaults package).
Renderer Renderer
// Pixelator is the component that applies block-average pixelation.
// Defaults to BlockAverage (wired by the defaults package).
Pixelator Pixelator
// Metric is the component that measures pixel-level image distance.
// Defaults to Pixelmatch (wired by the defaults package).
Metric Metric
// Strategy is the component that drives the search algorithm.
// Defaults to GuidedDFS (wired by the defaults package).
Strategy Strategy
// BeamWidth is the maximum number of candidates retained per depth level
// when using BeamStrategy. Values <= 0 are replaced by DefaultBeamWidth.
// Has no effect when Strategy is GuidedStrategy.
BeamWidth int
// CacheSize is the maximum number of stageImage results held by
// CachingScorer. Zero disables prefix-render memoization; positive values
// enable an LRU cache of that capacity shared across all Eval calls for a
// single Search invocation. Defaults to DefaultCacheSize.
CacheSize int
// Workers is the maximum number of grid offsets probed and searched
// concurrently. Values <= 0 default to runtime.GOMAXPROCS. Set to 1 to force
// fully sequential execution. Results are merged deterministically regardless
// of Workers, so this affects throughput only, never the output.
Workers int
}
Config holds all tunable parameters for an Engine. Every field is optional: zero values are replaced by the package defaults (see the Default* constants) when New is called. Component fields (Renderer, Pixelator, Metric, Strategy) are filled by importing the defaults package for its side-effect, or can be supplied directly for custom implementations.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine orchestrates the full unpixel pipeline: grid-origin discovery, candidate rendering, re-pixelation, distance measurement, and guided search. Create one with New and call Run to start the search.
func New ¶
New creates an Engine for the given pixelated image. Scalar zero values in cfg are replaced by package defaults; nil component fields are left for Run to fill via DefaultComponents (or must be set explicitly). New returns ErrNilImage if redacted is nil.
func (*Engine) Config ¶ added in v0.2.0
Config returns the engine's resolved configuration, with scalar defaults applied and BlockSize auto-inferred. Component fields (Renderer, Pixelator, Metric, Strategy) are non-nil only after Run has wired them. It is intended for introspection — for example, reading the inferred BlockSize.
func (*Engine) Run ¶
Run starts the search and returns a progress channel and a results channel. The progress channel carries Progress events and is closed after EventDone is delivered. The results channel receives one Result per surviving grid offset and is closed once all offsets have been searched. Cancelling ctx causes both channels to be closed promptly after the in-flight search step completes.
If any component field in cfg is nil, Run invokes DefaultComponents to fill it before starting. If DefaultComponents is also nil, Run panics with a descriptive message directing the caller to import the defaults package.
type Eval ¶
type Eval struct {
// Guess is the candidate string evaluated at this node.
Guess string
// Score is the marginal region diff score for the most recently added
// character (lower is better; compared against the threshold).
Score float64
// TotalScore is the whole-image diff score accumulated so far (used for
// display and ranking, not for pruning).
TotalScore float64
// TooBig is true when the rendered candidate is wider than the redacted
// image; such candidates are pruned regardless of score.
TooBig bool
// GuessImage is a cropped, pixelated rendering of Guess, populated only
// when the caller opts in via the Strategy implementation.
GuessImage *image.RGBA
}
Eval holds the result of evaluating a single candidate string at one search node.
type EventKind ¶
type EventKind int
EventKind identifies the type of a Progress event delivered on the channel returned by Engine.Run.
const ( // EventCandidate is emitted for every candidate string evaluated during // the search. It is high-frequency; the channel is non-blocking and events // are dropped when the buffer is full. EventCandidate EventKind = iota // EventOffsetProbed is emitted after each grid-origin probe during the // offset-discovery phase. It is high-frequency with the same drop semantics // as EventCandidate. EventOffsetProbed // EventNewBest is emitted whenever the search finds a new overall best // guess. Delivery is guaranteed: the sender blocks (with context) until the // consumer reads the event. EventNewBest // EventDone is the terminal event, delivered exactly once after all offsets // have been searched or the context is cancelled. Delivery is guaranteed. EventDone )
type Metric ¶
Metric measures the visual distance between two same-sized RGBA images. Compare returns a value in [0, 1] where 0 means pixel-perfect identical.
type Offset ¶
type Offset struct {
// X is the horizontal grid origin in pixels.
X int
// Y is the vertical grid origin in pixels.
Y int
// Score is the image-distance score for this origin (lower is better).
Score float64
}
Offset represents one candidate grid origin for the pixelation block alignment. The search probes multiple (X, Y) origins and ranks them by Score before committing to the full character-level search.
type Pixelator ¶
Pixelator replaces every blockSize×blockSize region of an image with the mean RGBA colour of that region. The originX and originY parameters align the grid so that it matches the original pixelation applied to the source.
type Progress ¶
type Progress struct {
// Kind identifies which event this Progress value represents.
Kind EventKind
// BestGuess is the candidate string with the lowest score found so far
// across all offsets.
BestGuess string
// BestScore is the total image-distance score of BestGuess (lower is better).
BestScore float64
// Guess is the candidate string that triggered this specific event.
Guess string
// Score is the image-distance score of Guess.
Score float64
// Depth is the current search depth, equal to the character length of Guess.
Depth int
// Offset is the grid origin currently being searched.
Offset Offset
// Evaluated is the cumulative number of candidates evaluated since Run started.
Evaluated int
// OffsetsDone is the number of grid offsets fully searched so far.
OffsetsDone int
// OffsetsTotal is the total number of surviving grid offsets to be searched.
OffsetsTotal int
// PreviewImage is an optional deep copy of the pixelated guess image.
// It is non-nil only when the Strategy implementation populates it.
PreviewImage *image.RGBA
// Elapsed is the wall-clock duration since Engine.Run was called.
Elapsed time.Duration
// Done is true only on the EventDone event.
Done bool
// Err carries any terminal error that caused the search to abort.
Err error
}
Progress carries a snapshot of search state, streamed on the channel returned by Engine.Run. Consumers should switch on Kind to decide which fields are meaningful for a given event.
type Renderer ¶
type Renderer interface {
Render(text string, style Style) (img *image.RGBA, sentinelX int, err error)
}
Renderer renders candidate text to an RGBA image, placing a blue sentinel block immediately to the right of the text. It returns the image and the x-coordinate of that sentinel, which marks the text's right edge in pixels.
type Result ¶
type Result struct {
// BestGuess is the candidate string with the lowest overall score found
// during the search rooted at this offset.
BestGuess string
// BestScore is the total image-distance score of BestGuess (lower is better).
BestScore float64
// Candidates holds every string that passed the threshold gate during the
// search, in discovery order.
Candidates []Eval
// TopN holds the best candidates sorted ascending by score (lowest score
// first), with ties broken by Guess string for determinism. Its length is
// at most Config.TopN. TopN[0] is the same candidate as BestGuess when the
// search produces any result. TopN is nil when no candidates were found.
TopN []Eval
// Confidence is 1 − TopN[0].Score, giving a value in [0, 1] where 1
// represents a pixel-perfect match. It is 0 when TopN is empty.
Confidence float64
// Ambiguity is TopN[1].Score − TopN[0].Score: the score gap between the
// best and second-best candidates. A larger gap means the best guess is
// more clearly distinguished from alternatives. It is 0 when len(TopN) < 2.
Ambiguity float64
// Offset is the grid origin that was searched to produce this result.
Offset Offset
// Err is non-nil if the search for this offset aborted due to an error.
Err error
}
Result is the final output produced by Engine.Run for one surviving grid offset. One Result is sent per offset on the results channel returned by Run.
type Strategy ¶
type Strategy interface {
Search(ctx context.Context, redacted *image.RGBA, cfg Config, out chan<- Progress, results chan<- Result)
}
Strategy runs the full character-by-character search over the candidate space and streams progress and results on the provided channels. Implementations may use different algorithms (guided DFS, beam search, etc.). Search must close neither channel; Engine.Run owns their lifecycle.
type Style ¶
type Style struct {
// FontSize is the font size in points. Defaults to 32.
FontSize float64
// Bold selects the bold font variant when true.
Bold bool
// PaddingTop is the top padding applied before the text, in pixels. Defaults to 8.
PaddingTop int
// PaddingLeft is the left padding applied before the text, in pixels. Defaults to 8.
PaddingLeft int
}
Style describes the text rendering parameters that must match the typography of the original, pre-pixelated screenshot.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
unpixel
command
Command unpixel reconstructs text hidden behind pixelation.
|
Command unpixel reconstructs text hidden behind pixelation. |
|
Package defaults wires the standard internal components into an unpixel.Config.
|
Package defaults wires the standard internal components into an unpixel.Config. |
|
internal
|
|
|
imutil
Package imutil provides image manipulation helpers used by the unpixel pipeline.
|
Package imutil provides image manipulation helpers used by the unpixel pipeline. |
|
metric
Package metric provides image distance metrics for the unpixel pipeline.
|
Package metric provides image distance metrics for the unpixel pipeline. |
|
pixelate
Package pixelate implements the block-average pixelation operation used by the unpixel pipeline.
|
Package pixelate implements the block-average pixelation operation used by the unpixel pipeline. |
|
render
Package render provides the XImage text renderer for the unpixel pipeline.
|
Package render provides the XImage text renderer for the unpixel pipeline. |
|
search
Package search (beam.go) adds BeamDFS, a beam-search variant of GuidedDFS that bounds the branching factor by retaining only the best cfg.BeamWidth candidates per depth level, and BeamStrategy, the unpixel.Strategy that drives it.
|
Package search (beam.go) adds BeamDFS, a beam-search variant of GuidedDFS that bounds the branching factor by retaining only the best cfg.BeamWidth candidates per depth level, and BeamStrategy, the unpixel.Strategy that drives it. |