Skip to content

Commit

Permalink
Colors are indices now
Browse files Browse the repository at this point in the history
Technically this makes the actual colors a client concern. However, it
also allows the client to have a different color palette an easy way.

The main reason for doing this however, was reducing payload size, in
preparation for the drawing gallery, to reduce memory usage when saving
drawings and potentially persisting them.

We might also change this to a binary format, as it'll be much smaller,
given the amount of JSON overhead.

A minimum line event will currently have a size of 70b, while in binary,
it would only have 11 bytes, if we don't microoptimise (less is
possible).
  • Loading branch information
Bios-Marcel committed Nov 17, 2024
1 parent f688fb5 commit 20055f2
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 254 deletions.
17 changes: 6 additions & 11 deletions internal/api/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,15 +356,10 @@ func (handler *V1Handler) getStats(writer http.ResponseWriter, _ *http.Request)
}
}

var (
// CanvasColor is the initially / empty canvas colors value used for
// Lobbydata objects.
CanvasColor = game.RGBColor{R: 255, G: 255, B: 255}
// SuggestedBrushSizes is suggested brush sizes value used for
// Lobbydata objects. A unit test makes sure these values are ordered
// and within the specified bounds.
SuggestedBrushSizes = [4]uint8{8, 16, 24, 32}
)
// SuggestedBrushSizes is suggested brush sizes value used for
// Lobbydata objects. A unit test makes sure these values are ordered
// and within the specified bounds.
var SuggestedBrushSizes = [4]uint8{8, 16, 24, 32}

// LobbyData is the data necessary for correctly configuring a lobby.
// While unofficial clients will probably need all of these values, the
Expand All @@ -385,7 +380,7 @@ type LobbyData struct {
// MaxBrushSize is the maximum amount of pixels the brush can draw in.
MaxBrushSize int `json:"maxBrushSize"`
// CanvasColor is the initially (empty) color of the canvas.
CanvasColor game.RGBColor `json:"canvasColor"`
CanvasColor uint8 `json:"canvasColor"`
// SuggestedBrushSizes are suggestions for the different brush sizes
// that the user can choose between. These brushes are guaranteed to
// be ordered from low to high and stay with the bounds.
Expand All @@ -403,7 +398,7 @@ func CreateLobbyData(lobby *game.Lobby) *LobbyData {
DrawingBoardBaseHeight: game.DrawingBoardBaseHeight,
MinBrushSize: game.MinBrushSize,
MaxBrushSize: game.MaxBrushSize,
CanvasColor: CanvasColor,
CanvasColor: 0, /* White */
SuggestedBrushSizes: SuggestedBrushSizes,
}
}
Expand Down
4 changes: 2 additions & 2 deletions internal/api/v1_easyjson.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

