package imageproc import ( "fmt" "image" "math" "net/url" "strings" ) type AnalysisImage struct { Image image.Image OriginalWidth int OriginalHeight int Region image.Rectangle Strategy string RoomLike bool RoomLikeConfidence string RoomLikeReason string } func PrepareAnalysisImage(src image.Image, source string) AnalysisImage { b := src.Bounds() roomLike, confidence, reason := DetectRoomLike(src, source) region := b strategy := "full_texture" if roomLike { top := b.Min.Y + int(float64(b.Dy())*0.45) left := b.Min.X + int(float64(b.Dx())*0.08) right := b.Max.X - int(float64(b.Dx())*0.08) region = image.Rect(left, top, right, b.Max.Y).Intersect(b) strategy = "room_like_lower_floor_crop" } return AnalysisImage{ Image: Crop(src, region), OriginalWidth: b.Dx(), OriginalHeight: b.Dy(), Region: region, Strategy: strategy, RoomLike: roomLike, RoomLikeConfidence: confidence, RoomLikeReason: reason, } } func DetectRoomLike(src image.Image, source string) (bool, string, string) { b := src.Bounds() w, h := b.Dx(), b.Dy() if w == 0 || h == 0 { return false, "", "" } urlHint, hint := roomSceneHint(source) ratio := float64(w) / float64(h) roomGeometry := false if urlHint { top := image.Rect(b.Min.X, b.Min.Y, b.Max.X, b.Min.Y+h/2) bottom := image.Rect(b.Min.X, b.Min.Y+h/2, b.Max.X, b.Max.Y) topEdge, topVar := regionEdgeAndVariance(src, top) bottomEdge, bottomVar := regionEdgeAndVariance(src, bottom) roomGeometry = (ratio > 1.28 || ratio < 0.78) && bottomEdge > topEdge*1.25 && bottomVar > topVar*1.10 } switch { case urlHint && roomGeometry: return true, "high", fmt.Sprintf("source image path has %q token plus room-like edge split (ratio %.2f)", hint, ratio) case urlHint: return true, "medium", fmt.Sprintf("source image path contains %q room-scene token", hint) default: return false, "", "" } } func roomSceneHint(source string) (bool, string) { candidate := source if parsed, err := url.Parse(source); err == nil && parsed.Path != "" { candidate = parsed.Path } if decoded, err := url.PathUnescape(candidate); err == nil { candidate = decoded } for _, token := range sceneTokens(strings.ToLower(candidate)) { switch token { case "room", "rooms", "lifestyle", "installed", "installation", "gallery", "ambient", "beauty": return true, token } } return false, "" } func sceneTokens(s string) []string { return strings.FieldsFunc(s, func(r rune) bool { return !((r >= 'a' && r <= 'z') || (r >= '0' && r <= '9')) }) } func regionEdgeAndVariance(src image.Image, rect image.Rectangle) (float64, float64) { rect = rect.Intersect(src.Bounds()) if rect.Dx() < 3 || rect.Dy() < 3 { return 0, 0 } step := int(math.Sqrt(float64(rect.Dx()*rect.Dy())/12000.0)) + 1 var n int var sum, sumSq float64 var edges int for y := rect.Min.Y + step; y < rect.Max.Y-step; y += step { for x := rect.Min.X + step; x < rect.Max.X-step; x += step { l := luminanceAt(src, x, y) lx := luminanceAt(src, x+step, y) - luminanceAt(src, x-step, y) ly := luminanceAt(src, x, y+step) - luminanceAt(src, x, y-step) if math.Hypot(lx, ly) > 26 { edges++ } sum += l sumSq += l * l n++ } } if n == 0 { return 0, 0 } mean := sum / float64(n) variance := sumSq/float64(n) - mean*mean return float64(edges) / float64(n), variance } func luminanceAt(src image.Image, x, y int) float64 { r, g, b := rgba8(src.At(x, y)) return 0.2126*float64(r) + 0.7152*float64(g) + 0.0722*float64(b) }