Skip to content

Commit

Permalink
Allow download the picture as a PNG image.
Browse files Browse the repository at this point in the history
Signed-off-by: Nahshon Unna-Tsameret <nahsh.ut@gmail.com>
  • Loading branch information
nunnatsa committed Apr 12, 2021
1 parent c27d100 commit 0177a6c
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 0 deletions.
4 changes: 4 additions & 0 deletions controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ func (c *Controller) do() {
default:
log.Printf(`unknown tool "%s"`, data)
}

case webapp.ClientEventDownload:
ch := chan [][]common.Color(data)
ch <- c.state.Canvas.Clone()
}
}

Expand Down
14 changes: 14 additions & 0 deletions state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ import (

type canvas [][]common.Color

func (c canvas) Clone() canvas {
if len(c) == 0 || len(c[0]) == 0 {
return nil
}

newCanvas := make([][]common.Color, len(c))
for y, line := range c {
newCanvas[y] = make([]common.Color, len(line))
copy(newCanvas[y], line)
}

return newCanvas
}

type cursor struct {
X uint8 `json:"x"`
Y uint8 `json:"y"`
Expand Down
24 changes: 24 additions & 0 deletions webapp/index.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
color: white;
background: black;
}

table, td, th {
border: 1px solid white;
}
Expand Down Expand Up @@ -52,6 +53,7 @@
width: 20px;
height: 20px;
}

</style>
</head>
<body>
Expand All @@ -71,6 +73,17 @@
<p>
<input id="pickColorFromPixel" type="button" value="Get current pixel color">
</p>
<form method="get" action="http://{{.Host}}:{{.Port}}/api/canvas/download">
<p>
<label id="pixelSizeRangeLabel" for="pixelSizeRange"></label>
</p>
<p>
<input type="range" name="pixelSize" min="1" max="20" value="3" step="1" id="pixelSizeRange">
</p>
<p>
<input id="download" type="submit" value="Download as Picture">
</p>
</form>
<p>
<input id="reset" type="button" value="Reset">
</p>
Expand Down Expand Up @@ -198,6 +211,11 @@
}
}

const pixelSizeRangeLabel = document.getElementById("pixelSizeRangeLabel")
const pixelSizeRange = document.getElementById("pixelSizeRange")
window.onload = updateRangeLabel
pixelSizeRange.oninput = updateRangeLabel

function setTool(toolName) {
$.post(
"http://{{.Host}}:{{.Port}}/api/canvas/tool",
Expand All @@ -223,6 +241,12 @@

return `rgb(${r}, ${g}, ${b}`
}

function updateRangeLabel() {
pixelSizeRange.style.width = document.getElementById("main").style.width
const plural = pixelSizeRange.value === "1" ? "" : "s"
pixelSizeRangeLabel.innerText = `Download Image Square Size: ${pixelSizeRange.value} pixel${plural}`
}
</script>
</body>
</html>
82 changes: 82 additions & 0 deletions webapp/websocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ package webapp
import (
"encoding/json"
"fmt"
"image"
"image/color"
"image/png"
"log"
"net/http"
"strconv"

"github.com/gorilla/websocket"

Expand All @@ -29,6 +33,8 @@ type ClientEventSetTool string

type ClientEventReset bool

type ClientEventDownload chan [][]common.Color

type WebApplication struct {
mux *http.ServeMux
notifier *notifier.Notifier
Expand All @@ -47,6 +53,7 @@ func NewWebApplication(mailbox *notifier.Notifier, port uint16, ch chan<- Client
mux.HandleFunc("/api/canvas/color", ca.setColor)
mux.HandleFunc("/api/canvas/tool", ca.setTool)
mux.HandleFunc("/api/canvas/reset", ca.reset)
mux.HandleFunc("/api/canvas/download", ca.downloadImage)

return ca
}
Expand Down Expand Up @@ -156,3 +163,78 @@ func (ca WebApplication) reset(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed)
}
}

func (ca WebApplication) downloadImage(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
r.ParseForm()

pixelSizeStr := r.Form.Get("pixelSize")
pixelSize, err := strconv.Atoi(pixelSizeStr)
if err != nil || pixelSize < 1 || pixelSize > 20 {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintln(w, `{"error": "wrong pixel size"}`)
return
}

canvasChannel := make(chan [][]common.Color)
ca.clientEvents <- ClientEventDownload(canvasChannel)
imageData := <-canvasChannel

imageCanvas, err := getImageCanvas(imageData, pixelSize)
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, `{"error": "%v"}`, err)
return
}

w.Header().Add("Content-Disposition", `attachment; filename="untitled.png"`)
w.Header().Set("Content-Type", "image/png")

log.Println("downloading a file; pixel size =", pixelSize)
png.Encode(w, imageCanvas)

} else {
w.WriteHeader(http.StatusMethodNotAllowed)
}
}

func getImageCanvas(imageData [][]common.Color, pixelSize int) (*image.RGBA, error) {
height := len(imageData) * pixelSize
if height == 0 {
return nil, fmt.Errorf("can't get the data")
}

width := len(imageData[0]) * pixelSize
if width == 0 {
return nil, fmt.Errorf("can't get the data")
}

img := image.NewRGBA(image.Rect(0, 0, width, height))
for y, line := range imageData {
for x, pixel := range line {
setPixel(img, x, y, toColor(pixel), pixelSize)
}
}

return img, nil
}

func setPixel(img *image.RGBA, x int, y int, pixel color.Color, pixelSize int) {
x = x * pixelSize
y = y * pixelSize
for x1 := x; x1 < x+pixelSize; x1++ {
for y1 := y; y1 < y+pixelSize; y1++ {
img.Set(x1, y1, pixel)
}
}
}

func toColor(pixel common.Color) color.Color {
r := uint8((pixel >> 16) & 0xFF)
g := uint8((pixel >> 8) & 0xFF)
b := uint8(pixel & 0xFF)

return color.RGBA{A: 0xFF, R: r, G: g, B: b}
}

0 comments on commit 0177a6c

Please sign in to comment.