// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package ocr import ( "context" "image" "image/color" ) // Preprocess returns owned pixels in recognition coordinates. Scale uses nearest // neighbor sampling so recognized word boxes map back through the capture transform. func Preprocess(ctx context.Context, source image.Image, options Options) (image.Image, error) { if source == nil || !valid_dimensions(source.Bounds().Dx(), source.Bounds().Dy()) { return nil, ErrInvalidImage } scale := options.Scale if scale == 0 { scale = 1 } width, height := source.Bounds().Dx(), source.Bounds().Dy() if scale < 1 || scale > 4 || height > MaxPixels/scale || !valid_dimensions(width*scale, height*scale) || options.Threshold < 0 || options.Threshold > 255 { return nil, ErrLimit } if scale == 1 && !options.Grayscale && !options.Invert && !options.BlackWhite { return source, nil } processed := image.NewRGBA(image.Rect(0, 0, width*scale, height*scale)) for y := 0; y < height*scale; y++ { if y%32 == 0 && ctx.Err() != nil { return nil, ctx.Err() } for x := 0; x < width*scale; x++ { r, g, b, _ := source.At(source.Bounds().Min.X+x/scale, source.Bounds().Min.Y+y/scale).RGBA() red, green, blue := uint8(r>>8), uint8(g>>8), uint8(b>>8) gray := uint8((299*uint32(red) + 587*uint32(green) + 114*uint32(blue) + 500) / 1000) if options.Grayscale || options.BlackWhite { red, green, blue = gray, gray, gray } if options.Invert { red, green, blue = 255-red, 255-green, 255-blue } if options.BlackWhite { value := uint8(0) if int(red) >= options.Threshold { value = 255 } red, green, blue = value, value, value } processed.SetRGBA(x, y, color.RGBA{R: red, G: green, B: blue, A: 255}) } } return processed, ctx.Err() }