Skip to content

Commit c249b96

Browse files
committed
Helper function to encode a PNG
Can be disabled by defining MUXY_GATEWAY_WITHOUT_PNG_ENCODER
1 parent 91211a5 commit c249b96

File tree

6 files changed

+1937
-3
lines changed

6 files changed

+1937
-3
lines changed

include/gateway.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,25 @@ namespace gateway
1313
struct GameMetadata
1414
{
1515
string GameName;
16-
// Base64 encoded image
16+
17+
// Base64 encoded image, PNG, JPG or SVG
1718
string GameLogo;
1819
string Theme;
1920
};
2021

22+
#ifndef MUXY_GATEWAY_WITHOUT_PNG_ENCODER
23+
/// Encodes an image as packed RGB(A) data into a base64 data url.
24+
/// @param image - Pointer to array of bytes in RGB or RGBA format, 8 bits per pixel.
25+
/// @param width - How many pixels wide the image is.
26+
/// @param height - How many pixels high the image is.
27+
/// @param components - 3 for RGB, 4 for RGBA. Other values are not supported.
28+
/// @param strideBytes - How many bytes a row of pixels are. May be larger than width * component.
29+
/// @param flipVertically - Set to true to flip the Y axis of the image efore encoding.
30+
/// @return Returns a data url in the form "data:image/png;base64,<base64encoded image>"
31+
/// @note This function is pretty slow, avoid calling this function on the main thread.
32+
string EncodeImageToBase64PNG(const void* image, uint32_t width, uint32_t height, uint32_t components, uint32_t strideBytes, bool flipVertically);
33+
#endif
34+
2135
class Payload
2236
{
2337
friend class SDK;

src/gateway.cpp

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,132 @@
11
#include "gateway.h"
22

3+
#ifndef MUXY_GATEWAY_WITHOUT_PNG_ENCODER
4+
#define STB_IMAGE_WRITE_IMPLEMENTATION
5+
#define STBI_WRITE_NO_STDIO
6+
#define STB_IMAGE_WRITE_STATIC
7+
#include "third_party/stb/stb_image_write.h"
8+
#endif
9+
310
namespace gateway
411
{
12+
13+
#ifndef MUXY_GATEWAY_WITHOUT_PNG_ENCODER
14+
string base64EncodeWithImageURLPrefix(const void* voidIn, uint32_t length)
15+
{
16+
uint32_t outputLength = length / 3 * 4;
17+
if (outputLength > 1024 * 1024)
18+
{
19+
return string("");
20+
}
21+
22+
const uint8_t* in = static_cast<const uint8_t*>(voidIn);
23+
static const char* codes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
24+
uint32_t o = 0;
25+
26+
const char* prefix = "data:image/png;base64,";
27+
uint32_t prefixSize = strlen(prefix);
28+
29+
char* out = new char[length / 3 * 4 + 4 + prefixSize];
30+
memcpy(out, prefix, prefixSize);
31+
32+
o += prefixSize;
33+
34+
for (uint32_t i = 0; i < length; i += 3)
35+
{
36+
// Read 6 bits.
37+
uint32_t bits = (in[i] & 0xFC) >> 2;
38+
out[o++] = codes[bits];
39+
40+
// Read in 2 bits
41+
bits = (in[i] & 0x3) << 4;
42+
if (i + 1 < length)
43+
{
44+
// Read in 4 bits
45+
bits |= (in[i + 1] & 0xF0) >> 4;
46+
out[o++] = codes[bits];
47+
48+
// Read in 4 bits
49+
bits = (in[i + 1] & 0xF) << 2;
50+
if (i + 2 < length)
51+
{
52+
// Read in 2 bits
53+
bits |= (in[i + 2] & 0xC0) >> 6;
54+
out[o++] = codes[bits];
55+
56+
// Read in 6 bits
57+
bits = in[i + 2] & 0x3F;
58+
out[o++] = codes[bits];
59+
}
60+
else
61+
{
62+
out[o++] = codes[bits];
63+
out[o++] = '=';
64+
}
65+
}
66+
else
67+
{
68+
out[o++] = codes[bits];
69+
out[o++] = '=';
70+
out[o++] = '=';
71+
}
72+
}
73+
74+
out[o] = '\0';
75+
string result = string(out, o - 1);
76+
77+
delete [] out;
78+
return result;
79+
}
80+
81+
string EncodeImageToBase64PNG(const void* untypedData, uint32_t w, uint32_t h, uint32_t components, uint32_t strideBytes, bool flipVertically)
82+
{
83+
const uint8_t* data = static_cast<const uint8_t*>(untypedData);
84+
if (components != 3 && components != 4)
85+
{
86+
return string("");
87+
}
88+
89+
if (flipVertically)
90+
{
91+
// Copy out the rows into a big buffer, flipping vertically.
92+
uint8_t* buffer = new uint8_t[w * h * components];
93+
for (uint32_t y = 0; y < h; ++y)
94+
{
95+
memcpy(&buffer[components * w * (h - y - 1)], &data[strideBytes * y], w * components);
96+
}
97+
98+
int outputLength = 0;
99+
unsigned char* png = stbi_write_png_to_mem(reinterpret_cast<const unsigned char*>(buffer),
100+
static_cast<int>(strideBytes),
101+
static_cast<int>(w),
102+
static_cast<int>(h),
103+
components,
104+
&outputLength);
105+
106+
string result = base64EncodeWithImageURLPrefix(png, outputLength);
107+
delete[] buffer;
108+
STBIW_FREE(png);
109+
return result;
110+
}
111+
else
112+
{
113+
int outputLength = 0;
114+
unsigned char* png = stbi_write_png_to_mem(reinterpret_cast<const unsigned char*>(data),
115+
static_cast<int>(strideBytes),
116+
static_cast<int>(w),
117+
static_cast<int>(h),
118+
components,
119+
&outputLength);
120+
121+
string result = base64EncodeWithImageURLPrefix(png, outputLength);
122+
STBIW_FREE(png);
123+
124+
return result;
125+
}
126+
}
127+
128+
#endif
129+
5130
Payload::Payload(const char* data, uint32_t length)
6131
: _Data(data)
7132
, _Length(length)

test/constrained_types.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ class ConstrainedString
5555
{
5656
return _data.c_str();
5757
}
58-
5958
private:
6059
std::string _data;
6160
};

test/gateway_gamemetadata.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#include "catch2/catch.hpp"
2+
#include "util.h"
3+
4+
#include "gateway.h"
5+
#include "gateway_c.h"
6+
7+
8+
// Generate an image 512x512 pixels, alternating between almost-black and debug pink.
9+
// Generates an RGB image.
10+
std::vector<uint8_t> generateImage()
11+
{
12+
static uint32_t width = 512;
13+
static uint32_t height = 512;
14+
15+
std::vector<uint8_t> result;
16+
result.resize(width * height * 3);
17+
18+
for (uint32_t y = 0; y < height / 8; ++y)
19+
{
20+
for (uint32_t x = 0; x < width / 8; ++x)
21+
{
22+
// Compute parity for black or pink.
23+
uint32_t parity = (x + y) % 2;
24+
25+
// Draw a 8x8 square.
26+
uint32_t upperLeftPixelIndex =
27+
(y * 8) * width +
28+
(x * 8);
29+
30+
if (parity == 0)
31+
{
32+
for (uint32_t innerY = 0; innerY < 8; ++innerY)
33+
{
34+
for (uint32_t innerX = 0; innerX < 8; ++innerX)
35+
{
36+
uint32_t pixelIndex = upperLeftPixelIndex + innerY * width + innerX;
37+
result[pixelIndex * 3 + 0] = 0x05;
38+
result[pixelIndex * 3 + 1] = 0x05;
39+
result[pixelIndex * 3 + 2] = 0x05;
40+
}
41+
}
42+
}
43+
else if (parity == 1)
44+
{
45+
for (uint32_t innerY = 0; innerY < 8; ++innerY)
46+
{
47+
for (uint32_t innerX = 0; innerX < 8; ++innerX)
48+
{
49+
uint32_t pixelIndex = upperLeftPixelIndex + innerY * width + innerX;
50+
result[pixelIndex * 3 + 0] = 0x7F;
51+
result[pixelIndex * 3 + 1] = 0;
52+
result[pixelIndex * 3 + 2] = 0x7F;
53+
}
54+
}
55+
}
56+
}
57+
}
58+
59+
return result;
60+
}
61+
62+
63+
TEST_CASE("Set basic image", "[gateawy][metadata]")
64+
{
65+
gateway::SDK sdk("This is a game");
66+
std::vector<uint8_t> image = generateImage();
67+
68+
gateway::GameMetadata metadata;
69+
metadata.GameLogo = gateway::EncodeImageToBase64PNG(image.data(), 512, 512, 3, 512 * 3, true);
70+
71+
REQUIRE(metadata.GameLogo.size() > 0);
72+
}

test/integration.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ TEST_CASE_METHOD(IntegrationTestFixture, "Transactions Support through gateway",
392392
gateway.OnActionUsed([&](const gateway::ActionUsed& used)
393393
{
394394
// This should get 5 calls, which are all coins
395-
REQUIRE(used.SKU == "costs-ten");
395+
REQUIRE(used.ActionID == "costs-ten");
396396
REQUIRE(used.Cost == 10);
397397

398398
coinCalls++;

0 commit comments

Comments
 (0)