2023-05-14 20:50:22 -06:00

296 lines
6.5 KiB
Go

package main
import (
"fmt"
"log"
"math"
"net/http"
"os"
"strconv"
"image"
"image/color"
"image/draw"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"github.com/mattn/go-ciede2000"
"github.com/go-chi/chi/v5"
"github.com/muesli/smartcrop"
"github.com/muesli/smartcrop/nfnt"
"github.com/nfnt/resize"
)
// Define a function to handle incoming requests
func requestHandler(w http.ResponseWriter, r *http.Request) {
// Get the 'name' variable from the request
name := chi.URLParam(r, "name")
// Use a default value if 'name' is not present
if name == "" {
name = "World"
}
// Respond with a greeting message
fmt.Fprintf(w, "Hello, %s!\n", name)
}
type EInkColor uint8
const (
EPD_7IN3F_BLACK EInkColor = 0x0
EPD_7IN3F_WHITE EInkColor = 0x1
EPD_7IN3F_GREEN EInkColor = 0x2
EPD_7IN3F_BLUE EInkColor = 0x3
EPD_7IN3F_RED EInkColor = 0x4
EPD_7IN3F_YELLOW EInkColor = 0x5
EPD_7IN3F_ORANGE EInkColor = 0x6
)
type ColorDefinition struct {
Color Colorf
Code EInkColor
}
var Colors = []ColorDefinition{
{Colorf{0, 0, 0}, EPD_7IN3F_BLACK},
{Colorf{1, 1, 1}, EPD_7IN3F_WHITE},
{Colorf{0.059, 0.329, 0.119}, EPD_7IN3F_GREEN},
{Colorf{0.061, 0.147, 0.336}, EPD_7IN3F_BLUE},
{Colorf{0.574, 0.066, 0.010}, EPD_7IN3F_RED},
{Colorf{0.982, 0.756, 0.004}, EPD_7IN3F_YELLOW},
{Colorf{0.795, 0.255, 0.018}, EPD_7IN3F_ORANGE},
}
func imageToRGBA(src image.Image) *image.RGBA {
// No conversion needed if image is an *image.RGBA.
if dst, ok := src.(*image.RGBA); ok {
return dst
}
// Use the image/draw package to convert to *image.RGBA.
b := src.Bounds()
dst := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
draw.Draw(dst, dst.Bounds(), src, b.Min, draw.Src)
return dst
}
// Colorf represents a color with float64 RGB values.
type Colorf struct {
R, G, B float64
}
// Convert an image to a 2D array of Colorf.
func convertImageToColors(src image.Image) [][]Colorf {
img := imageToRGBA(src)
bounds := img.Bounds()
width, height := bounds.Max.X, bounds.Max.Y
// Initialize the 2D slice.
colorfImg := make([][]Colorf, height)
for i := range colorfImg {
colorfImg[i] = make([]Colorf, width)
}
// Iterate over each pixel.
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
r, g, b, _ := img.At(x, y).RGBA()
// Convert the uint32 color values to float64, normalized to [0, 1].
colorf := Colorf{
R: float64(r) / 0xffff,
G: float64(g) / 0xffff,
B: float64(b) / 0xffff,
}
colorfImg[y][x] = colorf
}
}
return colorfImg
}
func convertToRGBA(colorf Colorf) color.RGBA {
// Convert the float64 color values to uint8, and ignore the alpha channel.
return color.RGBA{
R: uint8(colorf.R * 255),
G: uint8(colorf.G * 255),
B: uint8(colorf.B * 255),
A: 255,
}
}
func subtractColorf(c1, c2 Colorf) Colorf {
return Colorf{
R: c1.R - c2.R,
G: c1.G - c2.G,
B: c1.B - c2.B,
}
}
func addColorf(c1, c2 Colorf) Colorf {
return Colorf{
R: c1.R + c2.R,
G: c1.G + c2.G,
B: c1.B + c2.B,
}
}
func multColor(c Colorf, m float64) Colorf {
return Colorf{
R: c.R * m,
G: c.G * m,
B: c.B * m,
}
}
func clampColor(value Colorf, min, max float64) Colorf {
return Colorf{
R: clamp(value.R, min, max),
G: clamp(value.G, min, max),
B: clamp(value.B, min, max),
}
}
func clamp(value, min, max float64) float64 {
if value < min {
return min
}
if value > max {
return max
}
return value
}
func ditherFloydSteinberg(image [][]Colorf, x int, y int, diff Colorf) {
height := len(image)
width := len(image[0])
if x < 0 || y < 0 || x >= width || y >= height {
return
}
image[y][x] = clampColor(addColorf(image[y][x], diff), 0, 1)
}
func ConvertToEInkImage(src image.Image) []byte {
bounds := src.Bounds()
width, height := bounds.Max.X, bounds.Max.Y
image := convertImageToColors(src)
var einkImage []byte
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
// find closest color
minDist := math.MaxFloat64
bestColorCode := EInkColor(0)
bestColor := Colorf{0, 0, 0}
for _, def := range Colors {
dist := ciede2000.Diff(convertToRGBA(image[y][x]), convertToRGBA(def.Color))
if dist < minDist {
minDist = dist
bestColorCode = def.Code
bestColor = def.Color
}
}
einkImage = append(einkImage, byte(bestColorCode))
// dither colors using Floyd-Steinberg to create the illusion of shading
diff := subtractColorf(image[y][x], bestColor)
ditherFloydSteinberg(image, x+1, y, multColor(diff, 7.0/16.0))
ditherFloydSteinberg(image, x-1, y+1, multColor(diff, 3.0/16.0))
ditherFloydSteinberg(image, x, y+1, multColor(diff, 5.0/16.0))
ditherFloydSteinberg(image, x+1, y+1, multColor(diff, 1.0/16.0))
}
}
einkImage = packBytesIntoNibbles(einkImage)
return einkImage
}
func packBytesIntoNibbles(input []byte) []byte {
// input must divisible by 2
var output []byte
for x := 0; x < len(input); x += 2 {
output = append(output, (input[x]<<4)|input[x+1])
}
return output
}
func getImage(w http.ResponseWriter, r *http.Request) {
// Open the file
// file, err := os.Open("image.jpg")
// file, err := os.Open("image-out.jpg")
file, err := os.Open("dahlia-out.jpg")
if err != nil {
fmt.Println("Error: File could not be opened")
os.Exit(1)
}
defer file.Close()
img, _, _ := image.Decode(file)
analyzer := smartcrop.NewAnalyzer(nfnt.NewDefaultResizer())
topCrop, _ := analyzer.FindBestCrop(img, 800, 480)
fmt.Printf("Top crop: %+v\n", topCrop)
type SubImager interface {
SubImage(r image.Rectangle) image.Image
}
croppedImg := img.(SubImager).SubImage(topCrop)
resizedImg := resize.Resize(800, 480, croppedImg, resize.Lanczos3)
data := ConvertToEInkImage(resizedImg)
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
fmt.Printf("Bytes to send: %+v\n", len(data))
w.Write(data)
}
func basicAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != "username" || password != "password" {
http.Error(w, "Unauthorized.", 401)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
fmt.Println("Hello, Nix!")
// Create a new Chi router
router := chi.NewRouter()
// Register the requestHandler function to handle requests at the root path
// and a path with the 'name' parameter
router.Get("/", requestHandler)
router.Get("/{name}", requestHandler)
router.Group(func(r chi.Router) {
// r.Use(basicAuth)
r.Get("/getImage", getImage)
})
// Start the HTTP server on port 8080 and log any errors
log.Fatal(http.ListenAndServe("0.0.0.0:8080", router))
}