125 changes: 83 additions & 42 deletions internal/frontend/templates/lobby.html
Original file line number Diff line number Diff line change
Expand Up @@ -264,59 +264,59 @@
to prevent hover and active colors to appear. -->
<div class="color-button-row">
<button class="color-button" style="background-color: #ffffff !Important"
onmousedown="setColor('#ffffff')" onclick="setColor('#ffffff')"></button>
onmousedown="setColor(0)" onclick="setColor(0)"></button>
<button class="color-button" style="background-color: #c1c1c1 !Important"
onmousedown="setColor('#c1c1c1')" onclick="setColor('#c1c1c1')"></button>
onmousedown="setColor(1)" onclick="setColor(1)"></button>
<button class="color-button" style="background-color: #ef130b !Important"
onmousedown="setColor('#ef130b')" onclick="setColor('#ef130b')"></button>
onmousedown="setColor(2)" onclick="setColor(2)"></button>
<button class="color-button" style="background-color: #ff7100 !Important"
onmousedown="setColor('#ff7100')" onclick="setColor('#ff7100')"></button>
onmousedown="setColor(3)" onclick="setColor(3)"></button>
<button class="color-button" style="background-color: #ffe400 !Important"
onmousedown="setColor('#ffe400')" onclick="setColor('#ffe400')"></button>
onmousedown="setColor(4)" onclick="setColor(4)"></button>
<button class="color-button" style="background-color: #00cc00 !Important"
onmousedown="setColor('#00cc00')" onclick="setColor('#00cc00')"></button>
onmousedown="setColor(5)" onclick="setColor(5)"></button>
<button class="color-button" style="background-color: #00b2ff !Important"
onmousedown="setColor('#00b2ff')" onclick="setColor('#00b2ff')"></button>
onmousedown="setColor(6)" onclick="setColor(6)"></button>
<button class="color-button" style="background-color: #231fd3 !Important"
onmousedown="setColor('#231fd3')" onclick="setColor('#231fd3')"></button>
onmousedown="setColor(7)" onclick="setColor(7)"></button>
<button class="color-button" style="background-color: #a300ba !Important"
onmousedown="setColor('#a300ba')" onclick="setColor('#a300ba')"></button>
onmousedown="setColor(8)" onclick="setColor(8)"></button>
<button class="color-button" style="background-color: #d37caa !Important"
onmousedown="setColor('#d37caa')" onclick="setColor('#d37caa')"></button>
onmousedown="setColor(9)" onclick="setColor(9)"></button>
<button class="color-button" style="background-color: #a0522d !Important"
onmousedown="setColor('#a0522d')" onclick="setColor('#a0522d')"></button>
onmousedown="setColor(10)" onclick="setColor(10)"></button>
<button class="color-button" style="background-color: #592f2a !Important"
onmousedown="setColor('#592f2a')" onclick="setColor('#592f2a')"></button>
onmousedown="setColor(11)" onclick="setColor(11)"></button>
<button class="color-button" style="background-color: #ecbcb4 !Important"
onmousedown="setColor('#ecbcb4')" onclick="setColor('#ecbcb4')"></button>
onmousedown="setColor(12)" onclick="setColor(12)"></button>
</div>
<div class="color-button-row">
<button class="color-button" style="background-color: #000000 !Important"
onmousedown="setColor('#000000')" onclick="setColor('#000000')"></button>
onmousedown="setColor(13)" onclick="setColor(13)"></button>
<button class="color-button" style="background-color: #4c4c4c !Important"
onmousedown="setColor('#4c4c4c')" onclick="setColor('#4c4c4c')"></button>
onmousedown="setColor(14)" onclick="setColor(14)"></button>
<button class="color-button" style="background-color: #740b07 !Important"
onmousedown="setColor('#740b07')" onclick="setColor('#740b07')"></button>
onmousedown="setColor(15)" onclick="setColor(15)"></button>
<button class="color-button" style="background-color: #c23800 !Important"
onmousedown="setColor('#c23800')" onclick="setColor('#c23800')"></button>
onmousedown="setColor(16)" onclick="setColor(16)"></button>
<button class="color-button" style="background-color: #e8a200 !Important"
onmousedown="setColor('#e8a200')" onclick="setColor('#e8a200')"></button>
onmousedown="setColor(17)" onclick="setColor(17)"></button>
<button class="color-button" style="background-color: #005510 !Important"
onmousedown="setColor('#005510')" onclick="setColor('#005510')"></button>
onmousedown="setColor(18)" onclick="setColor(18)"></button>
<button class="color-button" style="background-color: #00569e !Important"
onmousedown="setColor('#00569e')" onclick="setColor('#00569e')"></button>
onmousedown="setColor(19)" onclick="setColor(19)"></button>
<button class="color-button" style="background-color: #0e0865 !Important"
onmousedown="setColor('#0e0865')" onclick="setColor('#0e0865')"></button>
onmousedown="setColor(20)" onclick="setColor(20)"></button>
<button class="color-button" style="background-color: #550069 !Important"
onmousedown="setColor('#550069')" onclick="setColor('#550069')"></button>
onmousedown="setColor(21)" onclick="setColor(21)"></button>
<button class="color-button" style="background-color: #a75574 !Important"
onmousedown="setColor('#a75574')" onclick="setColor('#a75574')"></button>
onmousedown="setColor(22)" onclick="setColor(22)"></button>
<button class="color-button" style="background-color: #63300d !Important"
onmousedown="setColor('#63300d')" onclick="setColor('#63300d')"></button>
onmousedown="setColor(23)" onclick="setColor(23)"></button>
<button class="color-button" style="background-color: #492f31 !Important"
onmousedown="setColor('#492f31')" onclick="setColor('#492f31')"></button>
onmousedown="setColor(24)" onclick="setColor(24)"></button>
<button class="color-button" style="background-color: #d1a3a4 !Important"
onmousedown="setColor('#d1a3a4')" onclick="setColor('#d1a3a4')"></button>
onmousedown="setColor(25)" onclick="setColor(25)"></button>
</div>
</div>
<!--The following buttons als override onmousedown and onmouseup to make
Expand Down Expand Up @@ -555,6 +555,35 @@
let sound = localStorage.getItem("sound") !== "false";
updateSoundIcon();

