diff --git a/.output-temp/cat.png b/.output-temp/cat.png new file mode 100644 index 0000000..53a3c23 Binary files /dev/null and b/.output-temp/cat.png differ diff --git a/.output-temp/cat.webp b/.output-temp/cat.webp new file mode 100644 index 0000000..23961c4 Binary files /dev/null and b/.output-temp/cat.webp differ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c4dd945 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM golang:1.25.3 AS build-stage + +WORKDIR /app + +COPY go.mod go.sum ./ + +RUN go mod download + +COPY *.go ./ + +RUN CGO_ENABLED=0 GOOS=linux go build -o /app.bin + +from build-stage AS run-tests-stage +RUN go test -v ./ + +FROM gcr.io/distroless/base-debian11 AS build-release-stage + +WORKDIR / + +COPY --from=build-stage /app.bin /app.bin + +EXPOSE ${LISTEN_PORT}/tcp + +USER nonroot:nonroot + +ENTRYPOINT ["/app.bin"] +CMD ["-p", "${LISTEN_PORT}"] \ No newline at end of file diff --git a/cat.png b/cat.png new file mode 100644 index 0000000..53a3c23 Binary files /dev/null and b/cat.png differ diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..0251f18 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,28 @@ +services: + backend: + image: rither:dev + environment: + - LISTEN_PORT=${BACKEND_PORT} + ports: + - "13200:13200" + networks: + - shared + + proxy: + environment: + - BACKEND_PORT=${BACKEND_PORT} + image: nginx + volumes: + - ./nginx/templates/:/etc/nginx/templates/ + ports: + - "8080:80" + - "13201:8080" + restart: always + networks: + - shared + depends_on: + - backend + +networks: + shared: + driver: bridge \ No newline at end of file diff --git a/docker/backend.local.conf b/docker/backend.local.conf new file mode 100644 index 0000000..ca4a052 --- /dev/null +++ b/docker/backend.local.conf @@ -0,0 +1,8 @@ +server { + listen 8080; + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_pass http://localhost:13200; + } +} \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..4932e46 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,20 @@ + + + + + + + + Dither + + + + + +
+ + +
+ + + \ No newline at end of file diff --git a/frontend/index.js b/frontend/index.js new file mode 100644 index 0000000..e69de29 diff --git a/go.mod b/go.mod index dcbb6f8..f6efa02 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.3 require ( github.com/gin-gonic/gin v1.11.0 - github.com/makeworld-the-better-one/dither v1.0.0 + github.com/makeworld-the-better-one/dither/v2 v2.4.0 ) require ( diff --git a/go.sum b/go.sum index 996326e..c045458 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzh github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/makeworld-the-better-one/dither v1.0.0 h1:sBZdGV4o6MG6UMMRJhzDhruwlt99yQe0ChwgL29LMWg= -github.com/makeworld-the-better-one/dither v1.0.0/go.mod h1:iYNC2QRNGWaeJ7G6eiItq30v4ZRPHOb2Od6g7AFYehI= +github.com/makeworld-the-better-one/dither/v2 v2.4.0 h1:Az/dYXiTcwcRSe59Hzw4RI1rSnAZns+1msaCXetrMFE= +github.com/makeworld-the-better-one/dither/v2 v2.4.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= diff --git a/main.go b/main.go index d135144..8638464 100644 --- a/main.go +++ b/main.go @@ -5,17 +5,16 @@ import ( "fmt" "image" "image/color" + "image/jpeg" + "image/png" "net/http" + "os" "strconv" "github.com/gin-gonic/gin" - "github.com/makeworld-the-better-one/dither" + "github.com/makeworld-the-better-one/dither/v2" ) -func ditherImage(src image.Image) { - -} - func hw(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{"message": 123}) } @@ -24,6 +23,113 @@ func heart(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{"emo": "<3"}) } +func ditherImageHandler(ctx *gin.Context) { + fileHeader, fileErr := ctx.FormFile("source") + + if fileErr != nil { + fmt.Printf("Could not fetch form file; %v", fileErr) + ctx.Status(http.StatusBadRequest) + return + } + + sourceFile, sourceFileErr := fileHeader.Open() + + if sourceFileErr != nil { + + fmt.Printf("Could not open source file; %v", sourceFileErr) + ctx.Status(http.StatusBadRequest) + return + } + + defer sourceFile.Close() + + img, formatName, decodeErr := image.Decode(sourceFile) + + fmt.Printf("Format: %v", formatName) + + if decodeErr != nil { + fmt.Printf("Failed to decode image from stream: %v", decodeErr) + ctx.JSON(http.StatusBadRequest, gin.H{"error:": decodeErr.Error()}) + return + } + + palette := []color.Color{color.Black, color.White} + + ditherer := dither.NewDitherer(palette) + ditherer.Matrix = dither.FalseFloydSteinberg + + ditheredImage := ditherer.DitherCopy(img) + + var encodeErr error + + switch formatName { + case "png": + encodeErr = png.Encode(ctx.Writer, ditheredImage) + case "jpeg": + options := jpeg.Options{Quality: 100} + encodeErr = jpeg.Encode(ctx.Writer, ditheredImage, &options) + default: + fmt.Printf("Failed to encode dithered image: %v", decodeErr) + ctx.JSON(http.StatusBadRequest, gin.H{"error:": "format unsupported: " + formatName}) + } + + if encodeErr != nil { + fmt.Printf("Failed to encode dithered image: %v", decodeErr) + ctx.JSON(http.StatusBadRequest, gin.H{"error:": encodeErr.Error()}) + return + } + + if encodeErr != nil { + fmt.Printf("Failed to stream image: %v", encodeErr) + + ctx.JSON(http.StatusBadRequest, gin.H{"error:": encodeErr.Error()}) + return + } + + ctx.Header("Content-Type", "image/png") + ctx.Status(http.StatusCreated) +} + +func ditherCat(ctx *gin.Context) { + picStream, err := os.Open("./.output-temp/cat.png") + + if err != nil { + fmt.Printf("Failed to stream image: %v", err) + + ctx.JSON(http.StatusBadRequest, gin.H{"error:": err.Error()}) + return + } + + defer picStream.Close() + + img, formatName, decodeErr := image.Decode(picStream) + + fmt.Printf("Format: %v", formatName) + + if decodeErr != nil { + fmt.Printf("Failed to decode image from stream: %v", decodeErr) + ctx.JSON(http.StatusBadRequest, gin.H{"error:": decodeErr.Error()}) + return + } + + palette := []color.Color{color.Black, color.White} + + ditherer := dither.NewDitherer(palette) + ditherer.Matrix = dither.FalseFloydSteinberg + + ditheredImage := ditherer.DitherCopy(img) + + encodeErr := png.Encode(ctx.Writer, ditheredImage) + + if encodeErr != nil { + fmt.Printf("Failed to encode dithered image: %v", decodeErr) + ctx.JSON(http.StatusBadRequest, gin.H{"error:": encodeErr.Error()}) + return + } + + ctx.Header("Content-Type", "image/png") +} + func main() { port := flag.Int("p", 8080, "port to listen on") @@ -31,13 +137,10 @@ func main() { router := gin.Default() - palette := []color.Color{color.Black, color.White} - - ditherer := dither.NewDitherer(palette) - ditherer.Matrix = dither.FalseFloydSteinberg - router.GET("/", hw) router.GET("/heart", heart) + router.GET("/cat", ditherCat) + router.POST("/dither", ditherImageHandler) //router.POST("dither",ditherImage()) diff --git a/nginx/sites-available/backend.local.conf b/nginx/sites-available/backend.local.conf new file mode 100644 index 0000000..ca4a052 --- /dev/null +++ b/nginx/sites-available/backend.local.conf @@ -0,0 +1,8 @@ +server { + listen 8080; + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_pass http://localhost:13200; + } +} \ No newline at end of file diff --git a/nginx/sites-enabled/backend.local.conf b/nginx/sites-enabled/backend.local.conf new file mode 120000 index 0000000..4684b38 --- /dev/null +++ b/nginx/sites-enabled/backend.local.conf @@ -0,0 +1 @@ +/etc/nginx/sites-available/backend.local.conf \ No newline at end of file diff --git a/nginx/templates/default_cfg.conf.template b/nginx/templates/default_cfg.conf.template new file mode 100644 index 0000000..7f97488 --- /dev/null +++ b/nginx/templates/default_cfg.conf.template @@ -0,0 +1,13 @@ +server { + listen 8080; + + location / { + proxy_pass http://backend:${BACKEND_PORT}; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_set_header Testing-Header "$remote_addr $host"; + } +} diff --git a/Dockerfile.multistage b/sxdgdxfgdsfg similarity index 100% rename from Dockerfile.multistage rename to sxdgdxfgdsfg