169 lines
3.2 KiB
Go
169 lines
3.2 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"image"
|
||
|
"image/color"
|
||
|
_ "image/jpeg"
|
||
|
"image/png"
|
||
|
"io/ioutil"
|
||
|
"math"
|
||
|
"os"
|
||
|
|
||
|
"github.com/animenotifier/arn"
|
||
|
)
|
||
|
|
||
|
func main() {
|
||
|
data, err := ioutil.ReadFile("input.jpg")
|
||
|
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
img, format, err := image.Decode(bytes.NewReader(data))
|
||
|
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
fmt.Println(img.Bounds().Dx(), img.Bounds().Dy(), format)
|
||
|
improved := ImproveQuality(img)
|
||
|
f, err := os.Create("output.png")
|
||
|
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
defer f.Close()
|
||
|
png.Encode(f, improved)
|
||
|
}
|
||
|
|
||
|
const max = float64(65535)
|
||
|
|
||
|
// Pixel ...
|
||
|
type Pixel struct {
|
||
|
X int
|
||
|
Y int
|
||
|
Color arn.HSLColor
|
||
|
}
|
||
|
|
||
|
// Area ...
|
||
|
type Area struct {
|
||
|
AverageColor color.Color
|
||
|
Pixels []Pixel
|
||
|
}
|
||
|
|
||
|
// Add ...
|
||
|
func (area *Area) Add(x, y int, hsl arn.HSLColor) {
|
||
|
area.Pixels = append(area.Pixels, Pixel{
|
||
|
X: x,
|
||
|
Y: y,
|
||
|
Color: hsl,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
hueTolerance = 0.1
|
||
|
lightnessTolerance = 0.1
|
||
|
saturationTolerance = 0.1
|
||
|
)
|
||
|
|
||
|
// ImproveQuality returns the average color of an image in HSL format.
|
||
|
func ImproveQuality(img image.Image) *image.NRGBA {
|
||
|
width := img.Bounds().Dx()
|
||
|
height := img.Bounds().Dy()
|
||
|
clone := image.NewNRGBA(image.Rect(0, 0, width, height))
|
||
|
hueAreas := []Area{}
|
||
|
|
||
|
for x := 0; x < width; x++ {
|
||
|
for y := 0; y < height; y++ {
|
||
|
color := img.At(x, y)
|
||
|
rUint, gUint, bUint, _ := color.RGBA()
|
||
|
r := float64(rUint) / max
|
||
|
g := float64(gUint) / max
|
||
|
b := float64(bUint) / max
|
||
|
h, s, l := arn.RGBToHSL(r, g, b)
|
||
|
areaIndex := -1
|
||
|
|
||
|
// Find similar area
|
||
|
for i := 0; i < len(hueAreas); i++ {
|
||
|
area := hueAreas[i]
|
||
|
|
||
|
// Is the pixel close to any pixel in the area we're checking?
|
||
|
for _, pixel := range area.Pixels {
|
||
|
xDist := x - pixel.X
|
||
|
yDist := y - pixel.Y
|
||
|
|
||
|
if xDist < 0 {
|
||
|
xDist = -xDist
|
||
|
}
|
||
|
|
||
|
if yDist < 0 {
|
||
|
yDist = -yDist
|
||
|
}
|
||
|
|
||
|
if xDist <= 1 && yDist <= 1 {
|
||
|
// Is the color similar?
|
||
|
if math.Abs(h-pixel.Color.Hue) <= hueTolerance && math.Abs(s-pixel.Color.Saturation) <= saturationTolerance && math.Abs(l-pixel.Color.Lightness) <= lightnessTolerance {
|
||
|
areaIndex = i
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if areaIndex != -1 {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Insert new area
|
||
|
if areaIndex == -1 {
|
||
|
areaIndex = len(hueAreas)
|
||
|
hueAreas = append(hueAreas, Area{})
|
||
|
}
|
||
|
|
||
|
hueAreas[areaIndex].Add(x, y, arn.HSLColor{
|
||
|
Hue: h,
|
||
|
Saturation: s,
|
||
|
Lightness: l,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fmt.Println(len(hueAreas), "areas")
|
||
|
|
||
|
// Build image from areas
|
||
|
for _, area := range hueAreas {
|
||
|
totalR := uint64(0)
|
||
|
totalG := uint64(0)
|
||
|
totalB := uint64(0)
|
||
|
|
||
|
// Calculate area average color
|
||
|
for _, pixel := range area.Pixels {
|
||
|
col := img.At(pixel.X, pixel.Y)
|
||
|
r, g, b, _ := col.RGBA()
|
||
|
totalR += uint64(r)
|
||
|
totalG += uint64(g)
|
||
|
totalB += uint64(b)
|
||
|
}
|
||
|
|
||
|
averageR := float64(totalR/uint64(len(area.Pixels))) / max
|
||
|
averageG := float64(totalG/uint64(len(area.Pixels))) / max
|
||
|
averageB := float64(totalB/uint64(len(area.Pixels))) / max
|
||
|
|
||
|
area.AverageColor = color.RGBA{
|
||
|
R: uint8(averageR * 255),
|
||
|
G: uint8(averageG * 255),
|
||
|
B: uint8(averageB * 255),
|
||
|
A: 255,
|
||
|
}
|
||
|
|
||
|
for _, pixel := range area.Pixels {
|
||
|
clone.Set(pixel.X, pixel.Y, area.AverageColor)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return clone
|
||
|
}
|