const colorMap = [
{hex: '#ffffff', rgb: hexStringToRgbColorObject('#ffffff')},
{hex: '#c1c1c1', rgb: hexStringToRgbColorObject('#c1c1c1')},
{hex: '#ef130b', rgb: hexStringToRgbColorObject('#ef130b')},
{hex: '#ff7100', rgb: hexStringToRgbColorObject('#ff7100')},
{hex: '#ffe400', rgb: hexStringToRgbColorObject('#ffe400')},
{hex: '#00cc00', rgb: hexStringToRgbColorObject('#00cc00')},
{hex: '#00b2ff', rgb: hexStringToRgbColorObject('#00b2ff')},
{hex: '#231fd3', rgb: hexStringToRgbColorObject('#231fd3')},
{hex: '#a300ba', rgb: hexStringToRgbColorObject('#a300ba')},
{hex: '#d37caa', rgb: hexStringToRgbColorObject('#d37caa')},
{hex: '#a0522d', rgb: hexStringToRgbColorObject('#a0522d')},
{hex: '#592f2a', rgb: hexStringToRgbColorObject('#592f2a')},
{hex: '#ecbcb4', rgb: hexStringToRgbColorObject('#ecbcb4')},
{hex: '#000000', rgb: hexStringToRgbColorObject('#000000')},
{hex: '#4c4c4c', rgb: hexStringToRgbColorObject('#4c4c4c')},
{hex: '#740b07', rgb: hexStringToRgbColorObject('#740b07')},
{hex: '#c23800', rgb: hexStringToRgbColorObject('#c23800')},
{hex: '#e8a200', rgb: hexStringToRgbColorObject('#e8a200')},
{hex: '#005510', rgb: hexStringToRgbColorObject('#005510')},
{hex: '#00569e', rgb: hexStringToRgbColorObject('#00569e')},
{hex: '#0e0865', rgb: hexStringToRgbColorObject('#0e0865')},
{hex: '#550069', rgb: hexStringToRgbColorObject('#550069')},
{hex: '#a75574', rgb: hexStringToRgbColorObject('#a75574')},
{hex: '#63300d', rgb: hexStringToRgbColorObject('#63300d')},
{hex: '#492f31', rgb: hexStringToRgbColorObject('#492f31')},
{hex: '#d1a3a4', rgb: hexStringToRgbColorObject('#d1a3a4')}
];

const set_dummy_word_hints = () => {
// Dummy wordhint to prevent layout changes.
applyWordHints([{
Expand Down Expand Up @@ -881,13 +910,18 @@
chooseToolNoUpdate(rubber);
}

let localColor;
setColorNoUpdate(sessionStorage.getItem("local_color") ?? "#000000");
let localColor, localColorIndex;

updateDrawingStateUI();
function indexToHexColor(index) {
return colorMap[index].hex;
}

function setColor(colorAsHex) {
setColorNoUpdate(colorAsHex);
function indexToRgbColor(index) {
return colorMap[index].rgb;
}

function setColor(index) {
setColorNoUpdate(index);

// If we select a new color, we assume we don't want to use the
// rubber anymore and automatically switch to the pen.
Expand All @@ -902,11 +936,15 @@
}
}

function setColorNoUpdate(colorAsHex) {
localColor = hexStringToRgbColorObject(colorAsHex);
sessionStorage.setItem("local_color", colorAsHex);
function setColorNoUpdate(index) {
localColorIndex = index;
localColor = indexToRgbColor(index);
sessionStorage.setItem("local_color", JSON.stringify(index));
}

setColorNoUpdate(JSON.parse(sessionStorage.getItem("local_color")) ?? 13 /* black*/);
updateDrawingStateUI();

function setLineWidth(value) {
setLineWidthNoUpdate(value);
updateDrawingStateUI();
Expand Down Expand Up @@ -1242,14 +1280,15 @@
appendMessage("non-guessing-player-message", parsed.data.author, parsed.data.content);
} else if (parsed.type === "line") {
drawLine(context, parsed.data.x, parsed.data.y,
parsed.data.x2, parsed.data.y2, parsed.data.color,
parsed.data.x2, parsed.data.y2,
indexToRgbColor(parsed.data.color),
parsed.data.width);
} else if (parsed.type === "fill") {
if (floodfillUint8ClampedArray(
imageData.data,
parsed.data.x,
parsed.data.y,
parsed.data.color,
indexToRgbColor(parsed.data.color),
imageData.width,
imageData.height)) {
context.putImageData(imageData, 0, 0);
Expand Down Expand Up @@ -1771,13 +1810,13 @@
floodfillUint8ClampedArray(
imageData.data,
drawData.x, drawData.y,
drawData.color,
indexToRgbColor(drawData.color),
imageData.width, imageData.height);
} else if (drawElement.type === "line") {
_drawLine(
drawData.x, drawData.y,
drawData.x2, drawData.y2,
drawData.color,
indexToRgbColor(drawData.color),
drawData.width);
} else {
console.log("Unknown draw element type: " + drawData.type);
Expand Down Expand Up @@ -1874,7 +1913,7 @@
//clicked and 0 won't be the uninitialized state.
if (allowDrawing && event.button === 0) {
if (localTool === fillBucket) {
fillAndSendEvent(context, event.offsetX, event.offsetY, localColor);
fillAndSendEvent(context, event.offsetX, event.offsetY, localColorIndex);
} else {
drawLineAndSendEvent(context, event.offsetX, event.offsetY, event.offsetX, event.offsetY);
}
Expand Down Expand Up @@ -1973,17 +2012,18 @@
// Clear initially, as it will be black otherwise.
clear(context);

function fillAndSendEvent(context, x, y, color) {
function fillAndSendEvent(context, x, y, colorIndex) {
const xScaled = convertToServerCoordinate(x);
const yScaled = convertToServerCoordinate(y);
const color = indexToRgbColor(colorIndex);
if (floodfillUint8ClampedArray(imageData.data, xScaled, yScaled, color, imageData.width, imageData.height)) {
context.putImageData(imageData, 0, 0);
const fillInstruction = {
type: "fill",
data: {
x: xScaled,
y: yScaled,
color: color
color: colorIndex,
},
};
socket.send(JSON.stringify(fillInstruction));
Expand All @@ -1992,6 +2032,7 @@

function drawLineAndSendEvent(context, x1, y1, x2, y2) {
const color = localTool === rubber ? rubberColor : localColor;
const colorIndex = localTool === rubber ? 0 /* white */ : localColorIndex;

const x1Scaled = convertToServerCoordinate(x1);
const y1Scaled = convertToServerCoordinate(y1);
Expand All @@ -2007,7 +2048,7 @@
y: y1Scaled,
x2: x2Scaled,
y2: y2Scaled,
color: color,
color: colorIndex,
width: localLineWidth,
}
};
Expand Down
34 changes: 18 additions & 16 deletions internal/game/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,30 +94,32 @@ type WordHint struct {
Underline bool `json:"underline"`
}

// RGBColor represents a 24-bit color consisting of red, green and blue.
type RGBColor struct {
R uint8 `json:"r"`
G uint8 `json:"g"`
B uint8 `json:"b"`
}

type LineEvent struct {
Type string `json:"type"`
// Data contains the coordinates, stroke width and color. The coors here
// aren't uint16, as it allows us to easily allow implementing drawing on
// the client, where the user drags the line over the canvas border.
// If we were to not accept out of bounds values, the lines would be chopped
// off before reaching the canvas border.
Data struct {
X int16 `json:"x"`
Y int16 `json:"y"`
X2 int16 `json:"x2"`
Y2 int16 `json:"y2"`
Color RGBColor `json:"color"`
Width uint8 `json:"width"`
X int16 `json:"x"`
Y int16 `json:"y"`
X2 int16 `json:"x2"`
Y2 int16 `json:"y2"`
// Color is a color index. This was previously an rgb value, but since
// the values are always the same, using an index saves bandwidth.
Color uint8 `json:"color"`
Width uint8 `json:"width"`
} `json:"data"`
}

type FillEvent struct {
Data *struct {
X uint16 `json:"x"`
Y uint16 `json:"y"`
Color RGBColor `json:"color"`
X uint16 `json:"x"`
Y uint16 `json:"y"`
// Color is a color index. This was previously an rgb value, but since
// the values are always the same, using an index saves bandwidth.
Color uint8 `json:"color"`
} `json:"data"`
Type string `json:"type"`
}
Expand Down
Loading

0 comments on commit 20055f2

Please sign in to comment.